@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.
- package/dist/contract/approvals.d.ts +281 -0
- package/dist/contract/approvals.d.ts.map +1 -0
- package/dist/contract/approvals.js +127 -0
- package/dist/contract/approvals.js.map +1 -0
- package/dist/contract/attendance.d.ts +240 -0
- package/dist/contract/attendance.d.ts.map +1 -0
- package/dist/contract/attendance.js +154 -0
- package/dist/contract/attendance.js.map +1 -0
- package/dist/contract/capabilities.d.ts +1 -1
- package/dist/contract/capabilities.d.ts.map +1 -1
- package/dist/contract/capabilities.js +80 -0
- package/dist/contract/capabilities.js.map +1 -1
- package/dist/contract/events.d.ts +60 -0
- package/dist/contract/events.d.ts.map +1 -1
- package/dist/contract/events.js +60 -0
- package/dist/contract/events.js.map +1 -1
- package/dist/contract/index.d.ts +3 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +3 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/leave.d.ts +168 -0
- package/dist/contract/leave.d.ts.map +1 -0
- package/dist/contract/leave.js +150 -0
- package/dist/contract/leave.js.map +1 -0
- package/dist/contract/permissions.d.ts +100 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +119 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/router.d.ts +2440 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +393 -0
- package/dist/contract/router.js.map +1 -1
- package/dist/policy/time.d.ts +48 -0
- package/dist/policy/time.d.ts.map +1 -0
- package/dist/policy/time.js +144 -0
- package/dist/policy/time.js.map +1 -0
- package/dist/policy/working-time.d.ts +98 -0
- package/dist/policy/working-time.d.ts.map +1 -0
- package/dist/policy/working-time.js +190 -0
- package/dist/policy/working-time.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs.d.ts +16 -0
- package/dist/server/jobs.d.ts.map +1 -0
- package/dist/server/jobs.js +166 -0
- package/dist/server/jobs.js.map +1 -0
- package/dist/server/router.d.ts +2731 -58
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +1306 -3
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +3344 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +325 -0
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/approvals.d.ts +163 -0
- package/dist/server/services/approvals.d.ts.map +1 -0
- package/dist/server/services/approvals.js +325 -0
- package/dist/server/services/approvals.js.map +1 -0
- package/dist/server/services/attendance.d.ts +150 -0
- package/dist/server/services/attendance.d.ts.map +1 -0
- package/dist/server/services/attendance.js +239 -0
- package/dist/server/services/attendance.js.map +1 -0
- package/dist/server/services/ledger.d.ts +135 -0
- package/dist/server/services/ledger.d.ts.map +1 -0
- package/dist/server/services/ledger.js +178 -0
- package/dist/server/services/ledger.js.map +1 -0
- package/dist/server/services/people.d.ts +3 -3
- package/migrations/0002_leave.sql +237 -0
- package/migrations/0003_attendance.sql +206 -0
- package/migrations/meta/0002_snapshot.json +3009 -0
- package/migrations/meta/0003_snapshot.json +3681 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/src/contract/approvals.ts +149 -0
- package/src/contract/attendance.ts +180 -0
- package/src/contract/capabilities.ts +80 -0
- package/src/contract/events.ts +81 -0
- package/src/contract/index.ts +3 -0
- package/src/contract/leave.ts +171 -0
- package/src/contract/permissions.ts +122 -0
- 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"}
|