@kernhq/module-inventory 0.1.2 → 0.2.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 (103) hide show
  1. package/README.md +55 -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 +52 -0
  5. package/dist/contract/capabilities.js.map +1 -0
  6. package/dist/contract/events.d.ts +33 -0
  7. package/dist/contract/events.d.ts.map +1 -0
  8. package/dist/contract/events.js +22 -0
  9. package/dist/contract/events.js.map +1 -0
  10. package/dist/contract/index.d.ts +15 -0
  11. package/dist/contract/index.d.ts.map +1 -0
  12. package/dist/contract/index.js +15 -0
  13. package/dist/contract/index.js.map +1 -0
  14. package/dist/contract/models.d.ts +146 -0
  15. package/dist/contract/models.d.ts.map +1 -0
  16. package/dist/contract/models.js +107 -0
  17. package/dist/contract/models.js.map +1 -0
  18. package/dist/contract/permissions.d.ts +22 -0
  19. package/dist/contract/permissions.d.ts.map +1 -0
  20. package/dist/contract/permissions.js +26 -0
  21. package/dist/contract/permissions.js.map +1 -0
  22. package/dist/{contract.d.ts → contract/router.d.ts} +24 -85
  23. package/dist/contract/router.d.ts.map +1 -0
  24. package/dist/contract/router.js +42 -0
  25. package/dist/contract/router.js.map +1 -0
  26. package/dist/contract/settings.d.ts +18 -0
  27. package/dist/contract/settings.d.ts.map +1 -0
  28. package/dist/contract/settings.js +29 -0
  29. package/dist/contract/settings.js.map +1 -0
  30. package/dist/server/index.d.ts +4 -1
  31. package/dist/server/index.d.ts.map +1 -1
  32. package/dist/server/index.js +9 -13
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/server/{_impl.d.ts → router.d.ts} +33 -8
  35. package/dist/server/{_impl.d.ts.map → router.d.ts.map} +1 -1
  36. package/dist/server/router.js +83 -0
  37. package/dist/server/router.js.map +1 -0
  38. package/dist/server/schema.d.ts +25 -11
  39. package/dist/server/schema.d.ts.map +1 -1
  40. package/dist/server/schema.js +28 -10
  41. package/dist/server/schema.js.map +1 -1
  42. package/dist/server/services/assets.d.ts +73 -0
  43. package/dist/server/services/assets.d.ts.map +1 -0
  44. package/dist/server/services/assets.js +261 -0
  45. package/dist/server/services/assets.js.map +1 -0
  46. package/dist/server/services/index.d.ts +10 -0
  47. package/dist/server/services/index.d.ts.map +1 -0
  48. package/dist/server/services/index.js +15 -0
  49. package/dist/server/services/index.js.map +1 -0
  50. package/dist/server/services/notify.d.ts +63 -0
  51. package/dist/server/services/notify.d.ts.map +1 -0
  52. package/dist/server/services/notify.js +105 -0
  53. package/dist/server/services/notify.js.map +1 -0
  54. package/migrations/0000_init.sql +12 -3
  55. package/migrations/0001_rls.sql +24 -0
  56. package/migrations/meta/0000_snapshot.json +40 -13
  57. package/migrations/meta/_journal.json +2 -2
  58. package/package.json +5 -4
  59. package/src/client/api.ts +1 -1
  60. package/src/client/components/AssetFormDialog.svelte +130 -47
  61. package/src/client/i18n.ts +11 -166
  62. package/src/client/index.ts +8 -1
  63. package/src/client/messages.test.ts +169 -0
  64. package/src/client/messages.ts +399 -0
  65. package/src/client/mock.test.ts +161 -0
  66. package/src/client/mock.ts +267 -45
  67. package/src/client/module.ts +22 -2
  68. package/src/client/pages/AssetsPage.svelte +358 -137
  69. package/src/client/permissions.ts +1 -1
  70. package/src/client/price.test.ts +106 -0
  71. package/src/client/price.ts +135 -0
  72. package/src/client/query.test.ts +58 -0
  73. package/src/client/query.ts +15 -2
  74. package/src/client/settings/GeneralSettings.svelte +0 -0
  75. package/src/client/settings/core-api.ts +32 -0
  76. package/src/client/widgets/OverviewWidget.svelte +16 -3
  77. package/src/contract/capabilities.ts +55 -0
  78. package/src/contract/events.ts +34 -0
  79. package/src/contract/index.ts +15 -0
  80. package/src/contract/models.ts +123 -0
  81. package/src/contract/permissions.ts +26 -0
  82. package/src/contract/router.ts +46 -0
  83. package/src/contract/settings.ts +30 -0
  84. package/src/module.test.ts +140 -7
  85. package/src/server/index.ts +16 -13
  86. package/src/server/inventory.int.test.ts +819 -0
  87. package/src/server/migrations.test.ts +138 -0
  88. package/src/server/router.ts +118 -0
  89. package/src/server/schema.ts +27 -10
  90. package/src/server/services/assets.ts +368 -0
  91. package/src/server/services/index.ts +23 -0
  92. package/src/server/services/notify.ts +151 -0
  93. package/tsconfig.base.json +22 -0
  94. package/tsconfig.client.json +1 -1
  95. package/tsconfig.json +1 -1
  96. package/vitest.config.ts +18 -3
  97. package/dist/contract.d.ts.map +0 -1
  98. package/dist/contract.js +0 -119
  99. package/dist/contract.js.map +0 -1
  100. package/dist/server/_impl.js +0 -204
  101. package/dist/server/_impl.js.map +0 -1
  102. package/src/contract.ts +0 -143
  103. package/src/server/_impl.ts +0 -275
package/dist/contract.js DELETED
@@ -1,119 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
@@ -1,204 +0,0 @@
1
- import { defineModule, defineServerModule, KernError, packageVersion, requires, uuidv7, workspaceScoped, } from '@kernhq/kernel';
2
- import { implement } from '@orpc/server';
3
- import { and, desc, eq, ilike, or, sql } from 'drizzle-orm';
4
- import { inventoryContract, inventoryEvents, MODULE_ID } from '../contract.js';
5
- import { assetHistory, assets, counters } from './schema.js';
6
- /**
7
- * The router, kept apart from `index.ts` so `module.test.ts` can walk it without booting a kernel.
8
- *
9
- * Two middlewares on every procedure, and the test fails if either is missing:
10
- * `workspaceScoped` (a real membership, and the module switched on for that workspace) and
11
- * `requires` (the permission this particular call needs).
12
- */
13
- export { defineModule, defineServerModule, packageVersion };
14
- const os = implement(inventoryContract).$context();
15
- /** The wire shape: drizzle gives Date objects for timestamps, the contract promises ISO strings. */
16
- function toAsset(row) {
17
- return {
18
- id: row.id,
19
- workspaceId: row.workspaceId,
20
- code: row.code,
21
- name: row.name,
22
- description: row.description,
23
- categoryId: row.categoryId,
24
- status: row.status,
25
- custodianUserId: row.custodianUserId,
26
- custodySince: row.custodySince?.toISOString() ?? null,
27
- serialNumber: row.serialNumber,
28
- location: row.location,
29
- purchasedOn: row.purchasedOn ?? null,
30
- purchasedFrom: row.purchasedFrom,
31
- priceMinor: row.priceMinor,
32
- currency: row.currency,
33
- warrantyUntil: row.warrantyUntil ?? null,
34
- photoFileId: row.photoFileId,
35
- createdAt: row.createdAt.toISOString(),
36
- updatedAt: row.updatedAt.toISOString(),
37
- archivedAt: row.archivedAt?.toISOString() ?? null,
38
- };
39
- }
40
- /**
41
- * The next asset tag for a workspace. One narrow row per workspace and key, incremented under the
42
- * insert's conflict lock — two concurrent creates each read the value their own update returned, so
43
- * codes stay unique without a retry loop. `INV-0042`, because people say asset tags out loud.
44
- */
45
- async function nextCode(tx, workspaceId) {
46
- const [row] = await tx
47
- .insert(counters)
48
- .values({ workspaceId, key: 'asset_code', value: 1 })
49
- .onConflictDoUpdate({
50
- target: [counters.workspaceId, counters.key],
51
- set: { value: sql `${counters.value} + 1` },
52
- })
53
- .returning();
54
- return `INV-${String(row.value).padStart(4, '0')}`;
55
- }
56
- export function implement_(kernel) {
57
- const scoped = os.use(workspaceScoped(MODULE_ID));
58
- const changed = (workspaceId, id, op) => kernel.realtime.change(workspaceId, { module: MODULE_ID, entity: 'asset', id, op });
59
- async function record(tx, input, assetId, actorId, action, changes = [], data) {
60
- await tx.insert(assetHistory).values({
61
- id: uuidv7(),
62
- workspaceId: input.workspaceId,
63
- assetId,
64
- actorId: actorId ?? null,
65
- action,
66
- changes,
67
- data: data ?? null,
68
- });
69
- }
70
- return os.router({
71
- assets: {
72
- list: scoped.assets.list.use(requires('inventory.asset.view')).handler(({ input }) => kernel.database.withWorkspace(input.workspaceId, async (tx) => {
73
- const filters = [eq(assets.workspaceId, input.workspaceId)];
74
- if (input.q) {
75
- // A code is something somebody reads off a sticker; matching name, code and serial
76
- // loosely matters more than word-splitting here. Full-text arrives with the core indexer.
77
- filters.push(or(ilike(assets.name, `%${input.q}%`), ilike(assets.code, `%${input.q}%`), ilike(sql `coalesce(${assets.serialNumber}, '')`, `%${input.q}%`)));
78
- }
79
- if (input.categoryId)
80
- filters.push(eq(assets.categoryId, input.categoryId));
81
- if (input.status)
82
- filters.push(eq(assets.status, input.status));
83
- const order = input.sort === 'name' ? assets.name : input.sort === 'code' ? assets.code : desc(assets.createdAt);
84
- const rows = await tx
85
- .select()
86
- .from(assets)
87
- .where(and(...filters))
88
- .orderBy(order)
89
- .limit(input.limit);
90
- return { items: rows.map(toAsset), nextCursor: null };
91
- })),
92
- get: scoped.assets.get.use(requires('inventory.asset.view')).handler(({ input }) => kernel.database.withWorkspace(input.workspaceId, async (tx) => {
93
- const [row] = await tx
94
- .select()
95
- .from(assets)
96
- .where(and(eq(assets.workspaceId, input.workspaceId), eq(assets.id, input.assetId)));
97
- if (!row)
98
- throw KernError.notFound('Asset');
99
- return toAsset(row);
100
- })),
101
- create: scoped.assets.create
102
- .use(requires('inventory.asset.manage'))
103
- .handler(async ({ input, context }) => {
104
- const row = await kernel.database.withWorkspace(input.workspaceId, async (tx) => {
105
- const [r] = await tx
106
- .insert(assets)
107
- .values({
108
- id: uuidv7(),
109
- workspaceId: input.workspaceId,
110
- code: await nextCode(tx, input.workspaceId),
111
- name: input.name,
112
- description: input.description,
113
- categoryId: input.categoryId ?? null,
114
- serialNumber: input.serialNumber ?? null,
115
- location: input.location ?? null,
116
- purchasedFrom: input.purchasedFrom ?? null,
117
- purchasedOn: input.purchasedOn ?? null,
118
- warrantyUntil: input.warrantyUntil ?? null,
119
- priceMinor: input.priceMinor ?? null,
120
- currency: input.currency ?? null,
121
- })
122
- .returning();
123
- await record(tx, input, r.id, context.principal.userId, 'created');
124
- return r;
125
- });
126
- // Both, every time. The event is for anything that reacts later; the realtime change is
127
- // what redraws a screen somebody is looking at now. A mutation that does neither leaves
128
- // the rest of the product believing the old answer.
129
- await kernel.emit(inventoryEvents.assetCreated, { assetId: row.id, workspaceId: input.workspaceId }, { workspaceId: input.workspaceId, actorId: context.principal.userId });
130
- await changed(input.workspaceId, row.id, 'created');
131
- return toAsset(row);
132
- }),
133
- update: scoped.assets.update
134
- .use(requires('inventory.asset.manage'))
135
- .handler(async ({ input, context }) => {
136
- const row = await kernel.database.withWorkspace(input.workspaceId, async (tx) => {
137
- const [prev] = await tx
138
- .select()
139
- .from(assets)
140
- .where(and(eq(assets.workspaceId, input.workspaceId), eq(assets.id, input.assetId)))
141
- .for('update');
142
- if (!prev)
143
- throw KernError.notFound('Asset');
144
- const patch = {
145
- name: input.name ?? prev.name,
146
- description: input.description ?? prev.description,
147
- categoryId: input.categoryId !== undefined ? (input.categoryId ?? null) : prev.categoryId,
148
- serialNumber: input.serialNumber !== undefined ? (input.serialNumber ?? null) : prev.serialNumber,
149
- location: input.location !== undefined ? (input.location ?? null) : prev.location,
150
- purchasedFrom: input.purchasedFrom !== undefined ? (input.purchasedFrom ?? null) : prev.purchasedFrom,
151
- purchasedOn: input.purchasedOn !== undefined ? (input.purchasedOn ?? null) : prev.purchasedOn,
152
- warrantyUntil: input.warrantyUntil !== undefined ? (input.warrantyUntil ?? null) : prev.warrantyUntil,
153
- priceMinor: input.priceMinor !== undefined ? (input.priceMinor ?? null) : prev.priceMinor,
154
- currency: input.currency !== undefined ? (input.currency ?? null) : prev.currency,
155
- updatedAt: new Date(),
156
- };
157
- const [r] = await tx.update(assets).set(patch).where(eq(assets.id, input.assetId)).returning();
158
- // Field-level diffs are the timeline — write only what actually moved.
159
- const fields = [
160
- 'name',
161
- 'description',
162
- 'categoryId',
163
- 'serialNumber',
164
- 'location',
165
- 'purchasedFrom',
166
- 'purchasedOn',
167
- 'warrantyUntil',
168
- 'priceMinor',
169
- 'currency',
170
- ];
171
- const iso = (v) => (v instanceof Date ? v.toISOString() : v) ?? null;
172
- const changes = fields
173
- .filter((f) => iso(patch[f]) !== iso(prev[f]))
174
- .map((f) => ({ field: f, from: iso(prev[f]), to: iso(patch[f]) }));
175
- if (changes.length > 0)
176
- await record(tx, input, input.assetId, context.principal.userId, 'updated', changes);
177
- return r;
178
- });
179
- await kernel.emit(inventoryEvents.assetUpdated, { assetId: row.id, workspaceId: input.workspaceId }, { workspaceId: input.workspaceId, actorId: context.principal.userId });
180
- await changed(input.workspaceId, row.id, 'updated');
181
- return toAsset(row);
182
- }),
183
- archive: scoped.assets.archive
184
- .use(requires('inventory.asset.manage'))
185
- .handler(async ({ input, context }) => {
186
- const row = await kernel.database.withWorkspace(input.workspaceId, async (tx) => {
187
- const [r] = await tx
188
- .update(assets)
189
- .set({ archivedAt: input.archived ? new Date() : null, updatedAt: new Date() })
190
- .where(and(eq(assets.workspaceId, input.workspaceId), eq(assets.id, input.assetId)))
191
- .returning();
192
- if (!r)
193
- throw KernError.notFound('Asset');
194
- await record(tx, input, input.assetId, context.principal.userId, input.archived ? 'retired' : 'restored');
195
- return r;
196
- });
197
- await kernel.emit(inventoryEvents.assetArchived, { assetId: row.id, workspaceId: input.workspaceId }, { workspaceId: input.workspaceId, actorId: context.principal.userId });
198
- await changed(input.workspaceId, row.id, 'updated');
199
- return toAsset(row);
200
- }),
201
- },
202
- });
203
- }
204
- //# sourceMappingURL=_impl.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_impl.js","sourceRoot":"","sources":["../../src/server/_impl.ts"],"names":[],"mappings":"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"}
package/src/contract.ts DELETED
@@ -1,143 +0,0 @@
1
- import { baseContract, defineEvent, definePermissions, PageInput, page, WorkspaceId } from '@kernhq/contracts'
2
- import { z } from 'zod'
3
-
4
- /**
5
- * What this module offers, as data.
6
- *
7
- * Imported by **both** halves — the server implements it, the client calls it — so nothing here may
8
- * touch Node. The contract is the only thing that crosses that line, which is why a procedure that
9
- * exists here and not in the router is a lie that compiles. `module.test.ts` checks exactly that.
10
- */
11
-
12
- /** Lowercase, 2-32 characters. Names the API prefix, the Postgres schema `mod_<id>` and every event. */
13
- export const MODULE_ID = 'inventory'
14
-
15
- /**
16
- * An asset's lifecycle. `in_stock` and `assigned` follow custody (an open row in
17
- * `custody_periods` means assigned); `under_repair` follows an open repair; `retired` is set by
18
- * hand when an item leaves the company. Stored, because every list filter needs it, and kept in
19
- * step inside the same transaction that writes the row it derives from.
20
- */
21
- export const AssetStatus = z.enum(['in_stock', 'assigned', 'under_repair', 'retired'])
22
- export type AssetStatus = z.infer<typeof AssetStatus>
23
-
24
- export const Asset = z.object({
25
- id: z.uuid(),
26
- workspaceId: WorkspaceId,
27
- /** Human-facing asset tag (`INV-0001`), assigned by the server, unique per workspace. */
28
- code: z.string().min(1).max(40),
29
- name: z.string().min(1).max(200),
30
- description: z.string().max(4000),
31
- categoryId: z.uuid().nullable(),
32
- status: AssetStatus,
33
- /**
34
- * The member currently holding the item. A plain uuid — cross-schema foreign keys are what the
35
- * module boundary exists to prevent — resolved against core membership at read time.
36
- */
37
- custodianUserId: z.uuid().nullable(),
38
- custodySince: z.string().nullable(),
39
- serialNumber: z.string().max(200).nullable(),
40
- location: z.string().max(200).nullable(),
41
- purchasedOn: z.string().nullable(),
42
- purchasedFrom: z.string().max(200).nullable(),
43
- /** Minor units (cents), the convention billing established; formatted on the client. */
44
- priceMinor: z.number().int().nullable(),
45
- currency: z.string().length(3).nullable(),
46
- warrantyUntil: z.string().nullable(),
47
- photoFileId: z.uuid().nullable(),
48
- createdAt: z.string(),
49
- updatedAt: z.string(),
50
- archivedAt: z.string().nullable(),
51
- })
52
- export type Asset = z.infer<typeof Asset>
53
-
54
- const ws = z.object({ workspaceId: WorkspaceId })
55
-
56
- const AssetCreateInput = z.object({
57
- name: z.string().min(1).max(200),
58
- description: z.string().max(4000).default(''),
59
- categoryId: z.uuid().nullish(),
60
- serialNumber: z.string().max(200).nullish(),
61
- location: z.string().max(200).nullish(),
62
- purchasedFrom: z.string().max(200).nullish(),
63
- purchasedOn: z.iso.date().nullish(),
64
- warrantyUntil: z.iso.date().nullish(),
65
- priceMinor: z.number().int().min(0).nullish(),
66
- currency: z.string().length(3).nullish(),
67
- })
68
-
69
- export const inventoryContract = {
70
- assets: {
71
- list: baseContract
72
- .route({ method: 'GET', path: '/assets', tags: ['inventory'] })
73
- .input(
74
- ws.extend({
75
- ...PageInput.shape,
76
- q: z.string().max(200).optional(),
77
- categoryId: z.uuid().optional(),
78
- status: AssetStatus.optional(),
79
- sort: z.enum(['recent', 'name', 'code']).default('recent'),
80
- }),
81
- )
82
- .output(page(Asset)),
83
- get: baseContract
84
- .route({ method: 'GET', path: '/assets/{assetId}', tags: ['inventory'] })
85
- .input(ws.extend({ assetId: z.uuid() }))
86
- .output(Asset),
87
- create: baseContract
88
- .route({ method: 'POST', path: '/assets', tags: ['inventory'] })
89
- .input(ws.extend(AssetCreateInput.shape))
90
- .output(Asset),
91
- update: baseContract
92
- .route({ method: 'PATCH', path: '/assets/{assetId}', tags: ['inventory'] })
93
- .input(
94
- ws.extend({
95
- assetId: z.uuid(),
96
- ...AssetCreateInput.partial().shape,
97
- }),
98
- )
99
- .output(Asset),
100
- archive: baseContract
101
- .route({ method: 'POST', path: '/assets/{assetId}/archive', tags: ['inventory'] })
102
- .input(ws.extend({ assetId: z.uuid(), archived: z.boolean().default(true) }))
103
- .output(Asset),
104
- },
105
- }
106
- export type InventoryContract = typeof inventoryContract
107
-
108
- /** `<module>.<entity>.<action>`. Anything that emits one declares it here. Payloads carry ids, never rows. */
109
- export const inventoryEvents = {
110
- assetCreated: defineEvent(
111
- 'inventory.asset.created',
112
- z.object({ assetId: z.uuid(), workspaceId: WorkspaceId }),
113
- ),
114
- assetUpdated: defineEvent(
115
- 'inventory.asset.updated',
116
- z.object({ assetId: z.uuid(), workspaceId: WorkspaceId }),
117
- ),
118
- assetArchived: defineEvent(
119
- 'inventory.asset.archived',
120
- z.object({ assetId: z.uuid(), workspaceId: WorkspaceId }),
121
- ),
122
- }
123
-
124
- /**
125
- * `<module>.<resource>.<action>`, each with the narrowest scope that works and the roles that hold it
126
- * by default. A workspace can add or remove any of them afterwards with a custom role.
127
- */
128
- export const inventoryPermissions = definePermissions([
129
- {
130
- key: 'inventory.asset.view',
131
- label: 'View assets',
132
- scope: 'workspace',
133
- defaultRoles: ['owner', 'admin', 'member', 'guest'],
134
- dangerous: false,
135
- },
136
- {
137
- key: 'inventory.asset.manage',
138
- label: 'Create and edit assets',
139
- scope: 'workspace',
140
- defaultRoles: ['owner', 'admin', 'member'],
141
- dangerous: false,
142
- },
143
- ])