@happyvertical/smrt-timesheets 0.55.24 → 0.56.1
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 +60 -0
- package/README.md +190 -12
- 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-Cpo4mIID.js → attendance-Cw75uJnT.js} +3 -3
- package/dist/chunks/{attendance-Cpo4mIID.js.map → attendance-Cw75uJnT.js.map} +1 -1
- package/dist/chunks/attendance-DJNytMTN.js +1246 -0
- package/dist/chunks/attendance-DJNytMTN.js.map +1 -0
- package/dist/chunks/models-tUePZ5x7.js +372 -0
- package/dist/chunks/models-tUePZ5x7.js.map +1 -0
- package/dist/chunks/{rollup-Du6uaqyM.js → rollup-CzCb-DxI.js} +211 -6
- package/dist/chunks/rollup-CzCb-DxI.js.map +1 -0
- package/dist/chunks/service-time-entry-BJVSGx33.js +286 -0
- package/dist/chunks/service-time-entry-BJVSGx33.js.map +1 -0
- package/dist/chunks/time-entry-actions-ChT1lcOp.js +576 -0
- package/dist/chunks/time-entry-actions-ChT1lcOp.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 +2020 -168
- 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/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/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 +902 -40
- package/package.json +8 -7
- package/dist/attendance.js.map +0 -1
- package/dist/chunks/models-_ZbpXWnb.js +0 -176
- package/dist/chunks/models-_ZbpXWnb.js.map +0 -1
- package/dist/chunks/rollup-Du6uaqyM.js.map +0 -1
- package/dist/chunks/service-time-entry-BGj39_EC.js +0 -227
- package/dist/chunks/service-time-entry-BGj39_EC.js.map +0 -1
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-timesheets",
|
|
6
|
-
"packageVersion": "0.
|
|
6
|
+
"packageVersion": "0.56.1",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
13
|
-
"moduleDoc:agents/period-rollup.md": "
|
|
10
|
+
"manifest": "e3e7dc0d327d7c84cf3286c39ae4e4f9966deb78d6d0ea77e699cb4dacc1463c",
|
|
11
|
+
"packageJson": "3d533bf63eccdc1fe8da80ff4024fa2f58827760002547c069be43cb1a1959d4",
|
|
12
|
+
"agents": "67979245cd75faa81e381084a73ab20fd166fbd875345a8c01e3a3da2ec8ffaf",
|
|
13
|
+
"moduleDoc:agents/period-rollup.md": "7e693235a49e8354a8910f480cde8a7aecdc4f28afaa6d778fd4bfaf67119b46"
|
|
14
14
|
},
|
|
15
15
|
"exports": [
|
|
16
16
|
".",
|
|
@@ -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": [
|
|
@@ -57,10 +59,213 @@
|
|
|
57
59
|
"timesheets"
|
|
58
60
|
],
|
|
59
61
|
"risks": [
|
|
60
|
-
"cross-package-refs:
|
|
62
|
+
"cross-package-refs:15",
|
|
61
63
|
"sensitive-fields-excluded"
|
|
62
64
|
],
|
|
63
65
|
"objects": [
|
|
66
|
+
{
|
|
67
|
+
"name": "AttendanceCorrection",
|
|
68
|
+
"qualifiedName": "@happyvertical/smrt-timesheets:AttendanceCorrection",
|
|
69
|
+
"collection": "attendancecorrections",
|
|
70
|
+
"description": "One append-only change to closed attendance: who changed whose time, why, and the before/after values as JSON (null for an addition's before or a removal's after).",
|
|
71
|
+
"tableName": "attendance_corrections",
|
|
72
|
+
"packageName": "@happyvertical/smrt-timesheets",
|
|
73
|
+
"extends": "SmrtObject",
|
|
74
|
+
"fields": [
|
|
75
|
+
{
|
|
76
|
+
"name": "tenantId",
|
|
77
|
+
"type": "text",
|
|
78
|
+
"required": true,
|
|
79
|
+
"columnType": "UUID"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "subjectProfileId",
|
|
83
|
+
"type": "crossPackageRef",
|
|
84
|
+
"required": false,
|
|
85
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
86
|
+
"columnType": "UUID"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "actorProfileId",
|
|
90
|
+
"type": "crossPackageRef",
|
|
91
|
+
"required": false,
|
|
92
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
93
|
+
"columnType": "UUID"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "kind",
|
|
97
|
+
"type": "text",
|
|
98
|
+
"required": false,
|
|
99
|
+
"columnType": "TEXT"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "punchId",
|
|
103
|
+
"type": "foreignKey",
|
|
104
|
+
"required": false,
|
|
105
|
+
"related": "AttendancePunch",
|
|
106
|
+
"columnType": "UUID"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "targetId",
|
|
110
|
+
"type": "text",
|
|
111
|
+
"required": false,
|
|
112
|
+
"columnType": "TEXT"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "beforeState",
|
|
116
|
+
"type": "text",
|
|
117
|
+
"required": false,
|
|
118
|
+
"columnType": "TEXT"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "afterState",
|
|
122
|
+
"type": "text",
|
|
123
|
+
"required": false,
|
|
124
|
+
"columnType": "TEXT"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "reason",
|
|
128
|
+
"type": "text",
|
|
129
|
+
"required": false,
|
|
130
|
+
"columnType": "TEXT"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "acknowledgementRequired",
|
|
134
|
+
"type": "boolean",
|
|
135
|
+
"required": true,
|
|
136
|
+
"columnType": "BOOLEAN",
|
|
137
|
+
"default": false
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "acknowledgedAt",
|
|
141
|
+
"type": "datetime",
|
|
142
|
+
"required": false,
|
|
143
|
+
"columnType": "TIMESTAMP"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "acknowledgedByProfileId",
|
|
147
|
+
"type": "crossPackageRef",
|
|
148
|
+
"required": false,
|
|
149
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
150
|
+
"columnType": "UUID"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"relationships": [
|
|
154
|
+
{
|
|
155
|
+
"name": "subjectProfileId",
|
|
156
|
+
"type": "crossPackageRef",
|
|
157
|
+
"required": false,
|
|
158
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
159
|
+
"columnType": "UUID"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "actorProfileId",
|
|
163
|
+
"type": "crossPackageRef",
|
|
164
|
+
"required": false,
|
|
165
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
166
|
+
"columnType": "UUID"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "punchId",
|
|
170
|
+
"type": "foreignKey",
|
|
171
|
+
"required": false,
|
|
172
|
+
"related": "AttendancePunch",
|
|
173
|
+
"columnType": "UUID"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "acknowledgedByProfileId",
|
|
177
|
+
"type": "crossPackageRef",
|
|
178
|
+
"required": false,
|
|
179
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
180
|
+
"columnType": "UUID"
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"methods": [
|
|
184
|
+
"delete",
|
|
185
|
+
"getAfter",
|
|
186
|
+
"getBefore",
|
|
187
|
+
"save"
|
|
188
|
+
],
|
|
189
|
+
"methodSignatures": [
|
|
190
|
+
{
|
|
191
|
+
"name": "delete",
|
|
192
|
+
"async": true,
|
|
193
|
+
"returns": "Promise<void>"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"name": "getAfter",
|
|
197
|
+
"returns": "T | null"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "getBefore",
|
|
201
|
+
"returns": "T | null"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "save",
|
|
205
|
+
"async": true,
|
|
206
|
+
"returns": "Promise<this>"
|
|
207
|
+
}
|
|
208
|
+
],
|
|
209
|
+
"tenant": {
|
|
210
|
+
"scoped": true,
|
|
211
|
+
"mode": "required",
|
|
212
|
+
"field": "tenantId"
|
|
213
|
+
},
|
|
214
|
+
"conflictColumns": [
|
|
215
|
+
"tenant_id",
|
|
216
|
+
"slug",
|
|
217
|
+
"context"
|
|
218
|
+
],
|
|
219
|
+
"surfaces": [],
|
|
220
|
+
"withheldSurfaces": [
|
|
221
|
+
{
|
|
222
|
+
"kind": "api",
|
|
223
|
+
"operation": "getAfter",
|
|
224
|
+
"code": "not-included",
|
|
225
|
+
"reason": "not listed in api.include",
|
|
226
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceCorrection"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"kind": "api",
|
|
230
|
+
"operation": "getBefore",
|
|
231
|
+
"code": "not-included",
|
|
232
|
+
"reason": "not listed in api.include",
|
|
233
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceCorrection"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"kind": "api",
|
|
237
|
+
"operation": "save",
|
|
238
|
+
"code": "lifecycle-method",
|
|
239
|
+
"reason": "`save` is a framework lifecycle method, not a distinct operation",
|
|
240
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceCorrection"
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"relationshipFeatures": [
|
|
244
|
+
"crossPackageRef",
|
|
245
|
+
"foreignKey",
|
|
246
|
+
"uuidColumns"
|
|
247
|
+
],
|
|
248
|
+
"tags": [],
|
|
249
|
+
"risks": []
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"name": "AttendanceCorrectionCollection",
|
|
253
|
+
"qualifiedName": "@happyvertical/smrt-timesheets:AttendanceCorrectionCollection",
|
|
254
|
+
"collection": "attendancecorrections",
|
|
255
|
+
"description": "The correction ledger.",
|
|
256
|
+
"tableName": "attendance_corrections",
|
|
257
|
+
"packageName": "@happyvertical/smrt-timesheets",
|
|
258
|
+
"extends": "SmrtCollection",
|
|
259
|
+
"fields": [],
|
|
260
|
+
"relationships": [],
|
|
261
|
+
"methods": [],
|
|
262
|
+
"surfaces": [],
|
|
263
|
+
"relationshipFeatures": [
|
|
264
|
+
"uuidColumns"
|
|
265
|
+
],
|
|
266
|
+
"tags": [],
|
|
267
|
+
"risks": []
|
|
268
|
+
},
|
|
64
269
|
{
|
|
65
270
|
"name": "AttendancePunch",
|
|
66
271
|
"qualifiedName": "@happyvertical/smrt-timesheets:AttendancePunch",
|
|
@@ -116,6 +321,12 @@
|
|
|
116
321
|
"columnType": "BOOLEAN",
|
|
117
322
|
"default": false
|
|
118
323
|
},
|
|
324
|
+
{
|
|
325
|
+
"name": "autoCloseReason",
|
|
326
|
+
"type": "text",
|
|
327
|
+
"required": false,
|
|
328
|
+
"columnType": "TEXT"
|
|
329
|
+
},
|
|
119
330
|
{
|
|
120
331
|
"name": "recordedOffline",
|
|
121
332
|
"type": "boolean",
|
|
@@ -123,6 +334,24 @@
|
|
|
123
334
|
"columnType": "BOOLEAN",
|
|
124
335
|
"default": false
|
|
125
336
|
},
|
|
337
|
+
{
|
|
338
|
+
"name": "source",
|
|
339
|
+
"type": "text",
|
|
340
|
+
"required": false,
|
|
341
|
+
"columnType": "TEXT"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"name": "deviceRefType",
|
|
345
|
+
"type": "text",
|
|
346
|
+
"required": false,
|
|
347
|
+
"columnType": "TEXT"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"name": "deviceRefId",
|
|
351
|
+
"type": "text",
|
|
352
|
+
"required": false,
|
|
353
|
+
"columnType": "TEXT"
|
|
354
|
+
},
|
|
126
355
|
{
|
|
127
356
|
"name": "serviceTimeEntryId",
|
|
128
357
|
"type": "foreignKey",
|
|
@@ -273,6 +502,12 @@
|
|
|
273
502
|
"columnType": "BOOLEAN",
|
|
274
503
|
"default": false
|
|
275
504
|
},
|
|
505
|
+
{
|
|
506
|
+
"name": "kind",
|
|
507
|
+
"type": "text",
|
|
508
|
+
"required": false,
|
|
509
|
+
"columnType": "TEXT"
|
|
510
|
+
},
|
|
276
511
|
{
|
|
277
512
|
"name": "openSlot",
|
|
278
513
|
"type": "text",
|
|
@@ -290,9 +525,15 @@
|
|
|
290
525
|
}
|
|
291
526
|
],
|
|
292
527
|
"methods": [
|
|
528
|
+
"delete",
|
|
293
529
|
"save"
|
|
294
530
|
],
|
|
295
531
|
"methodSignatures": [
|
|
532
|
+
{
|
|
533
|
+
"name": "delete",
|
|
534
|
+
"async": true,
|
|
535
|
+
"returns": "Promise<void>"
|
|
536
|
+
},
|
|
296
537
|
{
|
|
297
538
|
"name": "save",
|
|
298
539
|
"async": true,
|
|
@@ -367,6 +608,197 @@
|
|
|
367
608
|
"tags": [],
|
|
368
609
|
"risks": []
|
|
369
610
|
},
|
|
611
|
+
{
|
|
612
|
+
"name": "AttendanceSegment",
|
|
613
|
+
"qualifiedName": "@happyvertical/smrt-timesheets:AttendanceSegment",
|
|
614
|
+
"collection": "attendancesegments",
|
|
615
|
+
"description": "A running unit of work inside one attendance punch: a work reference plus an optional second (task) reference as queryable columns, so job costing can index and join them.",
|
|
616
|
+
"tableName": "attendance_segments",
|
|
617
|
+
"packageName": "@happyvertical/smrt-timesheets",
|
|
618
|
+
"extends": "SmrtObject",
|
|
619
|
+
"fields": [
|
|
620
|
+
{
|
|
621
|
+
"name": "tenantId",
|
|
622
|
+
"type": "text",
|
|
623
|
+
"required": true,
|
|
624
|
+
"columnType": "UUID"
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
"name": "punchId",
|
|
628
|
+
"type": "foreignKey",
|
|
629
|
+
"required": false,
|
|
630
|
+
"related": "AttendancePunch",
|
|
631
|
+
"columnType": "UUID"
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
"name": "workRefType",
|
|
635
|
+
"type": "text",
|
|
636
|
+
"required": false,
|
|
637
|
+
"columnType": "TEXT"
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
"name": "workRefId",
|
|
641
|
+
"type": "text",
|
|
642
|
+
"required": false,
|
|
643
|
+
"columnType": "TEXT"
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
"name": "taskRefType",
|
|
647
|
+
"type": "text",
|
|
648
|
+
"required": false,
|
|
649
|
+
"columnType": "TEXT"
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
"name": "taskRefId",
|
|
653
|
+
"type": "text",
|
|
654
|
+
"required": false,
|
|
655
|
+
"columnType": "TEXT"
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"name": "kind",
|
|
659
|
+
"type": "text",
|
|
660
|
+
"required": false,
|
|
661
|
+
"columnType": "TEXT"
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
"name": "description",
|
|
665
|
+
"type": "text",
|
|
666
|
+
"required": false,
|
|
667
|
+
"columnType": "TEXT"
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
"name": "startedAt",
|
|
671
|
+
"type": "datetime",
|
|
672
|
+
"required": false,
|
|
673
|
+
"columnType": "TIMESTAMP"
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
"name": "endedAt",
|
|
677
|
+
"type": "datetime",
|
|
678
|
+
"required": false,
|
|
679
|
+
"columnType": "TIMESTAMP"
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
"name": "durationSeconds",
|
|
683
|
+
"type": "integer",
|
|
684
|
+
"required": true,
|
|
685
|
+
"columnType": "INTEGER",
|
|
686
|
+
"default": 0
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
"name": "closedReason",
|
|
690
|
+
"type": "text",
|
|
691
|
+
"required": false,
|
|
692
|
+
"columnType": "TEXT",
|
|
693
|
+
"default": ""
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
"name": "reviewRequired",
|
|
697
|
+
"type": "boolean",
|
|
698
|
+
"required": true,
|
|
699
|
+
"columnType": "BOOLEAN",
|
|
700
|
+
"default": false
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"name": "openSlot",
|
|
704
|
+
"type": "text",
|
|
705
|
+
"required": true,
|
|
706
|
+
"columnType": "TEXT"
|
|
707
|
+
}
|
|
708
|
+
],
|
|
709
|
+
"relationships": [
|
|
710
|
+
{
|
|
711
|
+
"name": "punchId",
|
|
712
|
+
"type": "foreignKey",
|
|
713
|
+
"required": false,
|
|
714
|
+
"related": "AttendancePunch",
|
|
715
|
+
"columnType": "UUID"
|
|
716
|
+
}
|
|
717
|
+
],
|
|
718
|
+
"methods": [
|
|
719
|
+
"delete",
|
|
720
|
+
"save"
|
|
721
|
+
],
|
|
722
|
+
"methodSignatures": [
|
|
723
|
+
{
|
|
724
|
+
"name": "delete",
|
|
725
|
+
"async": true,
|
|
726
|
+
"returns": "Promise<void>"
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
"name": "save",
|
|
730
|
+
"async": true,
|
|
731
|
+
"returns": "Promise<this>"
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
"tenant": {
|
|
735
|
+
"scoped": true,
|
|
736
|
+
"mode": "required",
|
|
737
|
+
"field": "tenantId"
|
|
738
|
+
},
|
|
739
|
+
"conflictColumns": [
|
|
740
|
+
"tenant_id",
|
|
741
|
+
"slug",
|
|
742
|
+
"context"
|
|
743
|
+
],
|
|
744
|
+
"surfaces": [
|
|
745
|
+
{
|
|
746
|
+
"kind": "api",
|
|
747
|
+
"name": "attendancesegments.list",
|
|
748
|
+
"operation": "list",
|
|
749
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment",
|
|
750
|
+
"path": "/attendancesegments",
|
|
751
|
+
"method": "GET"
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
"kind": "api",
|
|
755
|
+
"name": "attendancesegments.get",
|
|
756
|
+
"operation": "get",
|
|
757
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment",
|
|
758
|
+
"path": "/attendancesegments/[id]",
|
|
759
|
+
"method": "GET"
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
"kind": "cli",
|
|
763
|
+
"name": "attendancesegment_list",
|
|
764
|
+
"operation": "list",
|
|
765
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
"kind": "cli",
|
|
769
|
+
"name": "attendancesegment_get",
|
|
770
|
+
"operation": "get",
|
|
771
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
"kind": "mcp",
|
|
775
|
+
"name": "attendancesegment_list",
|
|
776
|
+
"operation": "list",
|
|
777
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
"kind": "mcp",
|
|
781
|
+
"name": "attendancesegment_get",
|
|
782
|
+
"operation": "get",
|
|
783
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
784
|
+
}
|
|
785
|
+
],
|
|
786
|
+
"withheldSurfaces": [
|
|
787
|
+
{
|
|
788
|
+
"kind": "api",
|
|
789
|
+
"operation": "save",
|
|
790
|
+
"code": "lifecycle-method",
|
|
791
|
+
"reason": "`save` is a framework lifecycle method, not a distinct operation",
|
|
792
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
793
|
+
}
|
|
794
|
+
],
|
|
795
|
+
"relationshipFeatures": [
|
|
796
|
+
"foreignKey",
|
|
797
|
+
"uuidColumns"
|
|
798
|
+
],
|
|
799
|
+
"tags": [],
|
|
800
|
+
"risks": []
|
|
801
|
+
},
|
|
370
802
|
{
|
|
371
803
|
"name": "AttendanceReplay",
|
|
372
804
|
"qualifiedName": "@happyvertical/smrt-timesheets:AttendanceReplay",
|
|
@@ -501,6 +933,24 @@
|
|
|
501
933
|
"tags": [],
|
|
502
934
|
"risks": []
|
|
503
935
|
},
|
|
936
|
+
{
|
|
937
|
+
"name": "AttendanceSegmentCollection",
|
|
938
|
+
"qualifiedName": "@happyvertical/smrt-timesheets:AttendanceSegmentCollection",
|
|
939
|
+
"collection": "attendancesegments",
|
|
940
|
+
"description": "Work segments for job costing and attendance review.",
|
|
941
|
+
"tableName": "attendance_segments",
|
|
942
|
+
"packageName": "@happyvertical/smrt-timesheets",
|
|
943
|
+
"extends": "SmrtCollection",
|
|
944
|
+
"fields": [],
|
|
945
|
+
"relationships": [],
|
|
946
|
+
"methods": [],
|
|
947
|
+
"surfaces": [],
|
|
948
|
+
"relationshipFeatures": [
|
|
949
|
+
"uuidColumns"
|
|
950
|
+
],
|
|
951
|
+
"tags": [],
|
|
952
|
+
"risks": []
|
|
953
|
+
},
|
|
504
954
|
{
|
|
505
955
|
"name": "AttendanceReplayCollection",
|
|
506
956
|
"qualifiedName": "@happyvertical/smrt-timesheets:AttendanceReplayCollection",
|
|
@@ -845,7 +1295,6 @@
|
|
|
845
1295
|
"name": "ServiceChargeSnapshotCollection",
|
|
846
1296
|
"qualifiedName": "@happyvertical/smrt-timesheets:ServiceChargeSnapshotCollection",
|
|
847
1297
|
"collection": "servicechargesnapshots",
|
|
848
|
-
"description": "Collection over service_charge_snapshots.",
|
|
849
1298
|
"tableName": "service_charge_snapshots",
|
|
850
1299
|
"packageName": "@happyvertical/smrt-timesheets",
|
|
851
1300
|
"extends": "SmrtCollection",
|
|
@@ -863,7 +1312,6 @@
|
|
|
863
1312
|
"name": "ServiceCompensationSnapshotCollection",
|
|
864
1313
|
"qualifiedName": "@happyvertical/smrt-timesheets:ServiceCompensationSnapshotCollection",
|
|
865
1314
|
"collection": "servicecompensationsnapshots",
|
|
866
|
-
"description": "Collection over service_compensation_snapshots.",
|
|
867
1315
|
"tableName": "service_compensation_snapshots",
|
|
868
1316
|
"packageName": "@happyvertical/smrt-timesheets",
|
|
869
1317
|
"extends": "SmrtCollection",
|
|
@@ -1263,10 +1711,21 @@
|
|
|
1263
1711
|
"fields": [],
|
|
1264
1712
|
"relationships": [],
|
|
1265
1713
|
"methods": [
|
|
1714
|
+
"approve",
|
|
1266
1715
|
"forWork",
|
|
1267
|
-
"pendingApproval"
|
|
1716
|
+
"pendingApproval",
|
|
1717
|
+
"record",
|
|
1718
|
+
"submit"
|
|
1268
1719
|
],
|
|
1269
1720
|
"methodSignatures": [
|
|
1721
|
+
{
|
|
1722
|
+
"name": "approve",
|
|
1723
|
+
"async": true,
|
|
1724
|
+
"params": [
|
|
1725
|
+
"options: ServiceTimeEntryActionTarget"
|
|
1726
|
+
],
|
|
1727
|
+
"returns": "Promise<ServiceTimeEntry>"
|
|
1728
|
+
},
|
|
1270
1729
|
{
|
|
1271
1730
|
"name": "forWork",
|
|
1272
1731
|
"async": true,
|
|
@@ -1280,36 +1739,64 @@
|
|
|
1280
1739
|
"name": "pendingApproval",
|
|
1281
1740
|
"async": true,
|
|
1282
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>"
|
|
1283
1758
|
}
|
|
1284
1759
|
],
|
|
1285
1760
|
"surfaces": [
|
|
1286
1761
|
{
|
|
1287
1762
|
"kind": "api",
|
|
1288
|
-
"name": "servicetimeentries.
|
|
1289
|
-
"operation": "
|
|
1763
|
+
"name": "servicetimeentries.record",
|
|
1764
|
+
"operation": "record",
|
|
1290
1765
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1291
|
-
"path": "/servicetimeentries/
|
|
1766
|
+
"path": "/servicetimeentries/record",
|
|
1292
1767
|
"method": "POST"
|
|
1293
1768
|
},
|
|
1294
1769
|
{
|
|
1295
1770
|
"kind": "api",
|
|
1296
|
-
"name": "servicetimeentries.
|
|
1297
|
-
"operation": "
|
|
1771
|
+
"name": "servicetimeentries.submit",
|
|
1772
|
+
"operation": "submit",
|
|
1298
1773
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1299
|
-
"path": "/servicetimeentries/
|
|
1774
|
+
"path": "/servicetimeentries/submit",
|
|
1300
1775
|
"method": "POST"
|
|
1301
1776
|
},
|
|
1302
1777
|
{
|
|
1303
|
-
"kind": "
|
|
1304
|
-
"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",
|
|
1305
1788
|
"operation": "forWork",
|
|
1306
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
1789
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1790
|
+
"path": "/servicetimeentries/forWork",
|
|
1791
|
+
"method": "POST"
|
|
1307
1792
|
},
|
|
1308
1793
|
{
|
|
1309
|
-
"kind": "
|
|
1310
|
-
"name": "
|
|
1794
|
+
"kind": "api",
|
|
1795
|
+
"name": "servicetimeentries.pendingApproval",
|
|
1311
1796
|
"operation": "pendingApproval",
|
|
1312
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
1797
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1798
|
+
"path": "/servicetimeentries/pendingApproval",
|
|
1799
|
+
"method": "POST"
|
|
1313
1800
|
},
|
|
1314
1801
|
{
|
|
1315
1802
|
"kind": "mcp",
|
|
@@ -1415,6 +1902,19 @@
|
|
|
1415
1902
|
"required": false,
|
|
1416
1903
|
"columnType": "TEXT"
|
|
1417
1904
|
},
|
|
1905
|
+
{
|
|
1906
|
+
"name": "submittedAt",
|
|
1907
|
+
"type": "datetime",
|
|
1908
|
+
"required": false,
|
|
1909
|
+
"columnType": "TIMESTAMP"
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
"name": "submittedByProfileId",
|
|
1913
|
+
"type": "crossPackageRef",
|
|
1914
|
+
"required": false,
|
|
1915
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
1916
|
+
"columnType": "UUID"
|
|
1917
|
+
},
|
|
1418
1918
|
{
|
|
1419
1919
|
"name": "confirmedAt",
|
|
1420
1920
|
"type": "datetime",
|
|
@@ -1437,6 +1937,13 @@
|
|
|
1437
1937
|
"related": "@happyvertical/smrt-profiles:Profile",
|
|
1438
1938
|
"columnType": "UUID"
|
|
1439
1939
|
},
|
|
1940
|
+
{
|
|
1941
|
+
"name": "submittedByProfileId",
|
|
1942
|
+
"type": "crossPackageRef",
|
|
1943
|
+
"required": false,
|
|
1944
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
1945
|
+
"columnType": "UUID"
|
|
1946
|
+
},
|
|
1440
1947
|
{
|
|
1441
1948
|
"name": "confirmedByProfileId",
|
|
1442
1949
|
"type": "crossPackageRef",
|
|
@@ -1642,6 +2149,146 @@
|
|
|
1642
2149
|
"tags": [],
|
|
1643
2150
|
"risks": []
|
|
1644
2151
|
},
|
|
2152
|
+
{
|
|
2153
|
+
"name": "TimecardReopening",
|
|
2154
|
+
"qualifiedName": "@happyvertical/smrt-timesheets:TimecardReopening",
|
|
2155
|
+
"collection": "timecardreopenings",
|
|
2156
|
+
"description": "Append-only audit row for one reopening of a submitted or confirmed card: who reopened whose period, why, and the totals it had at that moment (base, adjustments and effective, in integer seconds).",
|
|
2157
|
+
"tableName": "timecard_reopenings",
|
|
2158
|
+
"packageName": "@happyvertical/smrt-timesheets",
|
|
2159
|
+
"extends": "SmrtObject",
|
|
2160
|
+
"fields": [
|
|
2161
|
+
{
|
|
2162
|
+
"name": "tenantId",
|
|
2163
|
+
"type": "text",
|
|
2164
|
+
"required": true,
|
|
2165
|
+
"columnType": "UUID"
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
"name": "timecardId",
|
|
2169
|
+
"type": "foreignKey",
|
|
2170
|
+
"required": false,
|
|
2171
|
+
"related": "PeriodTimecard",
|
|
2172
|
+
"columnType": "UUID"
|
|
2173
|
+
},
|
|
2174
|
+
{
|
|
2175
|
+
"name": "profileId",
|
|
2176
|
+
"type": "crossPackageRef",
|
|
2177
|
+
"required": false,
|
|
2178
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
2179
|
+
"columnType": "UUID"
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
"name": "actorProfileId",
|
|
2183
|
+
"type": "crossPackageRef",
|
|
2184
|
+
"required": false,
|
|
2185
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
2186
|
+
"columnType": "UUID"
|
|
2187
|
+
},
|
|
2188
|
+
{
|
|
2189
|
+
"name": "operationId",
|
|
2190
|
+
"type": "text",
|
|
2191
|
+
"required": false,
|
|
2192
|
+
"columnType": "TEXT"
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
"name": "fromStatus",
|
|
2196
|
+
"type": "text",
|
|
2197
|
+
"required": false,
|
|
2198
|
+
"columnType": "TEXT"
|
|
2199
|
+
},
|
|
2200
|
+
{
|
|
2201
|
+
"name": "reason",
|
|
2202
|
+
"type": "text",
|
|
2203
|
+
"required": false,
|
|
2204
|
+
"columnType": "TEXT"
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
"name": "previousState",
|
|
2208
|
+
"type": "text",
|
|
2209
|
+
"required": false,
|
|
2210
|
+
"columnType": "TEXT"
|
|
2211
|
+
}
|
|
2212
|
+
],
|
|
2213
|
+
"relationships": [
|
|
2214
|
+
{
|
|
2215
|
+
"name": "timecardId",
|
|
2216
|
+
"type": "foreignKey",
|
|
2217
|
+
"required": false,
|
|
2218
|
+
"related": "PeriodTimecard",
|
|
2219
|
+
"columnType": "UUID"
|
|
2220
|
+
},
|
|
2221
|
+
{
|
|
2222
|
+
"name": "profileId",
|
|
2223
|
+
"type": "crossPackageRef",
|
|
2224
|
+
"required": false,
|
|
2225
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
2226
|
+
"columnType": "UUID"
|
|
2227
|
+
},
|
|
2228
|
+
{
|
|
2229
|
+
"name": "actorProfileId",
|
|
2230
|
+
"type": "crossPackageRef",
|
|
2231
|
+
"required": false,
|
|
2232
|
+
"related": "@happyvertical/smrt-profiles:Profile",
|
|
2233
|
+
"columnType": "UUID"
|
|
2234
|
+
}
|
|
2235
|
+
],
|
|
2236
|
+
"methods": [
|
|
2237
|
+
"delete",
|
|
2238
|
+
"getPrevious",
|
|
2239
|
+
"save"
|
|
2240
|
+
],
|
|
2241
|
+
"methodSignatures": [
|
|
2242
|
+
{
|
|
2243
|
+
"name": "delete",
|
|
2244
|
+
"async": true,
|
|
2245
|
+
"returns": "Promise<void>"
|
|
2246
|
+
},
|
|
2247
|
+
{
|
|
2248
|
+
"name": "getPrevious",
|
|
2249
|
+
"returns": "T"
|
|
2250
|
+
},
|
|
2251
|
+
{
|
|
2252
|
+
"name": "save",
|
|
2253
|
+
"async": true,
|
|
2254
|
+
"returns": "Promise<this>"
|
|
2255
|
+
}
|
|
2256
|
+
],
|
|
2257
|
+
"tenant": {
|
|
2258
|
+
"scoped": true,
|
|
2259
|
+
"mode": "required",
|
|
2260
|
+
"field": "tenantId"
|
|
2261
|
+
},
|
|
2262
|
+
"conflictColumns": [
|
|
2263
|
+
"tenant_id",
|
|
2264
|
+
"slug",
|
|
2265
|
+
"context"
|
|
2266
|
+
],
|
|
2267
|
+
"surfaces": [],
|
|
2268
|
+
"withheldSurfaces": [
|
|
2269
|
+
{
|
|
2270
|
+
"kind": "api",
|
|
2271
|
+
"operation": "getPrevious",
|
|
2272
|
+
"code": "not-included",
|
|
2273
|
+
"reason": "not listed in api.include",
|
|
2274
|
+
"objectName": "@happyvertical/smrt-timesheets:TimecardReopening"
|
|
2275
|
+
},
|
|
2276
|
+
{
|
|
2277
|
+
"kind": "api",
|
|
2278
|
+
"operation": "save",
|
|
2279
|
+
"code": "lifecycle-method",
|
|
2280
|
+
"reason": "`save` is a framework lifecycle method, not a distinct operation",
|
|
2281
|
+
"objectName": "@happyvertical/smrt-timesheets:TimecardReopening"
|
|
2282
|
+
}
|
|
2283
|
+
],
|
|
2284
|
+
"relationshipFeatures": [
|
|
2285
|
+
"crossPackageRef",
|
|
2286
|
+
"foreignKey",
|
|
2287
|
+
"uuidColumns"
|
|
2288
|
+
],
|
|
2289
|
+
"tags": [],
|
|
2290
|
+
"risks": []
|
|
2291
|
+
},
|
|
1645
2292
|
{
|
|
1646
2293
|
"name": "PeriodTimecardCollection",
|
|
1647
2294
|
"qualifiedName": "@happyvertical/smrt-timesheets:PeriodTimecardCollection",
|
|
@@ -1677,6 +2324,24 @@
|
|
|
1677
2324
|
],
|
|
1678
2325
|
"tags": [],
|
|
1679
2326
|
"risks": []
|
|
2327
|
+
},
|
|
2328
|
+
{
|
|
2329
|
+
"name": "TimecardReopeningCollection",
|
|
2330
|
+
"qualifiedName": "@happyvertical/smrt-timesheets:TimecardReopeningCollection",
|
|
2331
|
+
"collection": "timecardreopenings",
|
|
2332
|
+
"description": "Scoped append-only reopening ledger.",
|
|
2333
|
+
"tableName": "timecard_reopenings",
|
|
2334
|
+
"packageName": "@happyvertical/smrt-timesheets",
|
|
2335
|
+
"extends": "SmrtCollection",
|
|
2336
|
+
"fields": [],
|
|
2337
|
+
"relationships": [],
|
|
2338
|
+
"methods": [],
|
|
2339
|
+
"surfaces": [],
|
|
2340
|
+
"relationshipFeatures": [
|
|
2341
|
+
"uuidColumns"
|
|
2342
|
+
],
|
|
2343
|
+
"tags": [],
|
|
2344
|
+
"risks": []
|
|
1680
2345
|
}
|
|
1681
2346
|
],
|
|
1682
2347
|
"surfaces": [
|
|
@@ -1760,6 +2425,46 @@
|
|
|
1760
2425
|
"operation": "get",
|
|
1761
2426
|
"objectName": "@happyvertical/smrt-timesheets:AttendanceBreak"
|
|
1762
2427
|
},
|
|
2428
|
+
{
|
|
2429
|
+
"kind": "api",
|
|
2430
|
+
"name": "attendancesegments.list",
|
|
2431
|
+
"operation": "list",
|
|
2432
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment",
|
|
2433
|
+
"path": "/attendancesegments",
|
|
2434
|
+
"method": "GET"
|
|
2435
|
+
},
|
|
2436
|
+
{
|
|
2437
|
+
"kind": "api",
|
|
2438
|
+
"name": "attendancesegments.get",
|
|
2439
|
+
"operation": "get",
|
|
2440
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment",
|
|
2441
|
+
"path": "/attendancesegments/[id]",
|
|
2442
|
+
"method": "GET"
|
|
2443
|
+
},
|
|
2444
|
+
{
|
|
2445
|
+
"kind": "cli",
|
|
2446
|
+
"name": "attendancesegment_list",
|
|
2447
|
+
"operation": "list",
|
|
2448
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
"kind": "cli",
|
|
2452
|
+
"name": "attendancesegment_get",
|
|
2453
|
+
"operation": "get",
|
|
2454
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
2455
|
+
},
|
|
2456
|
+
{
|
|
2457
|
+
"kind": "mcp",
|
|
2458
|
+
"name": "attendancesegment_list",
|
|
2459
|
+
"operation": "list",
|
|
2460
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
2461
|
+
},
|
|
2462
|
+
{
|
|
2463
|
+
"kind": "mcp",
|
|
2464
|
+
"name": "attendancesegment_get",
|
|
2465
|
+
"operation": "get",
|
|
2466
|
+
"objectName": "@happyvertical/smrt-timesheets:AttendanceSegment"
|
|
2467
|
+
},
|
|
1763
2468
|
{
|
|
1764
2469
|
"kind": "api",
|
|
1765
2470
|
"name": "servicechargesnapshots.list",
|
|
@@ -1882,31 +2587,43 @@
|
|
|
1882
2587
|
},
|
|
1883
2588
|
{
|
|
1884
2589
|
"kind": "api",
|
|
1885
|
-
"name": "servicetimeentries.
|
|
1886
|
-
"operation": "
|
|
2590
|
+
"name": "servicetimeentries.record",
|
|
2591
|
+
"operation": "record",
|
|
1887
2592
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1888
|
-
"path": "/servicetimeentries/
|
|
2593
|
+
"path": "/servicetimeentries/record",
|
|
1889
2594
|
"method": "POST"
|
|
1890
2595
|
},
|
|
1891
2596
|
{
|
|
1892
2597
|
"kind": "api",
|
|
1893
|
-
"name": "servicetimeentries.
|
|
1894
|
-
"operation": "
|
|
2598
|
+
"name": "servicetimeentries.submit",
|
|
2599
|
+
"operation": "submit",
|
|
1895
2600
|
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
1896
|
-
"path": "/servicetimeentries/
|
|
2601
|
+
"path": "/servicetimeentries/submit",
|
|
1897
2602
|
"method": "POST"
|
|
1898
2603
|
},
|
|
1899
2604
|
{
|
|
1900
|
-
"kind": "
|
|
1901
|
-
"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",
|
|
1902
2615
|
"operation": "forWork",
|
|
1903
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
2616
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
2617
|
+
"path": "/servicetimeentries/forWork",
|
|
2618
|
+
"method": "POST"
|
|
1904
2619
|
},
|
|
1905
2620
|
{
|
|
1906
|
-
"kind": "
|
|
1907
|
-
"name": "
|
|
2621
|
+
"kind": "api",
|
|
2622
|
+
"name": "servicetimeentries.pendingApproval",
|
|
1908
2623
|
"operation": "pendingApproval",
|
|
1909
|
-
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection"
|
|
2624
|
+
"objectName": "@happyvertical/smrt-timesheets:ServiceTimeEntryCollection",
|
|
2625
|
+
"path": "/servicetimeentries/pendingApproval",
|
|
2626
|
+
"method": "POST"
|
|
1910
2627
|
},
|
|
1911
2628
|
{
|
|
1912
2629
|
"kind": "mcp",
|
|
@@ -1923,29 +2640,174 @@
|
|
|
1923
2640
|
],
|
|
1924
2641
|
"prompts": [],
|
|
1925
2642
|
"relationshipsV2": {
|
|
1926
|
-
"foreignKeyFields":
|
|
1927
|
-
"crossPackageRefFields":
|
|
2643
|
+
"foreignKeyFields": 9,
|
|
2644
|
+
"crossPackageRefFields": 15,
|
|
1928
2645
|
"junctionCollections": 0,
|
|
1929
2646
|
"hierarchicalObjects": 0,
|
|
1930
2647
|
"polymorphicAssociations": 0,
|
|
1931
|
-
"uuidColumns":
|
|
2648
|
+
"uuidColumns": 92
|
|
1932
2649
|
},
|
|
1933
|
-
"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\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",
|
|
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",
|
|
1934
2651
|
"moduleDocs": [
|
|
1935
2652
|
{
|
|
1936
2653
|
"path": "agents/period-rollup.md",
|
|
1937
2654
|
"module": "period-rollup",
|
|
1938
|
-
"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"
|
|
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"
|
|
2656
|
+
}
|
|
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
|
+
}
|
|
1939
2797
|
}
|
|
1940
2798
|
],
|
|
1941
2799
|
"browser": {
|
|
1942
2800
|
"status": "server-only",
|
|
1943
2801
|
"issues": [
|
|
1944
|
-
"#
|
|
2802
|
+
"#3617",
|
|
2803
|
+
"#3624",
|
|
2804
|
+
"#3626"
|
|
1945
2805
|
],
|
|
1946
2806
|
"reason": "node:crypto",
|
|
1947
2807
|
"via": [
|
|
1948
|
-
"@happyvertical/smrt-
|
|
2808
|
+
"@happyvertical/smrt-profiles",
|
|
2809
|
+
"@happyvertical/smrt-tenancy",
|
|
2810
|
+
"@happyvertical/smrt-users"
|
|
1949
2811
|
]
|
|
1950
2812
|
}
|
|
1951
2813
|
}
|