@kernhq/module-hr 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/contract/approvals.d.ts +281 -0
  2. package/dist/contract/approvals.d.ts.map +1 -0
  3. package/dist/contract/approvals.js +127 -0
  4. package/dist/contract/approvals.js.map +1 -0
  5. package/dist/contract/attendance.d.ts +240 -0
  6. package/dist/contract/attendance.d.ts.map +1 -0
  7. package/dist/contract/attendance.js +154 -0
  8. package/dist/contract/attendance.js.map +1 -0
  9. package/dist/contract/capabilities.d.ts +1 -1
  10. package/dist/contract/capabilities.d.ts.map +1 -1
  11. package/dist/contract/capabilities.js +80 -0
  12. package/dist/contract/capabilities.js.map +1 -1
  13. package/dist/contract/events.d.ts +60 -0
  14. package/dist/contract/events.d.ts.map +1 -1
  15. package/dist/contract/events.js +60 -0
  16. package/dist/contract/events.js.map +1 -1
  17. package/dist/contract/index.d.ts +3 -0
  18. package/dist/contract/index.d.ts.map +1 -1
  19. package/dist/contract/index.js +3 -0
  20. package/dist/contract/index.js.map +1 -1
  21. package/dist/contract/leave.d.ts +168 -0
  22. package/dist/contract/leave.d.ts.map +1 -0
  23. package/dist/contract/leave.js +150 -0
  24. package/dist/contract/leave.js.map +1 -0
  25. package/dist/contract/permissions.d.ts +100 -0
  26. package/dist/contract/permissions.d.ts.map +1 -1
  27. package/dist/contract/permissions.js +119 -0
  28. package/dist/contract/permissions.js.map +1 -1
  29. package/dist/contract/router.d.ts +2440 -0
  30. package/dist/contract/router.d.ts.map +1 -1
  31. package/dist/contract/router.js +393 -0
  32. package/dist/contract/router.js.map +1 -1
  33. package/dist/policy/time.d.ts +48 -0
  34. package/dist/policy/time.d.ts.map +1 -0
  35. package/dist/policy/time.js +144 -0
  36. package/dist/policy/time.js.map +1 -0
  37. package/dist/policy/working-time.d.ts +98 -0
  38. package/dist/policy/working-time.d.ts.map +1 -0
  39. package/dist/policy/working-time.js +190 -0
  40. package/dist/policy/working-time.js.map +1 -0
  41. package/dist/server/index.d.ts.map +1 -1
  42. package/dist/server/index.js +2 -0
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/jobs.d.ts +16 -0
  45. package/dist/server/jobs.d.ts.map +1 -0
  46. package/dist/server/jobs.js +166 -0
  47. package/dist/server/jobs.js.map +1 -0
  48. package/dist/server/router.d.ts +2731 -58
  49. package/dist/server/router.d.ts.map +1 -1
  50. package/dist/server/router.js +1306 -3
  51. package/dist/server/router.js.map +1 -1
  52. package/dist/server/schema.d.ts +3344 -1
  53. package/dist/server/schema.d.ts.map +1 -1
  54. package/dist/server/schema.js +325 -0
  55. package/dist/server/schema.js.map +1 -1
  56. package/dist/server/services/approvals.d.ts +163 -0
  57. package/dist/server/services/approvals.d.ts.map +1 -0
  58. package/dist/server/services/approvals.js +325 -0
  59. package/dist/server/services/approvals.js.map +1 -0
  60. package/dist/server/services/attendance.d.ts +150 -0
  61. package/dist/server/services/attendance.d.ts.map +1 -0
  62. package/dist/server/services/attendance.js +239 -0
  63. package/dist/server/services/attendance.js.map +1 -0
  64. package/dist/server/services/ledger.d.ts +135 -0
  65. package/dist/server/services/ledger.d.ts.map +1 -0
  66. package/dist/server/services/ledger.js +178 -0
  67. package/dist/server/services/ledger.js.map +1 -0
  68. package/dist/server/services/people.d.ts +3 -3
  69. package/migrations/0002_leave.sql +237 -0
  70. package/migrations/0003_attendance.sql +206 -0
  71. package/migrations/meta/0002_snapshot.json +3009 -0
  72. package/migrations/meta/0003_snapshot.json +3681 -0
  73. package/migrations/meta/_journal.json +14 -0
  74. package/package.json +1 -1
  75. package/src/contract/approvals.ts +149 -0
  76. package/src/contract/attendance.ts +180 -0
  77. package/src/contract/capabilities.ts +80 -0
  78. package/src/contract/events.ts +81 -0
  79. package/src/contract/index.ts +3 -0
  80. package/src/contract/leave.ts +171 -0
  81. package/src/contract/permissions.ts +122 -0
  82. package/src/contract/router.ts +481 -0
@@ -0,0 +1,325 @@
1
+ import { KernError, uuidv7 } from '@kernhq/kernel';
2
+ import { and, desc, eq, inArray, isNull, lte, or, sql } from 'drizzle-orm';
3
+ import { approvalChains, approvalDecisions, approvalRequests, approvalSteps, delegations, employments, officeAssignments, offices, orgUnits, people, } from '../schema.js';
4
+ import { inForceOn, todayIso } from './db.js';
5
+ /**
6
+ * The approval engine. One of these, for everything that needs signing off.
7
+ *
8
+ * Three decisions carry most of the weight:
9
+ *
10
+ * - **The chain is snapshotted when the request is raised.** Editing the workflow afterwards must
11
+ * not change who has to sign something already in flight. Discovering that approved leave now
12
+ * needs another signature is very hard to explain to the person who already took the week.
13
+ * - **Approvers are resolved to people at request time**, not looked up at decision time. A
14
+ * reorganisation in the middle of an approval would otherwise silently move it to somebody else.
15
+ * - **One decision per approver per step**, enforced by a unique index. A double click is one
16
+ * decision; the database refuses the second rather than counting it towards the quorum twice.
17
+ */
18
+ export class ApprovalService {
19
+ kernel;
20
+ constructor(kernel) {
21
+ this.kernel = kernel;
22
+ }
23
+ /**
24
+ * Expand a subject — "the manager", "whoever heads this office" — into people.
25
+ *
26
+ * Everything is resolved as of `on` rather than now, so a request raised in March is signed by
27
+ * March's manager even when it is decided in May.
28
+ */
29
+ async resolveSubject(tx, workspaceId, subject, requesterId, on) {
30
+ switch (subject.kind) {
31
+ case 'person':
32
+ return subject.id ? [subject.id] : [];
33
+ case 'manager': {
34
+ const m = await this.managerOf(tx, workspaceId, requesterId, on);
35
+ return m ? [m] : [];
36
+ }
37
+ case 'manager_of_manager': {
38
+ const m = await this.managerOf(tx, workspaceId, requesterId, on);
39
+ if (!m)
40
+ return [];
41
+ const above = await this.managerOf(tx, workspaceId, m, on);
42
+ // Falls back to the direct manager rather than returning nobody: an empty step would either
43
+ // block the request forever or wave it through, and both are worse than one signature.
44
+ return above ? [above] : [m];
45
+ }
46
+ case 'org_unit_head': {
47
+ const [employment] = await tx
48
+ .select({ orgUnitId: employments.orgUnitId })
49
+ .from(employments)
50
+ .where(and(eq(employments.workspaceId, workspaceId), eq(employments.personId, requesterId), inForceOn(employments.effectiveFrom, employments.effectiveTo, on)))
51
+ .limit(1);
52
+ if (!employment?.orgUnitId)
53
+ return [];
54
+ const [unit] = await tx
55
+ .select({ headPersonId: orgUnits.headPersonId })
56
+ .from(orgUnits)
57
+ .where(and(eq(orgUnits.workspaceId, workspaceId), eq(orgUnits.id, employment.orgUnitId)))
58
+ .limit(1);
59
+ return unit?.headPersonId ? [unit.headPersonId] : [];
60
+ }
61
+ case 'office_head': {
62
+ // The local-HR step: whoever heads the requester's *primary* office. Non-primary offices
63
+ // grant presence, not authority, so they do not get a say here either.
64
+ const [assignment] = await tx
65
+ .select({ officeId: officeAssignments.officeId })
66
+ .from(officeAssignments)
67
+ .where(and(eq(officeAssignments.workspaceId, workspaceId), eq(officeAssignments.personId, requesterId), eq(officeAssignments.isPrimary, true), inForceOn(officeAssignments.effectiveFrom, officeAssignments.effectiveTo, on)))
68
+ .limit(1);
69
+ if (!assignment)
70
+ return [];
71
+ const [office] = await tx
72
+ .select({ headPersonId: offices.headPersonId })
73
+ .from(offices)
74
+ .where(and(eq(offices.workspaceId, workspaceId), eq(offices.id, assignment.officeId)))
75
+ .limit(1);
76
+ return office?.headPersonId ? [office.headPersonId] : [];
77
+ }
78
+ case 'permission': {
79
+ if (!subject.id)
80
+ return [];
81
+ // Asks core who holds the key, then maps those users to HR people. A permission-based step
82
+ // is how "any HR administrator" is expressed without naming anybody.
83
+ const members = await this.kernel
84
+ .call('core.workspaces.members', { workspaceId, permission: subject.id }, this.kernel.system)
85
+ .catch(() => []);
86
+ const userIds = members.map((m) => m.userId);
87
+ if (!userIds.length)
88
+ return [];
89
+ const rows = await tx
90
+ .select({ id: people.id })
91
+ .from(people)
92
+ .where(and(eq(people.workspaceId, workspaceId), inArray(people.userId, userIds)));
93
+ return rows.map((r) => r.id);
94
+ }
95
+ case 'group': {
96
+ if (!subject.id)
97
+ return [];
98
+ const members = await this.kernel
99
+ .call('core.groups.members', { workspaceId, groupId: subject.id }, this.kernel.system)
100
+ .catch(() => []);
101
+ const userIds = members.map((m) => m.userId);
102
+ if (!userIds.length)
103
+ return [];
104
+ const rows = await tx
105
+ .select({ id: people.id })
106
+ .from(people)
107
+ .where(and(eq(people.workspaceId, workspaceId), inArray(people.userId, userIds)));
108
+ return rows.map((r) => r.id);
109
+ }
110
+ default:
111
+ return [];
112
+ }
113
+ }
114
+ async managerOf(tx, workspaceId, personId, on) {
115
+ const [row] = await tx
116
+ .select({ managerPersonId: employments.managerPersonId })
117
+ .from(employments)
118
+ .where(and(eq(employments.workspaceId, workspaceId), eq(employments.personId, personId), inForceOn(employments.effectiveFrom, employments.effectiveTo, on)))
119
+ .limit(1);
120
+ return row?.managerPersonId ?? null;
121
+ }
122
+ /**
123
+ * The chain for a subject type: the workspace's default, or a single implicit manager step.
124
+ *
125
+ * The implicit fallback is what makes Level 1 work. A company with one approver should not have
126
+ * to build a chain to discover that it has one, and a workspace with the `approvals` capability
127
+ * off never sees a chain editor at all.
128
+ */
129
+ async chainFor(tx, workspaceId, subjectType) {
130
+ const [row] = await tx
131
+ .select()
132
+ .from(approvalChains)
133
+ .where(and(eq(approvalChains.workspaceId, workspaceId), eq(approvalChains.subjectType, subjectType), eq(approvalChains.isDefault, true), isNull(approvalChains.archivedAt)))
134
+ .limit(1);
135
+ if (row)
136
+ return row.spec;
137
+ return {
138
+ steps: [
139
+ {
140
+ name: 'Manager',
141
+ approvers: [{ kind: 'manager' }],
142
+ mode: 'any',
143
+ minApprovals: 1,
144
+ slaHours: null,
145
+ onTimeout: 'remind',
146
+ },
147
+ ],
148
+ };
149
+ }
150
+ /**
151
+ * Raise an approval, resolving every step's approvers now.
152
+ *
153
+ * Returns the request and the people the first step is waiting on, so the caller can notify them
154
+ * without re-reading.
155
+ */
156
+ async raise(tx, workspaceId, input) {
157
+ const on = input.on ?? todayIso();
158
+ const spec = await this.chainFor(tx, workspaceId, input.subjectType);
159
+ const resolved = [];
160
+ for (const step of spec.steps) {
161
+ const ids = new Set();
162
+ for (const subject of step.approvers)
163
+ for (const id of await this.resolveSubject(tx, workspaceId, subject, input.requesterPersonId, on))
164
+ ids.add(id);
165
+ // Nobody approves their own request. Where that would empty a step, the step is dropped
166
+ // rather than left unsatisfiable — a manager requesting leave is approved by the step above.
167
+ ids.delete(input.requesterPersonId);
168
+ resolved.push({ step, approverIds: [...ids] });
169
+ }
170
+ const usable = resolved.filter((r) => r.approverIds.length > 0);
171
+ const [request] = await tx
172
+ .insert(approvalRequests)
173
+ .values({
174
+ id: uuidv7(),
175
+ workspaceId,
176
+ subjectType: input.subjectType,
177
+ subjectId: input.subjectId,
178
+ summary: input.summary,
179
+ chain: spec,
180
+ // A chain that resolves to nobody at all is auto-approved rather than left pending for
181
+ // ever. A one-person company has no manager, and their leave still has to be bookable.
182
+ status: usable.length ? 'pending' : 'approved',
183
+ currentStep: 0,
184
+ requestedBy: input.requestedBy,
185
+ decidedAt: usable.length ? null : new Date(),
186
+ })
187
+ .returning();
188
+ for (const [index, r] of usable.entries()) {
189
+ await tx.insert(approvalSteps).values({
190
+ id: uuidv7(),
191
+ workspaceId,
192
+ requestId: request.id,
193
+ stepIndex: index,
194
+ name: r.step.name,
195
+ mode: r.step.mode,
196
+ minApprovals: r.step.minApprovals,
197
+ approverIds: r.approverIds,
198
+ status: index === 0 ? 'pending' : 'pending',
199
+ dueAt: r.step.slaHours ? new Date(Date.now() + r.step.slaHours * 3600_000) : null,
200
+ });
201
+ }
202
+ return {
203
+ request: request,
204
+ autoApproved: usable.length === 0,
205
+ firstStepApprovers: usable[0]?.approverIds ?? [],
206
+ };
207
+ }
208
+ /**
209
+ * Record a decision and advance.
210
+ *
211
+ * The unique index on `(step_id, approver_id)` is what makes this idempotent: a second decision
212
+ * from the same person is refused by the database, not counted twice towards a quorum.
213
+ */
214
+ async decide(tx, workspaceId, requestId, approverPersonId, decision, comment, onBehalfOfId) {
215
+ const [request] = await tx
216
+ .select()
217
+ .from(approvalRequests)
218
+ .where(and(eq(approvalRequests.workspaceId, workspaceId), eq(approvalRequests.id, requestId)))
219
+ .limit(1);
220
+ if (!request)
221
+ throw KernError.notFound('Approval request');
222
+ if (request.status !== 'pending')
223
+ throw KernError.conflict(`This request is already ${request.status}`);
224
+ const [step] = await tx
225
+ .select()
226
+ .from(approvalSteps)
227
+ .where(and(eq(approvalSteps.requestId, requestId), eq(approvalSteps.stepIndex, request.currentStep)))
228
+ .limit(1);
229
+ if (!step)
230
+ throw KernError.notFound('Approval step');
231
+ // The person acting must be on the step — either directly, or as somebody's delegate.
232
+ const actingAs = onBehalfOfId ?? approverPersonId;
233
+ if (!step.approverIds.includes(actingAs))
234
+ throw KernError.forbidden('You are not an approver on this step');
235
+ if (onBehalfOfId && !(await this.mayActFor(tx, workspaceId, approverPersonId, onBehalfOfId)))
236
+ throw KernError.forbidden('You do not hold a delegation from that person');
237
+ await tx.insert(approvalDecisions).values({
238
+ id: uuidv7(),
239
+ workspaceId,
240
+ stepId: step.id,
241
+ approverId: actingAs,
242
+ onBehalfOfId: onBehalfOfId ? approverPersonId : null,
243
+ decision,
244
+ comment,
245
+ });
246
+ const decisions = await tx.select().from(approvalDecisions).where(eq(approvalDecisions.stepId, step.id));
247
+ const approvals = decisions.filter((d) => d.decision === 'approve').length;
248
+ const rejections = decisions.filter((d) => d.decision === 'reject').length;
249
+ // One rejection ends it. Every workflow we have wants that, and "some approvers rejected but it
250
+ // went through anyway" is not a sentence anybody wants to read in an audit.
251
+ if (rejections > 0) {
252
+ await tx.update(approvalSteps).set({ status: 'rejected' }).where(eq(approvalSteps.id, step.id));
253
+ await tx
254
+ .update(approvalRequests)
255
+ .set({ status: 'rejected', decidedAt: new Date(), version: sql `${approvalRequests.version} + 1` })
256
+ .where(eq(approvalRequests.id, requestId));
257
+ return { status: 'rejected', request };
258
+ }
259
+ const needed = step.mode === 'all' ? step.approverIds.length : step.mode === 'any' ? 1 : step.minApprovals;
260
+ if (approvals < needed)
261
+ return { status: 'pending', request };
262
+ await tx.update(approvalSteps).set({ status: 'approved' }).where(eq(approvalSteps.id, step.id));
263
+ const [next] = await tx
264
+ .select()
265
+ .from(approvalSteps)
266
+ .where(and(eq(approvalSteps.requestId, requestId), eq(approvalSteps.stepIndex, request.currentStep + 1)))
267
+ .limit(1);
268
+ if (next) {
269
+ await tx
270
+ .update(approvalRequests)
271
+ .set({ currentStep: request.currentStep + 1, version: sql `${approvalRequests.version} + 1` })
272
+ .where(eq(approvalRequests.id, requestId));
273
+ return { status: 'pending', request, advancedTo: next.stepIndex };
274
+ }
275
+ await tx
276
+ .update(approvalRequests)
277
+ .set({ status: 'approved', decidedAt: new Date(), version: sql `${approvalRequests.version} + 1` })
278
+ .where(eq(approvalRequests.id, requestId));
279
+ return { status: 'approved', request };
280
+ }
281
+ /** Does `actor` hold a live delegation from `from`? */
282
+ async mayActFor(tx, workspaceId, actor, from) {
283
+ const today = todayIso();
284
+ const [row] = await tx
285
+ .select({ id: delegations.id })
286
+ .from(delegations)
287
+ .where(and(eq(delegations.workspaceId, workspaceId), eq(delegations.toPersonId, actor), eq(delegations.fromPersonId, from), lte(delegations.startsOn, today), sql `${delegations.endsOn} >= ${today}`))
288
+ .limit(1);
289
+ return !!row;
290
+ }
291
+ /** Cancel an in-flight approval, because its subject was withdrawn. */
292
+ async cancel(tx, workspaceId, subjectType, subjectId) {
293
+ await tx
294
+ .update(approvalRequests)
295
+ .set({ status: 'cancelled', decidedAt: new Date() })
296
+ .where(and(eq(approvalRequests.workspaceId, workspaceId), eq(approvalRequests.subjectType, subjectType), eq(approvalRequests.subjectId, subjectId), eq(approvalRequests.status, 'pending')));
297
+ }
298
+ /** Everything waiting on this person, including what they may decide by delegation. */
299
+ async inboxFor(tx, workspaceId, personId, includeDecided, limit) {
300
+ const today = todayIso();
301
+ const delegated = await tx
302
+ .select({ fromPersonId: delegations.fromPersonId })
303
+ .from(delegations)
304
+ .where(and(eq(delegations.workspaceId, workspaceId), eq(delegations.toPersonId, personId), lte(delegations.startsOn, today), sql `${delegations.endsOn} >= ${today}`));
305
+ const actingFor = [personId, ...delegated.map((d) => d.fromPersonId)];
306
+ const steps = await tx
307
+ .select()
308
+ .from(approvalSteps)
309
+ .where(and(eq(approvalSteps.workspaceId, workspaceId), sql `${approvalSteps.approverIds} && ${sql.raw(`ARRAY[${actingFor.map((i) => `'${i}'`).join(',')}]::uuid[]`)}`));
310
+ if (!steps.length)
311
+ return [];
312
+ const requestIds = [...new Set(steps.map((s) => s.requestId))];
313
+ const where = [eq(approvalRequests.workspaceId, workspaceId), inArray(approvalRequests.id, requestIds)];
314
+ if (!includeDecided)
315
+ where.push(eq(approvalRequests.status, 'pending'));
316
+ return tx
317
+ .select()
318
+ .from(approvalRequests)
319
+ .where(and(...where))
320
+ .orderBy(desc(approvalRequests.requestedAt))
321
+ .limit(limit);
322
+ }
323
+ }
324
+ export { or };
325
+ //# sourceMappingURL=approvals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.js","sourceRoot":"","sources":["../../../src/server/services/approvals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ import { type Tx } from '@kernhq/kernel';
2
+ import { inArray, sql } from 'drizzle-orm';
3
+ import { type RoundingPolicy, type ShiftSpec } from '../../policy/working-time.js';
4
+ /**
5
+ * Punches and the day sheet.
6
+ *
7
+ * The two rules this service exists to enforce:
8
+ *
9
+ * - **The server stamps the time.** A client's clock is a claim, recorded for audit and never
10
+ * trusted. Client clocks are wrong by accident constantly and on purpose occasionally, and a
11
+ * system that cannot tell an offline sync from an edited phone clock cannot defend any of its
12
+ * numbers.
13
+ * - **The day sheet is derived.** Every figure on it comes from punches + schedule + calendar +
14
+ * leave, and can be thrown away and rebuilt. Nothing is ever repaired by hand.
15
+ */
16
+ export interface ResolvedSchedule {
17
+ shiftFor(date: string): ShiftSpec | null;
18
+ rounding: RoundingPolicy;
19
+ autoClockOutAfterMinutes: number | null;
20
+ scheduleId: string | null;
21
+ }
22
+ /** A schedule with no shifts: somebody with no assignment still clocks in, they just owe no hours. */
23
+ export declare const NO_SCHEDULE: ResolvedSchedule;
24
+ export declare class AttendanceService {
25
+ /** The schedule in force for a person on a date, as something the pure layer can use. */
26
+ scheduleFor(tx: Tx, workspaceId: string, personId: string, on: string): Promise<ResolvedSchedule>;
27
+ /**
28
+ * Record a punch.
29
+ *
30
+ * `at` is the server's clock, always. `clientReportedAt` is kept beside it with the measured skew
31
+ * so an offline sync can be told from a device whose clock is wrong — and a claim beyond the
32
+ * threshold is marked `disputed` rather than silently accepted or silently dropped.
33
+ */
34
+ record(tx: Tx, workspaceId: string, input: {
35
+ personId: string;
36
+ direction: 'in' | 'out' | 'break_start' | 'break_end';
37
+ timezone: string;
38
+ method: string;
39
+ clientReportedAt?: string | null;
40
+ officeId?: string | null;
41
+ geo?: Record<string, number> | null;
42
+ note?: string | null;
43
+ idempotencyKey?: string | null;
44
+ /** Set only by the offline sync path, where the instant genuinely is the client's claim. */
45
+ claimedAt?: Date | null;
46
+ }, schedule: ResolvedSchedule): Promise<{
47
+ createdAt: Date;
48
+ id: string;
49
+ timezone: string;
50
+ workspaceId: string;
51
+ personId: string;
52
+ officeId: string | null;
53
+ note: string | null;
54
+ at: Date;
55
+ direction: string;
56
+ businessDate: string;
57
+ method: string;
58
+ trust: string;
59
+ clientReportedAt: Date | null;
60
+ skewMs: number | null;
61
+ deviceId: string | null;
62
+ geo: Record<string, number> | null;
63
+ voidedByPunchId: string | null;
64
+ idempotencyKey: string | null;
65
+ }>;
66
+ /** Live punches for a person on a business date, oldest first. Voided rows are excluded. */
67
+ punchesOn(tx: Tx, workspaceId: string, personId: string, businessDate: string): Promise<{
68
+ id: string;
69
+ workspaceId: string;
70
+ personId: string;
71
+ direction: string;
72
+ at: Date;
73
+ clientReportedAt: Date | null;
74
+ skewMs: number | null;
75
+ businessDate: string;
76
+ timezone: string;
77
+ method: string;
78
+ officeId: string | null;
79
+ deviceId: string | null;
80
+ geo: Record<string, number> | null;
81
+ trust: string;
82
+ voidedByPunchId: string | null;
83
+ idempotencyKey: string | null;
84
+ note: string | null;
85
+ createdAt: Date;
86
+ }[]>;
87
+ /**
88
+ * Rebuild one day from its punches.
89
+ *
90
+ * Idempotent by construction, which is what makes it safe to call on every punch, from a nightly
91
+ * sweep, and from a support request without thinking about it. A locked day is left alone and
92
+ * reported, never silently skipped — a recomputation that quietly declines to touch a closed
93
+ * month looks identical to one that had nothing to do.
94
+ */
95
+ recomputeDay(tx: Tx, workspaceId: string, personId: string, businessDate: string, timezone: string, schedule: ResolvedSchedule): Promise<{
96
+ locked: boolean;
97
+ }>;
98
+ /** Which business dates a person has punches on, in a range. Drives a bulk recompute. */
99
+ datesWithPunches(tx: Tx, workspaceId: string, personId: string, from: string, to: string): Promise<string[]>;
100
+ /**
101
+ * Void a punch by writing a correcting row that points at it.
102
+ *
103
+ * The original stays exactly as it was. Two rows is the point: "this was recorded and then
104
+ * corrected" and "this was never recorded" are different facts, and only one of them is true.
105
+ */
106
+ voidPunch(tx: Tx, workspaceId: string, punchId: string, reason: string, actorPersonId: string | null): Promise<{
107
+ original: {
108
+ id: string;
109
+ workspaceId: string;
110
+ personId: string;
111
+ direction: string;
112
+ at: Date;
113
+ clientReportedAt: Date | null;
114
+ skewMs: number | null;
115
+ businessDate: string;
116
+ timezone: string;
117
+ method: string;
118
+ officeId: string | null;
119
+ deviceId: string | null;
120
+ geo: Record<string, number> | null;
121
+ trust: string;
122
+ voidedByPunchId: string | null;
123
+ idempotencyKey: string | null;
124
+ note: string | null;
125
+ createdAt: Date;
126
+ };
127
+ correction: {
128
+ createdAt: Date;
129
+ id: string;
130
+ timezone: string;
131
+ workspaceId: string;
132
+ personId: string;
133
+ officeId: string | null;
134
+ note: string | null;
135
+ at: Date;
136
+ direction: string;
137
+ businessDate: string;
138
+ method: string;
139
+ trust: string;
140
+ clientReportedAt: Date | null;
141
+ skewMs: number | null;
142
+ deviceId: string | null;
143
+ geo: Record<string, number> | null;
144
+ voidedByPunchId: string | null;
145
+ idempotencyKey: string | null;
146
+ };
147
+ }>;
148
+ }
149
+ export { inArray, sql };
150
+ //# sourceMappingURL=attendance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attendance.d.ts","sourceRoot":"","sources":["../../../src/server/services/attendance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EAAqB,OAAO,EAAe,GAAG,EAAE,MAAM,aAAa,CAAA;AAG1E,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,8BAA8B,CAAA;AAIrC;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAA;IACxC,QAAQ,EAAE,cAAc,CAAA;IACxB,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,sGAAsG;AACtG,eAAO,MAAM,WAAW,EAAE,gBAKzB,CAAA;AAED,qBAAa,iBAAiB;IAC5B,yFAAyF;IACnF,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2CvG;;;;;;OAMG;IACG,MAAM,CACV,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,IAAI,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAA;QACrD,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QACnC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACpB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC9B,4FAA4F;QAC5F,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;KACxB,EACD,QAAQ,EAAE,gBAAgB;;;;;;;;;;;;;;;;;;;;IAuC5B,4FAA4F;IACtF,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;IAenF;;;;;;;OAOG;IACG,YAAY,CAChB,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IA0F/B,yFAAyF;IACnF,gBAAgB,CACpB,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,EAAE,CAAC;IAepB;;;;;OAKG;IACG,SAAS,CACb,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyC/B;AAYD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAA"}