@kernhq/module-inventory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +32 -0
  3. package/dist/contract.d.ts +387 -0
  4. package/dist/contract.d.ts.map +1 -0
  5. package/dist/contract.js +119 -0
  6. package/dist/contract.js.map +1 -0
  7. package/dist/server/_impl.d.ts +427 -0
  8. package/dist/server/_impl.d.ts.map +1 -0
  9. package/dist/server/_impl.js +204 -0
  10. package/dist/server/_impl.js.map +1 -0
  11. package/dist/server/index.d.ts +3 -0
  12. package/dist/server/index.d.ts.map +1 -0
  13. package/dist/server/index.js +32 -0
  14. package/dist/server/index.js.map +1 -0
  15. package/dist/server/schema.d.ts +1552 -0
  16. package/dist/server/schema.d.ts.map +1 -0
  17. package/dist/server/schema.js +175 -0
  18. package/dist/server/schema.js.map +1 -0
  19. package/drizzle.config.ts +8 -0
  20. package/migrations/0000_init.sql +129 -0
  21. package/migrations/0001_rls.sql +62 -0
  22. package/migrations/meta/0000_snapshot.json +994 -0
  23. package/migrations/meta/_journal.json +20 -0
  24. package/package.json +106 -0
  25. package/src/client/api-instance.ts +28 -0
  26. package/src/client/api.ts +10 -0
  27. package/src/client/components/AssetFormDialog.svelte +185 -0
  28. package/src/client/i18n.ts +169 -0
  29. package/src/client/index.ts +26 -0
  30. package/src/client/mock.ts +95 -0
  31. package/src/client/module.ts +96 -0
  32. package/src/client/pages/AssetsPage.svelte +266 -0
  33. package/src/client/permissions.ts +30 -0
  34. package/src/client/query.ts +12 -0
  35. package/src/client/widgets/OverviewWidget.svelte +92 -0
  36. package/src/contract.ts +143 -0
  37. package/src/module.test.ts +79 -0
  38. package/src/server/_impl.ts +275 -0
  39. package/src/server/index.ts +33 -0
  40. package/src/server/schema.ts +228 -0
  41. package/tsconfig.client.json +10 -0
  42. package/tsconfig.json +10 -0
  43. package/vitest.config.ts +5 -0
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @kernhq/module-inventory
2
+
3
+ The asset register for [Kern](https://github.com/KernAIO/app): what the company owns, who holds each
4
+ item, and everything that happened to it.
5
+
6
+ - **Assets** with an auto-assigned tag (`INV-0042`), serial number, purchase date and vendor, price,
7
+ warranty expiry, location, photo and description.
8
+ - **Custody**: hand an item to a member and it stays theirs — with the full history of who held it
9
+ before — until somebody hands it on or takes it back.
10
+ - **History**: every change to an asset is recorded as an append-only timeline entry: what moved,
11
+ from what, to what, by whom.
12
+
13
+ Part of a Kern instance; enable it per workspace in **Settings → Modules**.
14
+
15
+ ## Developing
16
+
17
+ ```bash
18
+ pnpm install
19
+ pnpm typecheck # tsc + svelte-check over the client
20
+ pnpm test
21
+ pnpm build
22
+ pnpm db:generate # drizzle-kit → migrations/ (RLS policies are hand-written)
23
+ ```
24
+
25
+ This package follows the standard Kern module shape: one contract shared by both halves, a server
26
+ module hosted by core, and a client module whose screens ship inside this package. See
27
+ `docs/adr/0008-a-module-ships-its-own-screens.md` in the app repository for the reasoning.
28
+
29
+ ## Licence
30
+
31
+ AGPL-3.0-only. This module is part of the Kern product; anything you build for your own Kern
32
+ instance does not have to be released, but modifications to this module do.
@@ -0,0 +1,387 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * What this module offers, as data.
4
+ *
5
+ * Imported by **both** halves — the server implements it, the client calls it — so nothing here may
6
+ * touch Node. The contract is the only thing that crosses that line, which is why a procedure that
7
+ * exists here and not in the router is a lie that compiles. `module.test.ts` checks exactly that.
8
+ */
9
+ /** Lowercase, 2-32 characters. Names the API prefix, the Postgres schema `mod_<id>` and every event. */
10
+ export declare const MODULE_ID = "inventory";
11
+ /**
12
+ * An asset's lifecycle. `in_stock` and `assigned` follow custody (an open row in
13
+ * `custody_periods` means assigned); `under_repair` follows an open repair; `retired` is set by
14
+ * hand when an item leaves the company. Stored, because every list filter needs it, and kept in
15
+ * step inside the same transaction that writes the row it derives from.
16
+ */
17
+ export declare const AssetStatus: z.ZodEnum<{
18
+ in_stock: "in_stock";
19
+ assigned: "assigned";
20
+ under_repair: "under_repair";
21
+ retired: "retired";
22
+ }>;
23
+ export type AssetStatus = z.infer<typeof AssetStatus>;
24
+ export declare const Asset: z.ZodObject<{
25
+ id: z.ZodUUID;
26
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
27
+ code: z.ZodString;
28
+ name: z.ZodString;
29
+ description: z.ZodString;
30
+ categoryId: z.ZodNullable<z.ZodUUID>;
31
+ status: z.ZodEnum<{
32
+ in_stock: "in_stock";
33
+ assigned: "assigned";
34
+ under_repair: "under_repair";
35
+ retired: "retired";
36
+ }>;
37
+ custodianUserId: z.ZodNullable<z.ZodUUID>;
38
+ custodySince: z.ZodNullable<z.ZodString>;
39
+ serialNumber: z.ZodNullable<z.ZodString>;
40
+ location: z.ZodNullable<z.ZodString>;
41
+ purchasedOn: z.ZodNullable<z.ZodString>;
42
+ purchasedFrom: z.ZodNullable<z.ZodString>;
43
+ priceMinor: z.ZodNullable<z.ZodNumber>;
44
+ currency: z.ZodNullable<z.ZodString>;
45
+ warrantyUntil: z.ZodNullable<z.ZodString>;
46
+ photoFileId: z.ZodNullable<z.ZodUUID>;
47
+ createdAt: z.ZodString;
48
+ updatedAt: z.ZodString;
49
+ archivedAt: z.ZodNullable<z.ZodString>;
50
+ }, z.core.$strip>;
51
+ export type Asset = z.infer<typeof Asset>;
52
+ export declare const inventoryContract: {
53
+ assets: {
54
+ list: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
55
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
56
+ q: z.ZodOptional<z.ZodString>;
57
+ categoryId: z.ZodOptional<z.ZodUUID>;
58
+ status: z.ZodOptional<z.ZodEnum<{
59
+ in_stock: "in_stock";
60
+ assigned: "assigned";
61
+ under_repair: "under_repair";
62
+ retired: "retired";
63
+ }>>;
64
+ sort: z.ZodDefault<z.ZodEnum<{
65
+ code: "code";
66
+ name: "name";
67
+ recent: "recent";
68
+ }>>;
69
+ cursor: z.ZodOptional<z.ZodString>;
70
+ limit: z.ZodDefault<z.ZodNumber>;
71
+ }, z.core.$strip>, z.ZodObject<{
72
+ items: z.ZodArray<z.ZodObject<{
73
+ id: z.ZodUUID;
74
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
75
+ code: z.ZodString;
76
+ name: z.ZodString;
77
+ description: z.ZodString;
78
+ categoryId: z.ZodNullable<z.ZodUUID>;
79
+ status: z.ZodEnum<{
80
+ in_stock: "in_stock";
81
+ assigned: "assigned";
82
+ under_repair: "under_repair";
83
+ retired: "retired";
84
+ }>;
85
+ custodianUserId: z.ZodNullable<z.ZodUUID>;
86
+ custodySince: z.ZodNullable<z.ZodString>;
87
+ serialNumber: z.ZodNullable<z.ZodString>;
88
+ location: z.ZodNullable<z.ZodString>;
89
+ purchasedOn: z.ZodNullable<z.ZodString>;
90
+ purchasedFrom: z.ZodNullable<z.ZodString>;
91
+ priceMinor: z.ZodNullable<z.ZodNumber>;
92
+ currency: z.ZodNullable<z.ZodString>;
93
+ warrantyUntil: z.ZodNullable<z.ZodString>;
94
+ photoFileId: z.ZodNullable<z.ZodUUID>;
95
+ createdAt: z.ZodString;
96
+ updatedAt: z.ZodString;
97
+ archivedAt: z.ZodNullable<z.ZodString>;
98
+ }, z.core.$strip>>;
99
+ nextCursor: z.ZodNullable<z.ZodString>;
100
+ total: z.ZodOptional<z.ZodNumber>;
101
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
102
+ BAD_REQUEST: {
103
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
104
+ };
105
+ UNAUTHORIZED: {};
106
+ FORBIDDEN: {
107
+ data: z.ZodOptional<z.ZodObject<{
108
+ permission: z.ZodOptional<z.ZodString>;
109
+ }, z.core.$strip>>;
110
+ };
111
+ NOT_FOUND: {};
112
+ CONFLICT: {
113
+ data: z.ZodOptional<z.ZodObject<{
114
+ reason: z.ZodOptional<z.ZodString>;
115
+ }, z.core.$strip>>;
116
+ };
117
+ RATE_LIMITED: {};
118
+ MODULE_DISABLED: {
119
+ data: z.ZodObject<{
120
+ module: z.ZodString;
121
+ }, z.core.$strip>;
122
+ };
123
+ }>, Record<never, never>>;
124
+ get: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
125
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
126
+ assetId: z.ZodUUID;
127
+ }, z.core.$strip>, z.ZodObject<{
128
+ id: z.ZodUUID;
129
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
130
+ code: z.ZodString;
131
+ name: z.ZodString;
132
+ description: z.ZodString;
133
+ categoryId: z.ZodNullable<z.ZodUUID>;
134
+ status: z.ZodEnum<{
135
+ in_stock: "in_stock";
136
+ assigned: "assigned";
137
+ under_repair: "under_repair";
138
+ retired: "retired";
139
+ }>;
140
+ custodianUserId: z.ZodNullable<z.ZodUUID>;
141
+ custodySince: z.ZodNullable<z.ZodString>;
142
+ serialNumber: z.ZodNullable<z.ZodString>;
143
+ location: z.ZodNullable<z.ZodString>;
144
+ purchasedOn: z.ZodNullable<z.ZodString>;
145
+ purchasedFrom: z.ZodNullable<z.ZodString>;
146
+ priceMinor: z.ZodNullable<z.ZodNumber>;
147
+ currency: z.ZodNullable<z.ZodString>;
148
+ warrantyUntil: z.ZodNullable<z.ZodString>;
149
+ photoFileId: z.ZodNullable<z.ZodUUID>;
150
+ createdAt: z.ZodString;
151
+ updatedAt: z.ZodString;
152
+ archivedAt: z.ZodNullable<z.ZodString>;
153
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
154
+ BAD_REQUEST: {
155
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
156
+ };
157
+ UNAUTHORIZED: {};
158
+ FORBIDDEN: {
159
+ data: z.ZodOptional<z.ZodObject<{
160
+ permission: z.ZodOptional<z.ZodString>;
161
+ }, z.core.$strip>>;
162
+ };
163
+ NOT_FOUND: {};
164
+ CONFLICT: {
165
+ data: z.ZodOptional<z.ZodObject<{
166
+ reason: z.ZodOptional<z.ZodString>;
167
+ }, z.core.$strip>>;
168
+ };
169
+ RATE_LIMITED: {};
170
+ MODULE_DISABLED: {
171
+ data: z.ZodObject<{
172
+ module: z.ZodString;
173
+ }, z.core.$strip>;
174
+ };
175
+ }>, Record<never, never>>;
176
+ create: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
177
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
178
+ name: z.ZodString;
179
+ description: z.ZodDefault<z.ZodString>;
180
+ categoryId: z.ZodOptional<z.ZodNullable<z.ZodUUID>>;
181
+ serialNumber: z.ZodOptional<z.ZodNullable<z.ZodString>>;
182
+ location: z.ZodOptional<z.ZodNullable<z.ZodString>>;
183
+ purchasedFrom: z.ZodOptional<z.ZodNullable<z.ZodString>>;
184
+ purchasedOn: z.ZodOptional<z.ZodNullable<z.ZodISODate>>;
185
+ warrantyUntil: z.ZodOptional<z.ZodNullable<z.ZodISODate>>;
186
+ priceMinor: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
187
+ currency: z.ZodOptional<z.ZodNullable<z.ZodString>>;
188
+ }, z.core.$strip>, z.ZodObject<{
189
+ id: z.ZodUUID;
190
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
191
+ code: z.ZodString;
192
+ name: z.ZodString;
193
+ description: z.ZodString;
194
+ categoryId: z.ZodNullable<z.ZodUUID>;
195
+ status: z.ZodEnum<{
196
+ in_stock: "in_stock";
197
+ assigned: "assigned";
198
+ under_repair: "under_repair";
199
+ retired: "retired";
200
+ }>;
201
+ custodianUserId: z.ZodNullable<z.ZodUUID>;
202
+ custodySince: z.ZodNullable<z.ZodString>;
203
+ serialNumber: z.ZodNullable<z.ZodString>;
204
+ location: z.ZodNullable<z.ZodString>;
205
+ purchasedOn: z.ZodNullable<z.ZodString>;
206
+ purchasedFrom: z.ZodNullable<z.ZodString>;
207
+ priceMinor: z.ZodNullable<z.ZodNumber>;
208
+ currency: z.ZodNullable<z.ZodString>;
209
+ warrantyUntil: z.ZodNullable<z.ZodString>;
210
+ photoFileId: z.ZodNullable<z.ZodUUID>;
211
+ createdAt: z.ZodString;
212
+ updatedAt: z.ZodString;
213
+ archivedAt: z.ZodNullable<z.ZodString>;
214
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
215
+ BAD_REQUEST: {
216
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
217
+ };
218
+ UNAUTHORIZED: {};
219
+ FORBIDDEN: {
220
+ data: z.ZodOptional<z.ZodObject<{
221
+ permission: z.ZodOptional<z.ZodString>;
222
+ }, z.core.$strip>>;
223
+ };
224
+ NOT_FOUND: {};
225
+ CONFLICT: {
226
+ data: z.ZodOptional<z.ZodObject<{
227
+ reason: z.ZodOptional<z.ZodString>;
228
+ }, z.core.$strip>>;
229
+ };
230
+ RATE_LIMITED: {};
231
+ MODULE_DISABLED: {
232
+ data: z.ZodObject<{
233
+ module: z.ZodString;
234
+ }, z.core.$strip>;
235
+ };
236
+ }>, Record<never, never>>;
237
+ update: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
238
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
239
+ name: z.ZodOptional<z.ZodString>;
240
+ description: z.ZodOptional<z.ZodDefault<z.ZodString>>;
241
+ categoryId: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUUID>>>;
242
+ serialNumber: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
243
+ location: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
244
+ purchasedFrom: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
245
+ purchasedOn: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodISODate>>>;
246
+ warrantyUntil: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodISODate>>>;
247
+ priceMinor: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
248
+ currency: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
249
+ assetId: z.ZodUUID;
250
+ }, z.core.$strip>, z.ZodObject<{
251
+ id: z.ZodUUID;
252
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
253
+ code: z.ZodString;
254
+ name: z.ZodString;
255
+ description: z.ZodString;
256
+ categoryId: z.ZodNullable<z.ZodUUID>;
257
+ status: z.ZodEnum<{
258
+ in_stock: "in_stock";
259
+ assigned: "assigned";
260
+ under_repair: "under_repair";
261
+ retired: "retired";
262
+ }>;
263
+ custodianUserId: z.ZodNullable<z.ZodUUID>;
264
+ custodySince: z.ZodNullable<z.ZodString>;
265
+ serialNumber: z.ZodNullable<z.ZodString>;
266
+ location: z.ZodNullable<z.ZodString>;
267
+ purchasedOn: z.ZodNullable<z.ZodString>;
268
+ purchasedFrom: z.ZodNullable<z.ZodString>;
269
+ priceMinor: z.ZodNullable<z.ZodNumber>;
270
+ currency: z.ZodNullable<z.ZodString>;
271
+ warrantyUntil: z.ZodNullable<z.ZodString>;
272
+ photoFileId: z.ZodNullable<z.ZodUUID>;
273
+ createdAt: z.ZodString;
274
+ updatedAt: z.ZodString;
275
+ archivedAt: z.ZodNullable<z.ZodString>;
276
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
277
+ BAD_REQUEST: {
278
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
279
+ };
280
+ UNAUTHORIZED: {};
281
+ FORBIDDEN: {
282
+ data: z.ZodOptional<z.ZodObject<{
283
+ permission: z.ZodOptional<z.ZodString>;
284
+ }, z.core.$strip>>;
285
+ };
286
+ NOT_FOUND: {};
287
+ CONFLICT: {
288
+ data: z.ZodOptional<z.ZodObject<{
289
+ reason: z.ZodOptional<z.ZodString>;
290
+ }, z.core.$strip>>;
291
+ };
292
+ RATE_LIMITED: {};
293
+ MODULE_DISABLED: {
294
+ data: z.ZodObject<{
295
+ module: z.ZodString;
296
+ }, z.core.$strip>;
297
+ };
298
+ }>, Record<never, never>>;
299
+ archive: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
300
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
301
+ assetId: z.ZodUUID;
302
+ archived: z.ZodDefault<z.ZodBoolean>;
303
+ }, z.core.$strip>, z.ZodObject<{
304
+ id: z.ZodUUID;
305
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
306
+ code: z.ZodString;
307
+ name: z.ZodString;
308
+ description: z.ZodString;
309
+ categoryId: z.ZodNullable<z.ZodUUID>;
310
+ status: z.ZodEnum<{
311
+ in_stock: "in_stock";
312
+ assigned: "assigned";
313
+ under_repair: "under_repair";
314
+ retired: "retired";
315
+ }>;
316
+ custodianUserId: z.ZodNullable<z.ZodUUID>;
317
+ custodySince: z.ZodNullable<z.ZodString>;
318
+ serialNumber: z.ZodNullable<z.ZodString>;
319
+ location: z.ZodNullable<z.ZodString>;
320
+ purchasedOn: z.ZodNullable<z.ZodString>;
321
+ purchasedFrom: z.ZodNullable<z.ZodString>;
322
+ priceMinor: z.ZodNullable<z.ZodNumber>;
323
+ currency: z.ZodNullable<z.ZodString>;
324
+ warrantyUntil: z.ZodNullable<z.ZodString>;
325
+ photoFileId: z.ZodNullable<z.ZodUUID>;
326
+ createdAt: z.ZodString;
327
+ updatedAt: z.ZodString;
328
+ archivedAt: z.ZodNullable<z.ZodString>;
329
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
330
+ BAD_REQUEST: {
331
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
332
+ };
333
+ UNAUTHORIZED: {};
334
+ FORBIDDEN: {
335
+ data: z.ZodOptional<z.ZodObject<{
336
+ permission: z.ZodOptional<z.ZodString>;
337
+ }, z.core.$strip>>;
338
+ };
339
+ NOT_FOUND: {};
340
+ CONFLICT: {
341
+ data: z.ZodOptional<z.ZodObject<{
342
+ reason: z.ZodOptional<z.ZodString>;
343
+ }, z.core.$strip>>;
344
+ };
345
+ RATE_LIMITED: {};
346
+ MODULE_DISABLED: {
347
+ data: z.ZodObject<{
348
+ module: z.ZodString;
349
+ }, z.core.$strip>;
350
+ };
351
+ }>, Record<never, never>>;
352
+ };
353
+ };
354
+ export type InventoryContract = typeof inventoryContract;
355
+ /** `<module>.<entity>.<action>`. Anything that emits one declares it here. Payloads carry ids, never rows. */
356
+ export declare const inventoryEvents: {
357
+ assetCreated: import("@kernhq/contracts").EventDef<"inventory.asset.created", z.ZodObject<{
358
+ assetId: z.ZodUUID;
359
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
360
+ }, z.core.$strip>>;
361
+ assetUpdated: import("@kernhq/contracts").EventDef<"inventory.asset.updated", z.ZodObject<{
362
+ assetId: z.ZodUUID;
363
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
364
+ }, z.core.$strip>>;
365
+ assetArchived: import("@kernhq/contracts").EventDef<"inventory.asset.archived", z.ZodObject<{
366
+ assetId: z.ZodUUID;
367
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
368
+ }, z.core.$strip>>;
369
+ };
370
+ /**
371
+ * `<module>.<resource>.<action>`, each with the narrowest scope that works and the roles that hold it
372
+ * by default. A workspace can add or remove any of them afterwards with a custom role.
373
+ */
374
+ export declare const inventoryPermissions: readonly [{
375
+ readonly key: "inventory.asset.view";
376
+ readonly label: "View assets";
377
+ readonly scope: "workspace";
378
+ readonly defaultRoles: ["owner", "admin", "member", "guest"];
379
+ readonly dangerous: false;
380
+ }, {
381
+ readonly key: "inventory.asset.manage";
382
+ readonly label: "Create and edit assets";
383
+ readonly scope: "workspace";
384
+ readonly defaultRoles: ["owner", "admin", "member"];
385
+ readonly dangerous: false;
386
+ }];
387
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;GAMG;AAEH,wGAAwG;AACxG,eAAO,MAAM,SAAS,cAAc,CAAA;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;;;EAA8D,CAAA;AACtF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AAErD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BhB,CAAA;AACF,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAA;AAiBzC,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC7B,CAAA;AACD,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAA;AAExD,8GAA8G;AAC9G,eAAO,MAAM,eAAe;;;;;;;;;;;;;CAa3B,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAe/B,CAAA"}
@@ -0,0 +1,119 @@
1
+ import { baseContract, defineEvent, definePermissions, PageInput, page, WorkspaceId } from '@kernhq/contracts';
2
+ import { z } from 'zod';
3
+ /**
4
+ * What this module offers, as data.
5
+ *
6
+ * Imported by **both** halves — the server implements it, the client calls it — so nothing here may
7
+ * touch Node. The contract is the only thing that crosses that line, which is why a procedure that
8
+ * exists here and not in the router is a lie that compiles. `module.test.ts` checks exactly that.
9
+ */
10
+ /** Lowercase, 2-32 characters. Names the API prefix, the Postgres schema `mod_<id>` and every event. */
11
+ export const MODULE_ID = 'inventory';
12
+ /**
13
+ * An asset's lifecycle. `in_stock` and `assigned` follow custody (an open row in
14
+ * `custody_periods` means assigned); `under_repair` follows an open repair; `retired` is set by
15
+ * hand when an item leaves the company. Stored, because every list filter needs it, and kept in
16
+ * step inside the same transaction that writes the row it derives from.
17
+ */
18
+ export const AssetStatus = z.enum(['in_stock', 'assigned', 'under_repair', 'retired']);
19
+ export const Asset = z.object({
20
+ id: z.uuid(),
21
+ workspaceId: WorkspaceId,
22
+ /** Human-facing asset tag (`INV-0001`), assigned by the server, unique per workspace. */
23
+ code: z.string().min(1).max(40),
24
+ name: z.string().min(1).max(200),
25
+ description: z.string().max(4000),
26
+ categoryId: z.uuid().nullable(),
27
+ status: AssetStatus,
28
+ /**
29
+ * The member currently holding the item. A plain uuid — cross-schema foreign keys are what the
30
+ * module boundary exists to prevent — resolved against core membership at read time.
31
+ */
32
+ custodianUserId: z.uuid().nullable(),
33
+ custodySince: z.string().nullable(),
34
+ serialNumber: z.string().max(200).nullable(),
35
+ location: z.string().max(200).nullable(),
36
+ purchasedOn: z.string().nullable(),
37
+ purchasedFrom: z.string().max(200).nullable(),
38
+ /** Minor units (cents), the convention billing established; formatted on the client. */
39
+ priceMinor: z.number().int().nullable(),
40
+ currency: z.string().length(3).nullable(),
41
+ warrantyUntil: z.string().nullable(),
42
+ photoFileId: z.uuid().nullable(),
43
+ createdAt: z.string(),
44
+ updatedAt: z.string(),
45
+ archivedAt: z.string().nullable(),
46
+ });
47
+ const ws = z.object({ workspaceId: WorkspaceId });
48
+ const AssetCreateInput = z.object({
49
+ name: z.string().min(1).max(200),
50
+ description: z.string().max(4000).default(''),
51
+ categoryId: z.uuid().nullish(),
52
+ serialNumber: z.string().max(200).nullish(),
53
+ location: z.string().max(200).nullish(),
54
+ purchasedFrom: z.string().max(200).nullish(),
55
+ purchasedOn: z.iso.date().nullish(),
56
+ warrantyUntil: z.iso.date().nullish(),
57
+ priceMinor: z.number().int().min(0).nullish(),
58
+ currency: z.string().length(3).nullish(),
59
+ });
60
+ export const inventoryContract = {
61
+ assets: {
62
+ list: baseContract
63
+ .route({ method: 'GET', path: '/assets', tags: ['inventory'] })
64
+ .input(ws.extend({
65
+ ...PageInput.shape,
66
+ q: z.string().max(200).optional(),
67
+ categoryId: z.uuid().optional(),
68
+ status: AssetStatus.optional(),
69
+ sort: z.enum(['recent', 'name', 'code']).default('recent'),
70
+ }))
71
+ .output(page(Asset)),
72
+ get: baseContract
73
+ .route({ method: 'GET', path: '/assets/{assetId}', tags: ['inventory'] })
74
+ .input(ws.extend({ assetId: z.uuid() }))
75
+ .output(Asset),
76
+ create: baseContract
77
+ .route({ method: 'POST', path: '/assets', tags: ['inventory'] })
78
+ .input(ws.extend(AssetCreateInput.shape))
79
+ .output(Asset),
80
+ update: baseContract
81
+ .route({ method: 'PATCH', path: '/assets/{assetId}', tags: ['inventory'] })
82
+ .input(ws.extend({
83
+ assetId: z.uuid(),
84
+ ...AssetCreateInput.partial().shape,
85
+ }))
86
+ .output(Asset),
87
+ archive: baseContract
88
+ .route({ method: 'POST', path: '/assets/{assetId}/archive', tags: ['inventory'] })
89
+ .input(ws.extend({ assetId: z.uuid(), archived: z.boolean().default(true) }))
90
+ .output(Asset),
91
+ },
92
+ };
93
+ /** `<module>.<entity>.<action>`. Anything that emits one declares it here. Payloads carry ids, never rows. */
94
+ export const inventoryEvents = {
95
+ assetCreated: defineEvent('inventory.asset.created', z.object({ assetId: z.uuid(), workspaceId: WorkspaceId })),
96
+ assetUpdated: defineEvent('inventory.asset.updated', z.object({ assetId: z.uuid(), workspaceId: WorkspaceId })),
97
+ assetArchived: defineEvent('inventory.asset.archived', z.object({ assetId: z.uuid(), workspaceId: WorkspaceId })),
98
+ };
99
+ /**
100
+ * `<module>.<resource>.<action>`, each with the narrowest scope that works and the roles that hold it
101
+ * by default. A workspace can add or remove any of them afterwards with a custom role.
102
+ */
103
+ export const inventoryPermissions = definePermissions([
104
+ {
105
+ key: 'inventory.asset.view',
106
+ label: 'View assets',
107
+ scope: 'workspace',
108
+ defaultRoles: ['owner', 'admin', 'member', 'guest'],
109
+ dangerous: false,
110
+ },
111
+ {
112
+ key: 'inventory.asset.manage',
113
+ label: 'Create and edit assets',
114
+ scope: 'workspace',
115
+ defaultRoles: ['owner', 'admin', 'member'],
116
+ dangerous: false,
117
+ },
118
+ ]);
119
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}