@kernhq/module-hr 0.13.2 → 0.14.1
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 +35 -0
- package/dist/contract/attendance.d.ts.map +1 -1
- package/dist/contract/attendance.js +25 -1
- package/dist/contract/attendance.js.map +1 -1
- package/dist/contract/capabilities.d.ts.map +1 -1
- package/dist/contract/capabilities.js +10 -0
- package/dist/contract/capabilities.js.map +1 -1
- package/dist/contract/events.d.ts +17 -16
- package/dist/contract/events.d.ts.map +1 -1
- package/dist/contract/events.js +23 -16
- package/dist/contract/events.js.map +1 -1
- package/dist/contract/models.d.ts +1 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +11 -0
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/router.d.ts +12 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +11 -3
- package/dist/contract/router.js.map +1 -1
- package/dist/contract/settings.d.ts +0 -1
- package/dist/contract/settings.d.ts.map +1 -1
- package/dist/contract/settings.js +11 -5
- package/dist/contract/settings.js.map +1 -1
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/jobs.d.ts.map +1 -1
- package/dist/server/jobs.js +13 -5
- package/dist/server/jobs.js.map +1 -1
- package/dist/server/router.d.ts +12 -0
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +99 -16
- package/dist/server/router.js.map +1 -1
- package/dist/server/services/access.d.ts +176 -0
- package/dist/server/services/access.d.ts.map +1 -0
- package/dist/server/services/access.js +260 -0
- package/dist/server/services/access.js.map +1 -0
- package/dist/server/services/people.d.ts +1 -0
- package/dist/server/services/people.d.ts.map +1 -1
- package/dist/server/services/people.js +3 -0
- package/dist/server/services/people.js.map +1 -1
- package/package.json +1 -1
- package/src/client/components/DayDetail.svelte +32 -14
- package/src/client/components/PersonPanel.svelte +66 -5
- package/src/client/components/redaction.ts +39 -0
- package/src/client/messages.ts +241 -22
- package/src/client/pages/DirectoryPage.svelte +48 -1
- package/src/client/permissions.ts +6 -8
- package/src/client/settings/AccrualSettings.svelte +596 -57
- package/src/client/settings/GeneralSettings.svelte +12 -9
- package/src/contract/attendance.ts +27 -1
- package/src/contract/capabilities.ts +10 -0
- package/src/contract/events.ts +23 -19
- package/src/contract/models.ts +11 -0
- package/src/contract/router.ts +11 -3
- package/src/contract/settings.ts +11 -5
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Principal } from '@kernhq/contracts';
|
|
2
|
+
import type { Kernel, Tx } from '@kernhq/kernel';
|
|
3
|
+
/**
|
|
4
|
+
* Who a viewer may read, and how much of them.
|
|
5
|
+
*
|
|
6
|
+
* The module has always declared four widths of "other people" and enforced one. `hr.person.view`
|
|
7
|
+
* defaults to `member`, so every colleague read every colleague's whole row — personal email, phone
|
|
8
|
+
* number, hire date, termination date — and `view_team`, `view_office` and `view_all` sat in the
|
|
9
|
+
* role editor gating nothing. This is the thing that reads them.
|
|
10
|
+
*
|
|
11
|
+
* **The split is by field, not by row, and that is a product decision.** A staff directory every
|
|
12
|
+
* colleague can open is what a company wants; hiding half the workforce from an engineer looking up
|
|
13
|
+
* who runs Payroll would be a worse product, not a safer one. So `hr.person.view` still returns
|
|
14
|
+
* every person — as a **card**: name, employee number, work email, photo, status, timezone, office.
|
|
15
|
+
* The four fields in `PERSONNEL_FIELDS` are the *personnel record* behind that card, and the three
|
|
16
|
+
* widening keys decide whose card fills out into one:
|
|
17
|
+
*
|
|
18
|
+
* - nobody's but your own, with `hr.person.view` alone;
|
|
19
|
+
* - `hr.person.view_team` — the org-unit subtree you head, plus your direct reports;
|
|
20
|
+
* - `hr.person.view_office` — everybody assigned to an office you head, primary or not, because a
|
|
21
|
+
* non-primary assignment is exactly what "visible to that office's local HR" means;
|
|
22
|
+
* - `hr.person.view_all` — the workspace, and the only one that answers `null`.
|
|
23
|
+
*
|
|
24
|
+
* None of the three implies another, so a country HR manager does not silently become a global one.
|
|
25
|
+
* You always see your own record: identity, never a grant. `hr.person.manage` reads what it writes —
|
|
26
|
+
* see `grantsFor`.
|
|
27
|
+
*
|
|
28
|
+
* **Why this is a computed set of ids and not `.use(requires('hr.person.view_team'))`.**
|
|
29
|
+
* `Authz.can` falls through to the workspace-level effective set whenever there is no object-scope
|
|
30
|
+
* binding for the id it was handed, so an object-scoped `requires` passes for anybody holding the
|
|
31
|
+
* key at workspace level and scopes *nothing*. It would look like it worked. The scope has to be
|
|
32
|
+
* derived from the org chart — `org_units.head_person_id` over the ltree subtree, `offices.head_
|
|
33
|
+
* person_id`, `employments.manager_person_id` — which is what these columns are for and what the
|
|
34
|
+
* GiST index on `org_units.path` was built to answer.
|
|
35
|
+
*
|
|
36
|
+
* **What is not resolved yet:** an object-scope *binding* of `view_team`/`view_office` to a unit or
|
|
37
|
+
* office id, which `permissions.ts` describes. `Authz` exposes no way to ask "is this bound *at*
|
|
38
|
+
* this object" — `can(object, id)` answers true for anybody holding the key at workspace level too
|
|
39
|
+
* — so honouring it would mean one `can` call per office in the workspace on every directory read.
|
|
40
|
+
* Headship is what the columns can answer today; the binding needs either `PermissionScopeKind` to
|
|
41
|
+
* grow `org_unit`/`office` or `Authz` to expose its bindings, both changes to the kernel.
|
|
42
|
+
*/
|
|
43
|
+
export declare class HrAccessService {
|
|
44
|
+
private readonly kernel;
|
|
45
|
+
/** Everything, however it was arrived at. Frozen so no caller can narrow the shared answer. */
|
|
46
|
+
private static readonly UNBOUNDED;
|
|
47
|
+
constructor(kernel: Kernel);
|
|
48
|
+
/**
|
|
49
|
+
* Which of the three widening keys this viewer holds, at workspace level.
|
|
50
|
+
*
|
|
51
|
+
* Workspace scope on purpose. The keys are declared `scope: 'object'` so an administrator *can*
|
|
52
|
+
* bind them to a unit or an office, but the question here is only "do you hold this key at all";
|
|
53
|
+
* *which* team or office it covers comes from the org chart below, never from the check.
|
|
54
|
+
*
|
|
55
|
+
* `view_all` is asked through `can`, so an owner and an instance admin pass the way they pass
|
|
56
|
+
* everything else and the two narrower reads are skipped. Below them one `effective()` answers
|
|
57
|
+
* every remaining key, rather than three `can` calls resolving the same set three times.
|
|
58
|
+
*
|
|
59
|
+
* **`hr.person.manage` reads everything it can write.** The edit form is populated from the
|
|
60
|
+
* record it is about to save back, so a writer who saw a redacted card would blank the very
|
|
61
|
+
* fields they could not see — silent data loss on an ordinary edit. Managing is workspace-wide
|
|
62
|
+
* already and has no narrow variant to lose, so this takes nothing away from anybody: it only
|
|
63
|
+
* closes the hole a custom role opens by granting `manage` without `view_all`.
|
|
64
|
+
*/
|
|
65
|
+
grantsFor(principal: Principal, workspaceId: string): Promise<HrAccessGrants>;
|
|
66
|
+
/**
|
|
67
|
+
* The people whose full personnel record this viewer may read. `null` means every person.
|
|
68
|
+
*
|
|
69
|
+
* `null` rather than a list of every id in the workspace: an HR administrator in a company of
|
|
70
|
+
* five thousand should not pay for a five-thousand-element set on every directory page, and
|
|
71
|
+
* "unbounded" is a different fact from "happens to cover everybody today".
|
|
72
|
+
*
|
|
73
|
+
* The viewer's own person id is resolved here rather than passed in, because the answer for an
|
|
74
|
+
* unbounded viewer must not cost a lookup at all — and because every caller would otherwise
|
|
75
|
+
* repeat the same two lines before asking.
|
|
76
|
+
*/
|
|
77
|
+
visiblePersonIds(tx: Tx, workspaceId: string, principal: Principal): Promise<string[] | null>;
|
|
78
|
+
/** The viewer's own HR record, if they have one. Null for a member who was never made a person. */
|
|
79
|
+
personIdOf(tx: Tx, workspaceId: string, principal: Principal): Promise<string | null>;
|
|
80
|
+
/**
|
|
81
|
+
* `view_team`: everybody in the org-unit subtree this person heads, plus their direct reports.
|
|
82
|
+
*
|
|
83
|
+
* Two questions rather than one, because they answer different shapes of manager. A department
|
|
84
|
+
* head is named on `org_units.head_person_id` and needs the whole subtree under it — one GiST
|
|
85
|
+
* lookup on `path <@`, not a recursive walk. A team lead who heads no unit at all is named on
|
|
86
|
+
* their reports' `employments.manager_person_id`, which has its own index.
|
|
87
|
+
*
|
|
88
|
+
* Headship of an *archived* unit grants nothing: a department that has been dissolved should not
|
|
89
|
+
* keep handing out records. Units inside the subtree are not filtered the same way — the people
|
|
90
|
+
* in them are still employed, and losing them because a sub-department was tidied up would be a
|
|
91
|
+
* hole rather than a narrowing.
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* Scope is asked as of **today**, through `inForceOn`, not "the row with no end date".
|
|
95
|
+
*
|
|
96
|
+
* The two disagree exactly where this module dates rows, and it dates them routinely, in both
|
|
97
|
+
* directions:
|
|
98
|
+
*
|
|
99
|
+
* - `people.offboard` writes `effective_to` = the last working day, which is normally in the
|
|
100
|
+
* *future*. Keying on a null end date drops somebody the moment their leaving is recorded, so
|
|
101
|
+
* their manager loses the record for the whole notice period — including `terminatedOn`, the
|
|
102
|
+
* one field that says when they actually go. That is the failure nobody reports, because a
|
|
103
|
+
* record that vanishes reads as "HR took it away".
|
|
104
|
+
* - `changeEmployment` closes the open row at `effective_from - 1` and opens the new one, so a
|
|
105
|
+
* transfer dated 1 October and recorded in August would hand the record to the receiving
|
|
106
|
+
* manager five weeks early and take it from the current one just as early. A future joiner is
|
|
107
|
+
* the same shape: created `onboarding` with a future `hired_on`.
|
|
108
|
+
*
|
|
109
|
+
* Every other resolver here answers "who is this today" the same way — `approvals.ts`,
|
|
110
|
+
* `resolve.ts` and the office roster all use `inForceOn`. Visibility must not be the one that
|
|
111
|
+
* disagrees — and the roster was itself changed to this predicate in the same commit, because it
|
|
112
|
+
* had been on the null-end-date test and would otherwise have listed a different set of people
|
|
113
|
+
* from the one whose records this service lets you read, in the same response.
|
|
114
|
+
*/
|
|
115
|
+
private teamOf;
|
|
116
|
+
/**
|
|
117
|
+
* `view_office`: everybody assigned to an office this person heads, as of today.
|
|
118
|
+
*
|
|
119
|
+
* Every open assignment, not only the primary one. The primary decides holidays, timezone and
|
|
120
|
+
* policy; the others grant presence — appearing in that office's directory, being visible to its
|
|
121
|
+
* local HR — and that second half is precisely this permission.
|
|
122
|
+
*
|
|
123
|
+
* An office's `parent_office_id` is not walked. A campus is geography, not authority, and there
|
|
124
|
+
* is no materialised path to walk it with; heading the campus and heading its buildings are two
|
|
125
|
+
* grants until somebody asks for the third.
|
|
126
|
+
*/
|
|
127
|
+
private officeOf;
|
|
128
|
+
}
|
|
129
|
+
/** Which of the three widening keys a viewer holds. `all` implies the other two. */
|
|
130
|
+
export interface HrAccessGrants {
|
|
131
|
+
/** `hr.person.view_all` — every record in the workspace. */
|
|
132
|
+
all: boolean;
|
|
133
|
+
/** `hr.person.view_team` — the org-unit subtree you head, plus your direct reports. */
|
|
134
|
+
team: boolean;
|
|
135
|
+
/** `hr.person.view_office` — everybody assigned to an office you head. */
|
|
136
|
+
office: boolean;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The fields a directory card does not carry.
|
|
140
|
+
*
|
|
141
|
+
* Personal email and phone are contact details somebody gave their employer, not their colleagues.
|
|
142
|
+
* Hire and termination dates are employment facts: the first is somebody's seniority and the second
|
|
143
|
+
* is the date they were let go, which is not a thing to publish to the whole company on the day it
|
|
144
|
+
* happens.
|
|
145
|
+
*
|
|
146
|
+
* Everything else on `Person` stays: name, employee number, work email, photo, status, timezone,
|
|
147
|
+
* office, and `custom`. A directory that cannot tell you somebody's work email is not a directory.
|
|
148
|
+
*
|
|
149
|
+
* Nulled rather than omitted, because all four are `.nullable()` in the contract — so a card parses
|
|
150
|
+
* as a `Person` and no client has to learn a second shape.
|
|
151
|
+
*/
|
|
152
|
+
export declare const PERSONNEL_FIELDS: readonly ["personalEmail", "phone", "hiredOn", "terminatedOn"];
|
|
153
|
+
/** The shape `forViewer` narrows. Structural, so it fits a row with extra columns joined onto it. */
|
|
154
|
+
export interface PersonnelFields {
|
|
155
|
+
personalEmail: string | null;
|
|
156
|
+
phone: string | null;
|
|
157
|
+
hiredOn: string | null;
|
|
158
|
+
terminatedOn: string | null;
|
|
159
|
+
personnelHidden: boolean;
|
|
160
|
+
}
|
|
161
|
+
/** What `visiblePersonIds` answered, ready to be asked about one person at a time. */
|
|
162
|
+
export type VisiblePeople = ReadonlySet<string> | null;
|
|
163
|
+
/** Build the lookup once per page rather than scanning an array per row. */
|
|
164
|
+
export declare const visibleSet: (ids: string[] | null) => VisiblePeople;
|
|
165
|
+
/** Does this viewer read the record behind the card, or only the card? */
|
|
166
|
+
export declare const seesRecordOf: (visible: VisiblePeople, personId: string) => boolean;
|
|
167
|
+
/**
|
|
168
|
+
* A person as this viewer may read them: the whole record, or the directory card.
|
|
169
|
+
*
|
|
170
|
+
* Returns the object it was given when nothing is hidden, so the common path — an HR administrator
|
|
171
|
+
* with `view_all` — allocates nothing.
|
|
172
|
+
*/
|
|
173
|
+
export declare function forViewer<T extends PersonnelFields & {
|
|
174
|
+
id: string;
|
|
175
|
+
}>(person: T, visible: VisiblePeople): T;
|
|
176
|
+
//# sourceMappingURL=access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../../src/server/services/access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AAMhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,eAAe;IAQd,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,+FAA+F;IAC/F,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAI/B;gBAE2B,MAAM,EAAE,MAAM;IAE3C;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgBnF;;;;;;;;;;OAUG;IACG,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;IAkBnG,mGAAmG;IAC7F,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAU3F;;;;;;;;;;;;OAYG;IACH;;;;;;;;;;;;;;;;;;;;;OAqBG;YACW,MAAM;IAoDpB;;;;;;;;;;OAUG;YACW,QAAQ;CAmCvB;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,GAAG,EAAE,OAAO,CAAA;IACZ,uFAAuF;IACvF,IAAI,EAAE,OAAO,CAAA;IACb,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,gEAAiE,CAAA;AAE9F,qGAAqG;AACrG,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,eAAe,EAAE,OAAO,CAAA;CACzB;AAED,sFAAsF;AACtF,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAA;AAEtD,4EAA4E;AAC5E,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,EAAE,GAAG,IAAI,KAAG,aAAqD,CAAA;AAEvG,0EAA0E;AAC1E,eAAO,MAAM,YAAY,GAAI,SAAS,aAAa,EAAE,UAAU,MAAM,KAAG,OAC7B,CAAA;AAE3C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG,CAAC,CAa1G"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm';
|
|
2
|
+
import { HR_PERMISSIONS } from '../../contract/index.js';
|
|
3
|
+
import { employments, officeAssignments, offices, orgUnits, people } from '../schema.js';
|
|
4
|
+
import { inForceOn, todayIso } from './db.js';
|
|
5
|
+
/**
|
|
6
|
+
* Who a viewer may read, and how much of them.
|
|
7
|
+
*
|
|
8
|
+
* The module has always declared four widths of "other people" and enforced one. `hr.person.view`
|
|
9
|
+
* defaults to `member`, so every colleague read every colleague's whole row — personal email, phone
|
|
10
|
+
* number, hire date, termination date — and `view_team`, `view_office` and `view_all` sat in the
|
|
11
|
+
* role editor gating nothing. This is the thing that reads them.
|
|
12
|
+
*
|
|
13
|
+
* **The split is by field, not by row, and that is a product decision.** A staff directory every
|
|
14
|
+
* colleague can open is what a company wants; hiding half the workforce from an engineer looking up
|
|
15
|
+
* who runs Payroll would be a worse product, not a safer one. So `hr.person.view` still returns
|
|
16
|
+
* every person — as a **card**: name, employee number, work email, photo, status, timezone, office.
|
|
17
|
+
* The four fields in `PERSONNEL_FIELDS` are the *personnel record* behind that card, and the three
|
|
18
|
+
* widening keys decide whose card fills out into one:
|
|
19
|
+
*
|
|
20
|
+
* - nobody's but your own, with `hr.person.view` alone;
|
|
21
|
+
* - `hr.person.view_team` — the org-unit subtree you head, plus your direct reports;
|
|
22
|
+
* - `hr.person.view_office` — everybody assigned to an office you head, primary or not, because a
|
|
23
|
+
* non-primary assignment is exactly what "visible to that office's local HR" means;
|
|
24
|
+
* - `hr.person.view_all` — the workspace, and the only one that answers `null`.
|
|
25
|
+
*
|
|
26
|
+
* None of the three implies another, so a country HR manager does not silently become a global one.
|
|
27
|
+
* You always see your own record: identity, never a grant. `hr.person.manage` reads what it writes —
|
|
28
|
+
* see `grantsFor`.
|
|
29
|
+
*
|
|
30
|
+
* **Why this is a computed set of ids and not `.use(requires('hr.person.view_team'))`.**
|
|
31
|
+
* `Authz.can` falls through to the workspace-level effective set whenever there is no object-scope
|
|
32
|
+
* binding for the id it was handed, so an object-scoped `requires` passes for anybody holding the
|
|
33
|
+
* key at workspace level and scopes *nothing*. It would look like it worked. The scope has to be
|
|
34
|
+
* derived from the org chart — `org_units.head_person_id` over the ltree subtree, `offices.head_
|
|
35
|
+
* person_id`, `employments.manager_person_id` — which is what these columns are for and what the
|
|
36
|
+
* GiST index on `org_units.path` was built to answer.
|
|
37
|
+
*
|
|
38
|
+
* **What is not resolved yet:** an object-scope *binding* of `view_team`/`view_office` to a unit or
|
|
39
|
+
* office id, which `permissions.ts` describes. `Authz` exposes no way to ask "is this bound *at*
|
|
40
|
+
* this object" — `can(object, id)` answers true for anybody holding the key at workspace level too
|
|
41
|
+
* — so honouring it would mean one `can` call per office in the workspace on every directory read.
|
|
42
|
+
* Headship is what the columns can answer today; the binding needs either `PermissionScopeKind` to
|
|
43
|
+
* grow `org_unit`/`office` or `Authz` to expose its bindings, both changes to the kernel.
|
|
44
|
+
*/
|
|
45
|
+
export class HrAccessService {
|
|
46
|
+
kernel;
|
|
47
|
+
/** Everything, however it was arrived at. Frozen so no caller can narrow the shared answer. */
|
|
48
|
+
static UNBOUNDED = Object.freeze({
|
|
49
|
+
all: true,
|
|
50
|
+
team: true,
|
|
51
|
+
office: true,
|
|
52
|
+
});
|
|
53
|
+
constructor(kernel) {
|
|
54
|
+
this.kernel = kernel;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Which of the three widening keys this viewer holds, at workspace level.
|
|
58
|
+
*
|
|
59
|
+
* Workspace scope on purpose. The keys are declared `scope: 'object'` so an administrator *can*
|
|
60
|
+
* bind them to a unit or an office, but the question here is only "do you hold this key at all";
|
|
61
|
+
* *which* team or office it covers comes from the org chart below, never from the check.
|
|
62
|
+
*
|
|
63
|
+
* `view_all` is asked through `can`, so an owner and an instance admin pass the way they pass
|
|
64
|
+
* everything else and the two narrower reads are skipped. Below them one `effective()` answers
|
|
65
|
+
* every remaining key, rather than three `can` calls resolving the same set three times.
|
|
66
|
+
*
|
|
67
|
+
* **`hr.person.manage` reads everything it can write.** The edit form is populated from the
|
|
68
|
+
* record it is about to save back, so a writer who saw a redacted card would blank the very
|
|
69
|
+
* fields they could not see — silent data loss on an ordinary edit. Managing is workspace-wide
|
|
70
|
+
* already and has no narrow variant to lose, so this takes nothing away from anybody: it only
|
|
71
|
+
* closes the hole a custom role opens by granting `manage` without `view_all`.
|
|
72
|
+
*/
|
|
73
|
+
async grantsFor(principal, workspaceId) {
|
|
74
|
+
const all = await this.kernel.authz.can(principal, HR_PERMISSIONS.personViewAll, {
|
|
75
|
+
kind: 'workspace',
|
|
76
|
+
id: workspaceId,
|
|
77
|
+
workspaceId,
|
|
78
|
+
});
|
|
79
|
+
if (all)
|
|
80
|
+
return HrAccessService.UNBOUNDED;
|
|
81
|
+
const held = await this.kernel.authz.effective(principal, workspaceId);
|
|
82
|
+
if (held.has(HR_PERMISSIONS.personManage))
|
|
83
|
+
return HrAccessService.UNBOUNDED;
|
|
84
|
+
return {
|
|
85
|
+
all: false,
|
|
86
|
+
team: held.has(HR_PERMISSIONS.personViewTeam),
|
|
87
|
+
office: held.has(HR_PERMISSIONS.personViewOffice),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The people whose full personnel record this viewer may read. `null` means every person.
|
|
92
|
+
*
|
|
93
|
+
* `null` rather than a list of every id in the workspace: an HR administrator in a company of
|
|
94
|
+
* five thousand should not pay for a five-thousand-element set on every directory page, and
|
|
95
|
+
* "unbounded" is a different fact from "happens to cover everybody today".
|
|
96
|
+
*
|
|
97
|
+
* The viewer's own person id is resolved here rather than passed in, because the answer for an
|
|
98
|
+
* unbounded viewer must not cost a lookup at all — and because every caller would otherwise
|
|
99
|
+
* repeat the same two lines before asking.
|
|
100
|
+
*/
|
|
101
|
+
async visiblePersonIds(tx, workspaceId, principal) {
|
|
102
|
+
const grants = await this.grantsFor(principal, workspaceId);
|
|
103
|
+
if (grants.all)
|
|
104
|
+
return null;
|
|
105
|
+
const viewer = await this.personIdOf(tx, workspaceId, principal);
|
|
106
|
+
// Plenty of members are not employees. They see the directory and no record but — since they
|
|
107
|
+
// have none — nobody's.
|
|
108
|
+
if (!viewer)
|
|
109
|
+
return [];
|
|
110
|
+
const ids = new Set([viewer]);
|
|
111
|
+
// One date for both resolvers, so a request that straddles midnight cannot answer the team from
|
|
112
|
+
// one day and the office from the next.
|
|
113
|
+
const today = todayIso();
|
|
114
|
+
if (grants.team)
|
|
115
|
+
for (const id of await this.teamOf(tx, workspaceId, viewer, today))
|
|
116
|
+
ids.add(id);
|
|
117
|
+
if (grants.office)
|
|
118
|
+
for (const id of await this.officeOf(tx, workspaceId, viewer, today))
|
|
119
|
+
ids.add(id);
|
|
120
|
+
return [...ids];
|
|
121
|
+
}
|
|
122
|
+
/** The viewer's own HR record, if they have one. Null for a member who was never made a person. */
|
|
123
|
+
async personIdOf(tx, workspaceId, principal) {
|
|
124
|
+
if (!principal.userId)
|
|
125
|
+
return null;
|
|
126
|
+
const [row] = await tx
|
|
127
|
+
.select({ id: people.id })
|
|
128
|
+
.from(people)
|
|
129
|
+
.where(and(eq(people.workspaceId, workspaceId), eq(people.userId, principal.userId)))
|
|
130
|
+
.limit(1);
|
|
131
|
+
return row?.id ?? null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* `view_team`: everybody in the org-unit subtree this person heads, plus their direct reports.
|
|
135
|
+
*
|
|
136
|
+
* Two questions rather than one, because they answer different shapes of manager. A department
|
|
137
|
+
* head is named on `org_units.head_person_id` and needs the whole subtree under it — one GiST
|
|
138
|
+
* lookup on `path <@`, not a recursive walk. A team lead who heads no unit at all is named on
|
|
139
|
+
* their reports' `employments.manager_person_id`, which has its own index.
|
|
140
|
+
*
|
|
141
|
+
* Headship of an *archived* unit grants nothing: a department that has been dissolved should not
|
|
142
|
+
* keep handing out records. Units inside the subtree are not filtered the same way — the people
|
|
143
|
+
* in them are still employed, and losing them because a sub-department was tidied up would be a
|
|
144
|
+
* hole rather than a narrowing.
|
|
145
|
+
*/
|
|
146
|
+
/**
|
|
147
|
+
* Scope is asked as of **today**, through `inForceOn`, not "the row with no end date".
|
|
148
|
+
*
|
|
149
|
+
* The two disagree exactly where this module dates rows, and it dates them routinely, in both
|
|
150
|
+
* directions:
|
|
151
|
+
*
|
|
152
|
+
* - `people.offboard` writes `effective_to` = the last working day, which is normally in the
|
|
153
|
+
* *future*. Keying on a null end date drops somebody the moment their leaving is recorded, so
|
|
154
|
+
* their manager loses the record for the whole notice period — including `terminatedOn`, the
|
|
155
|
+
* one field that says when they actually go. That is the failure nobody reports, because a
|
|
156
|
+
* record that vanishes reads as "HR took it away".
|
|
157
|
+
* - `changeEmployment` closes the open row at `effective_from - 1` and opens the new one, so a
|
|
158
|
+
* transfer dated 1 October and recorded in August would hand the record to the receiving
|
|
159
|
+
* manager five weeks early and take it from the current one just as early. A future joiner is
|
|
160
|
+
* the same shape: created `onboarding` with a future `hired_on`.
|
|
161
|
+
*
|
|
162
|
+
* Every other resolver here answers "who is this today" the same way — `approvals.ts`,
|
|
163
|
+
* `resolve.ts` and the office roster all use `inForceOn`. Visibility must not be the one that
|
|
164
|
+
* disagrees — and the roster was itself changed to this predicate in the same commit, because it
|
|
165
|
+
* had been on the null-end-date test and would otherwise have listed a different set of people
|
|
166
|
+
* from the one whose records this service lets you read, in the same response.
|
|
167
|
+
*/
|
|
168
|
+
async teamOf(tx, workspaceId, viewerPersonId, today) {
|
|
169
|
+
const ids = new Set();
|
|
170
|
+
const headed = await tx
|
|
171
|
+
.select({ path: orgUnits.path })
|
|
172
|
+
.from(orgUnits)
|
|
173
|
+
.where(and(eq(orgUnits.workspaceId, workspaceId), eq(orgUnits.headPersonId, viewerPersonId), isNull(orgUnits.archivedAt)));
|
|
174
|
+
if (headed.length) {
|
|
175
|
+
const subtree = or(...headed.map((u) => sql `${orgUnits.path} <@ ${u.path}::ltree`));
|
|
176
|
+
const rows = await tx
|
|
177
|
+
.select({ personId: employments.personId })
|
|
178
|
+
.from(employments)
|
|
179
|
+
.innerJoin(orgUnits, and(eq(orgUnits.id, employments.orgUnitId), eq(orgUnits.workspaceId, employments.workspaceId)))
|
|
180
|
+
.where(and(eq(employments.workspaceId, workspaceId), inForceOn(employments.effectiveFrom, employments.effectiveTo, today), subtree));
|
|
181
|
+
for (const r of rows)
|
|
182
|
+
ids.add(r.personId);
|
|
183
|
+
}
|
|
184
|
+
const reports = await tx
|
|
185
|
+
.select({ personId: employments.personId })
|
|
186
|
+
.from(employments)
|
|
187
|
+
.where(and(eq(employments.workspaceId, workspaceId), eq(employments.managerPersonId, viewerPersonId), inForceOn(employments.effectiveFrom, employments.effectiveTo, today)));
|
|
188
|
+
for (const r of reports)
|
|
189
|
+
ids.add(r.personId);
|
|
190
|
+
return [...ids];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* `view_office`: everybody assigned to an office this person heads, as of today.
|
|
194
|
+
*
|
|
195
|
+
* Every open assignment, not only the primary one. The primary decides holidays, timezone and
|
|
196
|
+
* policy; the others grant presence — appearing in that office's directory, being visible to its
|
|
197
|
+
* local HR — and that second half is precisely this permission.
|
|
198
|
+
*
|
|
199
|
+
* An office's `parent_office_id` is not walked. A campus is geography, not authority, and there
|
|
200
|
+
* is no materialised path to walk it with; heading the campus and heading its buildings are two
|
|
201
|
+
* grants until somebody asks for the third.
|
|
202
|
+
*/
|
|
203
|
+
async officeOf(tx, workspaceId, viewerPersonId, today) {
|
|
204
|
+
const headed = await tx
|
|
205
|
+
.select({ id: offices.id })
|
|
206
|
+
.from(offices)
|
|
207
|
+
.where(and(eq(offices.workspaceId, workspaceId), eq(offices.headPersonId, viewerPersonId), isNull(offices.archivedAt)));
|
|
208
|
+
if (!headed.length)
|
|
209
|
+
return [];
|
|
210
|
+
const rows = await tx
|
|
211
|
+
.select({ personId: officeAssignments.personId })
|
|
212
|
+
.from(officeAssignments)
|
|
213
|
+
.where(and(eq(officeAssignments.workspaceId, workspaceId), inArray(officeAssignments.officeId, headed.map((o) => o.id)),
|
|
214
|
+
// As of today, for the reason spelled out on `teamOf`: an office move is dated, so a null
|
|
215
|
+
// end date is neither "here now" nor "here on the day we are asking about".
|
|
216
|
+
inForceOn(officeAssignments.effectiveFrom, officeAssignments.effectiveTo, today)));
|
|
217
|
+
return [...new Set(rows.map((r) => r.personId))];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* The fields a directory card does not carry.
|
|
222
|
+
*
|
|
223
|
+
* Personal email and phone are contact details somebody gave their employer, not their colleagues.
|
|
224
|
+
* Hire and termination dates are employment facts: the first is somebody's seniority and the second
|
|
225
|
+
* is the date they were let go, which is not a thing to publish to the whole company on the day it
|
|
226
|
+
* happens.
|
|
227
|
+
*
|
|
228
|
+
* Everything else on `Person` stays: name, employee number, work email, photo, status, timezone,
|
|
229
|
+
* office, and `custom`. A directory that cannot tell you somebody's work email is not a directory.
|
|
230
|
+
*
|
|
231
|
+
* Nulled rather than omitted, because all four are `.nullable()` in the contract — so a card parses
|
|
232
|
+
* as a `Person` and no client has to learn a second shape.
|
|
233
|
+
*/
|
|
234
|
+
export const PERSONNEL_FIELDS = ['personalEmail', 'phone', 'hiredOn', 'terminatedOn'];
|
|
235
|
+
/** Build the lookup once per page rather than scanning an array per row. */
|
|
236
|
+
export const visibleSet = (ids) => (ids === null ? null : new Set(ids));
|
|
237
|
+
/** Does this viewer read the record behind the card, or only the card? */
|
|
238
|
+
export const seesRecordOf = (visible, personId) => visible === null || visible.has(personId);
|
|
239
|
+
/**
|
|
240
|
+
* A person as this viewer may read them: the whole record, or the directory card.
|
|
241
|
+
*
|
|
242
|
+
* Returns the object it was given when nothing is hidden, so the common path — an HR administrator
|
|
243
|
+
* with `view_all` — allocates nothing.
|
|
244
|
+
*/
|
|
245
|
+
export function forViewer(person, visible) {
|
|
246
|
+
if (seesRecordOf(visible, person.id))
|
|
247
|
+
return person;
|
|
248
|
+
// `personnelHidden` is what lets a screen tell "withheld" from "empty". This is the only place
|
|
249
|
+
// that nulls these fields, so it is the only place that can honestly say so — a client trying to
|
|
250
|
+
// infer it would be re-deriving the org chart from data it never fetched.
|
|
251
|
+
return {
|
|
252
|
+
...person,
|
|
253
|
+
personalEmail: null,
|
|
254
|
+
phone: null,
|
|
255
|
+
hiredOn: null,
|
|
256
|
+
terminatedOn: null,
|
|
257
|
+
personnelHidden: true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../../../src/server/services/access.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"people.d.ts","sourceRoot":"","sources":["../../../src/server/services/people.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACvF,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,EAAiB,MAAM,cAAc,CAAA;AAGpF,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,OAAO,MAAM,CAAC,YAAY,CAAA;AAClD,MAAM,MAAM,aAAa,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA;AAE3D;;;;;;;GAOG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE3C,sGAAsG;IACtG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS;gBAMJ,YAAY
|
|
1
|
+
{"version":3,"file":"people.d.ts","sourceRoot":"","sources":["../../../src/server/services/people.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,EAAE,EAAU,MAAM,gBAAgB,CAAA;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACvF,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,EAAiB,MAAM,cAAc,CAAA;AAGpF,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,OAAO,MAAM,CAAC,YAAY,CAAA;AAClD,MAAM,MAAM,aAAa,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA;AAE3D;;;;;;;GAOG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE3C,sGAAsG;IACtG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS;gBAMJ,YAAY;;;;;;;;;;;;;;;;;;IAUtC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,aAAa;wBAGI,cAAc;;;;;;;;;;;;;;;;IAOlD,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAUvE,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAS3F;;;;;;;OAOG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUvF,2EAA2E;IACrE,MAAM,CACV,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,EAC7D,MAAM,SAAQ;IAiBhB;;;;;;OAMG;IACG,gBAAgB,CACpB,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,aAAa,GAAG,UAAU,GAAG,WAAW,CAAC,CAAC,GACnF,OAAO,CAAC,aAAa,CAAC;IAkDzB;;;;;;OAMG;IACG,YAAY,CAChB,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,OAAO,EAClB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;IAkDvB,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,iBAAiB,CAAC,YAAY;;;;;;;;;;;IAI9D,KAAK,eAAW;CACjB"}
|
|
@@ -24,6 +24,9 @@ export class PeopleService {
|
|
|
24
24
|
// consumer; the column is only ever written from the same union.
|
|
25
25
|
status: row.status,
|
|
26
26
|
custom: row.custom ?? {},
|
|
27
|
+
// False here and set true by `forViewer` alone, which is the only thing that withholds these
|
|
28
|
+
// fields. A record that never passed through it was never redacted.
|
|
29
|
+
personnelHidden: false,
|
|
27
30
|
createdAt: row.createdAt.toISOString(),
|
|
28
31
|
updatedAt: row.updatedAt.toISOString(),
|
|
29
32
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"people.js","sourceRoot":"","sources":["../../../src/server/services/people.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"people.js","sourceRoot":"","sources":["../../../src/server/services/people.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -109,20 +109,38 @@ const directionLabel = (direction: string) =>
|
|
|
109
109
|
? t('att_punch_break_start')
|
|
110
110
|
: t('att_punch_break_end')
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
/**
|
|
113
|
+
* What recorded the punch, in words.
|
|
114
|
+
*
|
|
115
|
+
* Keyed by the method the contract names, and `auto` is the machine's own — the job that closes a
|
|
116
|
+
* shift nobody clocked out of. Every method used to be a rung on a ternary ending in
|
|
117
|
+
* `att_method_manual`, so `auto`, and anything else the contract might add, landed on **"Entered by
|
|
118
|
+
* hand"**: the one badge on this panel that names a culprit, telling somebody arguing about their
|
|
119
|
+
* own timesheet that a person had typed a punch no person touched.
|
|
120
|
+
*
|
|
121
|
+
* So the fallback no longer claims anything. A method with no string here is a build behind the
|
|
122
|
+
* server, and it renders the raw code — the same bargain `anomalyLabel` below makes, and the same
|
|
123
|
+
* reasoning: a code somebody can quote in a question beats a confident wrong sentence about who
|
|
124
|
+
* touched their record.
|
|
125
|
+
*/
|
|
126
|
+
const METHOD_KEYS: Record<string, string> = {
|
|
127
|
+
web: 'hr.att_method_web',
|
|
128
|
+
mobile: 'hr.att_method_mobile',
|
|
129
|
+
kiosk: 'hr.att_method_kiosk',
|
|
130
|
+
qr: 'hr.att_method_qr',
|
|
131
|
+
device: 'hr.att_method_device',
|
|
132
|
+
import: 'hr.att_method_import',
|
|
133
|
+
manual: 'hr.att_method_manual',
|
|
134
|
+
auto: 'hr.att_method_auto',
|
|
135
|
+
}
|
|
136
|
+
const methodLabel = (method: string): string => {
|
|
137
|
+
// Namespaced keys, because `t()` answers a miss with the key it was **given**: a bare
|
|
138
|
+
// `att_method_auto` comes back as `hr.att_method_auto`, which never equals what was passed, so a
|
|
139
|
+
// missing string would test as present and render the key at somebody.
|
|
140
|
+
const key = METHOD_KEYS[method]
|
|
141
|
+
const text = key ? t(key) : undefined
|
|
142
|
+
return text && text !== key ? text : method
|
|
143
|
+
}
|
|
126
144
|
|
|
127
145
|
/**
|
|
128
146
|
* The time, with the date added only when it is not this day's.
|