@kernhq/module-hr 0.3.0 → 0.5.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/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.map +1 -1
- package/dist/contract/capabilities.js +36 -0
- package/dist/contract/capabilities.js.map +1 -1
- package/dist/contract/events.d.ts +18 -0
- package/dist/contract/events.d.ts.map +1 -1
- package/dist/contract/events.js +18 -0
- package/dist/contract/events.js.map +1 -1
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/permissions.d.ts +43 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +50 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/router.d.ts +996 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +165 -1
- 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 +1087 -0
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +521 -3
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +1261 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +116 -0
- package/dist/server/schema.js.map +1 -1
- 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 +2 -2
- package/migrations/0003_attendance.sql +206 -0
- package/migrations/meta/0003_snapshot.json +3681 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/client/index.ts +18 -0
- package/src/contract/attendance.ts +180 -0
- package/src/contract/capabilities.ts +36 -0
- package/src/contract/events.ts +24 -0
- package/src/contract/index.ts +1 -0
- package/src/contract/permissions.ts +51 -0
- package/src/contract/router.ts +203 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { IsoDate } from '../contract/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Turning raw punches into a day's worked minutes.
|
|
4
|
+
*
|
|
5
|
+
* Pure: punches and a schedule in, numbers out. No database, no clock of its own. That is what makes
|
|
6
|
+
* the cases which actually break a payroll — a night shift, a daylight-saving boundary, a missing
|
|
7
|
+
* clock-out, a break somebody forgot to end — testable as a table rather than against a service.
|
|
8
|
+
*/
|
|
9
|
+
/** A shift as a schedule declares it: wall-clock readings, meaningless until a date and zone arrive. */
|
|
10
|
+
export interface ShiftSpec {
|
|
11
|
+
/** `09:00` */
|
|
12
|
+
start: string;
|
|
13
|
+
/** `18:00`, or `06:00` for a shift that ends the next morning. */
|
|
14
|
+
end: string;
|
|
15
|
+
/** Unpaid break subtracted from the worked total. */
|
|
16
|
+
breakMinutes: number;
|
|
17
|
+
/** Minutes after `start` that are not counted late. */
|
|
18
|
+
graceInMinutes: number;
|
|
19
|
+
/** Minutes before `end` that are not counted as leaving early. */
|
|
20
|
+
graceOutMinutes: number;
|
|
21
|
+
}
|
|
22
|
+
export interface RoundingPolicy {
|
|
23
|
+
/** Round the worked total to a multiple of this many minutes. 0 disables rounding. */
|
|
24
|
+
stepMinutes: number;
|
|
25
|
+
/**
|
|
26
|
+
* Which way. `nearest` is even-handed; `employee` always rounds in their favour; `employer`
|
|
27
|
+
* always against. The third exists because some contracts specify it, not because it is a good
|
|
28
|
+
* idea.
|
|
29
|
+
*/
|
|
30
|
+
direction: 'nearest' | 'employee' | 'employer';
|
|
31
|
+
}
|
|
32
|
+
export declare const NO_ROUNDING: RoundingPolicy;
|
|
33
|
+
export type PunchDirection = 'in' | 'out' | 'break_start' | 'break_end';
|
|
34
|
+
export interface PunchInput {
|
|
35
|
+
/** The instant, server-stamped. Milliseconds since the epoch. */
|
|
36
|
+
at: number;
|
|
37
|
+
direction: PunchDirection;
|
|
38
|
+
}
|
|
39
|
+
export interface DayComputation {
|
|
40
|
+
businessDate: IsoDate;
|
|
41
|
+
scheduledMinutes: number;
|
|
42
|
+
workedMinutes: number;
|
|
43
|
+
breakMinutes: number;
|
|
44
|
+
overtimeMinutes: number;
|
|
45
|
+
lateMinutes: number;
|
|
46
|
+
earlyLeaveMinutes: number;
|
|
47
|
+
status: 'present' | 'absent' | 'partial' | 'pending';
|
|
48
|
+
/** Things a human should look at: an unpaired punch, a break left open. */
|
|
49
|
+
anomalies: string[];
|
|
50
|
+
firstIn: number | null;
|
|
51
|
+
lastOut: number | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Does this shift cross midnight?
|
|
55
|
+
*
|
|
56
|
+
* `22:00`–`06:00` does; `09:00`–`18:00` does not. Every night-shift decision follows from this one
|
|
57
|
+
* question, so it is answered in one place.
|
|
58
|
+
*/
|
|
59
|
+
export declare const crossesMidnight: (shift: ShiftSpec) => boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Which business day a punch belongs to.
|
|
62
|
+
*
|
|
63
|
+
* For a day shift, the local date. For a night shift, **the date the shift started**: somebody
|
|
64
|
+
* clocking out at 06:00 on Tuesday finished Monday's shift, and putting those minutes on Tuesday
|
|
65
|
+
* leaves Monday short and Tuesday long — which is how a month's total comes out right while every
|
|
66
|
+
* individual day is wrong.
|
|
67
|
+
*
|
|
68
|
+
* The zone is the person's, from their primary office. A punch on a business trip records the zone
|
|
69
|
+
* it happened in for audit but is still attributed here, or a week abroad splits somebody's month
|
|
70
|
+
* across two calendars.
|
|
71
|
+
*/
|
|
72
|
+
export declare function businessDateFor(instantMs: number, timeZone: string, shift: ShiftSpec | null): IsoDate;
|
|
73
|
+
/**
|
|
74
|
+
* The scheduled span of a shift on a date — computed from instants, not from clock readings.
|
|
75
|
+
*
|
|
76
|
+
* A 09:00–18:00 shift is nine hours on almost every day and eight or ten across a transition,
|
|
77
|
+
* because the clock moved underneath it. Subtracting wall-clock times reports nine every time, and
|
|
78
|
+
* quietly pays somebody for an hour they did not work, once a year, in one direction.
|
|
79
|
+
*/
|
|
80
|
+
export declare function scheduledMinutesOn(date: IsoDate, timeZone: string, shift: ShiftSpec): number;
|
|
81
|
+
/**
|
|
82
|
+
* Compute one day from its punches.
|
|
83
|
+
*
|
|
84
|
+
* Punches pair in order: `in` opens a span, `out` closes it, `break_start`/`break_end` carve unpaid
|
|
85
|
+
* time out of it. An unpaired punch does not throw — it is flagged and the day becomes `pending`,
|
|
86
|
+
* because somebody who forgot to clock out still worked, and the sheet has to say something useful
|
|
87
|
+
* rather than nothing.
|
|
88
|
+
*/
|
|
89
|
+
export declare function computeDay(opts: {
|
|
90
|
+
businessDate: IsoDate;
|
|
91
|
+
timeZone: string;
|
|
92
|
+
shift: ShiftSpec | null;
|
|
93
|
+
punches: PunchInput[];
|
|
94
|
+
rounding?: RoundingPolicy;
|
|
95
|
+
/** Approved leave or a holiday: not an absence, whatever the punches say. */
|
|
96
|
+
excused?: boolean;
|
|
97
|
+
}): DayComputation;
|
|
98
|
+
//# sourceMappingURL=working-time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"working-time.d.ts","sourceRoot":"","sources":["../../src/policy/working-time.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAGnD;;;;;;GAMG;AAEH,wGAAwG;AACxG,MAAM,WAAW,SAAS;IACxB,cAAc;IACd,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAA;IACX,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAA;IACpB,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAA;IACtB,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,sFAAsF;IACtF,WAAW,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAA;CAC/C;AAED,eAAO,MAAM,WAAW,EAAE,cAAyD,CAAA;AAEnF,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAA;AAEvE,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,cAAc,CAAA;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,OAAO,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAA;IACpD,2EAA2E;IAC3E,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,SAAS,KAAG,OAAyD,CAAA;AAO5G;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAWrG;AAwBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAK5F;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,YAAY,EAAE,OAAO,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG,cAAc,CA8GjB"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { dateIn, minutesOfDayIn, nextDate, previousDate, zonedToInstant } from './time.js';
|
|
2
|
+
export const NO_ROUNDING = { stepMinutes: 0, direction: 'nearest' };
|
|
3
|
+
/**
|
|
4
|
+
* Does this shift cross midnight?
|
|
5
|
+
*
|
|
6
|
+
* `22:00`–`06:00` does; `09:00`–`18:00` does not. Every night-shift decision follows from this one
|
|
7
|
+
* question, so it is answered in one place.
|
|
8
|
+
*/
|
|
9
|
+
export const crossesMidnight = (shift) => toMinutes(shift.end) <= toMinutes(shift.start);
|
|
10
|
+
const toMinutes = (wall) => {
|
|
11
|
+
const [h, m] = wall.split(':').map(Number);
|
|
12
|
+
return h * 60 + m;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Which business day a punch belongs to.
|
|
16
|
+
*
|
|
17
|
+
* For a day shift, the local date. For a night shift, **the date the shift started**: somebody
|
|
18
|
+
* clocking out at 06:00 on Tuesday finished Monday's shift, and putting those minutes on Tuesday
|
|
19
|
+
* leaves Monday short and Tuesday long — which is how a month's total comes out right while every
|
|
20
|
+
* individual day is wrong.
|
|
21
|
+
*
|
|
22
|
+
* The zone is the person's, from their primary office. A punch on a business trip records the zone
|
|
23
|
+
* it happened in for audit but is still attributed here, or a week abroad splits somebody's month
|
|
24
|
+
* across two calendars.
|
|
25
|
+
*/
|
|
26
|
+
export function businessDateFor(instantMs, timeZone, shift) {
|
|
27
|
+
const localDate = dateIn(instantMs, timeZone);
|
|
28
|
+
if (!shift || !crossesMidnight(shift))
|
|
29
|
+
return localDate;
|
|
30
|
+
const minutes = minutesOfDayIn(instantMs, timeZone);
|
|
31
|
+
const startMin = toMinutes(shift.start);
|
|
32
|
+
const endMin = toMinutes(shift.end);
|
|
33
|
+
// Anything in the small hours up to a margin past the shift's end belongs to the previous day, so
|
|
34
|
+
// a late clock-out is still attributed to the shift it actually finished.
|
|
35
|
+
const tail = Math.min(startMin, endMin + 240);
|
|
36
|
+
return minutes < tail ? previousDate(localDate) : localDate;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Round a **worked total** to the policy's step.
|
|
40
|
+
*
|
|
41
|
+
* Applied to the total rather than to each punch, so a day is rounded once. Rounding both ends
|
|
42
|
+
* separately compounds — a fifteen-minute step can move a single day by half an hour, which is how
|
|
43
|
+
* a rounding policy nobody objected to turns into a month somebody does.
|
|
44
|
+
*/
|
|
45
|
+
function roundMinutes(minutes, policy) {
|
|
46
|
+
if (policy.stepMinutes <= 0)
|
|
47
|
+
return minutes;
|
|
48
|
+
const step = policy.stepMinutes;
|
|
49
|
+
switch (policy.direction) {
|
|
50
|
+
case 'nearest':
|
|
51
|
+
return Math.round(minutes / step) * step;
|
|
52
|
+
// In the employee's favour means *more* paid time, so up; the employer's, down. An earlier
|
|
53
|
+
// version took a `favour` argument meant for per-punch rounding and inverted both.
|
|
54
|
+
case 'employee':
|
|
55
|
+
return Math.ceil(minutes / step) * step;
|
|
56
|
+
case 'employer':
|
|
57
|
+
return Math.floor(minutes / step) * step;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The scheduled span of a shift on a date — computed from instants, not from clock readings.
|
|
62
|
+
*
|
|
63
|
+
* A 09:00–18:00 shift is nine hours on almost every day and eight or ten across a transition,
|
|
64
|
+
* because the clock moved underneath it. Subtracting wall-clock times reports nine every time, and
|
|
65
|
+
* quietly pays somebody for an hour they did not work, once a year, in one direction.
|
|
66
|
+
*/
|
|
67
|
+
export function scheduledMinutesOn(date, timeZone, shift) {
|
|
68
|
+
const start = zonedToInstant(date, shift.start, timeZone);
|
|
69
|
+
const endDate = crossesMidnight(shift) ? nextDate(date) : date;
|
|
70
|
+
const end = zonedToInstant(endDate, shift.end, timeZone);
|
|
71
|
+
return Math.max(0, Math.round((end - start) / 60000) - shift.breakMinutes);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Compute one day from its punches.
|
|
75
|
+
*
|
|
76
|
+
* Punches pair in order: `in` opens a span, `out` closes it, `break_start`/`break_end` carve unpaid
|
|
77
|
+
* time out of it. An unpaired punch does not throw — it is flagged and the day becomes `pending`,
|
|
78
|
+
* because somebody who forgot to clock out still worked, and the sheet has to say something useful
|
|
79
|
+
* rather than nothing.
|
|
80
|
+
*/
|
|
81
|
+
export function computeDay(opts) {
|
|
82
|
+
const { businessDate, timeZone, shift, excused } = opts;
|
|
83
|
+
const rounding = opts.rounding ?? NO_ROUNDING;
|
|
84
|
+
const punches = [...opts.punches].sort((a, b) => a.at - b.at);
|
|
85
|
+
const anomalies = [];
|
|
86
|
+
const scheduledMinutes = shift ? scheduledMinutesOn(businessDate, timeZone, shift) : 0;
|
|
87
|
+
if (!punches.length)
|
|
88
|
+
return {
|
|
89
|
+
businessDate,
|
|
90
|
+
scheduledMinutes,
|
|
91
|
+
workedMinutes: 0,
|
|
92
|
+
breakMinutes: 0,
|
|
93
|
+
overtimeMinutes: 0,
|
|
94
|
+
lateMinutes: 0,
|
|
95
|
+
earlyLeaveMinutes: 0,
|
|
96
|
+
status: excused ? 'present' : scheduledMinutes > 0 ? 'absent' : 'present',
|
|
97
|
+
anomalies: [],
|
|
98
|
+
firstIn: null,
|
|
99
|
+
lastOut: null,
|
|
100
|
+
};
|
|
101
|
+
let workedMs = 0;
|
|
102
|
+
let breakMs = 0;
|
|
103
|
+
let openIn = null;
|
|
104
|
+
let openBreak = null;
|
|
105
|
+
let firstIn = null;
|
|
106
|
+
let lastOut = null;
|
|
107
|
+
for (const punch of punches) {
|
|
108
|
+
switch (punch.direction) {
|
|
109
|
+
case 'in':
|
|
110
|
+
if (openIn !== null)
|
|
111
|
+
anomalies.push('double_clock_in');
|
|
112
|
+
else
|
|
113
|
+
openIn = punch.at;
|
|
114
|
+
if (firstIn === null)
|
|
115
|
+
firstIn = punch.at;
|
|
116
|
+
break;
|
|
117
|
+
case 'out':
|
|
118
|
+
if (openIn === null)
|
|
119
|
+
anomalies.push('clock_out_without_in');
|
|
120
|
+
else {
|
|
121
|
+
workedMs += punch.at - openIn;
|
|
122
|
+
openIn = null;
|
|
123
|
+
lastOut = punch.at;
|
|
124
|
+
}
|
|
125
|
+
// A break still open when the shift ends is closed here rather than dropped: the person was
|
|
126
|
+
// not working, and discarding it would pay them for it.
|
|
127
|
+
if (openBreak !== null) {
|
|
128
|
+
breakMs += punch.at - openBreak;
|
|
129
|
+
openBreak = null;
|
|
130
|
+
anomalies.push('break_not_ended');
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case 'break_start':
|
|
134
|
+
if (openBreak !== null)
|
|
135
|
+
anomalies.push('double_break_start');
|
|
136
|
+
else
|
|
137
|
+
openBreak = punch.at;
|
|
138
|
+
break;
|
|
139
|
+
case 'break_end':
|
|
140
|
+
if (openBreak === null)
|
|
141
|
+
anomalies.push('break_end_without_start');
|
|
142
|
+
else {
|
|
143
|
+
breakMs += punch.at - openBreak;
|
|
144
|
+
openBreak = null;
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (openIn !== null)
|
|
150
|
+
anomalies.push('missing_clock_out');
|
|
151
|
+
if (openBreak !== null && !anomalies.includes('break_not_ended'))
|
|
152
|
+
anomalies.push('break_not_ended');
|
|
153
|
+
let workedMinutes = Math.max(0, Math.round(workedMs / 60000) - Math.round(breakMs / 60000));
|
|
154
|
+
// The schedule's declared break applies only when nobody punched one — otherwise somebody who
|
|
155
|
+
// clocked their lunch has it deducted twice.
|
|
156
|
+
if (shift && breakMs === 0 && shift.breakMinutes > 0 && workedMinutes > shift.breakMinutes)
|
|
157
|
+
workedMinutes -= shift.breakMinutes;
|
|
158
|
+
workedMinutes = roundMinutes(workedMinutes, rounding);
|
|
159
|
+
let lateMinutes = 0;
|
|
160
|
+
let earlyLeaveMinutes = 0;
|
|
161
|
+
if (shift && firstIn !== null) {
|
|
162
|
+
const scheduledStart = zonedToInstant(businessDate, shift.start, timeZone);
|
|
163
|
+
lateMinutes = Math.max(0, Math.round((firstIn - scheduledStart) / 60000) - shift.graceInMinutes);
|
|
164
|
+
}
|
|
165
|
+
if (shift && lastOut !== null) {
|
|
166
|
+
const endDate = crossesMidnight(shift) ? nextDate(businessDate) : businessDate;
|
|
167
|
+
const scheduledEnd = zonedToInstant(endDate, shift.end, timeZone);
|
|
168
|
+
earlyLeaveMinutes = Math.max(0, Math.round((scheduledEnd - lastOut) / 60000) - shift.graceOutMinutes);
|
|
169
|
+
}
|
|
170
|
+
const overtimeMinutes = Math.max(0, workedMinutes - scheduledMinutes);
|
|
171
|
+
const status = anomalies.includes('missing_clock_out')
|
|
172
|
+
? 'pending'
|
|
173
|
+
: workedMinutes > 0
|
|
174
|
+
? 'present'
|
|
175
|
+
: 'partial';
|
|
176
|
+
return {
|
|
177
|
+
businessDate,
|
|
178
|
+
scheduledMinutes,
|
|
179
|
+
workedMinutes,
|
|
180
|
+
breakMinutes: Math.round(breakMs / 60000),
|
|
181
|
+
overtimeMinutes,
|
|
182
|
+
lateMinutes,
|
|
183
|
+
earlyLeaveMinutes,
|
|
184
|
+
status,
|
|
185
|
+
anomalies,
|
|
186
|
+
firstIn,
|
|
187
|
+
lastOut,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=working-time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"working-time.js","sourceRoot":"","sources":["../../src/policy/working-time.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,QAAQ;;;;;iCA6HnB,CAAA;AAkBF,eAAe,QAAQ,CAAA"}
|
package/dist/server/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { defineModule, defineServerModule, packageVersion, uuidv7 } from '@kernhq/kernel';
|
|
4
4
|
import { eq } from 'drizzle-orm';
|
|
5
5
|
import { HrSettings, hrCapabilities, hrContract, hrEvents, hrPermissions, MODULE_ID, } from '../contract/index.js';
|
|
6
|
+
import { hrJobs } from './jobs.js';
|
|
6
7
|
import { COUNTRY_PACKS, packDays } from './packs/index.js';
|
|
7
8
|
import { implement_ } from './router.js';
|
|
8
9
|
import { calendarDays, calendars, offices, people, schema } from './schema.js';
|
|
@@ -27,6 +28,7 @@ export const hrModule = defineServerModule({
|
|
|
27
28
|
schema,
|
|
28
29
|
migrationsFolder: join(dirname(fileURLToPath(import.meta.url)), '../../migrations'),
|
|
29
30
|
router: implement_,
|
|
31
|
+
jobs: hrJobs(),
|
|
30
32
|
/**
|
|
31
33
|
* A workspace that switches HR on gets one office and the calendar for its country.
|
|
32
34
|
*
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAe,cAAc,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACtG,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAChC,OAAO,EACL,UAAU,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,aAAa,EACb,SAAS,GACV,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAE9E,MAAM,CAAC,MAAM,QAAQ,GAAG,kBAAkB,CAAC;IACzC,UAAU,EAAE,YAAY,CAAC;QACvB,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;QACxC,WAAW,EAAE,2DAA2D;QACxE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,aAAa;QAC1B,YAAY,EAAE,cAAc;QAC5B,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,UAAU;QACpB,WAAW,EAAE;YACX,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE;YACrE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE;SAC1E;KACF,CAAC;IACF,sFAAsF;IACtF,QAAQ,EAAE,UAAU;IACpB,MAAM;IACN,gBAAgB,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,kBAAkB,CAAC;IACnF,MAAM,EAAE,UAAU;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JobDef } from '@kernhq/kernel';
|
|
2
|
+
import { inArray } from 'drizzle-orm';
|
|
3
|
+
import { offices } from './schema.js';
|
|
4
|
+
import { todayIn } from './services/db.js';
|
|
5
|
+
/**
|
|
6
|
+
* HR's scheduled work.
|
|
7
|
+
*
|
|
8
|
+
* One rule runs through all of it: **a cron expression fires in UTC, and this module's users do
|
|
9
|
+
* not live there.** A nightly job at 00:00 UTC closes an Amsterdam shift at 01:00 and an Istanbul
|
|
10
|
+
* one at 03:00, and gets Tehran's half-hour offset wrong in a way nobody would ever guess from the
|
|
11
|
+
* code. So the jobs run hourly and fan out per office, deciding for each whether *that office's*
|
|
12
|
+
* local boundary has passed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function hrJobs(): JobDef[];
|
|
15
|
+
export { inArray, offices, todayIn };
|
|
16
|
+
//# sourceMappingURL=jobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/server/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAU,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAW,OAAO,EAAoB,MAAM,aAAa,CAAA;AAChE,OAAO,EAAkB,OAAO,EAAsB,MAAM,aAAa,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAG1C;;;;;;;;GAQG;AACH,wBAAgB,MAAM,IAAI,MAAM,EAAE,CAqLjC;AAeD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { and, eq, inArray, isNull, lte, sql } from 'drizzle-orm';
|
|
2
|
+
import { attendanceDays, offices, punches, schedules } from './schema.js';
|
|
3
|
+
import { AttendanceService } from './services/attendance.js';
|
|
4
|
+
import { todayIn } from './services/db.js';
|
|
5
|
+
import { ResolveService } from './services/resolve.js';
|
|
6
|
+
/**
|
|
7
|
+
* HR's scheduled work.
|
|
8
|
+
*
|
|
9
|
+
* One rule runs through all of it: **a cron expression fires in UTC, and this module's users do
|
|
10
|
+
* not live there.** A nightly job at 00:00 UTC closes an Amsterdam shift at 01:00 and an Istanbul
|
|
11
|
+
* one at 03:00, and gets Tehran's half-hour offset wrong in a way nobody would ever guess from the
|
|
12
|
+
* code. So the jobs run hourly and fan out per office, deciding for each whether *that office's*
|
|
13
|
+
* local boundary has passed.
|
|
14
|
+
*/
|
|
15
|
+
export function hrJobs() {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
/**
|
|
19
|
+
* Keep the punch partitions rolling.
|
|
20
|
+
*
|
|
21
|
+
* Runs monthly, creates a year ahead, and goes through the SQL function that also enables
|
|
22
|
+
* row-level security on what it creates. A partition made with a bare `CREATE TABLE ...
|
|
23
|
+
* PARTITION OF` is readable directly by any role holding SELECT on it, whatever the parent's
|
|
24
|
+
* policy says — which an integration test caught rather than a review.
|
|
25
|
+
*
|
|
26
|
+
* A missing partition still would not lose a punch: the DEFAULT partition catches it. This
|
|
27
|
+
* exists so that never becomes normal.
|
|
28
|
+
*/
|
|
29
|
+
name: 'ensure-partitions',
|
|
30
|
+
cron: '0 3 1 * *',
|
|
31
|
+
handler: async (_input, { kernel }) => {
|
|
32
|
+
await kernel.database.db.execute(sql `
|
|
33
|
+
do $$
|
|
34
|
+
declare
|
|
35
|
+
m date := (date_trunc('month', now()) - interval '1 month')::date;
|
|
36
|
+
stop date := (date_trunc('month', now()) + interval '12 months')::date;
|
|
37
|
+
begin
|
|
38
|
+
while m < stop loop
|
|
39
|
+
perform "mod_hr".ensure_punch_partition(m);
|
|
40
|
+
m := (m + interval '1 month')::date;
|
|
41
|
+
end loop;
|
|
42
|
+
end $$;
|
|
43
|
+
`);
|
|
44
|
+
kernel.log.info({ module: 'hr' }, 'punch partitions ensured');
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
/**
|
|
49
|
+
* Close shifts somebody forgot to clock out of.
|
|
50
|
+
*
|
|
51
|
+
* Hourly, and per office rather than globally: "it is past 3am" is a different moment in every
|
|
52
|
+
* office, and a single UTC-timed sweep would close a Tehran shift mid-afternoon.
|
|
53
|
+
*
|
|
54
|
+
* The auto clock-out is written as a punch like any other, with `method: 'manual'` and a note,
|
|
55
|
+
* so the sheet shows that a machine closed the day rather than the person. That distinction is
|
|
56
|
+
* what a regularization request is later arguing about.
|
|
57
|
+
*/
|
|
58
|
+
name: 'auto-clock-out',
|
|
59
|
+
cron: '5 * * * *',
|
|
60
|
+
handler: async (_input, { kernel }) => {
|
|
61
|
+
const attendance = new AttendanceService();
|
|
62
|
+
const resolve = new ResolveService();
|
|
63
|
+
const workspaces = await activeWorkspaces(kernel);
|
|
64
|
+
for (const workspaceId of workspaces)
|
|
65
|
+
await kernel.database.withWorkspace(workspaceId, async (tx) => {
|
|
66
|
+
const withAuto = await tx
|
|
67
|
+
.select({ id: schedules.id, after: schedules.autoClockOutAfterMinutes })
|
|
68
|
+
.from(schedules)
|
|
69
|
+
.where(and(eq(schedules.workspaceId, workspaceId), isNull(schedules.archivedAt), sql `${schedules.autoClockOutAfterMinutes} is not null`));
|
|
70
|
+
if (!withAuto.length)
|
|
71
|
+
return;
|
|
72
|
+
// Anyone with an `in` and no matching `out`, older than the longest configured window.
|
|
73
|
+
const longest = Math.max(...withAuto.map((s) => s.after ?? 0));
|
|
74
|
+
const cutoff = new Date(Date.now() - longest * 60_000);
|
|
75
|
+
const open = await tx
|
|
76
|
+
.select({
|
|
77
|
+
personId: punches.personId,
|
|
78
|
+
businessDate: punches.businessDate,
|
|
79
|
+
at: punches.at,
|
|
80
|
+
})
|
|
81
|
+
.from(punches)
|
|
82
|
+
.where(and(eq(punches.workspaceId, workspaceId), eq(punches.direction, 'in'), isNull(punches.voidedByPunchId), lte(punches.at, cutoff)));
|
|
83
|
+
for (const row of open) {
|
|
84
|
+
const rows = await attendance.punchesOn(tx, workspaceId, row.personId, row.businessDate);
|
|
85
|
+
const stillOpen = rows.reduce((acc, r) => (r.direction === 'in' ? true : r.direction === 'out' ? false : acc), false);
|
|
86
|
+
if (!stillOpen)
|
|
87
|
+
continue;
|
|
88
|
+
const resolution = await resolve.forPerson(tx, workspaceId, row.personId);
|
|
89
|
+
const schedule = await attendance.scheduleFor(tx, workspaceId, row.personId, row.businessDate);
|
|
90
|
+
if (!schedule.autoClockOutAfterMinutes)
|
|
91
|
+
continue;
|
|
92
|
+
if (Date.now() - row.at.getTime() < schedule.autoClockOutAfterMinutes * 60_000)
|
|
93
|
+
continue;
|
|
94
|
+
await tx.insert(punches).values({
|
|
95
|
+
workspaceId,
|
|
96
|
+
personId: row.personId,
|
|
97
|
+
direction: 'out',
|
|
98
|
+
at: new Date(row.at.getTime() + schedule.autoClockOutAfterMinutes * 60_000),
|
|
99
|
+
businessDate: row.businessDate,
|
|
100
|
+
timezone: resolution.timezone,
|
|
101
|
+
method: 'manual',
|
|
102
|
+
trust: 'trusted',
|
|
103
|
+
note: 'Closed automatically: no clock-out recorded',
|
|
104
|
+
});
|
|
105
|
+
await attendance.recomputeDay(tx, workspaceId, row.personId, row.businessDate, resolution.timezone, schedule);
|
|
106
|
+
kernel.log.info({ module: 'hr', personId: row.personId, businessDate: row.businessDate }, 'auto clock-out');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
/**
|
|
113
|
+
* Rebuild recent unlocked days.
|
|
114
|
+
*
|
|
115
|
+
* Punches recompute their own day inline, so this exists for what that path cannot see: a
|
|
116
|
+
* calendar edited after the fact, a schedule changed retroactively, an enqueue that never
|
|
117
|
+
* ran. Anything it finds and changes is a bug worth knowing about rather than routine
|
|
118
|
+
* maintenance — which is why it logs a count instead of running silently.
|
|
119
|
+
*
|
|
120
|
+
* Locked days are never touched: a closed month must not move underneath a payroll already
|
|
121
|
+
* filed.
|
|
122
|
+
*/
|
|
123
|
+
name: 'reconcile-days',
|
|
124
|
+
cron: '30 2 * * *',
|
|
125
|
+
handler: async (_input, { kernel }) => {
|
|
126
|
+
const attendance = new AttendanceService();
|
|
127
|
+
const resolve = new ResolveService();
|
|
128
|
+
const workspaces = await activeWorkspaces(kernel);
|
|
129
|
+
const WINDOW_DAYS = 14;
|
|
130
|
+
for (const workspaceId of workspaces)
|
|
131
|
+
await kernel.database.withWorkspace(workspaceId, async (tx) => {
|
|
132
|
+
const since = new Date(Date.now() - WINDOW_DAYS * 86_400_000).toISOString().slice(0, 10);
|
|
133
|
+
const days = await tx
|
|
134
|
+
.select({
|
|
135
|
+
personId: attendanceDays.personId,
|
|
136
|
+
businessDate: attendanceDays.businessDate,
|
|
137
|
+
})
|
|
138
|
+
.from(attendanceDays)
|
|
139
|
+
.where(and(eq(attendanceDays.workspaceId, workspaceId), eq(attendanceDays.locked, false), sql `${attendanceDays.businessDate} >= ${since}`));
|
|
140
|
+
let touched = 0;
|
|
141
|
+
for (const day of days) {
|
|
142
|
+
const resolution = await resolve.forPerson(tx, workspaceId, day.personId, day.businessDate);
|
|
143
|
+
const schedule = await attendance.scheduleFor(tx, workspaceId, day.personId, day.businessDate);
|
|
144
|
+
const r = await attendance.recomputeDay(tx, workspaceId, day.personId, day.businessDate, resolution.timezone, schedule);
|
|
145
|
+
if (!r.locked)
|
|
146
|
+
touched++;
|
|
147
|
+
}
|
|
148
|
+
if (touched)
|
|
149
|
+
kernel.log.info({ module: 'hr', workspaceId, touched }, 'days reconciled');
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Workspaces with HR switched on.
|
|
157
|
+
*
|
|
158
|
+
* Read from this module's own tables rather than asked of core on every tick: a workspace with an
|
|
159
|
+
* office has HR enabled, and one job run should not be N broker calls.
|
|
160
|
+
*/
|
|
161
|
+
async function activeWorkspaces(kernel) {
|
|
162
|
+
const { rows } = await kernel.database.pool.query(`select distinct workspace_id from mod_hr.offices where archived_at is null`);
|
|
163
|
+
return rows.map((r) => r.workspace_id);
|
|
164
|
+
}
|
|
165
|
+
export { inArray, offices, todayIn };
|
|
166
|
+
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/server/jobs.ts"],"names":[],"mappings":"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"}
|