@company-semantics/contracts 51.3.0 → 52.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
PeopleOrgChartSeatOccupantSchema,
|
|
5
|
+
PeopleOrgChartSeatSchema,
|
|
6
|
+
} from "../people-org-chart.js";
|
|
7
|
+
|
|
8
|
+
const PERSON_ID = "3f2504e0-4f89-41d3-9a0c-0305e82c3301";
|
|
9
|
+
const USER_ID = "9c5b94b1-35ad-49bb-b118-8e8fc24abf80";
|
|
10
|
+
const UNIT_ID = "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b";
|
|
11
|
+
const POSITION_ID = "11111111-1111-4111-8111-111111111111";
|
|
12
|
+
|
|
13
|
+
describe("PeopleOrgChartSeatOccupantSchema", () => {
|
|
14
|
+
const accountless = {
|
|
15
|
+
personId: PERSON_ID,
|
|
16
|
+
userId: null,
|
|
17
|
+
fullName: "Ada Lovelace",
|
|
18
|
+
avatarUrl: null,
|
|
19
|
+
primaryUnitId: null,
|
|
20
|
+
ownedUnits: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
it("parses an accountless occupant keyed by personId", () => {
|
|
24
|
+
const occupant = PeopleOrgChartSeatOccupantSchema.parse(accountless);
|
|
25
|
+
expect(occupant.personId).toBe(PERSON_ID);
|
|
26
|
+
expect(occupant.userId).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("parses an account-linked occupant carrying a uuid userId", () => {
|
|
30
|
+
const occupant = PeopleOrgChartSeatOccupantSchema.parse({
|
|
31
|
+
...accountless,
|
|
32
|
+
userId: USER_ID,
|
|
33
|
+
});
|
|
34
|
+
expect(occupant.userId).toBe(USER_ID);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("requires personId", () => {
|
|
38
|
+
const { personId: _omitted, ...withoutPersonId } = accountless;
|
|
39
|
+
expect(() =>
|
|
40
|
+
PeopleOrgChartSeatOccupantSchema.parse(withoutPersonId),
|
|
41
|
+
).toThrow();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// userId is .nullable() WITHOUT .optional(): the server always emits the key,
|
|
45
|
+
// so an absent userId must be unrepresentable rather than silently null.
|
|
46
|
+
it("rejects an absent userId — nullable is not optional", () => {
|
|
47
|
+
const { userId: _omitted, ...withoutUserId } = accountless;
|
|
48
|
+
const result = PeopleOrgChartSeatOccupantSchema.safeParse(withoutUserId);
|
|
49
|
+
expect(result.success).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("PeopleOrgChartSeatSchema", () => {
|
|
54
|
+
it("admits a filled seat whose only occupant is accountless", () => {
|
|
55
|
+
const seat = PeopleOrgChartSeatSchema.parse({
|
|
56
|
+
id: POSITION_ID,
|
|
57
|
+
unitId: UNIT_ID,
|
|
58
|
+
title: "Analyst",
|
|
59
|
+
status: "filled",
|
|
60
|
+
hiring: false,
|
|
61
|
+
occupants: [
|
|
62
|
+
{
|
|
63
|
+
personId: PERSON_ID,
|
|
64
|
+
userId: null,
|
|
65
|
+
fullName: "Ada Lovelace",
|
|
66
|
+
avatarUrl: null,
|
|
67
|
+
primaryUnitId: null,
|
|
68
|
+
ownedUnits: [],
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
expect(seat.occupants).toHaveLength(1);
|
|
73
|
+
expect(seat.occupants[0]?.userId).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -88,11 +88,30 @@ export type PeopleOrgChartSeatStatus = z.infer<
|
|
|
88
88
|
typeof PeopleOrgChartSeatStatusSchema
|
|
89
89
|
>;
|
|
90
90
|
|
|
91
|
-
// Person-scoped attributes of a seat's holder —
|
|
92
|
-
//
|
|
93
|
-
//
|
|
91
|
+
// Person-scoped attributes of a seat's holder — they travel with the person
|
|
92
|
+
// across seats (ADR-BE-329: a vacant seat grants none of these by
|
|
93
|
+
// construction).
|
|
94
|
+
//
|
|
95
|
+
// KEYED BY personId. The org chart is a graph of POSITIONS with PEOPLE
|
|
96
|
+
// projected onto them; the account link is an attribute of the person, never
|
|
97
|
+
// their identity. `userId` is the 0..1 account link and is null for the many
|
|
98
|
+
// people who never sign in (an HRIS import is the normal case, not the edge).
|
|
99
|
+
//
|
|
100
|
+
// For an ACCOUNTLESS occupant each of the following is a FACT, not a fallback:
|
|
101
|
+
// avatarUrl always null -- `persons` carries no avatar column
|
|
102
|
+
// primaryUnitId always null -- `home_assignments` is user-keyed, so there is
|
|
103
|
+
// no representable home; null means "no home
|
|
104
|
+
// row", NOT "home unknown, substitute the seat"
|
|
105
|
+
// ownedUnits always [] -- the projection of "no grant row exists".
|
|
106
|
+
// Authority is account-keyed; an accountless
|
|
107
|
+
// person holds none. This is the authority
|
|
108
|
+
// boundary made visible.
|
|
94
109
|
export const PeopleOrgChartSeatOccupantSchema = z.object({
|
|
95
|
-
|
|
110
|
+
personId: z.string().uuid(),
|
|
111
|
+
// .nullable() WITHOUT .optional(): this is a server-generated response, so
|
|
112
|
+
// `undefined` must be unrepresentable. PersonSchema.userId is
|
|
113
|
+
// .nullable().optional() because it is an input-shaped vocabulary.
|
|
114
|
+
userId: z.string().uuid().nullable(),
|
|
96
115
|
fullName: z.string(),
|
|
97
116
|
avatarUrl: z.string().nullable(),
|
|
98
117
|
// Stable HOME unit (home_assignments), not the seat unit — mirrors the legacy
|
|
@@ -123,8 +142,8 @@ export const PeopleOrgChartSeatSchema = z.object({
|
|
|
123
142
|
// fulfillment row ⇒ false.
|
|
124
143
|
hiring: z.boolean(),
|
|
125
144
|
// The seat's current holders. [] ⇒ vacant (the old "open role"); >= 1 ⇒
|
|
126
|
-
// occupied (the old "member").
|
|
127
|
-
//
|
|
145
|
+
// occupied (the old "member"). Every canonical occupant appears, account or
|
|
146
|
+
// not — occupancy, not account linkage, is what admits a person here.
|
|
128
147
|
occupants: z.array(PeopleOrgChartSeatOccupantSchema),
|
|
129
148
|
});
|
|
130
149
|
|