@kernhq/module-hr 0.10.0 → 0.10.2
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 +1 -0
- package/dist/contract/attendance.d.ts.map +1 -1
- package/dist/contract/attendance.js +9 -0
- package/dist/contract/attendance.js.map +1 -1
- package/dist/contract/capabilities.d.ts +16 -0
- package/dist/contract/capabilities.d.ts.map +1 -1
- package/dist/contract/capabilities.js +16 -0
- package/dist/contract/capabilities.js.map +1 -1
- package/dist/contract/router.d.ts +1 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/policy/accrual.d.ts +35 -4
- package/dist/policy/accrual.d.ts.map +1 -1
- package/dist/policy/accrual.js +141 -15
- package/dist/policy/accrual.js.map +1 -1
- package/dist/policy/working-time.d.ts +115 -9
- package/dist/policy/working-time.d.ts.map +1 -1
- package/dist/policy/working-time.js +120 -20
- package/dist/policy/working-time.js.map +1 -1
- package/dist/server/jobs.d.ts +14 -6
- package/dist/server/jobs.d.ts.map +1 -1
- package/dist/server/jobs.js +489 -144
- package/dist/server/jobs.js.map +1 -1
- package/dist/server/packs/index.d.ts +14 -0
- package/dist/server/packs/index.d.ts.map +1 -1
- package/dist/server/packs/index.js +19 -0
- package/dist/server/packs/index.js.map +1 -1
- package/dist/server/router.d.ts +1888 -111
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +154 -63
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +17 -0
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +81 -2
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/approvals.d.ts +0 -2
- package/dist/server/services/approvals.d.ts.map +1 -1
- package/dist/server/services/approvals.js +12 -4
- package/dist/server/services/approvals.js.map +1 -1
- package/dist/server/services/attendance.d.ts +120 -8
- package/dist/server/services/attendance.d.ts.map +1 -1
- package/dist/server/services/attendance.js +226 -14
- package/dist/server/services/attendance.js.map +1 -1
- package/dist/server/services/ledger.d.ts +0 -2
- package/dist/server/services/ledger.d.ts.map +1 -1
- package/dist/server/services/ledger.js +1 -2
- package/dist/server/services/ledger.js.map +1 -1
- package/dist/server/services/policies.d.ts +6 -2
- package/dist/server/services/policies.d.ts.map +1 -1
- package/dist/server/services/policies.js +7 -2
- package/dist/server/services/policies.js.map +1 -1
- package/migrations/0005_approval_requester.sql +11 -1
- package/migrations/0006_schedule_no_overlap.sql +85 -0
- package/migrations/0007_hot_path_indexes.sql +38 -0
- package/migrations/0009_beyond_cap_minutes.sql +15 -0
- package/migrations/meta/0005_snapshot.json +4047 -0
- package/migrations/meta/0007_snapshot.json +4225 -0
- package/migrations/meta/0009_snapshot.json +4231 -0
- package/migrations/meta/_journal.json +21 -0
- package/package.json +7 -7
- package/src/client/components/ClockControls.svelte +127 -20
- package/src/client/components/ClockControls.test.ts +446 -0
- package/src/client/components/DelegationDialog.svelte +12 -3
- package/src/client/components/LeaveRequestDialog.svelte +254 -25
- package/src/client/components/PersonFormDialog.svelte +53 -3
- package/src/client/components/PersonPanel.svelte +134 -26
- package/src/client/i18n.ts +5 -927
- package/src/client/messages.test.ts +154 -0
- package/src/client/messages.ts +3388 -0
- package/src/client/mock.ts +1576 -161
- package/src/client/module.ts +15 -1
- package/src/client/pages/ApprovalsPage.svelte +120 -46
- package/src/client/pages/AttendancePage.svelte +106 -20
- package/src/client/pages/DirectoryPage.svelte +192 -55
- package/src/client/pages/LeavePage.svelte +280 -25
- package/src/client/pages/OfficesPage.svelte +26 -8
- package/src/client/pages/leave-and-attendance.test.ts +588 -0
- package/src/client/query.ts +12 -0
- package/src/client/settings/CalendarsSettings.svelte +1363 -12
- package/src/client/settings/CapabilitiesSettings.svelte +276 -19
- package/src/client/settings/GeneralSettings.svelte +420 -0
- package/src/client/settings/LeaveSettings.svelte +884 -12
- package/src/client/settings/OfficesSettings.svelte +1182 -12
- package/src/client/settings/SchedulesSettings.svelte +1149 -12
- package/src/client/summary.ts +14 -7
- package/src/client/widgets/ApprovalsWidget.svelte +133 -20
- package/src/client/widgets/HeadcountWidget.svelte +74 -8
- package/src/client/widgets/LeaveBalanceWidget.svelte +40 -5
- package/src/client/widgets/WhosOutWidget.svelte +47 -8
- package/src/contract/attendance.ts +9 -0
- package/src/contract/capabilities.ts +17 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../../src/server/services/ledger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAW,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../../src/server/services/ledger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAW,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AA6B5F,0FAA0F;AAC1F,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAe,EAAE,IAAY,EAAU,EAAE,CAC9D,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;AAEvG,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,CAAA;AAErC,MAAM,OAAO,aAAa;IACxB;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAM,EAAE,WAAmB,EAAE,QAAgB,EAAE,UAAkB;QAC9E,MAAM,KAAK,GAAG,MAAM,EAAE;aACnB,MAAM,EAAE;aACR,IAAI,CAAC,UAAU,CAAC;aAChB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,GAAG,CAAA,GAAG,UAAU,CAAC,UAAU,UAAU,CAAC,CAAC,CAAA;QAE7F,MAAM,IAAI,GAAG,MAAM,EAAE;aAClB,MAAM,CAAC;YACN,WAAW,EAAE,WAAW,CAAC,WAAW;YACpC,KAAK,EAAE,GAAG,CAAQ,gBAAgB,WAAW,CAAC,aAAa,YAAY;SACxE,CAAC;aACD,IAAI,CAAC,WAAW,CAAC;aACjB,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,EACxC,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAClC,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CACvC,CACF;aACA,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;QACnC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAEzE,6FAA6F;QAC7F,wFAAwF;QACxF,MAAM,OAAO,GAAG,MAAM,EAAE;aACrB,MAAM,CAAC;YACN,WAAW,EAAE,GAAG,CAAQ,kBAAkB;YAC1C,MAAM,EAAE,GAAG,CAAQ,WAAW;YAC9B,OAAO,EAAE,GAAG,CAAQ,mCAAmC;SACxD,CAAC;aACD,IAAI,CAAC,GAAG,CAAA,GAAG,gBAAgB,0DAA0D,CAAC;aACtF,KAAK,CACJ,GAAG,CAAA,oBAAoB,WAAW,sBAAsB,QAAQ;oDACpB,CAC7C;aACA,OAAO,CAAC,GAAG,CAAA,6BAA6B,CAAC,CAAA;QAE5C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAA;QAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;QAC1C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;YAC/D,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;QACvF,CAAC;QAED,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACxB,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;YAC/C,MAAM,cAAc,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;YAClD,OAAO;gBACL,QAAQ;gBACR,WAAW,EAAE,IAAI,CAAC,EAAE;gBACpB,aAAa,EAAE,IAAI,CAAC,IAAI;gBACxB,IAAI,EAAE,IAAI,CAAC,IAAmC;gBAC9C,UAAU;gBACV,cAAc;gBACd,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;gBACzC,cAAc;gBACd,gBAAgB,EAAE,cAAc,GAAG,cAAc;gBACjD,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC;gBAC1C,SAAS,EAAE,MAAM,CAAC,cAAc,GAAG,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC;aAC9D,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CACf,EAAM,EACN,WAAmB,EACnB,QAAgB,EAChB,WAAmB,EACnB,UAAkB;QAElB,MAAM,EAAE;aACL,MAAM,CAAC,kBAAkB,CAAC;aAC1B,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;aACxE,mBAAmB,EAAE,CAAA;QAExB,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;sBACF,kBAAkB;8BACV,WAAW;2BACd,QAAQ;+BACJ,WAAW;6BACb,UAAU;;KAElC,CAAC,CAAA;QAEF,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE;aACnB,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,CAAQ,gBAAgB,WAAW,CAAC,aAAa,YAAY,EAAE,CAAC;aACnF,IAAI,CAAC,WAAW,CAAC;aACjB,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,EACxC,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAClC,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,EACxC,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,UAAU,CAAC,CACvC,CACF,CAAA;QACH,OAAO,MAAM,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAA;IAChC,CAAC;IAED,gEAAgE;IAChE,KAAK,CAAC,MAAM,CACV,EAAM,EACN,WAAmB,EACnB,KAYC;QAED,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE;aACnB,MAAM,CAAC,WAAW,CAAC;aACnB,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,CAAC;aAC/C,SAAS,EAAE,CAAA;QACd,MAAM,EAAE;aACL,MAAM,CAAC,kBAAkB,CAAC;aAC1B,GAAG,CAAC;YACH,oBAAoB,EAAE,GAAG,CAAA,GAAG,kBAAkB,CAAC,oBAAoB,MAAM,KAAK,CAAC,aAAa,EAAE;YAC9F,WAAW,EAAE,GAAI,CAAC,EAAE;YACpB,OAAO,EAAE,GAAG,CAAA,GAAG,kBAAkB,CAAC,OAAO,MAAM;YAC/C,SAAS,EAAE,IAAI,IAAI,EAAE;SACtB,CAAC;aACD,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,kBAAkB,CAAC,WAAW,EAAE,WAAW,CAAC,EAC/C,EAAE,CAAC,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,EAC/C,EAAE,CAAC,kBAAkB,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,EACrD,EAAE,CAAC,kBAAkB,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CACpD,CACF,CAAA;QACH,OAAO,GAAI,CAAA;IACb,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CACX,EAAM,EACN,WAAmB,EACnB,OAAe,EACf,MAAc,EACd,OAAsB,EACtB,EAAU;QAEV,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE;aACxB,MAAM,EAAE;aACR,IAAI,CAAC,WAAW,CAAC;aACjB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;aACjF,KAAK,CAAC,CAAC,CAAC,CAAA;QACX,IAAI,CAAC,QAAQ;YAAE,MAAM,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAA;QAEvD,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE;aACvB,MAAM,CAAC,EAAE,EAAE,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC;aAC9B,IAAI,CAAC,WAAW,CAAC;aACjB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;aAC9F,KAAK,CAAC,CAAC,CAAC,CAAA;QACX,gGAAgG;QAChG,4FAA4F;QAC5F,0EAA0E;QAC1E,IAAI,OAAO;YAAE,MAAM,SAAS,CAAC,QAAQ,CAAC,sCAAsC,CAAC,CAAA;QAE7E,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE;YAClC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,IAAI,EAAE,UAAU;YAChB,aAAa,EAAE,CAAC,QAAQ,CAAC,aAAa;YACtC,WAAW,EAAE,EAAE;YACf,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,eAAe,EAAE,QAAQ,CAAC,EAAE;YAC5B,MAAM;YACN,SAAS,EAAE,OAAO;SACnB,CAAC,CAAA;IACJ,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,UAAU,CAAC,EAAM,EAAE,WAAmB,EAAE,SAAiB;QAC7D,OAAO,EAAE;aACN,MAAM,EAAE;aACR,IAAI,CAAC,WAAW,CAAC;aACjB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;IAC/F,CAAC;IAED,oGAAoG;IACpG,KAAK,CAAC,cAAc,CAAC,EAAM,EAAE,WAAmB,EAAE,SAAmB;QACnE,IAAI,CAAC,SAAS,CAAC,MAAM;YAAE,OAAM;QAC7B,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;eACT,kBAAkB;;;;;;mBAMd,WAAW;kCACI,WAAW;iCACZ,GAAG,CAAC,IAAI,CAC1B,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAA,GAAG,CAAC,EAAE,CAAC,EAC/B,GAAG,CAAA,IAAI,CACR;;;gCAGkB,WAAW;;;;KAItC,CAAC,CAAA;IACJ,CAAC;CACF;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type Tx } from '@kernhq/kernel';
|
|
2
|
-
import { asc, inArray } from 'drizzle-orm';
|
|
3
2
|
import { type PolicyKind, type PolicySubjectKind, type ResolvedPolicy } from '../../contract/index.js';
|
|
4
3
|
import type { ResolveService } from './resolve.js';
|
|
5
4
|
/**
|
|
@@ -36,6 +35,12 @@ export declare class PolicyService {
|
|
|
36
35
|
* The one question every recomputation asks before writing. A locked month must not move
|
|
37
36
|
* underneath a payroll that has already been filed, so anything affecting it becomes an
|
|
38
37
|
* adjustment in the open period instead.
|
|
38
|
+
*
|
|
39
|
+
* `attendance_days.locked` is a cache of this answer and defers to it in both directions — the
|
|
40
|
+
* flag is never believed ahead of the period, in either state. `on` and `legalEntityId` have to
|
|
41
|
+
* describe the same moment: the entity is the one that employed the person **on that date**, not
|
|
42
|
+
* the one they are in today, or a month filed under an entity somebody has since left reads as
|
|
43
|
+
* open.
|
|
39
44
|
*/
|
|
40
45
|
isLocked(tx: Tx, workspaceId: string, on: string, legalEntityId?: string | null): Promise<boolean>;
|
|
41
46
|
/** Refuses a write into a closed month with a sentence rather than a constraint error. */
|
|
@@ -49,5 +54,4 @@ export declare class PolicyService {
|
|
|
49
54
|
* otherwise mark every row stale on a rewrite that changed nothing.
|
|
50
55
|
*/
|
|
51
56
|
export declare function hashConfig(config: Record<string, unknown>): string;
|
|
52
|
-
export { asc, inArray };
|
|
53
57
|
//# sourceMappingURL=policies.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../../../src/server/services/policies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../../../src/server/services/policies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,EAAE,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EACL,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,yBAAyB,CAAA;AAGhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD;;;;;;;GAOG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,cAAc;IAEpD;;;;;;OAMG;IACG,SAAS,CACb,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,UAAU,EAChB,EAAE,GAAE,MAAmB,GACtB,OAAO,CAAC,cAAc,CAAC;IAwD1B;;;;;OAKG;IACG,SAAS,CACb,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAAE,EACnB,IAAI,EAAE,UAAU,EAChB,EAAE,GAAE,MAAmB,GACtB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAwDvC,2FAA2F;IAC3F,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM;IAInD;;;;;;;;;;;;OAYG;IACG,QAAQ,CACZ,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,aAAa,GAAE,MAAM,GAAG,IAAW,GAClC,OAAO,CAAC,OAAO,CAAC;IAqBnB,0FAA0F;IACpF,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,aAAa,GAAE,MAAM,GAAG,IAAW;CAO9F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAQlE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { KernError } from '@kernhq/kernel';
|
|
2
|
-
import { and,
|
|
2
|
+
import { and, desc, eq, isNull, lte, or, sql } from 'drizzle-orm';
|
|
3
3
|
import { SUBJECT_PRIORITY, } from '../../contract/index.js';
|
|
4
4
|
import { periods, policies, policyAssignments } from '../schema.js';
|
|
5
5
|
import { inForceOn, todayIso } from './db.js';
|
|
@@ -118,6 +118,12 @@ export class PolicyService {
|
|
|
118
118
|
* The one question every recomputation asks before writing. A locked month must not move
|
|
119
119
|
* underneath a payroll that has already been filed, so anything affecting it becomes an
|
|
120
120
|
* adjustment in the open period instead.
|
|
121
|
+
*
|
|
122
|
+
* `attendance_days.locked` is a cache of this answer and defers to it in both directions — the
|
|
123
|
+
* flag is never believed ahead of the period, in either state. `on` and `legalEntityId` have to
|
|
124
|
+
* describe the same moment: the entity is the one that employed the person **on that date**, not
|
|
125
|
+
* the one they are in today, or a month filed under an entity somebody has since left reads as
|
|
126
|
+
* open.
|
|
121
127
|
*/
|
|
122
128
|
async isLocked(tx, workspaceId, on, legalEntityId = null) {
|
|
123
129
|
const [row] = await tx
|
|
@@ -154,5 +160,4 @@ export function hashConfig(config) {
|
|
|
154
160
|
}
|
|
155
161
|
return Math.abs(hash).toString(36);
|
|
156
162
|
}
|
|
157
|
-
export { asc, inArray };
|
|
158
163
|
//# sourceMappingURL=policies.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policies.js","sourceRoot":"","sources":["../../../src/server/services/policies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAW,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"policies.js","sourceRoot":"","sources":["../../../src/server/services/policies.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,5 +16,15 @@ alter table "mod_hr"."approval_requests"
|
|
|
16
16
|
-- The inbox lists by approver and then names the requester, so the lookup is by request, not by
|
|
17
17
|
-- requester — this index is for "everything Ayşe has asked for", which the person page reads.
|
|
18
18
|
-- `requested_at` is the ORM's name for it; the column is `created_at`, from the shared helper.
|
|
19
|
+
--
|
|
20
|
+
-- `desc nulls last`, not bare `desc`. Postgres reads `desc` as DESC NULLS FIRST, while drizzle
|
|
21
|
+
-- writes DESC NULLS LAST for `t.requestedAt.desc()` — so the database and the snapshot described
|
|
22
|
+
-- the same index differently, permanently and invisibly, until check-snapshot-drift.mjs learned to
|
|
23
|
+
-- compare sort order.
|
|
24
|
+
--
|
|
25
|
+
-- No corrective migration for instances that already ran this. `created_at` is NOT NULL, so the two
|
|
26
|
+
-- orderings are indistinguishable: there is no row for the null ordering to place. Dropping and
|
|
27
|
+
-- rebuilding the index would take an ACCESS EXCLUSIVE lock to change nothing anybody can observe.
|
|
28
|
+
-- Every statement in this file is `if not exists`, so re-running it after this edit is a no-op.
|
|
19
29
|
create index if not exists "hr_approval_requests_requester_idx"
|
|
20
|
-
on "mod_hr"."approval_requests" ("workspace_id", "requester_person_id", "created_at" desc);
|
|
30
|
+
on "mod_hr"."approval_requests" ("workspace_id", "requester_person_id", "created_at" desc nulls last);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
-- Two schedules may not be in force for one person on the same day.
|
|
2
|
+
--
|
|
3
|
+
-- `employments`, `office_assignments`, `policy_assignments` and `periods` each got an exclusion
|
|
4
|
+
-- constraint the day their table was created; `schedule_assignments` was the one effective-dated
|
|
5
|
+
-- table left with nothing but a btree, and the gap is not theoretical. `scheduleFor` selects the
|
|
6
|
+
-- assignment in force with `limit 1` and no `order by`, so with two rows in force it takes whichever
|
|
7
|
+
-- the executor hands back first — and the inline recompute and the nightly job read at different
|
|
8
|
+
-- times, on different plans. The symptom is `scheduled_minutes`, `late_minutes` and
|
|
9
|
+
-- `overtime_minutes` changing between one computation and the next, on rows a locked payroll period
|
|
10
|
+
-- has already been filed against.
|
|
11
|
+
--
|
|
12
|
+
-- The overlap is easy to create: `attendance.schedules.assign` closes only rows whose `effective_to`
|
|
13
|
+
-- is null, so a backdated assignment lands on top of an already-closed row and neither is trimmed.
|
|
14
|
+
|
|
15
|
+
-- ---------------------------------------------------------------------------------------------
|
|
16
|
+
-- Repair first. An exclusion constraint is validated in full when it is added, so one overlapping
|
|
17
|
+
-- pair anywhere in the instance aborts the whole migration and the release with it.
|
|
18
|
+
--
|
|
19
|
+
-- The repair is the rule `assign` already means to apply, run over every row rather than only the
|
|
20
|
+
-- open one: each assignment ends the day before the next one for that person begins. Nothing is
|
|
21
|
+
-- deleted — an effective-dated table exists to answer "which schedule was she on in March", and a
|
|
22
|
+
-- row removed to satisfy a constraint takes that answer with it. Rows are ordered by
|
|
23
|
+
-- `(effective_from, created_at, id)` so two assignments starting on the same day resolve the same
|
|
24
|
+
-- way every time: the later-created one wins, and the earlier is left recording a period that
|
|
25
|
+
-- covers no day.
|
|
26
|
+
--
|
|
27
|
+
-- `force row level security` is on this table, and a migration runs on a plain pooled connection
|
|
28
|
+
-- with no `app.workspace_id` set — so the policy evaluates to null for every row and an UPDATE here
|
|
29
|
+
-- would report success having touched nothing. FORCE means the owner is not exempt either. The
|
|
30
|
+
-- table is unforced for the length of the repair and forced again immediately; ALTER TABLE holds an
|
|
31
|
+
-- ACCESS EXCLUSIVE lock, and drizzle runs the folder in one transaction, so no other session can
|
|
32
|
+
-- read the table through the gap.
|
|
33
|
+
alter table "mod_hr"."schedule_assignments" no force row level security;--> statement-breakpoint
|
|
34
|
+
|
|
35
|
+
with ordered as (
|
|
36
|
+
select
|
|
37
|
+
"id",
|
|
38
|
+
"effective_from",
|
|
39
|
+
"effective_to",
|
|
40
|
+
lead("effective_from") over (
|
|
41
|
+
partition by "workspace_id", "person_id"
|
|
42
|
+
order by "effective_from", "created_at", "id"
|
|
43
|
+
) as next_from
|
|
44
|
+
from "mod_hr"."schedule_assignments"
|
|
45
|
+
)
|
|
46
|
+
update "mod_hr"."schedule_assignments" a
|
|
47
|
+
set "effective_to" = ordered.next_from - 1
|
|
48
|
+
from ordered
|
|
49
|
+
where a."id" = ordered."id"
|
|
50
|
+
and ordered.next_from is not null
|
|
51
|
+
and (a."effective_to" is null or a."effective_to" >= ordered.next_from);--> statement-breakpoint
|
|
52
|
+
|
|
53
|
+
alter table "mod_hr"."schedule_assignments" force row level security;--> statement-breakpoint
|
|
54
|
+
|
|
55
|
+
-- ---------------------------------------------------------------------------------------------
|
|
56
|
+
-- The constraint. Same shape as `hr_employments_no_overlap`, with one addition the others do not
|
|
57
|
+
-- carry: rows whose `effective_to` fell before their `effective_from` sit outside it.
|
|
58
|
+
--
|
|
59
|
+
-- `daterange(from, to, '[]')` does not return an empty range for a reversed pair, it raises — so
|
|
60
|
+
-- without the predicate a backdated assignment would fail at `assign` time with a raw Postgres
|
|
61
|
+
-- range error instead of doing the sensible thing. Such a row applies to no day (`inForceOn` needs
|
|
62
|
+
-- `effective_from <= d` and `effective_to >= d` at once, which it can never satisfy), so it cannot
|
|
63
|
+
-- be the second schedule in force that this constraint exists to prevent.
|
|
64
|
+
--
|
|
65
|
+
-- `add constraint` has no `if not exists`, hence the catalogue check — the same shape
|
|
66
|
+
-- `ensure_punch_partition` uses in 0003 for `create policy`.
|
|
67
|
+
do $$
|
|
68
|
+
begin
|
|
69
|
+
if not exists (
|
|
70
|
+
select 1
|
|
71
|
+
from pg_constraint c
|
|
72
|
+
join pg_class t on t.oid = c.conrelid
|
|
73
|
+
join pg_namespace n on n.oid = t.relnamespace
|
|
74
|
+
where n.nspname = 'mod_hr'
|
|
75
|
+
and t.relname = 'schedule_assignments'
|
|
76
|
+
and c.conname = 'hr_schedule_assign_no_overlap'
|
|
77
|
+
) then
|
|
78
|
+
alter table "mod_hr"."schedule_assignments"
|
|
79
|
+
add constraint "hr_schedule_assign_no_overlap"
|
|
80
|
+
exclude using gist (
|
|
81
|
+
"person_id" with =,
|
|
82
|
+
daterange("effective_from", "effective_to", '[]') with &&
|
|
83
|
+
) where ("effective_to" is null or "effective_to" >= "effective_from");
|
|
84
|
+
end if;
|
|
85
|
+
end $$;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- Two queries that ran without an index they could use. Neither is new; both only get slower.
|
|
2
|
+
|
|
3
|
+
-- ---------------------------------------------------------------------------------------------
|
|
4
|
+
-- The approvals inbox.
|
|
5
|
+
--
|
|
6
|
+
-- `inboxFor` asks `approver_ids && ARRAY[…]::uuid[]`, and the three btrees on this table cannot
|
|
7
|
+
-- answer an array overlap at all — so the query a manager triggers every time they open the inbox
|
|
8
|
+
-- read the whole table, and the table has no ceiling: a step is written for every request ever
|
|
9
|
+
-- raised and never removed.
|
|
10
|
+
--
|
|
11
|
+
-- Plain GIN, not `btree_gin`: GIN indexes the array's elements, which is exactly what `&&` searches.
|
|
12
|
+
-- Folding `workspace_id` in beside it would need the extension and buys nothing — the overlap has
|
|
13
|
+
-- already reduced the table to the rows naming this approver by the time the workspace filter runs.
|
|
14
|
+
create index if not exists "hr_approval_steps_approvers_idx"
|
|
15
|
+
on "mod_hr"."approval_steps" using gin ("approver_ids");--> statement-breakpoint
|
|
16
|
+
|
|
17
|
+
-- ---------------------------------------------------------------------------------------------
|
|
18
|
+
-- The auto-clock-out sweep.
|
|
19
|
+
--
|
|
20
|
+
-- The hourly job asks for punches on `workspace_id`, `direction = 'in'`, `voided_by_punch_id is
|
|
21
|
+
-- null` and `at <= cutoff`. Before this index none of those four columns was indexed —
|
|
22
|
+
-- `hr_punches_person_idx` starts on `(workspace_id, person_id, …)` and `hr_punches_idem_uq` is
|
|
23
|
+
-- partial on `idempotency_key` — so the sweep scanned every partition of a table sized at half a
|
|
24
|
+
-- million rows a year, once an hour, for every workspace.
|
|
25
|
+
--
|
|
26
|
+
-- Why the split between predicate and columns is the way round it is: `voided_by_punch_id is null`
|
|
27
|
+
-- is emitted literally, so the planner can always prove the query implies the partial predicate,
|
|
28
|
+
-- while `direction = 'in'` arrives as a bind parameter that a generic plan cannot match against a
|
|
29
|
+
-- constant in a predicate. A predicate the planner cannot prove is an index the query never uses.
|
|
30
|
+
--
|
|
31
|
+
-- Created on the partitioned parent, which is what makes it a partitioned index: Postgres builds a
|
|
32
|
+
-- matching index on every existing partition and on every one `ensure_punch_partition` creates
|
|
33
|
+
-- later. That also rules out `concurrently` — Postgres does not support it on a partitioned parent,
|
|
34
|
+
-- and drizzle runs the whole folder in one transaction regardless. On an instance with real punch
|
|
35
|
+
-- history this statement holds a write lock for the length of the build.
|
|
36
|
+
create index if not exists "hr_punches_open_idx"
|
|
37
|
+
on "mod_hr"."punches" ("workspace_id", "direction", "at")
|
|
38
|
+
where "voided_by_punch_id" is null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- Overtime a statutory ceiling will not take, as a number rather than only as a word.
|
|
2
|
+
--
|
|
3
|
+
-- `computeDay` applies the overtime policy's cap and knows how much went past it, but the day sheet
|
|
4
|
+
-- had nowhere to put the figure: it reached the row only as the `overtime_beyond_cap` anomaly, and
|
|
5
|
+
-- text cannot be summed or filtered. A ceiling like Türkiye's 270 annual hours is exactly the number
|
|
6
|
+
-- somebody has to total over a year to know whether the company is inside the law.
|
|
7
|
+
--
|
|
8
|
+
-- Nullable, and deliberately not defaulted to zero: null means no cap was in force that day, zero
|
|
9
|
+
-- means one was and nothing exceeded it. A report that cannot tell those apart reads every uncapped
|
|
10
|
+
-- day as a compliant one. The anomaly stays beside it — the column is what a report sums, the
|
|
11
|
+
-- anomaly is what puts the day in front of a person.
|
|
12
|
+
--
|
|
13
|
+
-- Additive, so the image before this one reads the table unchanged. Generated by drizzle-kit and
|
|
14
|
+
-- then given `if not exists`, because a migration re-run after an edit must be a no-op.
|
|
15
|
+
ALTER TABLE "mod_hr"."attendance_days" ADD COLUMN IF NOT EXISTS "beyond_cap_minutes" integer;
|