@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.
- package/LICENSE +662 -0
- package/README.md +32 -0
- package/dist/contract.d.ts +387 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +119 -0
- package/dist/contract.js.map +1 -0
- package/dist/server/_impl.d.ts +427 -0
- package/dist/server/_impl.d.ts.map +1 -0
- package/dist/server/_impl.js +204 -0
- package/dist/server/_impl.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +32 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/schema.d.ts +1552 -0
- package/dist/server/schema.d.ts.map +1 -0
- package/dist/server/schema.js +175 -0
- package/dist/server/schema.js.map +1 -0
- package/drizzle.config.ts +8 -0
- package/migrations/0000_init.sql +129 -0
- package/migrations/0001_rls.sql +62 -0
- package/migrations/meta/0000_snapshot.json +994 -0
- package/migrations/meta/_journal.json +20 -0
- package/package.json +106 -0
- package/src/client/api-instance.ts +28 -0
- package/src/client/api.ts +10 -0
- package/src/client/components/AssetFormDialog.svelte +185 -0
- package/src/client/i18n.ts +169 -0
- package/src/client/index.ts +26 -0
- package/src/client/mock.ts +95 -0
- package/src/client/module.ts +96 -0
- package/src/client/pages/AssetsPage.svelte +266 -0
- package/src/client/permissions.ts +30 -0
- package/src/client/query.ts +12 -0
- package/src/client/widgets/OverviewWidget.svelte +92 -0
- package/src/contract.ts +143 -0
- package/src/module.test.ts +79 -0
- package/src/server/_impl.ts +275 -0
- package/src/server/index.ts +33 -0
- package/src/server/schema.ts +228 -0
- package/tsconfig.client.json +10 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { moduleSchema } from '@kernhq/kernel';
|
|
2
|
+
import { sql } from 'drizzle-orm';
|
|
3
|
+
import { boolean, char, date, index, integer, jsonb, pgEnum, text, timestamp, uniqueIndex, uuid, } from 'drizzle-orm/pg-core';
|
|
4
|
+
/**
|
|
5
|
+
* This module's tables, in its own Postgres schema.
|
|
6
|
+
*
|
|
7
|
+
* Two rules, neither optional:
|
|
8
|
+
*
|
|
9
|
+
* - every tenant table carries `workspace_id` and an index that starts with it;
|
|
10
|
+
* - every tenant table gets a row-level security policy, hand-written in the migration, because
|
|
11
|
+
* drizzle-kit does not generate one. RLS is the last line — the API check is the first, and
|
|
12
|
+
* somebody will eventually write a query that skips it.
|
|
13
|
+
*/
|
|
14
|
+
export const schema = moduleSchema('inventory');
|
|
15
|
+
/** Local column factories, so the conventions stay in one place. */
|
|
16
|
+
const id = () => uuid('id').primaryKey().default(sql `uuidv7()`);
|
|
17
|
+
const ws = () => uuid('workspace_id').notNull();
|
|
18
|
+
const ts = (name) => timestamp(name, { withTimezone: true, mode: 'date' });
|
|
19
|
+
const created = () => ts('created_at').notNull().defaultNow();
|
|
20
|
+
const updated = () => ts('updated_at').notNull().defaultNow();
|
|
21
|
+
/**
|
|
22
|
+
* Asset lifecycle. Stored rather than derived because every list filter asks for it; every
|
|
23
|
+
* transition is written inside the same transaction as the row it derives from.
|
|
24
|
+
*/
|
|
25
|
+
export const assetStatus = pgEnum('asset_status', ['in_stock', 'assigned', 'under_repair', 'retired']);
|
|
26
|
+
export const counters = schema.table('counters',
|
|
27
|
+
/** Per-workspace sequence sources (`asset_code`). Narrow on purpose: one row per key. */
|
|
28
|
+
{
|
|
29
|
+
workspaceId: ws().primaryKey(),
|
|
30
|
+
key: text('key').primaryKey(),
|
|
31
|
+
value: integer('value').notNull(),
|
|
32
|
+
});
|
|
33
|
+
export const categories = schema.table('categories', {
|
|
34
|
+
id: id(),
|
|
35
|
+
workspaceId: ws(),
|
|
36
|
+
name: text('name').notNull(),
|
|
37
|
+
order: integer('order').notNull().default(0),
|
|
38
|
+
createdAt: created(),
|
|
39
|
+
updatedAt: updated(),
|
|
40
|
+
}, (t) => [
|
|
41
|
+
uniqueIndex('inventory_categories_ws_name_uq').on(t.workspaceId, t.name),
|
|
42
|
+
index('inventory_categories_ws_idx').on(t.workspaceId, t.order),
|
|
43
|
+
]);
|
|
44
|
+
export const assets = schema.table('assets', {
|
|
45
|
+
id: id(),
|
|
46
|
+
workspaceId: ws(),
|
|
47
|
+
code: text('code').notNull(),
|
|
48
|
+
name: text('name').notNull(),
|
|
49
|
+
description: text('description').notNull().default(''),
|
|
50
|
+
categoryId: uuid('category_id'),
|
|
51
|
+
status: assetStatus('status').notNull().default('in_stock'),
|
|
52
|
+
/** Denormalized from `custody_periods`, which stays authoritative for history. */
|
|
53
|
+
custodianUserId: uuid('custodian_user_id'),
|
|
54
|
+
custodySince: ts('custody_since'),
|
|
55
|
+
serialNumber: text('serial_number'),
|
|
56
|
+
location: text('location'),
|
|
57
|
+
purchasedOn: date('purchased_on'),
|
|
58
|
+
purchasedFrom: text('purchased_from'),
|
|
59
|
+
priceMinor: integer('price_minor'),
|
|
60
|
+
currency: char('currency', { length: 3 }),
|
|
61
|
+
warrantyUntil: date('warranty_until'),
|
|
62
|
+
photoFileId: uuid('photo_file_id'),
|
|
63
|
+
createdAt: created(),
|
|
64
|
+
updatedAt: updated(),
|
|
65
|
+
archivedAt: ts('archived_at'),
|
|
66
|
+
}, (t) => [
|
|
67
|
+
uniqueIndex('inventory_assets_ws_code_uq').on(t.workspaceId, t.code),
|
|
68
|
+
index('inventory_assets_ws_created_idx').on(t.workspaceId, t.createdAt),
|
|
69
|
+
index('inventory_assets_ws_status_idx').on(t.workspaceId, t.status),
|
|
70
|
+
index('inventory_assets_ws_category_idx').on(t.workspaceId, t.categoryId),
|
|
71
|
+
// The "what leaves warranty this month" scan, before a job makes it a widget's cheap query.
|
|
72
|
+
index('inventory_assets_ws_warranty_idx')
|
|
73
|
+
.on(t.workspaceId, t.warrantyUntil)
|
|
74
|
+
.where(sql `warranty_until is not null`),
|
|
75
|
+
]);
|
|
76
|
+
/**
|
|
77
|
+
* Workspace-defined extra fields, copied from tracker's `field_defs` (which cannot be shared:
|
|
78
|
+
* cross-schema joins are what the module boundary exists to prevent). The `key` **is** the key a
|
|
79
|
+
* value lives under in `assets.custom`, hence unique per workspace whatever the category scope.
|
|
80
|
+
*/
|
|
81
|
+
export const fieldDefs = schema.table('field_defs', {
|
|
82
|
+
id: id(),
|
|
83
|
+
workspaceId: ws(),
|
|
84
|
+
categoryId: uuid('category_id'),
|
|
85
|
+
key: text('key').notNull(),
|
|
86
|
+
name: text('name').notNull(),
|
|
87
|
+
description: text('description'),
|
|
88
|
+
type: text('type').notNull(), // text | number | date | select | multiselect | checkbox | url
|
|
89
|
+
options: jsonb('options').$type(),
|
|
90
|
+
defaultValue: jsonb('default_value'),
|
|
91
|
+
required: boolean('required').notNull().default(false),
|
|
92
|
+
searchable: boolean('searchable').notNull().default(false),
|
|
93
|
+
showInList: boolean('show_in_list').notNull().default(false),
|
|
94
|
+
order: integer('order').notNull().default(0),
|
|
95
|
+
archivedAt: ts('archived_at'),
|
|
96
|
+
createdAt: created(),
|
|
97
|
+
updatedAt: updated(),
|
|
98
|
+
}, (t) => [
|
|
99
|
+
uniqueIndex('inventory_field_defs_ws_key_uq').on(t.workspaceId, t.key),
|
|
100
|
+
index('inventory_field_defs_ws_idx').on(t.workspaceId, t.order),
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* Custody over time. Never updated in place — a change closes the open row and inserts a new one,
|
|
104
|
+
* the way HR keeps employments. The exclusion constraint (hand-written migration) makes two open
|
|
105
|
+
* periods for one asset impossible at the database level, not merely unlikely in the service.
|
|
106
|
+
*/
|
|
107
|
+
export const custodyPeriods = schema.table('custody_periods', {
|
|
108
|
+
id: id(),
|
|
109
|
+
workspaceId: ws(),
|
|
110
|
+
assetId: uuid('asset_id').notNull(),
|
|
111
|
+
userId: uuid('user_id').notNull(),
|
|
112
|
+
note: text('note'),
|
|
113
|
+
effectiveFrom: ts('effective_from').notNull().defaultNow(),
|
|
114
|
+
effectiveTo: ts('effective_to'),
|
|
115
|
+
createdBy: uuid('created_by'),
|
|
116
|
+
createdAt: created(),
|
|
117
|
+
}, (t) => [index('inventory_custody_ws_asset_idx').on(t.workspaceId, t.assetId, t.effectiveFrom)]);
|
|
118
|
+
/**
|
|
119
|
+
* Append-only. What changed, when, by whom — the timeline an asset page renders and the answer to
|
|
120
|
+
* "who had this laptop before me". Written inside the caller's transaction; nothing edits or
|
|
121
|
+
* deletes rows here, ever.
|
|
122
|
+
*/
|
|
123
|
+
export const assetHistory = schema.table('asset_history', {
|
|
124
|
+
id: id(),
|
|
125
|
+
workspaceId: ws(),
|
|
126
|
+
assetId: uuid('asset_id').notNull(),
|
|
127
|
+
actorId: uuid('actor_id'),
|
|
128
|
+
action: text('action').notNull(), // created | updated | transferred | returned | repair_logged | repair_completed | attachment_added | retired | restored
|
|
129
|
+
changes: jsonb('changes').$type(),
|
|
130
|
+
data: jsonb('data'),
|
|
131
|
+
occurredAt: created(),
|
|
132
|
+
}, (t) => [index('inventory_asset_history_asset_idx').on(t.workspaceId, t.assetId, t.occurredAt)]);
|
|
133
|
+
export const repairs = schema.table('repairs', {
|
|
134
|
+
id: id(),
|
|
135
|
+
workspaceId: ws(),
|
|
136
|
+
assetId: uuid('asset_id').notNull(),
|
|
137
|
+
summary: text('summary').notNull(),
|
|
138
|
+
detail: text('detail'),
|
|
139
|
+
vendor: text('vendor'),
|
|
140
|
+
costMinor: integer('cost_minor'),
|
|
141
|
+
currency: char('currency', { length: 3 }),
|
|
142
|
+
sentOn: date('sent_on').notNull(),
|
|
143
|
+
/** Null while the item is still away — also how `under_repair` is derived. */
|
|
144
|
+
returnedOn: date('returned_on'),
|
|
145
|
+
createdBy: uuid('created_by'),
|
|
146
|
+
createdAt: created(),
|
|
147
|
+
updatedAt: updated(),
|
|
148
|
+
}, (t) => [index('inventory_repairs_ws_asset_idx').on(t.workspaceId, t.assetId, t.sentOn)]);
|
|
149
|
+
/** Bytes live in core object storage via `uploadFile`; this only records that an asset has one. */
|
|
150
|
+
export const attachments = schema.table('attachments', {
|
|
151
|
+
id: id(),
|
|
152
|
+
workspaceId: ws(),
|
|
153
|
+
assetId: uuid('asset_id').notNull(),
|
|
154
|
+
repairId: uuid('repair_id'),
|
|
155
|
+
fileId: uuid('file_id').notNull(),
|
|
156
|
+
name: text('name').notNull(),
|
|
157
|
+
mimeType: text('mime_type'),
|
|
158
|
+
size: integer('size'),
|
|
159
|
+
uploadedBy: uuid('uploaded_by'),
|
|
160
|
+
createdAt: created(),
|
|
161
|
+
}, (t) => [
|
|
162
|
+
uniqueIndex('inventory_attachments_asset_file_uq').on(t.assetId, t.fileId),
|
|
163
|
+
index('inventory_attachments_ws_asset_idx').on(t.workspaceId, t.assetId, t.createdAt),
|
|
164
|
+
]);
|
|
165
|
+
/** Every tenant table, so the RLS migration can be checked against one list rather than memory. */
|
|
166
|
+
export const TENANT_TABLES = [
|
|
167
|
+
'categories',
|
|
168
|
+
'assets',
|
|
169
|
+
'field_defs',
|
|
170
|
+
'custody_periods',
|
|
171
|
+
'asset_history',
|
|
172
|
+
'repairs',
|
|
173
|
+
'attachments',
|
|
174
|
+
];
|
|
175
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit'
|
|
2
|
+
// Generates SQL migrations for this module's own schema. Run: pnpm db:generate
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
dialect: 'postgresql',
|
|
5
|
+
schema: './src/server/schema.ts',
|
|
6
|
+
out: './migrations',
|
|
7
|
+
schemaFilter: ['mod_inventory'],
|
|
8
|
+
})
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
-- `create schema if not exists` matters: the kernel creates `mod_inventory` before running these, so
|
|
2
|
+
-- the bare form fails on boot.
|
|
3
|
+
CREATE SCHEMA IF NOT EXISTS "mod_inventory";
|
|
4
|
+
--> statement-breakpoint
|
|
5
|
+
CREATE TYPE "public"."asset_status" AS ENUM('in_stock', 'assigned', 'under_repair', 'retired');--> statement-breakpoint
|
|
6
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."asset_history" (
|
|
7
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
8
|
+
"workspace_id" uuid NOT NULL,
|
|
9
|
+
"asset_id" uuid NOT NULL,
|
|
10
|
+
"actor_id" uuid,
|
|
11
|
+
"action" text NOT NULL,
|
|
12
|
+
"changes" jsonb,
|
|
13
|
+
"data" jsonb,
|
|
14
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
15
|
+
);
|
|
16
|
+
--> statement-breakpoint
|
|
17
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."assets" (
|
|
18
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
19
|
+
"workspace_id" uuid NOT NULL,
|
|
20
|
+
"code" text NOT NULL,
|
|
21
|
+
"name" text NOT NULL,
|
|
22
|
+
"description" text DEFAULT '' NOT NULL,
|
|
23
|
+
"category_id" uuid,
|
|
24
|
+
"status" "asset_status" DEFAULT 'in_stock' NOT NULL,
|
|
25
|
+
"custodian_user_id" uuid,
|
|
26
|
+
"custody_since" timestamp with time zone,
|
|
27
|
+
"serial_number" text,
|
|
28
|
+
"location" text,
|
|
29
|
+
"purchased_on" date,
|
|
30
|
+
"purchased_from" text,
|
|
31
|
+
"price_minor" integer,
|
|
32
|
+
"currency" char(3),
|
|
33
|
+
"warranty_until" date,
|
|
34
|
+
"photo_file_id" uuid,
|
|
35
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
36
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
37
|
+
"archived_at" timestamp with time zone
|
|
38
|
+
);
|
|
39
|
+
--> statement-breakpoint
|
|
40
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."attachments" (
|
|
41
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
42
|
+
"workspace_id" uuid NOT NULL,
|
|
43
|
+
"asset_id" uuid NOT NULL,
|
|
44
|
+
"repair_id" uuid,
|
|
45
|
+
"file_id" uuid NOT NULL,
|
|
46
|
+
"name" text NOT NULL,
|
|
47
|
+
"mime_type" text,
|
|
48
|
+
"size" integer,
|
|
49
|
+
"uploaded_by" uuid,
|
|
50
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
51
|
+
);
|
|
52
|
+
--> statement-breakpoint
|
|
53
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."categories" (
|
|
54
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
55
|
+
"workspace_id" uuid NOT NULL,
|
|
56
|
+
"name" text NOT NULL,
|
|
57
|
+
"order" integer DEFAULT 0 NOT NULL,
|
|
58
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
59
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
60
|
+
);
|
|
61
|
+
--> statement-breakpoint
|
|
62
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."counters" (
|
|
63
|
+
"workspace_id" uuid NOT NULL,
|
|
64
|
+
"key" text NOT NULL,
|
|
65
|
+
"value" integer NOT NULL,
|
|
66
|
+
CONSTRAINT "counters_workspace_id_key_pk" PRIMARY KEY("workspace_id","key")
|
|
67
|
+
);
|
|
68
|
+
--> statement-breakpoint
|
|
69
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."custody_periods" (
|
|
70
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
71
|
+
"workspace_id" uuid NOT NULL,
|
|
72
|
+
"asset_id" uuid NOT NULL,
|
|
73
|
+
"user_id" uuid NOT NULL,
|
|
74
|
+
"note" text,
|
|
75
|
+
"effective_from" timestamp with time zone DEFAULT now() NOT NULL,
|
|
76
|
+
"effective_to" timestamp with time zone,
|
|
77
|
+
"created_by" uuid,
|
|
78
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
79
|
+
);
|
|
80
|
+
--> statement-breakpoint
|
|
81
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."field_defs" (
|
|
82
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
83
|
+
"workspace_id" uuid NOT NULL,
|
|
84
|
+
"category_id" uuid,
|
|
85
|
+
"key" text NOT NULL,
|
|
86
|
+
"name" text NOT NULL,
|
|
87
|
+
"description" text,
|
|
88
|
+
"type" text NOT NULL,
|
|
89
|
+
"options" jsonb,
|
|
90
|
+
"default_value" jsonb,
|
|
91
|
+
"required" boolean DEFAULT false NOT NULL,
|
|
92
|
+
"searchable" boolean DEFAULT false NOT NULL,
|
|
93
|
+
"show_in_list" boolean DEFAULT false NOT NULL,
|
|
94
|
+
"order" integer DEFAULT 0 NOT NULL,
|
|
95
|
+
"archived_at" timestamp with time zone,
|
|
96
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
97
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
98
|
+
);
|
|
99
|
+
--> statement-breakpoint
|
|
100
|
+
CREATE TABLE IF NOT EXISTS "mod_inventory"."repairs" (
|
|
101
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
102
|
+
"workspace_id" uuid NOT NULL,
|
|
103
|
+
"asset_id" uuid NOT NULL,
|
|
104
|
+
"summary" text NOT NULL,
|
|
105
|
+
"detail" text,
|
|
106
|
+
"vendor" text,
|
|
107
|
+
"cost_minor" integer,
|
|
108
|
+
"currency" char(3),
|
|
109
|
+
"sent_on" date NOT NULL,
|
|
110
|
+
"returned_on" date,
|
|
111
|
+
"created_by" uuid,
|
|
112
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
113
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
114
|
+
);
|
|
115
|
+
--> statement-breakpoint
|
|
116
|
+
CREATE INDEX IF NOT EXISTS "inventory_asset_history_asset_idx" ON "mod_inventory"."asset_history" USING btree ("workspace_id","asset_id","created_at");--> statement-breakpoint
|
|
117
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "inventory_assets_ws_code_uq" ON "mod_inventory"."assets" USING btree ("workspace_id","code");--> statement-breakpoint
|
|
118
|
+
CREATE INDEX IF NOT EXISTS "inventory_assets_ws_created_idx" ON "mod_inventory"."assets" USING btree ("workspace_id","created_at");--> statement-breakpoint
|
|
119
|
+
CREATE INDEX IF NOT EXISTS "inventory_assets_ws_status_idx" ON "mod_inventory"."assets" USING btree ("workspace_id","status");--> statement-breakpoint
|
|
120
|
+
CREATE INDEX IF NOT EXISTS "inventory_assets_ws_category_idx" ON "mod_inventory"."assets" USING btree ("workspace_id","category_id");--> statement-breakpoint
|
|
121
|
+
CREATE INDEX IF NOT EXISTS "inventory_assets_ws_warranty_idx" ON "mod_inventory"."assets" USING btree ("workspace_id","warranty_until") WHERE warranty_until is not null;--> statement-breakpoint
|
|
122
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "inventory_attachments_asset_file_uq" ON "mod_inventory"."attachments" USING btree ("asset_id","file_id");--> statement-breakpoint
|
|
123
|
+
CREATE INDEX IF NOT EXISTS "inventory_attachments_ws_asset_idx" ON "mod_inventory"."attachments" USING btree ("workspace_id","asset_id","created_at");--> statement-breakpoint
|
|
124
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "inventory_categories_ws_name_uq" ON "mod_inventory"."categories" USING btree ("workspace_id","name");--> statement-breakpoint
|
|
125
|
+
CREATE INDEX IF NOT EXISTS "inventory_categories_ws_idx" ON "mod_inventory"."categories" USING btree ("workspace_id","order");--> statement-breakpoint
|
|
126
|
+
CREATE INDEX IF NOT EXISTS "inventory_custody_ws_asset_idx" ON "mod_inventory"."custody_periods" USING btree ("workspace_id","asset_id","effective_from");--> statement-breakpoint
|
|
127
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "inventory_field_defs_ws_key_uq" ON "mod_inventory"."field_defs" USING btree ("workspace_id","key");--> statement-breakpoint
|
|
128
|
+
CREATE INDEX IF NOT EXISTS "inventory_field_defs_ws_idx" ON "mod_inventory"."field_defs" USING btree ("workspace_id","order");--> statement-breakpoint
|
|
129
|
+
CREATE INDEX IF NOT EXISTS "inventory_repairs_ws_asset_idx" ON "mod_inventory"."repairs" USING btree ("workspace_id","asset_id","sent_on");
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
-- Row-level security. Hand-written, because drizzle-kit does not generate it.
|
|
2
|
+
--
|
|
3
|
+
-- This is the last line, not the first: the API already checks membership and permission. It exists
|
|
4
|
+
-- for the query that skips them — a job, a report, a mistake — and for anyone who reaches the
|
|
5
|
+
-- database another way. A tenant table without a policy is simply readable.
|
|
6
|
+
--
|
|
7
|
+
-- `@kernhq/kernel` exports `rlsPolicySql('mod_inventory', '<table>')`, which emits exactly this
|
|
8
|
+
-- block per table. Superusers bypass RLS, so a database owned by one will pass a test that proves
|
|
9
|
+
-- nothing: run the application as a plain role.
|
|
10
|
+
|
|
11
|
+
alter table "mod_inventory"."categories" enable row level security;--> statement-breakpoint
|
|
12
|
+
alter table "mod_inventory"."categories" force row level security;--> statement-breakpoint
|
|
13
|
+
create policy "categories_ws_isolation" on "mod_inventory"."categories"
|
|
14
|
+
using (workspace_id::text = current_setting('app.workspace_id', true))
|
|
15
|
+
with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
|
|
16
|
+
|
|
17
|
+
alter table "mod_inventory"."assets" enable row level security;--> statement-breakpoint
|
|
18
|
+
alter table "mod_inventory"."assets" force row level security;--> statement-breakpoint
|
|
19
|
+
create policy "assets_ws_isolation" on "mod_inventory"."assets"
|
|
20
|
+
using (workspace_id::text = current_setting('app.workspace_id', true))
|
|
21
|
+
with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
|
|
22
|
+
|
|
23
|
+
alter table "mod_inventory"."field_defs" enable row level security;--> statement-breakpoint
|
|
24
|
+
alter table "mod_inventory"."field_defs" force row level security;--> statement-breakpoint
|
|
25
|
+
create policy "field_defs_ws_isolation" on "mod_inventory"."field_defs"
|
|
26
|
+
using (workspace_id::text = current_setting('app.workspace_id', true))
|
|
27
|
+
with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
|
|
28
|
+
|
|
29
|
+
alter table "mod_inventory"."custody_periods" enable row level security;--> statement-breakpoint
|
|
30
|
+
alter table "mod_inventory"."custody_periods" force row level security;--> statement-breakpoint
|
|
31
|
+
create policy "custody_periods_ws_isolation" on "mod_inventory"."custody_periods"
|
|
32
|
+
using (workspace_id::text = current_setting('app.workspace_id', true))
|
|
33
|
+
with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
|
|
34
|
+
|
|
35
|
+
alter table "mod_inventory"."asset_history" enable row level security;--> statement-breakpoint
|
|
36
|
+
alter table "mod_inventory"."asset_history" force row level security;--> statement-breakpoint
|
|
37
|
+
create policy "asset_history_ws_isolation" on "mod_inventory"."asset_history"
|
|
38
|
+
using (workspace_id::text = current_setting('app.workspace_id', true))
|
|
39
|
+
with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
|
|
40
|
+
|
|
41
|
+
alter table "mod_inventory"."repairs" enable row level security;--> statement-breakpoint
|
|
42
|
+
alter table "mod_inventory"."repairs" force row level security;--> statement-breakpoint
|
|
43
|
+
create policy "repairs_ws_isolation" on "mod_inventory"."repairs"
|
|
44
|
+
using (workspace_id::text = current_setting('app.workspace_id', true))
|
|
45
|
+
with check (workspace_id::text = current_setting('app.workspace_id', true));--> statement-breakpoint
|
|
46
|
+
|
|
47
|
+
alter table "mod_inventory"."attachments" enable row level security;--> statement-breakpoint
|
|
48
|
+
alter table "mod_inventory"."attachments" force row level security;--> statement-breakpoint
|
|
49
|
+
create policy "attachments_ws_isolation" on "mod_inventory"."attachments"
|
|
50
|
+
using (workspace_id::text = current_setting('app.workspace_id', true))
|
|
51
|
+
with check (workspace_id::text = current_setting('app.workspace_id', true));
|
|
52
|
+
|
|
53
|
+
-- Custody is effective-dated: a change closes the open row and inserts a new one. This constraint
|
|
54
|
+
-- is what makes two overlapping custody periods for one asset impossible at the database level —
|
|
55
|
+
-- two concurrent transfers cannot both win. `effective_to` null means "still open", which tstzrange
|
|
56
|
+
-- treats as unbounded, so an open period conflicts with everything that would follow it.
|
|
57
|
+
alter table "mod_inventory"."custody_periods"
|
|
58
|
+
add constraint "inventory_custody_no_overlap"
|
|
59
|
+
exclude using gist (
|
|
60
|
+
asset_id with =,
|
|
61
|
+
tstzrange(effective_from, effective_to, '[)') with &&
|
|
62
|
+
);
|