@kernhq/module-inventory 0.1.2 → 0.3.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 (213) hide show
  1. package/README.md +134 -9
  2. package/dist/contract/capabilities.d.ts +49 -0
  3. package/dist/contract/capabilities.d.ts.map +1 -0
  4. package/dist/contract/capabilities.js +94 -0
  5. package/dist/contract/capabilities.js.map +1 -0
  6. package/dist/contract/events.d.ts +76 -0
  7. package/dist/contract/events.d.ts.map +1 -0
  8. package/dist/contract/events.js +62 -0
  9. package/dist/contract/events.js.map +1 -0
  10. package/dist/contract/index.d.ts +16 -0
  11. package/dist/contract/index.d.ts.map +1 -0
  12. package/dist/contract/index.js +16 -0
  13. package/dist/contract/index.js.map +1 -0
  14. package/dist/contract/models.d.ts +468 -0
  15. package/dist/contract/models.d.ts.map +1 -0
  16. package/dist/contract/models.js +320 -0
  17. package/dist/contract/models.js.map +1 -0
  18. package/dist/contract/notifications.d.ts +24 -0
  19. package/dist/contract/notifications.d.ts.map +1 -0
  20. package/dist/contract/notifications.js +72 -0
  21. package/dist/contract/notifications.js.map +1 -0
  22. package/dist/contract/permissions.d.ts +72 -0
  23. package/dist/contract/permissions.d.ts.map +1 -0
  24. package/dist/contract/permissions.js +79 -0
  25. package/dist/contract/permissions.js.map +1 -0
  26. package/dist/contract/router.d.ts +1378 -0
  27. package/dist/contract/router.d.ts.map +1 -0
  28. package/dist/contract/router.js +266 -0
  29. package/dist/contract/router.js.map +1 -0
  30. package/dist/contract/settings.d.ts +20 -0
  31. package/dist/contract/settings.d.ts.map +1 -0
  32. package/dist/contract/settings.js +49 -0
  33. package/dist/contract/settings.js.map +1 -0
  34. package/dist/server/index.d.ts +8 -1
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +288 -8
  37. package/dist/server/index.js.map +1 -1
  38. package/dist/server/jobs.d.ts +48 -0
  39. package/dist/server/jobs.d.ts.map +1 -0
  40. package/dist/server/jobs.js +358 -0
  41. package/dist/server/jobs.js.map +1 -0
  42. package/dist/server/router.d.ts +1789 -0
  43. package/dist/server/router.d.ts.map +1 -0
  44. package/dist/server/router.js +439 -0
  45. package/dist/server/router.js.map +1 -0
  46. package/dist/server/schema.d.ts +156 -9
  47. package/dist/server/schema.d.ts.map +1 -1
  48. package/dist/server/schema.js +183 -11
  49. package/dist/server/schema.js.map +1 -1
  50. package/dist/server/services/assets.d.ts +139 -0
  51. package/dist/server/services/assets.d.ts.map +1 -0
  52. package/dist/server/services/assets.js +429 -0
  53. package/dist/server/services/assets.js.map +1 -0
  54. package/dist/server/services/attachments.d.ts +80 -0
  55. package/dist/server/services/attachments.d.ts.map +1 -0
  56. package/dist/server/services/attachments.js +182 -0
  57. package/dist/server/services/attachments.js.map +1 -0
  58. package/dist/server/services/audience.d.ts +15 -0
  59. package/dist/server/services/audience.d.ts.map +1 -0
  60. package/dist/server/services/audience.js +64 -0
  61. package/dist/server/services/audience.js.map +1 -0
  62. package/dist/server/services/categories.d.ts +57 -0
  63. package/dist/server/services/categories.d.ts.map +1 -0
  64. package/dist/server/services/categories.js +124 -0
  65. package/dist/server/services/categories.js.map +1 -0
  66. package/dist/server/services/cursor.d.ts +68 -0
  67. package/dist/server/services/cursor.d.ts.map +1 -0
  68. package/dist/server/services/cursor.js +39 -0
  69. package/dist/server/services/cursor.js.map +1 -0
  70. package/dist/server/services/custody.d.ts +175 -0
  71. package/dist/server/services/custody.d.ts.map +1 -0
  72. package/dist/server/services/custody.js +367 -0
  73. package/dist/server/services/custody.js.map +1 -0
  74. package/dist/server/services/db-errors.d.ts +7 -0
  75. package/dist/server/services/db-errors.d.ts.map +1 -0
  76. package/dist/server/services/db-errors.js +32 -0
  77. package/dist/server/services/db-errors.js.map +1 -0
  78. package/dist/server/services/index.d.ts +26 -0
  79. package/dist/server/services/index.d.ts.map +1 -0
  80. package/dist/server/services/index.js +39 -0
  81. package/dist/server/services/index.js.map +1 -0
  82. package/dist/server/services/members.d.ts +27 -0
  83. package/dist/server/services/members.d.ts.map +1 -0
  84. package/dist/server/services/members.js +39 -0
  85. package/dist/server/services/members.js.map +1 -0
  86. package/dist/server/services/notify.d.ts +105 -0
  87. package/dist/server/services/notify.d.ts.map +1 -0
  88. package/dist/server/services/notify.js +147 -0
  89. package/dist/server/services/notify.js.map +1 -0
  90. package/dist/server/services/offboarding.d.ts +70 -0
  91. package/dist/server/services/offboarding.d.ts.map +1 -0
  92. package/dist/server/services/offboarding.js +116 -0
  93. package/dist/server/services/offboarding.js.map +1 -0
  94. package/dist/server/services/repairs.d.ts +204 -0
  95. package/dist/server/services/repairs.d.ts.map +1 -0
  96. package/dist/server/services/repairs.js +476 -0
  97. package/dist/server/services/repairs.js.map +1 -0
  98. package/dist/server/services/search.d.ts +85 -0
  99. package/dist/server/services/search.d.ts.map +1 -0
  100. package/dist/server/services/search.js +142 -0
  101. package/dist/server/services/search.js.map +1 -0
  102. package/dist/server/services/stats.d.ts +42 -0
  103. package/dist/server/services/stats.d.ts.map +1 -0
  104. package/dist/server/services/stats.js +80 -0
  105. package/dist/server/services/stats.js.map +1 -0
  106. package/dist/server/services/status.d.ts +102 -0
  107. package/dist/server/services/status.d.ts.map +1 -0
  108. package/dist/server/services/status.js +71 -0
  109. package/dist/server/services/status.js.map +1 -0
  110. package/migrations/0000_init.sql +12 -3
  111. package/migrations/0001_rls.sql +24 -0
  112. package/migrations/0002_custody_and_categories.sql +23 -0
  113. package/migrations/0003_repairs.sql +23 -0
  114. package/migrations/0004_platform_surfaces.sql +51 -0
  115. package/migrations/0005_repair_dates.sql +35 -0
  116. package/migrations/0006_workspace_registry_read.sql +50 -0
  117. package/migrations/0007_history_sequence.sql +83 -0
  118. package/migrations/meta/0000_snapshot.json +40 -13
  119. package/migrations/meta/0002_snapshot.json +1054 -0
  120. package/migrations/meta/0003_snapshot.json +1070 -0
  121. package/migrations/meta/0004_snapshot.json +1130 -0
  122. package/migrations/meta/0005_snapshot.json +1135 -0
  123. package/migrations/meta/_journal.json +44 -2
  124. package/package.json +5 -4
  125. package/src/client/api-instance.ts +27 -2
  126. package/src/client/api.ts +1 -1
  127. package/src/client/bidi.test.ts +148 -0
  128. package/src/client/bidi.ts +85 -0
  129. package/src/client/components/AssetDetailPanel.svelte +614 -0
  130. package/src/client/components/AssetFormDialog.svelte +191 -59
  131. package/src/client/components/AssetPhoto.svelte +178 -0
  132. package/src/client/components/AttachmentsSection.svelte +327 -0
  133. package/src/client/components/CustodyDialog.svelte +201 -0
  134. package/src/client/components/RepairDialog.svelte +271 -0
  135. package/src/client/components/RepairsSection.svelte +318 -0
  136. package/src/client/components/Timeline.svelte +347 -0
  137. package/src/client/components/TimelineText.svelte +124 -0
  138. package/src/client/core-api.ts +71 -0
  139. package/src/client/custody.test.ts +31 -0
  140. package/src/client/custody.ts +34 -0
  141. package/src/client/errors.test.ts +365 -0
  142. package/src/client/errors.ts +201 -0
  143. package/src/client/i18n.ts +11 -166
  144. package/src/client/index.ts +8 -1
  145. package/src/client/links.test.ts +74 -0
  146. package/src/client/links.ts +44 -0
  147. package/src/client/members.test.ts +132 -0
  148. package/src/client/members.ts +116 -0
  149. package/src/client/messages.test.ts +296 -0
  150. package/src/client/messages.ts +1424 -0
  151. package/src/client/mock.test.ts +555 -0
  152. package/src/client/mock.ts +1261 -52
  153. package/src/client/module.ts +76 -2
  154. package/src/client/pages/AssetsPage.svelte +638 -145
  155. package/src/client/permissions.ts +8 -1
  156. package/src/client/price.test.ts +254 -0
  157. package/src/client/price.ts +279 -0
  158. package/src/client/query.test.ts +58 -0
  159. package/src/client/query.ts +51 -2
  160. package/src/client/repairs.test.ts +38 -0
  161. package/src/client/repairs.ts +38 -0
  162. package/src/client/settings/CategoriesSettings.svelte +421 -0
  163. package/src/client/settings/GeneralSettings.svelte +403 -0
  164. package/src/client/status.ts +29 -0
  165. package/src/client/timeline.test.ts +175 -0
  166. package/src/client/timeline.ts +206 -0
  167. package/src/client/widgets/OverviewWidget.svelte +140 -26
  168. package/src/client/widgets/RepairsWidget.svelte +124 -0
  169. package/src/contract/capabilities.ts +99 -0
  170. package/src/contract/events.ts +83 -0
  171. package/src/contract/index.ts +16 -0
  172. package/src/contract/models.ts +360 -0
  173. package/src/contract/notifications.ts +73 -0
  174. package/src/contract/permissions.ts +79 -0
  175. package/src/contract/router.ts +300 -0
  176. package/src/contract/settings.ts +50 -0
  177. package/src/module.test.ts +330 -7
  178. package/src/server/index.ts +318 -8
  179. package/src/server/inventory.int.test.ts +4374 -0
  180. package/src/server/jobs.ts +444 -0
  181. package/src/server/migrations.test.ts +251 -0
  182. package/src/server/router.ts +574 -0
  183. package/src/server/schema.ts +184 -10
  184. package/src/server/services/assets.ts +528 -0
  185. package/src/server/services/attachments.ts +215 -0
  186. package/src/server/services/audience.ts +77 -0
  187. package/src/server/services/categories.ts +136 -0
  188. package/src/server/services/cursor.ts +104 -0
  189. package/src/server/services/custody.ts +471 -0
  190. package/src/server/services/db-errors.ts +42 -0
  191. package/src/server/services/index.ts +56 -0
  192. package/src/server/services/members.ts +54 -0
  193. package/src/server/services/notify.ts +196 -0
  194. package/src/server/services/offboarding.ts +150 -0
  195. package/src/server/services/repairs.ts +567 -0
  196. package/src/server/services/search.ts +166 -0
  197. package/src/server/services/stats.ts +88 -0
  198. package/src/server/services/status.test.ts +34 -0
  199. package/src/server/services/status.ts +143 -0
  200. package/tsconfig.base.json +22 -0
  201. package/tsconfig.client.json +1 -1
  202. package/tsconfig.json +1 -1
  203. package/vitest.config.ts +18 -3
  204. package/dist/contract.d.ts +0 -387
  205. package/dist/contract.d.ts.map +0 -1
  206. package/dist/contract.js +0 -119
  207. package/dist/contract.js.map +0 -1
  208. package/dist/server/_impl.d.ts +0 -427
  209. package/dist/server/_impl.d.ts.map +0 -1
  210. package/dist/server/_impl.js +0 -204
  211. package/dist/server/_impl.js.map +0 -1
  212. package/src/contract.ts +0 -143
  213. package/src/server/_impl.ts +0 -275
@@ -0,0 +1,320 @@
1
+ import { WorkspaceId } from '@kernhq/contracts';
2
+ import { z } from 'zod';
3
+ /**
4
+ * The nouns this module owns, and the shapes that cross the wire.
5
+ *
6
+ * Imported by **both** halves — the server implements against them, the client calls against them —
7
+ * so nothing here may touch Node. Types only; the procedures live in `router.ts`.
8
+ */
9
+ /** Lowercase, 2-32 characters. Names the API prefix, the Postgres schema `mod_<id>` and every event. */
10
+ export const MODULE_ID = 'inventory';
11
+ /**
12
+ * An asset's lifecycle. `in_stock` and `assigned` follow custody (an open row in `custody_periods`
13
+ * means assigned); `under_repair` follows an open repair; `reserved` follows a booking that has not
14
+ * been collected; `lost` and `retired` are set by hand when an item stops being usable.
15
+ *
16
+ * Stored rather than derived, because every list filter asks for it — and kept in step inside the
17
+ * same transaction that writes the row it derives from, never by a job afterwards.
18
+ *
19
+ * **`under_repair` belongs to the `repairs` capability**, so a workspace with that switch off never
20
+ * has an asset in it: the procedure that ends a repair answers 404 there, and a status nothing can
21
+ * move an item out of is a register the workspace cannot correct. The one thing a job does here is
22
+ * bring the rows *nobody touches* back into step after that switch moves, in both directions —
23
+ * `deriveStatus` in `src/server/services/status.ts` argues it, and nothing is destroyed either way.
24
+ */
25
+ export const AssetStatus = z.enum(['in_stock', 'assigned', 'reserved', 'under_repair', 'lost', 'retired']);
26
+ /**
27
+ * A value a workspace defined for itself, stored under its `key` in `assets.custom`.
28
+ *
29
+ * `unknown` rather than a union: the field definition says what the type is, and validating a value
30
+ * against a definition the client may not have loaded yet would fail honest input. The server checks
31
+ * it against `field_defs` before writing.
32
+ */
33
+ export const CustomValues = z.record(z.string(), z.unknown());
34
+ /**
35
+ * A workspace's own grouping of what it owns — Laptops, Furniture, Cameras.
36
+ *
37
+ * Archived rather than deleted, and `archivedAt` is the whole reason: `assets.category_id` is a
38
+ * plain uuid with no foreign key, so a deleted category leaves every asset filed under it pointing
39
+ * at a row that is not there. The list column goes blank and the timeline entry that recorded the
40
+ * move loses the name it recorded. Archiving takes it out of every picker and every filter and
41
+ * leaves each asset able to say what it is.
42
+ */
43
+ export const Category = z.object({
44
+ id: z.uuid(),
45
+ workspaceId: WorkspaceId,
46
+ name: z.string().min(1).max(120),
47
+ /** Where it sits in a picker. Equal orders fall back to the name, so a workspace that never
48
+ * reorders anything still gets an alphabetical list rather than an arbitrary one. */
49
+ order: z.number().int(),
50
+ createdAt: z.string(),
51
+ updatedAt: z.string(),
52
+ archivedAt: z.string().nullable(),
53
+ });
54
+ export const CategoryInput = z.object({
55
+ name: z.string().trim().min(1).max(120),
56
+ order: z.number().int().min(0).max(9999).optional(),
57
+ });
58
+ /**
59
+ * One stretch of time during which one person held one asset.
60
+ *
61
+ * Effective-dated, exactly as HR keeps employments: nothing is ever updated in place, a change
62
+ * closes the open row and inserts a new one. `effectiveTo === null` means "still holding it", and
63
+ * `inventory_custody_no_overlap` — a GiST exclusion constraint on
64
+ * `(asset_id =, tstzrange(effective_from, effective_to, '[)') &&)` — makes two of those for one
65
+ * asset impossible in the database rather than merely unlikely in the service.
66
+ */
67
+ export const CustodyPeriod = z.object({
68
+ id: z.uuid(),
69
+ workspaceId: WorkspaceId,
70
+ assetId: z.uuid(),
71
+ /**
72
+ * The member who held it. A plain uuid — a cross-schema foreign key is what the module boundary
73
+ * exists to prevent — resolved against core membership at read time, which is also why a person
74
+ * who has since left the workspace still has readable history.
75
+ */
76
+ userId: z.uuid(),
77
+ note: z.string().max(500).nullable(),
78
+ effectiveFrom: z.string(),
79
+ effectiveTo: z.string().nullable(),
80
+ /** Who did the handing over, which is not always who received it. */
81
+ createdBy: z.uuid().nullable(),
82
+ createdAt: z.string(),
83
+ });
84
+ /**
85
+ * One entry in an asset's timeline.
86
+ *
87
+ * `action` is a **plain string, deliberately not an enum**. The rows are append-only and outlive
88
+ * the image that wrote them: an instance rolled back to a previous release would fail to parse its
89
+ * own history the moment a newer image had written an action the older enum does not list — a
90
+ * timeline that 500s rather than one that says a little less. The client renders a sentence per
91
+ * action it knows and a neutral one for anything else. What is written today is `created`,
92
+ * `updated`, `assigned`, `transferred`, `returned`, `retired`, `restored`, `repair_logged`,
93
+ * `repair_completed`, `attachment_added` and `attachment_removed`.
94
+ */
95
+ export const AssetHistoryEntry = z.object({
96
+ id: z.uuid(),
97
+ assetId: z.uuid(),
98
+ /** Null for anything the platform did rather than a person. */
99
+ actorId: z.uuid().nullable(),
100
+ action: z.string().max(40),
101
+ changes: z.array(z.object({ field: z.string(), from: z.unknown(), to: z.unknown() })),
102
+ /** Action-specific ids — who received an item, who handed it over. Never a row. */
103
+ data: z.record(z.string(), z.unknown()),
104
+ occurredAt: z.string(),
105
+ });
106
+ export const Asset = z.object({
107
+ id: z.uuid(),
108
+ workspaceId: WorkspaceId,
109
+ /** Human-facing asset tag (`INV-0001`), assigned by the server, unique per workspace. */
110
+ code: z.string().min(1).max(40),
111
+ name: z.string().min(1).max(200),
112
+ description: z.string().max(4000),
113
+ categoryId: z.uuid().nullable(),
114
+ status: AssetStatus,
115
+ /**
116
+ * The member currently holding the item. A plain uuid — cross-schema foreign keys are what the
117
+ * module boundary exists to prevent — resolved against core membership at read time.
118
+ */
119
+ custodianUserId: z.uuid().nullable(),
120
+ custodySince: z.string().nullable(),
121
+ serialNumber: z.string().max(200).nullable(),
122
+ location: z.string().max(200).nullable(),
123
+ purchasedOn: z.string().nullable(),
124
+ purchasedFrom: z.string().max(200).nullable(),
125
+ /** Minor units (cents), the convention billing established; formatted on the client. */
126
+ priceMinor: z.number().int().nullable(),
127
+ currency: z.string().length(3).nullable(),
128
+ warrantyUntil: z.string().nullable(),
129
+ photoFileId: z.uuid().nullable(),
130
+ custom: CustomValues,
131
+ createdAt: z.string(),
132
+ updatedAt: z.string(),
133
+ archivedAt: z.string().nullable(),
134
+ });
135
+ /**
136
+ * What a custody procedure answers with: the asset as it now stands, and the period this call
137
+ * opened.
138
+ *
139
+ * Both, because a screen needs both and fetching the asset again afterwards is a second round trip
140
+ * that can read a row somebody else has changed in between — the panel would then show a handover
141
+ * that has already been undone. `period` is null on a return: something closed, nothing opened.
142
+ */
143
+ export const CustodyResult = z.object({
144
+ asset: Asset,
145
+ period: CustodyPeriod.nullable(),
146
+ });
147
+ /**
148
+ * One trip an item made to a repairer.
149
+ *
150
+ * `returnedOn === null` means it is still away, and that single fact is what puts the asset in
151
+ * `under_repair` — see `deriveStatus` in `src/server/services/status.ts`, which is the only place
152
+ * the three columns that drive a status are read together.
153
+ *
154
+ * **A repair is not custody, and neither one cancels the other.** A laptop assigned to Dan that goes
155
+ * to the repairer is still Dan's responsibility: the custody period stays open, `custodianUserId`
156
+ * stays set, and only `status` moves. That is why a repair does not touch custody and custody does
157
+ * not refuse a repaired item — the two answer different questions ("who is answerable for it" and
158
+ * "where is it"), and collapsing them would mean an item could not come back to the person who sent
159
+ * it.
160
+ */
161
+ export const Repair = z.object({
162
+ id: z.uuid(),
163
+ workspaceId: WorkspaceId,
164
+ assetId: z.uuid(),
165
+ summary: z.string().min(1).max(200),
166
+ detail: z.string().max(4000).nullable(),
167
+ vendor: z.string().max(200).nullable(),
168
+ /** Minor units, like `Asset.priceMinor` — one convention for money across the module. */
169
+ costMinor: z.number().int().nullable(),
170
+ currency: z.string().length(3).nullable(),
171
+ sentOn: z.string(),
172
+ /** Null while it is still away. Set by `repairs.complete` and by nothing else. */
173
+ returnedOn: z.string().nullable(),
174
+ createdBy: z.uuid().nullable(),
175
+ createdAt: z.string(),
176
+ updatedAt: z.string(),
177
+ });
178
+ /**
179
+ * A repair in a list, with the two things a reader needs to know *which item* it is about.
180
+ *
181
+ * **Joined at read time, never stored here.** `mod_inventory.repairs` holds no asset name, so
182
+ * renaming an asset renames it everywhere at once — a copied label is a label that goes stale, and
183
+ * this module already refuses to copy a person's name for the same reason. The join is inside one
184
+ * schema, which is the kind a module is allowed to make; the kind it is not is a join across the
185
+ * boundary into another module's tables.
186
+ *
187
+ * It extends `Repair` rather than replacing it, so a component that renders a repair renders one of
188
+ * these without knowing the difference. `create`, `update` and `complete` answer with a plain
189
+ * `Repair`: the caller of those already knows which asset it asked about.
190
+ */
191
+ export const RepairListItem = Repair.extend({
192
+ assetCode: z.string().min(1).max(40),
193
+ assetName: z.string().min(1).max(200),
194
+ });
195
+ /**
196
+ * Everything a person can say about a repair.
197
+ *
198
+ * **No field here carries `.default()`, for the reason `AssetInput` spells out**: `update` is built
199
+ * from `.partial()`, and `.partial()` does not strip a default — zod still substitutes it for a key
200
+ * the request never sent, so a patch that renames a repair would silently re-date it. `sentOn` is
201
+ * optional rather than defaulted for the same reason; `repairs.create` fills today's date on the
202
+ * server, which is the only clock this module trusts.
203
+ */
204
+ export const RepairInput = z.object({
205
+ summary: z.string().trim().min(1).max(200),
206
+ detail: z.string().max(4000).nullish(),
207
+ vendor: z.string().max(200).nullish(),
208
+ costMinor: z.number().int().min(0).nullish(),
209
+ currency: z.string().length(3).nullish(),
210
+ /** `sent_on` is `not null` in the database, so this is optional but never nullable. */
211
+ sentOn: z.iso.date().optional(),
212
+ });
213
+ export const RepairPatchInput = RepairInput.partial();
214
+ /**
215
+ * What a repair mutation answers with: the repair, and the asset as it now stands.
216
+ *
217
+ * Both, for the reason `CustodyResult` carries both — sending an item away moves `assets.status`,
218
+ * and a screen that had to fetch the asset again afterwards would be reading a row somebody else
219
+ * may have changed in between.
220
+ */
221
+ export const RepairResult = z.object({
222
+ repair: Repair,
223
+ asset: Asset,
224
+ });
225
+ /**
226
+ * A file recorded against an asset — a receipt, a warranty card, a manual.
227
+ *
228
+ * **The bytes are core's, not this module's.** A module does not upload: the browser uploads through
229
+ * core's file service and hands this module the id it was given, and reading one back is a download
230
+ * URL core signs. All that lives here is the fact that this asset has that file, plus the name and
231
+ * size copied at attach time so a list can be drawn without asking core once per row.
232
+ *
233
+ * `repairId` is what separates "the invoice for the screen replacement" from "the purchase receipt":
234
+ * null means it belongs to the asset itself.
235
+ */
236
+ export const Attachment = z.object({
237
+ id: z.uuid(),
238
+ workspaceId: WorkspaceId,
239
+ assetId: z.uuid(),
240
+ repairId: z.uuid().nullable(),
241
+ fileId: z.uuid(),
242
+ name: z.string().max(300),
243
+ mimeType: z.string().max(200).nullable(),
244
+ /** Bytes, as core reported them when the file was attached. */
245
+ size: z.number().int().nullable(),
246
+ uploadedBy: z.uuid().nullable(),
247
+ createdAt: z.string(),
248
+ });
249
+ /**
250
+ * The register in numbers: what a page's count line and a dashboard card need in one request.
251
+ *
252
+ * `total` counts what the list shows by default — live rows — and `archived` is beside it rather
253
+ * than inside it, because a count line that silently included archived rows would disagree with the
254
+ * list under it.
255
+ *
256
+ * **`outForRepair` is null when the workspace does not track repairs.** Zero would be a claim
257
+ * ("nothing is away"), and a workspace with the `repairs` capability off has not made that claim —
258
+ * it has no opinion at all. The screens show the tile only when a number arrives, which is the same
259
+ * rule as hiding the Repairs tab, expressed in the data instead of in a second capability lookup.
260
+ */
261
+ export const InventoryStats = z.object({
262
+ total: z.number().int().nonnegative(),
263
+ archived: z.number().int().nonnegative(),
264
+ /** Every status, zero-filled, so a screen can render the set without knowing which exist. */
265
+ byStatus: z.record(AssetStatus, z.number().int().nonnegative()),
266
+ outForRepair: z.number().int().nonnegative().nullable(),
267
+ /** Live assets nobody is holding — what is actually available to hand out. */
268
+ unassigned: z.number().int().nonnegative(),
269
+ });
270
+ /**
271
+ * How a list is ordered, and therefore what a page cursor is a bookmark *into*.
272
+ *
273
+ * Exported rather than written inline in `router.ts` because the server validates a cursor against
274
+ * this same list: a bookmark issued under one sort is meaningless under another, and the only way
275
+ * to say so is for both halves to read one enum.
276
+ */
277
+ export const AssetSort = z.enum(['recent', 'name', 'code']);
278
+ /**
279
+ * Everything a person can say about an asset. `create` requires `name`; `update` takes any subset.
280
+ *
281
+ * **No field here carries `.default()`, and that is load-bearing.** `update` is built from
282
+ * `AssetInput.partial()`, and `.partial()` does not strip a default — it only wraps the field in
283
+ * `optional`, so zod still substitutes the default for a key the request never sent. `description`
284
+ * had `.default('')`, so `PATCH {assetId, name}` reached the handler as
285
+ * `{assetId, name, description: ''}`; the service correctly read a present value as "set it" and a
286
+ * rename destroyed the text, writing a bogus `description` diff into `asset_history` as it went.
287
+ * The care the service takes over `undefined` versus `null` is defeated one layer above it, here.
288
+ * A value `create` should fill in belongs on `AssetCreateInput` below, which is never partialled.
289
+ */
290
+ export const AssetInput = z.object({
291
+ name: z.string().min(1).max(200),
292
+ description: z.string().max(4000).optional(),
293
+ categoryId: z.uuid().nullish(),
294
+ serialNumber: z.string().max(200).nullish(),
295
+ location: z.string().max(200).nullish(),
296
+ purchasedFrom: z.string().max(200).nullish(),
297
+ purchasedOn: z.iso.date().nullish(),
298
+ warrantyUntil: z.iso.date().nullish(),
299
+ priceMinor: z.number().int().min(0).nullish(),
300
+ currency: z.string().length(3).nullish(),
301
+ photoFileId: z.uuid().nullish(),
302
+ // `custom` is deliberately absent: there is nothing to validate a value against until
303
+ // `fields.*` exists, and accepting arbitrary JSON into a column a workspace has not defined
304
+ // is how a schemaless field bag becomes permanent. It arrives with the field definitions.
305
+ });
306
+ /**
307
+ * What `create` accepts: the same fields, with the one value a new row may not go without.
308
+ *
309
+ * `description` is `not null` in the database, so a create with no description needs *something*.
310
+ * Defaulting it here rather than in `AssetInput` is what keeps `update` able to tell "leave it
311
+ * alone" from "clear it" — see the note above.
312
+ */
313
+ export const AssetCreateInput = AssetInput.extend({
314
+ description: z.string().max(4000).default(''),
315
+ });
316
+ /** What `update` accepts: any subset, and nothing filled in for a key that never arrived. */
317
+ export const AssetPatchInput = AssetInput.partial();
318
+ /** Shared by every workspace-scoped procedure, which is all of them. */
319
+ export const ws = z.object({ workspaceId: WorkspaceId });
320
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/contract/models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { core } from '@kernhq/contracts';
2
+ /**
3
+ * What Inventory will interrupt somebody about.
4
+ *
5
+ * Four things, and each of them changes what somebody has to *do*. That is the test: a rename, a new
6
+ * category, an item coming back — those are facts a screen shows when somebody looks, and a
7
+ * notification type nobody wants is a notification type everybody switches off along with the one
8
+ * that mattered.
9
+ *
10
+ * A type declared here and never sent is the same lie as a permission nothing checks. Each of these
11
+ * names its sender, and every one of them exists:
12
+ *
13
+ * - `custody.assigned` — `CustodyService`, on `assign` and `transfer`.
14
+ * - `warranty.expiring` — the `warranty-sweep` job, once per asset.
15
+ * - `repair.overdue` — the `repair-overdue` job, once per repair.
16
+ * - `custody.return_due` — the offboarding subscription, when HR says somebody has left or core says
17
+ * they have been removed from the workspace.
18
+ *
19
+ * The keys keep their `inventory.custody.*` spelling rather than being renamed to
20
+ * `inventory.asset.*`: a notification type is the key a person's own preferences are stored under,
21
+ * so renaming one silently resets everybody who had switched it off.
22
+ */
23
+ export declare const inventoryNotificationTypes: core.NotificationTypeDef[];
24
+ //# sourceMappingURL=notifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../src/contract/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,0BAA0B,EAAE,IAAI,CAAC,mBAAmB,EAiDhE,CAAA"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What Inventory will interrupt somebody about.
3
+ *
4
+ * Four things, and each of them changes what somebody has to *do*. That is the test: a rename, a new
5
+ * category, an item coming back — those are facts a screen shows when somebody looks, and a
6
+ * notification type nobody wants is a notification type everybody switches off along with the one
7
+ * that mattered.
8
+ *
9
+ * A type declared here and never sent is the same lie as a permission nothing checks. Each of these
10
+ * names its sender, and every one of them exists:
11
+ *
12
+ * - `custody.assigned` — `CustodyService`, on `assign` and `transfer`.
13
+ * - `warranty.expiring` — the `warranty-sweep` job, once per asset.
14
+ * - `repair.overdue` — the `repair-overdue` job, once per repair.
15
+ * - `custody.return_due` — the offboarding subscription, when HR says somebody has left or core says
16
+ * they have been removed from the workspace.
17
+ *
18
+ * The keys keep their `inventory.custody.*` spelling rather than being renamed to
19
+ * `inventory.asset.*`: a notification type is the key a person's own preferences are stored under,
20
+ * so renaming one silently resets everybody who had switched it off.
21
+ */
22
+ export const inventoryNotificationTypes = [
23
+ {
24
+ /**
25
+ * Everybody who has ever found a laptop on their desk with no idea it was now theirs is the
26
+ * reason this is `urgent` and reaches email — an asset register that records a handover the
27
+ * recipient never learns about has recorded an argument for later.
28
+ */
29
+ type: 'inventory.custody.assigned',
30
+ label: 'An item was handed to you',
31
+ description: 'Somebody made you the holder of an asset in the register.',
32
+ defaults: { inapp: true, push: true, email: true },
33
+ urgent: true,
34
+ },
35
+ {
36
+ /**
37
+ * Not urgent, and email is off by default. A warranty running out in a month is a thing to plan
38
+ * for, not a thing to stop for — and it arrives on a schedule rather than because somebody did
39
+ * something, which is exactly the kind of message that trains people to ignore the rest.
40
+ */
41
+ type: 'inventory.warranty.expiring',
42
+ label: 'A warranty is about to run out',
43
+ description: "An item's warranty expires soon. Sent once per item, to whoever is holding it.",
44
+ defaults: { inapp: true, push: false, email: false },
45
+ urgent: false,
46
+ },
47
+ {
48
+ /**
49
+ * Push, because this one is an action somebody has to take — ring the repairer — and nobody
50
+ * goes looking for it. Still not urgent: a laptop that has been away a fortnight can wait until
51
+ * the phone is picked up.
52
+ */
53
+ type: 'inventory.repair.overdue',
54
+ label: 'A repair has been away too long',
55
+ description: 'An item sent for repair has not been logged as returned. Sent once per repair.',
56
+ defaults: { inapp: true, push: true, email: false },
57
+ urgent: false,
58
+ },
59
+ {
60
+ /**
61
+ * Somebody is leaving and still has company property. Email, because it is a list of things to
62
+ * collect from a person who may not be at their desk much longer, and an in-app badge nobody
63
+ * opens until Monday is how a laptop leaves the building.
64
+ */
65
+ type: 'inventory.custody.return_due',
66
+ label: 'Somebody leaving still holds company property',
67
+ description: 'A member has left, or is leaving, and items are still recorded as theirs.',
68
+ defaults: { inapp: true, push: true, email: true },
69
+ urgent: true,
70
+ },
71
+ ];
72
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../src/contract/notifications.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAA+B;IACpE;QACE;;;;WAIG;QACH,IAAI,EAAE,4BAA4B;QAClC,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,2DAA2D;QACxE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QAClD,MAAM,EAAE,IAAI;KACb;IACD;QACE;;;;WAIG;QACH,IAAI,EAAE,6BAA6B;QACnC,KAAK,EAAE,gCAAgC;QACvC,WAAW,EAAE,gFAAgF;QAC7F,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;QACpD,MAAM,EAAE,KAAK;KACd;IACD;QACE;;;;WAIG;QACH,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE,iCAAiC;QACxC,WAAW,EAAE,gFAAgF;QAC7F,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;QACnD,MAAM,EAAE,KAAK;KACd;IACD;QACE;;;;WAIG;QACH,IAAI,EAAE,8BAA8B;QACpC,KAAK,EAAE,+CAA+C;QACtD,WAAW,EAAE,2EAA2E;QACxF,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QAClD,MAAM,EAAE,IAAI;KACb;CACF,CAAA"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * `<module>.<resource>.<action>`, each with the narrowest scope that works and the roles that hold
3
+ * it by default. A workspace can add or remove any of them afterwards with a custom role.
4
+ *
5
+ * A key with nothing checking it is a role editor full of switches that do nothing, so these arrive
6
+ * with the procedures that enforce them — `field.manage` and the stock and purchasing keys with
7
+ * their own phases.
8
+ *
9
+ * **Reading custody is `asset.view`, and that is a decision rather than an omission.** Who holds an
10
+ * item looks like a privacy question, and it is not one here: `custodianUserId` and `custodySince`
11
+ * are fields of `Asset`, returned by `assets.list` and `assets.get` under `asset.view` since the
12
+ * module existed, and `assets.list` has always taken a `custodianUserId` filter. A separate
13
+ * `custody.view` would be a lock on a door beside an open window — it would refuse the timeline
14
+ * while the row above it named the same person. "Who has the projector" is also the question an
15
+ * asset register exists to answer; a company where a member cannot find that out asks in chat
16
+ * instead, which is worse for whoever is holding it. A workspace that disagrees takes
17
+ * `inventory.asset.view` off `guest`, which is one switch and already there. Writing custody is
18
+ * `custody.manage`, because handing an item over makes somebody answerable for it.
19
+ */
20
+ export declare const inventoryPermissions: readonly [{
21
+ readonly key: "inventory.asset.view";
22
+ readonly label: "View assets";
23
+ readonly scope: "workspace";
24
+ readonly defaultRoles: ["owner", "admin", "member", "guest"];
25
+ readonly dangerous: false;
26
+ }, {
27
+ readonly key: "inventory.asset.manage";
28
+ readonly label: "Create and edit assets";
29
+ readonly scope: "workspace";
30
+ readonly defaultRoles: ["owner", "admin", "member"];
31
+ readonly dangerous: false;
32
+ }, {
33
+ /**
34
+ * Not `asset.manage`: correcting a serial number and making a colleague answerable for a
35
+ * £2,000 laptop are different acts, and plenty of workspaces want the first from everybody and
36
+ * the second from the office manager. Held by `member` by default all the same — an office
37
+ * where only an admin may hand over a charger keeps its register by not using it.
38
+ */
39
+ readonly key: "inventory.custody.manage";
40
+ readonly label: "Hand assets over and take them back";
41
+ readonly scope: "workspace";
42
+ readonly defaultRoles: ["owner", "admin", "member"];
43
+ readonly dangerous: false;
44
+ }, {
45
+ /**
46
+ * Sending an item away and recording what it cost, held by `member` for the same reason
47
+ * `custody.manage` is: the person who notices a broken screen is the person who should be able
48
+ * to log it, and an office where only an admin may do that keeps its repair record by not
49
+ * keeping one. Reading repairs rides `asset.view` — "where is the projector" is the question
50
+ * the register exists to answer, and a repair is one of the two answers.
51
+ *
52
+ * Not folded into `asset.manage`: correcting a serial number and committing the company to a
53
+ * £400 screen replacement are different acts, and the money is why.
54
+ */
55
+ readonly key: "inventory.repair.manage";
56
+ readonly label: "Log repairs";
57
+ readonly scope: "workspace";
58
+ readonly defaultRoles: ["owner", "admin", "member"];
59
+ readonly dangerous: false;
60
+ }, {
61
+ /**
62
+ * Categories are workspace configuration — one list everybody's assets are filed against — so
63
+ * this sits with the people who set the workspace up rather than with everybody who may edit an
64
+ * asset. Reading them rides `asset.view`, because the picker on the asset form needs them.
65
+ */
66
+ readonly key: "inventory.category.manage";
67
+ readonly label: "Manage asset categories";
68
+ readonly scope: "workspace";
69
+ readonly defaultRoles: ["owner", "admin"];
70
+ readonly dangerous: false;
71
+ }];
72
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/contract/permissions.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;IAgB7B;;;;;OAKG;;;;;;;IAQH;;;;;;;;;OASG;;;;;;;IAQH;;;;OAIG;;;;;;EAOL,CAAA"}
@@ -0,0 +1,79 @@
1
+ import { definePermissions } from '@kernhq/contracts';
2
+ /**
3
+ * `<module>.<resource>.<action>`, each with the narrowest scope that works and the roles that hold
4
+ * it by default. A workspace can add or remove any of them afterwards with a custom role.
5
+ *
6
+ * A key with nothing checking it is a role editor full of switches that do nothing, so these arrive
7
+ * with the procedures that enforce them — `field.manage` and the stock and purchasing keys with
8
+ * their own phases.
9
+ *
10
+ * **Reading custody is `asset.view`, and that is a decision rather than an omission.** Who holds an
11
+ * item looks like a privacy question, and it is not one here: `custodianUserId` and `custodySince`
12
+ * are fields of `Asset`, returned by `assets.list` and `assets.get` under `asset.view` since the
13
+ * module existed, and `assets.list` has always taken a `custodianUserId` filter. A separate
14
+ * `custody.view` would be a lock on a door beside an open window — it would refuse the timeline
15
+ * while the row above it named the same person. "Who has the projector" is also the question an
16
+ * asset register exists to answer; a company where a member cannot find that out asks in chat
17
+ * instead, which is worse for whoever is holding it. A workspace that disagrees takes
18
+ * `inventory.asset.view` off `guest`, which is one switch and already there. Writing custody is
19
+ * `custody.manage`, because handing an item over makes somebody answerable for it.
20
+ */
21
+ export const inventoryPermissions = definePermissions([
22
+ {
23
+ key: 'inventory.asset.view',
24
+ label: 'View assets',
25
+ scope: 'workspace',
26
+ defaultRoles: ['owner', 'admin', 'member', 'guest'],
27
+ dangerous: false,
28
+ },
29
+ {
30
+ key: 'inventory.asset.manage',
31
+ label: 'Create and edit assets',
32
+ scope: 'workspace',
33
+ defaultRoles: ['owner', 'admin', 'member'],
34
+ dangerous: false,
35
+ },
36
+ {
37
+ /**
38
+ * Not `asset.manage`: correcting a serial number and making a colleague answerable for a
39
+ * £2,000 laptop are different acts, and plenty of workspaces want the first from everybody and
40
+ * the second from the office manager. Held by `member` by default all the same — an office
41
+ * where only an admin may hand over a charger keeps its register by not using it.
42
+ */
43
+ key: 'inventory.custody.manage',
44
+ label: 'Hand assets over and take them back',
45
+ scope: 'workspace',
46
+ defaultRoles: ['owner', 'admin', 'member'],
47
+ dangerous: false,
48
+ },
49
+ {
50
+ /**
51
+ * Sending an item away and recording what it cost, held by `member` for the same reason
52
+ * `custody.manage` is: the person who notices a broken screen is the person who should be able
53
+ * to log it, and an office where only an admin may do that keeps its repair record by not
54
+ * keeping one. Reading repairs rides `asset.view` — "where is the projector" is the question
55
+ * the register exists to answer, and a repair is one of the two answers.
56
+ *
57
+ * Not folded into `asset.manage`: correcting a serial number and committing the company to a
58
+ * £400 screen replacement are different acts, and the money is why.
59
+ */
60
+ key: 'inventory.repair.manage',
61
+ label: 'Log repairs',
62
+ scope: 'workspace',
63
+ defaultRoles: ['owner', 'admin', 'member'],
64
+ dangerous: false,
65
+ },
66
+ {
67
+ /**
68
+ * Categories are workspace configuration — one list everybody's assets are filed against — so
69
+ * this sits with the people who set the workspace up rather than with everybody who may edit an
70
+ * asset. Reading them rides `asset.view`, because the picker on the asset form needs them.
71
+ */
72
+ key: 'inventory.category.manage',
73
+ label: 'Manage asset categories',
74
+ scope: 'workspace',
75
+ defaultRoles: ['owner', 'admin'],
76
+ dangerous: false,
77
+ },
78
+ ]);
79
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/contract/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;IACpD;QACE,GAAG,EAAE,sBAAsB;QAC3B,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC;QACnD,SAAS,EAAE,KAAK;KACjB;IACD;QACE,GAAG,EAAE,wBAAwB;QAC7B,KAAK,EAAE,wBAAwB;QAC/B,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC;QAC1C,SAAS,EAAE,KAAK;KACjB;IACD;QACE;;;;;WAKG;QACH,GAAG,EAAE,0BAA0B;QAC/B,KAAK,EAAE,qCAAqC;QAC5C,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC;QAC1C,SAAS,EAAE,KAAK;KACjB;IACD;QACE;;;;;;;;;WASG;QACH,GAAG,EAAE,yBAAyB;QAC9B,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC;QAC1C,SAAS,EAAE,KAAK;KACjB;IACD;QACE;;;;WAIG;QACH,GAAG,EAAE,2BAA2B;QAChC,KAAK,EAAE,yBAAyB;QAChC,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QAChC,SAAS,EAAE,KAAK;KACjB;CACF,CAAC,CAAA"}