@kernhq/module-hr 0.1.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 (75) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +71 -0
  3. package/dist/contract/capabilities.d.ts +45 -0
  4. package/dist/contract/capabilities.d.ts.map +1 -0
  5. package/dist/contract/capabilities.js +114 -0
  6. package/dist/contract/capabilities.js.map +1 -0
  7. package/dist/contract/events.d.ts +74 -0
  8. package/dist/contract/events.d.ts.map +1 -0
  9. package/dist/contract/events.js +63 -0
  10. package/dist/contract/events.js.map +1 -0
  11. package/dist/contract/index.d.ts +15 -0
  12. package/dist/contract/index.d.ts.map +1 -0
  13. package/dist/contract/index.js +15 -0
  14. package/dist/contract/index.js.map +1 -0
  15. package/dist/contract/models.d.ts +457 -0
  16. package/dist/contract/models.d.ts.map +1 -0
  17. package/dist/contract/models.js +381 -0
  18. package/dist/contract/models.js.map +1 -0
  19. package/dist/contract/permissions.d.ts +174 -0
  20. package/dist/contract/permissions.d.ts.map +1 -0
  21. package/dist/contract/permissions.js +209 -0
  22. package/dist/contract/permissions.js.map +1 -0
  23. package/dist/contract/router.d.ts +2720 -0
  24. package/dist/contract/router.d.ts.map +1 -0
  25. package/dist/contract/router.js +520 -0
  26. package/dist/contract/router.js.map +1 -0
  27. package/dist/contract/settings.d.ts +20 -0
  28. package/dist/contract/settings.d.ts.map +1 -0
  29. package/dist/contract/settings.js +34 -0
  30. package/dist/contract/settings.js.map +1 -0
  31. package/dist/policy/calendar.d.ts +64 -0
  32. package/dist/policy/calendar.d.ts.map +1 -0
  33. package/dist/policy/calendar.js +113 -0
  34. package/dist/policy/calendar.js.map +1 -0
  35. package/dist/server/index.d.ts +8 -0
  36. package/dist/server/index.d.ts.map +1 -0
  37. package/dist/server/index.js +141 -0
  38. package/dist/server/index.js.map +1 -0
  39. package/dist/server/packs/index.d.ts +50 -0
  40. package/dist/server/packs/index.d.ts.map +1 -0
  41. package/dist/server/packs/index.js +121 -0
  42. package/dist/server/packs/index.js.map +1 -0
  43. package/dist/server/router.d.ts +3095 -0
  44. package/dist/server/router.d.ts.map +1 -0
  45. package/dist/server/router.js +1537 -0
  46. package/dist/server/router.js.map +1 -0
  47. package/dist/server/schema.d.ts +2828 -0
  48. package/dist/server/schema.d.ts.map +1 -0
  49. package/dist/server/schema.js +313 -0
  50. package/dist/server/schema.js.map +1 -0
  51. package/dist/server/services/db.d.ts +22 -0
  52. package/dist/server/services/db.d.ts.map +1 -0
  53. package/dist/server/services/db.js +23 -0
  54. package/dist/server/services/db.js.map +1 -0
  55. package/dist/server/services/people.d.ts +110 -0
  56. package/dist/server/services/people.d.ts.map +1 -0
  57. package/dist/server/services/people.js +182 -0
  58. package/dist/server/services/people.js.map +1 -0
  59. package/dist/server/services/resolve.d.ts +66 -0
  60. package/dist/server/services/resolve.d.ts.map +1 -0
  61. package/dist/server/services/resolve.js +145 -0
  62. package/dist/server/services/resolve.js.map +1 -0
  63. package/migrations/0000_init.sql +223 -0
  64. package/migrations/0001_rls.sql +141 -0
  65. package/migrations/meta/0000_snapshot.json +1715 -0
  66. package/migrations/meta/_journal.json +20 -0
  67. package/package.json +59 -0
  68. package/src/client/index.ts +62 -0
  69. package/src/contract/capabilities.ts +117 -0
  70. package/src/contract/events.ts +84 -0
  71. package/src/contract/index.ts +14 -0
  72. package/src/contract/models.ts +437 -0
  73. package/src/contract/permissions.ts +217 -0
  74. package/src/contract/router.ts +611 -0
  75. package/src/contract/settings.ts +35 -0
@@ -0,0 +1,64 @@
1
+ import type { CalendarDay, IsoDate, WorkingWeek } from '../contract/index.js';
2
+ /**
3
+ * Working-day arithmetic, as pure functions.
4
+ *
5
+ * No database, no kernel, no `Date.now()`. Everything here takes the calendar and the week as
6
+ * arguments and returns a number, which is what makes the cases that actually break payroll —
7
+ * Iran's Friday weekend, a half Thursday, a company closure landing on a public holiday, a range
8
+ * that spans a daylight-saving boundary — testable as a table rather than against a running
9
+ * Postgres.
10
+ *
11
+ * **Dates here are calendar dates, never instants.** A working day is a property of a place's
12
+ * calendar, not of a moment: whether 2026-03-29 is a working day in Amsterdam does not depend on
13
+ * what time it is, and the fact that the day happens to be 23 hours long is somebody else's problem
14
+ * (the attendance day sheet's, when it arrives). Doing this with `Date` arithmetic is where the DST
15
+ * bugs come from, so nothing here adds 86400 seconds to anything.
16
+ */
17
+ declare const WEEKDAY_KEYS: readonly ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
18
+ export type WeekdayKey = (typeof WEEKDAY_KEYS)[number];
19
+ /**
20
+ * Which weekday an ISO date falls on, without constructing a local `Date`.
21
+ *
22
+ * `new Date('2026-08-24')` is parsed as UTC midnight and then read back in the *runtime's* zone, so
23
+ * west of Greenwich it reports the previous day. Every "off by one day" bug in a date library is
24
+ * some version of that. This computes the weekday arithmetically from the civil date, so the answer
25
+ * is the same on every machine.
26
+ */
27
+ export declare function weekdayOf(date: IsoDate): WeekdayKey;
28
+ /** Every date from `from` to `to` inclusive, as ISO strings. Pure civil-date arithmetic. */
29
+ export declare function datesBetween(from: IsoDate, to: IsoDate): IsoDate[];
30
+ export declare function daysInMonth(year: number, month: number): number;
31
+ export declare const isLeapYear: (y: number) => boolean;
32
+ export interface DayResult {
33
+ date: IsoDate;
34
+ /** 1 a full working day, 0.5 a half, 0 not worked. */
35
+ fraction: number;
36
+ /** Why it is not a plain working day — the holiday's name, or null. */
37
+ reason: string | null;
38
+ }
39
+ /**
40
+ * How much of each day in a range is worked.
41
+ *
42
+ * Precedence, nearest wins: **a calendar day beats the working week.** A `working_override` on a
43
+ * Saturday makes it a working day even though the week says otherwise, and a company closure on a
44
+ * Tuesday makes it not one. That direction matters — the other way round, a company that works
45
+ * through a national holiday could not say so.
46
+ */
47
+ export declare function workingDays(from: IsoDate, to: IsoDate, week: WorkingWeek, days: ReadonlyArray<Pick<CalendarDay, 'date' | 'name' | 'workingFraction'>>): DayResult[];
48
+ /** The number a leave request costs, or a month's expected working days. */
49
+ export declare const countWorkingDays: (results: readonly DayResult[]) => number;
50
+ /**
51
+ * Compose a calendar with the ones it extends: nearest wins, per date and kind.
52
+ *
53
+ * `chain` runs nearest-first — the office's own calendar, then the country pack it extends. A
54
+ * `custom` day on the near calendar overrides a `pack` day on the far one for the same date, which
55
+ * is exactly what "we work through this national holiday" means.
56
+ */
57
+ export declare function composeDays<T extends Pick<CalendarDay, 'date' | 'kind' | 'calendarId'>>(chain: ReadonlyArray<{
58
+ calendarId: string;
59
+ days: readonly T[];
60
+ }>): Array<T & {
61
+ overrides: boolean;
62
+ }>;
63
+ export {};
64
+ //# sourceMappingURL=calendar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../src/policy/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAE7E;;;;;;;;;;;;;;GAcG;AAEH,QAAA,MAAM,YAAY,4DAA6D,CAAA;AAC/E,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAOnD;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,GAAG,OAAO,EAAE,CAqBlE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/D;AACD,eAAO,MAAM,UAAU,GAAI,GAAG,MAAM,YAAoD,CAAA;AAExF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,OAAO,CAAA;IACb,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,iBAAiB,CAAC,CAAC,GAC1E,SAAS,EAAE,CAQb;AAED,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,GAAI,SAAS,SAAS,SAAS,EAAE,KAAG,MACR,CAAA;AAMzD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAAC,EACrF,KAAK,EAAE,aAAa,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;CAAE,CAAC,GAC/D,KAAK,CAAC,CAAC,GAAG;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAiBnC"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Working-day arithmetic, as pure functions.
3
+ *
4
+ * No database, no kernel, no `Date.now()`. Everything here takes the calendar and the week as
5
+ * arguments and returns a number, which is what makes the cases that actually break payroll —
6
+ * Iran's Friday weekend, a half Thursday, a company closure landing on a public holiday, a range
7
+ * that spans a daylight-saving boundary — testable as a table rather than against a running
8
+ * Postgres.
9
+ *
10
+ * **Dates here are calendar dates, never instants.** A working day is a property of a place's
11
+ * calendar, not of a moment: whether 2026-03-29 is a working day in Amsterdam does not depend on
12
+ * what time it is, and the fact that the day happens to be 23 hours long is somebody else's problem
13
+ * (the attendance day sheet's, when it arrives). Doing this with `Date` arithmetic is where the DST
14
+ * bugs come from, so nothing here adds 86400 seconds to anything.
15
+ */
16
+ const WEEKDAY_KEYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
17
+ /**
18
+ * Which weekday an ISO date falls on, without constructing a local `Date`.
19
+ *
20
+ * `new Date('2026-08-24')` is parsed as UTC midnight and then read back in the *runtime's* zone, so
21
+ * west of Greenwich it reports the previous day. Every "off by one day" bug in a date library is
22
+ * some version of that. This computes the weekday arithmetically from the civil date, so the answer
23
+ * is the same on every machine.
24
+ */
25
+ export function weekdayOf(date) {
26
+ const [y, m, d] = date.split('-').map(Number);
27
+ // Sakamoto's algorithm — civil date in, day of week out, no Date object involved.
28
+ const t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
29
+ const yy = m < 3 ? y - 1 : y;
30
+ const index = (yy + Math.floor(yy / 4) - Math.floor(yy / 100) + Math.floor(yy / 400) + t[m - 1] + d) % 7;
31
+ return WEEKDAY_KEYS[index];
32
+ }
33
+ /** Every date from `from` to `to` inclusive, as ISO strings. Pure civil-date arithmetic. */
34
+ export function datesBetween(from, to) {
35
+ const out = [];
36
+ let [y, m, d] = from.split('-').map(Number);
37
+ const iso = () => `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
38
+ // A guard rather than a `while (true)`: a reversed or absurd range should return nothing or stop,
39
+ // never spin. Ten years of days is far more than any caller here asks for.
40
+ for (let guard = 0; guard < 3700; guard++) {
41
+ const here = iso();
42
+ if (here > to)
43
+ break;
44
+ out.push(here);
45
+ d++;
46
+ if (d > daysInMonth(y, m)) {
47
+ d = 1;
48
+ m++;
49
+ if (m > 12) {
50
+ m = 1;
51
+ y++;
52
+ }
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ export function daysInMonth(year, month) {
58
+ if (month === 2)
59
+ return isLeapYear(year) ? 29 : 28;
60
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
61
+ }
62
+ export const isLeapYear = (y) => (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
63
+ /**
64
+ * How much of each day in a range is worked.
65
+ *
66
+ * Precedence, nearest wins: **a calendar day beats the working week.** A `working_override` on a
67
+ * Saturday makes it a working day even though the week says otherwise, and a company closure on a
68
+ * Tuesday makes it not one. That direction matters — the other way round, a company that works
69
+ * through a national holiday could not say so.
70
+ */
71
+ export function workingDays(from, to, week, days) {
72
+ const byDate = new Map(days.map((d) => [d.date, d]));
73
+ return datesBetween(from, to).map((date) => {
74
+ const special = byDate.get(date);
75
+ if (special)
76
+ return { date, fraction: clamp(special.workingFraction), reason: special.name };
77
+ const fraction = clamp(week[weekdayOf(date)] ?? 0);
78
+ return { date, fraction, reason: fraction === 0 ? 'weekend' : null };
79
+ });
80
+ }
81
+ /** The number a leave request costs, or a month's expected working days. */
82
+ export const countWorkingDays = (results) => round2(results.reduce((sum, r) => sum + r.fraction, 0));
83
+ const clamp = (n) => (n < 0 ? 0 : n > 1 ? 1 : n);
84
+ /** Halves and quarters add up exactly; floating point does not. Two places is enough for both. */
85
+ const round2 = (n) => Math.round(n * 100) / 100;
86
+ /**
87
+ * Compose a calendar with the ones it extends: nearest wins, per date and kind.
88
+ *
89
+ * `chain` runs nearest-first — the office's own calendar, then the country pack it extends. A
90
+ * `custom` day on the near calendar overrides a `pack` day on the far one for the same date, which
91
+ * is exactly what "we work through this national holiday" means.
92
+ */
93
+ export function composeDays(chain) {
94
+ const seen = new Map();
95
+ for (const level of chain) {
96
+ for (const day of level.days) {
97
+ const key = `${day.date}:${day.kind}`;
98
+ // Nearest wins: a level further along the chain never replaces what an earlier one supplied.
99
+ if (!seen.has(key))
100
+ seen.set(key, { ...day, overrides: false });
101
+ }
102
+ }
103
+ // A near day that shadows a far day of a *different* kind on the same date still overrides it —
104
+ // "company closure" on a date the pack calls a working day, for instance.
105
+ const byDate = new Map();
106
+ for (const day of seen.values())
107
+ byDate.set(day.date, (byDate.get(day.date) ?? 0) + 1);
108
+ const nearest = chain[0]?.calendarId;
109
+ return [...seen.values()]
110
+ .map((d) => ({ ...d, overrides: (byDate.get(d.date) ?? 0) > 1 && d.calendarId === nearest }))
111
+ .sort((a, b) => a.date.localeCompare(b.date));
112
+ }
113
+ //# sourceMappingURL=calendar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calendar.js","sourceRoot":"","sources":["../../src/policy/calendar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare const hrModule: import("@kernhq/kernel").ServerModule<import("zod").ZodObject<{
2
+ country: import("zod").ZodDefault<import("zod").ZodString>;
3
+ employeeNumberPrefix: import("zod").ZodDefault<import("zod").ZodString>;
4
+ employeeNumberNext: import("zod").ZodDefault<import("zod").ZodNumber>;
5
+ directoryVisibleToMembers: import("zod").ZodDefault<import("zod").ZodBoolean>;
6
+ }, import("zod/v4/core").$strip>>;
7
+ export default hrModule;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,QAAQ;;;;;iCA4HnB,CAAA;AAkBF,eAAe,QAAQ,CAAA"}
@@ -0,0 +1,141 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { defineModule, defineServerModule, packageVersion, uuidv7 } from '@kernhq/kernel';
4
+ import { eq } from 'drizzle-orm';
5
+ import { HrSettings, hrCapabilities, hrContract, hrEvents, hrPermissions, MODULE_ID, } from '../contract/index.js';
6
+ import { COUNTRY_PACKS, packDays } from './packs/index.js';
7
+ import { implement_ } from './router.js';
8
+ import { calendarDays, calendars, offices, people, schema } from './schema.js';
9
+ export const hrModule = defineServerModule({
10
+ definition: defineModule({
11
+ id: MODULE_ID,
12
+ name: 'People',
13
+ version: packageVersion(import.meta.url),
14
+ description: 'Staff directory, offices, org chart and holiday calendars',
15
+ icon: 'users',
16
+ permissions: hrPermissions,
17
+ capabilities: hrCapabilities,
18
+ events: hrEvents,
19
+ settings: HrSettings,
20
+ objectTypes: [
21
+ { type: 'person', label: 'Person', icon: 'user', channelable: false },
22
+ { type: 'office', label: 'Office', icon: 'building', channelable: false },
23
+ ],
24
+ }),
25
+ /** Attached so the developer panel can check the router against what was promised. */
26
+ contract: hrContract,
27
+ schema,
28
+ migrationsFolder: join(dirname(fileURLToPath(import.meta.url)), '../../migrations'),
29
+ router: implement_,
30
+ /**
31
+ * A workspace that switches HR on gets one office and the calendar for its country.
32
+ *
33
+ * This is what lets the `offices` capability be a *reveal* rather than a migration. The concept
34
+ * exists from the first second — every person is assigned to this office, the resolution ladder
35
+ * always has a rung to land on, and nothing in the module needs a "no office" branch. A workspace
36
+ * that only ever has one place of work never sees the word.
37
+ *
38
+ * Idempotent: it runs again when somebody switches HR off and back on, and must not make a second
39
+ * default office.
40
+ */
41
+ onWorkspaceEnabled: async (workspaceId, kernel) => {
42
+ const settings = await kernel.settings.module(workspaceId, MODULE_ID, HrSettings);
43
+ await kernel.database.withWorkspace(workspaceId, async (tx) => {
44
+ const existing = await tx.select().from(offices).where(eq(offices.workspaceId, workspaceId)).limit(1);
45
+ if (existing.length)
46
+ return;
47
+ const country = settings.country;
48
+ const pack = COUNTRY_PACKS[country];
49
+ let calendarId = null;
50
+ if (pack) {
51
+ const [calendar] = await tx
52
+ .insert(calendars)
53
+ .values({
54
+ id: uuidv7(),
55
+ workspaceId,
56
+ name: pack.name,
57
+ country,
58
+ workingWeek: pack.workingWeek,
59
+ source: 'pack',
60
+ packKey: country,
61
+ })
62
+ .returning();
63
+ calendarId = calendar.id;
64
+ const year = new Date().getUTCFullYear();
65
+ const days = packDays(country, year);
66
+ if (days.length)
67
+ await tx.insert(calendarDays).values(days.map((d) => ({
68
+ id: uuidv7(),
69
+ workspaceId,
70
+ calendarId: calendar.id,
71
+ date: d.date,
72
+ kind: d.kind,
73
+ name: d.name,
74
+ workingFraction: String(d.workingFraction),
75
+ source: 'pack',
76
+ paid: true,
77
+ })));
78
+ }
79
+ await tx.insert(offices).values({
80
+ id: uuidv7(),
81
+ workspaceId,
82
+ name: 'Head office',
83
+ kind: 'head_office',
84
+ country,
85
+ // The instance's zone would be an accident of deployment; the pack's country is a decision.
86
+ timezone: DEFAULT_TIMEZONE_FOR[country] ?? 'UTC',
87
+ calendarId,
88
+ isDefault: true,
89
+ });
90
+ });
91
+ },
92
+ /**
93
+ * Answers other modules may ask, without reaching into `mod_hr`.
94
+ *
95
+ * A join across schemas is the thing the module boundary exists to prevent, so anything chat, a
96
+ * future calendar or payroll wants goes through here with its own principal — which means a
97
+ * permission check still applies rather than being bypassed by the shape of the query.
98
+ */
99
+ procedures: {
100
+ 'person.byUserId': {
101
+ handler: async (input, { kernel }) => kernel.database.withWorkspace(input.workspaceId, async (tx) => {
102
+ const [row] = await tx
103
+ .select({ id: people.id, displayName: people.displayName, status: people.status })
104
+ .from(people)
105
+ .where(eq(people.userId, input.userId))
106
+ .limit(1);
107
+ return row ?? null;
108
+ }),
109
+ },
110
+ },
111
+ subscriptions: {
112
+ /**
113
+ * A member leaving the workspace does **not** delete their HR record.
114
+ *
115
+ * Employment history outlives an account: payroll, tax and labour law all require it to, and
116
+ * "she left, so we deleted her file" is the answer that fails an audit. The link is cleared so
117
+ * the record stops pointing at an account that is gone.
118
+ */
119
+ 'core.member.removed': async (event, kernel) => {
120
+ const { workspaceId, userId } = event.payload;
121
+ await kernel.database.withWorkspace(workspaceId, (tx) => tx.update(people).set({ userId: null }).where(eq(people.userId, userId)));
122
+ },
123
+ },
124
+ });
125
+ /**
126
+ * A sensible zone per country pack, used only to build the first office.
127
+ *
128
+ * Every one of these is somewhere an administrator can change in a single click, so being wrong is
129
+ * cheap; being *absent* is not, because a workspace with no zone attributes everybody's day in UTC.
130
+ * Countries spanning several zones get their most populous one.
131
+ */
132
+ const DEFAULT_TIMEZONE_FOR = {
133
+ TR: 'Europe/Istanbul',
134
+ DE: 'Europe/Berlin',
135
+ GB: 'Europe/London',
136
+ US: 'America/New_York',
137
+ NL: 'Europe/Amsterdam',
138
+ IR: 'Asia/Tehran',
139
+ };
140
+ export default hrModule;
141
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { CalendarDayKind, WorkingWeek } from '../../contract/index.js';
2
+ /**
3
+ * Country packs: a working week and a year's public holidays, as **data**.
4
+ *
5
+ * These are defaults a workspace can edit, not a claim about the law. Kern does not give legal
6
+ * advice, and the workspace owner is responsible for their own configuration — the documentation
7
+ * says so, and so should any screen that offers to apply one.
8
+ *
9
+ * Three things worth knowing before editing this file:
10
+ *
11
+ * - **Fixed-date holidays only, for now.** Anything that moves — Easter, and every Islamic holiday,
12
+ * which follows the lunar Hijri calendar and drifts about eleven days a year against this one —
13
+ * has to be published per year rather than computed. `movable: true` marks a pack that is
14
+ * therefore incomplete beyond the years listed, and the preview says so rather than quietly
15
+ * returning a short list.
16
+ * - **A pack applies to one year at a time.** That is deliberate: a refresh is an operation an
17
+ * administrator performs and reviews, not something that happens to them.
18
+ * - **Every day here lands as `source: 'pack'`.** That is what lets an upgrade replace them and
19
+ * leave days HR added alone.
20
+ */
21
+ export interface PackDay {
22
+ /** `MM-DD` for a fixed holiday; a full `YYYY-MM-DD` for one that moves. */
23
+ on: string;
24
+ name: string;
25
+ kind: CalendarDayKind;
26
+ workingFraction?: number;
27
+ }
28
+ export interface CountryPack {
29
+ name: string;
30
+ workingWeek: WorkingWeek;
31
+ /** Holidays on the same date every year. */
32
+ fixed: PackDay[];
33
+ /** Holidays that move, listed per year. Absent years yield the fixed set alone. */
34
+ movableByYear?: Record<string, PackDay[]>;
35
+ /** True when this country has holidays that move and are not listed for every year. */
36
+ movable: boolean;
37
+ /** Where the dates came from, so a wrong one can be traced rather than argued about. */
38
+ source: string;
39
+ }
40
+ export declare const COUNTRY_PACKS: Record<string, CountryPack>;
41
+ /** A pack's days for one year, as concrete dates. */
42
+ export declare function packDays(packKey: string, year: number): Array<{
43
+ date: string;
44
+ name: string;
45
+ kind: CalendarDayKind;
46
+ workingFraction: number;
47
+ }>;
48
+ /** True when this pack cannot be complete for the year — the caller should say so before applying. */
49
+ export declare const packIsIncompleteFor: (packKey: string, year: number) => boolean;
50
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/packs/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE3E;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,OAAO;IACtB,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,eAAe,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,WAAW,CAAA;IACxB,4CAA4C;IAC5C,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;IACzC,uFAAuF;IACvF,OAAO,EAAE,OAAO,CAAA;IAChB,wFAAwF;IACxF,MAAM,EAAE,MAAM,CAAA;CACf;AAID,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAiGrD,CAAA;AAED,qDAAqD;AACrD,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC,CAgBvF;AAED,sGAAsG;AACtG,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,MAAM,MAAM,KAAG,OAInE,CAAA"}
@@ -0,0 +1,121 @@
1
+ const MON_FRI = { mon: 1, tue: 1, wed: 1, thu: 1, fri: 1, sat: 0, sun: 0 };
2
+ export const COUNTRY_PACKS = {
3
+ TR: {
4
+ name: 'Türkiye',
5
+ workingWeek: MON_FRI,
6
+ source: 'Law 2429 on National Holidays and General Holidays',
7
+ // Ramazan and Kurban Bayramı follow the Hijri calendar and are not listed here.
8
+ movable: true,
9
+ fixed: [
10
+ { on: '01-01', name: 'Yılbaşı', kind: 'public_holiday' },
11
+ { on: '04-23', name: 'Ulusal Egemenlik ve Çocuk Bayramı', kind: 'public_holiday' },
12
+ { on: '05-01', name: 'Emek ve Dayanışma Günü', kind: 'public_holiday' },
13
+ { on: '05-19', name: 'Atatürk’ü Anma, Gençlik ve Spor Bayramı', kind: 'public_holiday' },
14
+ { on: '07-15', name: 'Demokrasi ve Millî Birlik Günü', kind: 'public_holiday' },
15
+ { on: '08-30', name: 'Zafer Bayramı', kind: 'public_holiday' },
16
+ { on: '10-29', name: 'Cumhuriyet Bayramı', kind: 'public_holiday' },
17
+ ],
18
+ },
19
+ DE: {
20
+ name: 'Deutschland',
21
+ workingWeek: MON_FRI,
22
+ // Bavaria, Baden-Württemberg and Saxony each add their own; an office carries a `region`
23
+ // precisely so a per-state pack can be added without changing anything here.
24
+ source: 'Federal (bundeseinheitliche) public holidays; states add their own',
25
+ movable: true,
26
+ fixed: [
27
+ { on: '01-01', name: 'Neujahr', kind: 'public_holiday' },
28
+ { on: '05-01', name: 'Tag der Arbeit', kind: 'public_holiday' },
29
+ { on: '10-03', name: 'Tag der Deutschen Einheit', kind: 'public_holiday' },
30
+ { on: '12-25', name: 'Erster Weihnachtstag', kind: 'public_holiday' },
31
+ { on: '12-26', name: 'Zweiter Weihnachtstag', kind: 'public_holiday' },
32
+ ],
33
+ },
34
+ GB: {
35
+ name: 'United Kingdom',
36
+ workingWeek: MON_FRI,
37
+ // England and Wales only. Scotland and Northern Ireland differ, and the substitute-day rule
38
+ // (a holiday on a weekend moves to the next weekday) is not applied here.
39
+ source: 'GOV.UK bank holidays, England and Wales',
40
+ movable: true,
41
+ fixed: [
42
+ { on: '01-01', name: "New Year's Day", kind: 'public_holiday' },
43
+ { on: '12-25', name: 'Christmas Day', kind: 'public_holiday' },
44
+ { on: '12-26', name: 'Boxing Day', kind: 'public_holiday' },
45
+ ],
46
+ },
47
+ US: {
48
+ name: 'United States',
49
+ workingWeek: MON_FRI,
50
+ // Federal holidays only. There is no federal minimum paid leave, and state sick-leave law
51
+ // differs sharply — which is why this pack deliberately carries no leave defaults at all.
52
+ source: '5 U.S.C. §6103, federal holidays',
53
+ movable: true,
54
+ fixed: [
55
+ { on: '01-01', name: "New Year's Day", kind: 'public_holiday' },
56
+ { on: '06-19', name: 'Juneteenth', kind: 'public_holiday' },
57
+ { on: '07-04', name: 'Independence Day', kind: 'public_holiday' },
58
+ { on: '11-11', name: 'Veterans Day', kind: 'public_holiday' },
59
+ { on: '12-25', name: 'Christmas Day', kind: 'public_holiday' },
60
+ ],
61
+ },
62
+ NL: {
63
+ name: 'Nederland',
64
+ workingWeek: MON_FRI,
65
+ source: 'Algemeen erkende feestdagen',
66
+ movable: true,
67
+ fixed: [
68
+ { on: '01-01', name: 'Nieuwjaarsdag', kind: 'public_holiday' },
69
+ { on: '04-27', name: 'Koningsdag', kind: 'public_holiday' },
70
+ { on: '05-05', name: 'Bevrijdingsdag', kind: 'public_holiday' },
71
+ { on: '12-25', name: 'Eerste Kerstdag', kind: 'public_holiday' },
72
+ { on: '12-26', name: 'Tweede Kerstdag', kind: 'public_holiday' },
73
+ ],
74
+ },
75
+ IR: {
76
+ name: 'ایران',
77
+ // Friday is the weekend and Thursday is commonly a half day. This is the pack that proves the
78
+ // working week has to be data: a Monday-to-Friday assumption is wrong for a whole country.
79
+ workingWeek: { sat: 1, sun: 1, mon: 1, tue: 1, wed: 1, thu: 0.5, fri: 0 },
80
+ // Nowruz is fixed against the Persian solar calendar, so its Gregorian date shifts by a day
81
+ // across leap years; every religious holiday follows the lunar Hijri calendar and moves by
82
+ // about eleven days a year. Neither is computed here.
83
+ source: 'Iranian national and religious holidays',
84
+ movable: true,
85
+ fixed: [
86
+ { on: '03-20', name: 'نوروز', kind: 'public_holiday' },
87
+ { on: '03-21', name: 'نوروز', kind: 'public_holiday' },
88
+ { on: '03-22', name: 'نوروز', kind: 'public_holiday' },
89
+ { on: '03-23', name: 'نوروز', kind: 'public_holiday' },
90
+ { on: '04-01', name: 'روز جمهوری اسلامی', kind: 'public_holiday' },
91
+ { on: '04-02', name: 'روز طبیعت', kind: 'public_holiday' },
92
+ ],
93
+ },
94
+ };
95
+ /** A pack's days for one year, as concrete dates. */
96
+ export function packDays(packKey, year) {
97
+ const pack = COUNTRY_PACKS[packKey];
98
+ if (!pack)
99
+ return [];
100
+ const fixed = pack.fixed.map((d) => ({
101
+ date: `${year}-${d.on}`,
102
+ name: d.name,
103
+ kind: d.kind,
104
+ workingFraction: d.workingFraction ?? 0,
105
+ }));
106
+ const movable = (pack.movableByYear?.[String(year)] ?? []).map((d) => ({
107
+ date: d.on,
108
+ name: d.name,
109
+ kind: d.kind,
110
+ workingFraction: d.workingFraction ?? 0,
111
+ }));
112
+ return [...fixed, ...movable].sort((a, b) => a.date.localeCompare(b.date));
113
+ }
114
+ /** True when this pack cannot be complete for the year — the caller should say so before applying. */
115
+ export const packIsIncompleteFor = (packKey, year) => {
116
+ const pack = COUNTRY_PACKS[packKey];
117
+ if (!pack)
118
+ return false;
119
+ return pack.movable && !pack.movableByYear?.[String(year)];
120
+ };
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/packs/index.ts"],"names":[],"mappings":"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"}