@fayz-ai/plugin-inventory 0.1.7 → 0.8.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.
@@ -0,0 +1,9 @@
1
+ export declare const MIGRATION_000_PLG_RENAME = "-- 000_plg_rename.sql \u2014 rename legacy inventory tables to plg_inventory_* for pools\n-- provisioned before the industry-pool rename. Guarded: fires only when the legacy\n-- name exists and the target does not, so fresh pools skip every branch.\nDO $$\nBEGIN\n IF to_regclass('public.stock_locations') IS NOT NULL AND to_regclass('public.plg_inventory_stock_locations') IS NULL THEN\n ALTER TABLE public.stock_locations RENAME TO plg_inventory_stock_locations;\n END IF;\n IF to_regclass('public.stock_movements') IS NOT NULL AND to_regclass('public.plg_inventory_stock_movements') IS NULL THEN\n ALTER TABLE public.stock_movements RENAME TO plg_inventory_stock_movements;\n END IF;\n IF to_regclass('public.stock_positions') IS NOT NULL AND to_regclass('public.plg_inventory_stock_positions') IS NULL THEN\n ALTER TABLE public.stock_positions RENAME TO plg_inventory_stock_positions;\n END IF;\n IF to_regclass('public.recipes') IS NOT NULL AND to_regclass('public.plg_inventory_recipes') IS NULL THEN\n ALTER TABLE public.recipes RENAME TO plg_inventory_recipes;\n END IF;\n IF to_regclass('public.recipe_ingredients') IS NOT NULL AND to_regclass('public.plg_inventory_recipe_ingredients') IS NULL THEN\n ALTER TABLE public.recipe_ingredients RENAME TO plg_inventory_recipe_ingredients;\n END IF;\n IF to_regclass('public.measurement_units') IS NOT NULL AND to_regclass('public.plg_inventory_measurement_units') IS NULL THEN\n ALTER TABLE public.measurement_units RENAME TO plg_inventory_measurement_units;\n END IF;\n IF to_regclass('public.product_categories') IS NOT NULL AND to_regclass('public.plg_inventory_product_categories') IS NULL THEN\n ALTER TABLE public.product_categories RENAME TO plg_inventory_product_categories;\n END IF;\nEND $$;\n";
2
+ export declare const MIGRATION_001_INVENTORY_BASE = "-- Inventory Plugin: Base Tables\n-- Products use public.products archetype directly\n-- These are plugin-specific extension tables\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_categories (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n parent_id uuid REFERENCES public.plg_inventory_product_categories(id),\n is_active boolean NOT NULL DEFAULT true,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_product_categories ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_product_categories_tenant ON public.plg_inventory_product_categories(tenant_id);\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_stock_locations (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n description text,\n is_active boolean NOT NULL DEFAULT true,\n unit_id uuid,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_stock_locations ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_locations_tenant ON public.plg_inventory_stock_locations(tenant_id);\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_stock_movements (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n product_id uuid NOT NULL REFERENCES public.products(id),\n quantity numeric(14,4) NOT NULL,\n movement_type text NOT NULL,\n unit_cost numeric(14,2) DEFAULT 0,\n total_cost numeric(14,2) DEFAULT 0,\n stock_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),\n destination_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),\n supplier_id uuid REFERENCES public.people(id),\n document_number text,\n reason text,\n notes text,\n movement_date date NOT NULL DEFAULT CURRENT_DATE,\n user_id uuid,\n batch_number text,\n expiration_date date,\n metadata jsonb DEFAULT '{}'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_stock_movements ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_tenant ON public.plg_inventory_stock_movements(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_product ON public.plg_inventory_stock_movements(product_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_date ON public.plg_inventory_stock_movements(tenant_id, movement_date);\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_stock_positions (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n product_id uuid NOT NULL REFERENCES public.products(id),\n quantity numeric(14,4) NOT NULL,\n unit_cost numeric(14,2) DEFAULT 0,\n stock_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),\n batch_number text,\n expiration_date date,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_stock_positions ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_positions_tenant ON public.plg_inventory_stock_positions(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_positions_product ON public.plg_inventory_stock_positions(product_id);\n";
3
+ export declare const MIGRATION_002_RECIPES = "-- Inventory Plugin: Recipes & Technical Specs\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_recipes (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n description text,\n product_id uuid REFERENCES public.products(id),\n yield_quantity numeric(14,4) DEFAULT 1,\n yield_unit_id uuid,\n preparation_time_minutes integer,\n instructions text,\n is_active boolean NOT NULL DEFAULT true,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_recipes ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_recipes_tenant ON public.plg_inventory_recipes(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_recipes_product ON public.plg_inventory_recipes(product_id);\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_recipe_ingredients (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n recipe_id uuid NOT NULL REFERENCES public.plg_inventory_recipes(id) ON DELETE CASCADE,\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n product_id uuid NOT NULL REFERENCES public.products(id),\n quantity numeric(14,4) NOT NULL,\n unit_id uuid,\n display_order integer DEFAULT 0,\n notes text,\n created_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_recipe_ingredients ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_ingredients_tenant ON public.plg_inventory_recipe_ingredients(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_ingredients_recipe ON public.plg_inventory_recipe_ingredients(recipe_id);\n";
4
+ export declare const MIGRATION_003_MEASUREMENT_UNITS = "-- Inventory Plugin: Measurement Units\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_measurement_units (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n name text NOT NULL,\n abbreviation text NOT NULL,\n is_active boolean NOT NULL DEFAULT true,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_measurement_units ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_measurement_units_tenant ON public.plg_inventory_measurement_units(tenant_id);\n";
5
+ export declare const MIGRATIONS: Array<{
6
+ id: string;
7
+ sql: string;
8
+ }>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,wBAAwB,qwDA2BpC,CAAA;AAED,eAAO,MAAM,4BAA4B,+9GAqExC,CAAA;AAED,eAAO,MAAM,qBAAqB,+rDAkCjC,CAAA;AAED,eAAO,MAAM,+BAA+B,uoBAa3C,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAKzD,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@fayz-ai/plugin-inventory",
3
- "version": "0.1.7",
3
+ "fayz": {
4
+ "status": "beta"
5
+ },
6
+ "version": "0.8.0",
4
7
  "description": "Fayz SDK — plugin-inventory plugin",
5
8
  "type": "module",
6
9
  "main": "./dist/index.cjs",
@@ -26,9 +29,9 @@
26
29
  "lucide-react": "^0.400.0",
27
30
  "zustand": "^4.5.0",
28
31
  "@tanstack/react-table": "^8.20.0",
29
- "@fayz-ai/core": "^0.6.6",
30
- "@fayz-ai/ui": "^0.6.6",
31
- "@fayz-ai/saas": "^0.6.6"
32
+ "@fayz-ai/ui": "^0.8.0",
33
+ "@fayz-ai/saas": "^0.8.0",
34
+ "@fayz-ai/core": "^0.8.0"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@types/react": "^18.3.0",
@@ -43,6 +46,9 @@
43
46
  "fayz-plugin",
44
47
  "fayz-sdk"
45
48
  ],
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
46
52
  "scripts": {
47
53
  "build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --noEmit false",
48
54
  "dev": "tsup --watch",
package/src/README.md CHANGED
@@ -7,7 +7,7 @@ Product catalog, stock management, movement tracking, and recipe/production form
7
7
  ```typescript
8
8
  import { createInventoryPlugin } from '@fayz-ai/saas-core/plugins/inventory'
9
9
 
10
- // In your createSaasApp config:
10
+ // In your defineSaas config:
11
11
  plugins: [
12
12
  createInventoryPlugin({
13
13
  currency: { code: 'BRL', locale: 'pt-BR', symbol: 'R$' },
@@ -8,6 +8,7 @@ import type {
8
8
  } from '../types'
9
9
  import { getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core'
10
10
  import { getInventoryTenantId } from '../lib/tenant'
11
+ import { T } from './tables'
11
12
 
12
13
  function getTenantId(): string | undefined {
13
14
  // Local override wins; else use the app's active tenant so writes pass RLS.
@@ -33,7 +34,7 @@ function camelToSnake(obj: Record<string, unknown>): Record<string, unknown> {
33
34
  return result
34
35
  }
35
36
 
36
- /** Map a saas_core.products row to our Product type */
37
+ /** Map a public.products row to our Product type */
37
38
  function mapProductRow(row: Record<string, any>): Product {
38
39
  const meta = row.metadata ?? {}
39
40
  return {
@@ -64,11 +65,11 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
64
65
  function getClients() {
65
66
  const supabase = getSupabaseClientOptional() as any
66
67
  if (!supabase) throw new Error('Supabase not initialized')
67
- return { core: supabase.schema('saas_core'), pub: supabase }
68
+ return { core: supabase, pub: supabase }
68
69
  }
69
70
 
70
71
  const provider: InventoryDataProvider = {
71
- // --- Products (saas_core.products) ---
72
+ // --- Products (public.products) ---
72
73
  async getProducts(query: ProductQuery): Promise<PaginatedResult<Product>> {
73
74
  const { core, pub } = getClients()
74
75
  let qb = core.from('products').select('*', { count: 'exact' })
@@ -133,7 +134,7 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
133
134
  // --- Stock Movements (via view with product join — single query) ---
134
135
  async getMovements(query: MovementQuery): Promise<PaginatedResult<StockMovement>> {
135
136
  const { pub } = getClients()
136
- // Single query via v_stock_movements view (JOINs with saas_core.products)
137
+ // Single query via v_stock_movements view (JOINs with public.products)
137
138
  let qb = pub.from('v_stock_movements').select('*', { count: 'exact' })
138
139
  if (query.productId) qb = qb.eq('product_id', query.productId)
139
140
  if (query.movementType) {
@@ -169,7 +170,7 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
169
170
  if (m.destinationLocationId && !m.destinationLocationName) locationIds.add(m.destinationLocationId)
170
171
  }
171
172
  if (locationIds.size > 0) {
172
- const { data: locs } = await pub.from('stock_locations').select('id, name').in('id', [...locationIds])
173
+ const { data: locs } = await pub.from(T.stockLocations).select('id, name').in('id', [...locationIds])
173
174
  const locMap = new Map((locs ?? []).map((l: any) => [l.id, l.name]))
174
175
  for (const m of movements) {
175
176
  if (m.stockLocationId && !m.stockLocationName) m.stockLocationName = locMap.get(m.stockLocationId)
@@ -192,7 +193,7 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
192
193
  total_cost: unitCost * input.quantity,
193
194
  movement_date: input.movementDate ?? new Date().toISOString().slice(0, 10),
194
195
  }
195
- const { data, error } = await pub.from('stock_movements').insert(row).select().single()
196
+ const { data, error } = await pub.from(T.stockMovements).insert(row).select().single()
196
197
  if (error) throw new Error(error.message)
197
198
 
198
199
  // Fetch product name + current stock for the response and stock update
@@ -210,18 +211,18 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
210
211
  if (input.stockLocationId) {
211
212
  try {
212
213
  const posDelta = (input.movementType === 'entry') ? input.quantity : -(input.quantity)
213
- const { data: existing } = await pub.from('stock_positions')
214
+ const { data: existing } = await pub.from(T.stockPositions)
214
215
  .select('id, quantity')
215
216
  .eq('product_id', input.productId)
216
217
  .eq('stock_location_id', input.stockLocationId)
217
218
  .maybeSingle()
218
219
 
219
220
  if (existing) {
220
- await pub.from('stock_positions')
221
+ await pub.from(T.stockPositions)
221
222
  .update({ quantity: (existing.quantity ?? 0) + posDelta, unit_cost: input.unitCost ?? 0 })
222
223
  .eq('id', existing.id)
223
224
  } else {
224
- await pub.from('stock_positions').insert({
225
+ await pub.from(T.stockPositions).insert({
225
226
  tenant_id: tenantId,
226
227
  product_id: input.productId,
227
228
  stock_location_id: input.stockLocationId,
@@ -234,18 +235,18 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
234
235
 
235
236
  // For transfers, also update the destination position
236
237
  if (input.movementType === 'transfer' && input.destinationLocationId) {
237
- const { data: destExisting } = await pub.from('stock_positions')
238
+ const { data: destExisting } = await pub.from(T.stockPositions)
238
239
  .select('id, quantity')
239
240
  .eq('product_id', input.productId)
240
241
  .eq('stock_location_id', input.destinationLocationId)
241
242
  .maybeSingle()
242
243
 
243
244
  if (destExisting) {
244
- await pub.from('stock_positions')
245
+ await pub.from(T.stockPositions)
245
246
  .update({ quantity: (destExisting.quantity ?? 0) + input.quantity, unit_cost: input.unitCost ?? 0 })
246
247
  .eq('id', destExisting.id)
247
248
  } else {
248
- await pub.from('stock_positions').insert({
249
+ await pub.from(T.stockPositions).insert({
249
250
  tenant_id: tenantId,
250
251
  product_id: input.productId,
251
252
  stock_location_id: input.destinationLocationId,
@@ -266,11 +267,11 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
266
267
  movement.productName = product?.name
267
268
  // Resolve location name
268
269
  if (input.stockLocationId) {
269
- const { data: loc } = await pub.from('stock_locations').select('name').eq('id', input.stockLocationId).single()
270
+ const { data: loc } = await pub.from(T.stockLocations).select('name').eq('id', input.stockLocationId).single()
270
271
  movement.stockLocationName = loc?.name
271
272
  }
272
273
  if (input.destinationLocationId) {
273
- const { data: loc } = await pub.from('stock_locations').select('name').eq('id', input.destinationLocationId).single()
274
+ const { data: loc } = await pub.from(T.stockLocations).select('name').eq('id', input.destinationLocationId).single()
274
275
  movement.destinationLocationName = loc?.name
275
276
  }
276
277
  return movement as StockMovement
@@ -279,40 +280,40 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
279
280
  // --- Stock Positions ---
280
281
  async getPositions(productId: string): Promise<StockPosition[]> {
281
282
  const { pub } = getClients()
282
- const { data } = await pub.from('stock_positions').select('*').eq('product_id', productId)
283
+ const { data } = await pub.from(T.stockPositions).select('*').eq('product_id', productId)
283
284
  return (data ?? []).map((r: any) => snakeToCamel(r) as unknown as StockPosition)
284
285
  },
285
286
 
286
287
  // --- Stock Locations ---
287
288
  async getLocations(): Promise<StockLocation[]> {
288
289
  const { pub } = getClients()
289
- const { data } = await pub.from('stock_locations').select('*').eq('is_active', true).order('name')
290
+ const { data } = await pub.from(T.stockLocations).select('*').eq('is_active', true).order('name')
290
291
  return (data ?? []).map((r: any) => snakeToCamel(r) as unknown as StockLocation)
291
292
  },
292
293
 
293
294
  async createLocation(input): Promise<StockLocation> {
294
295
  const { pub } = getClients()
295
296
  const tenantId = getTenantId()
296
- const { data } = await pub.from('stock_locations').insert({ ...camelToSnake(input as any), tenant_id: tenantId }).select().single()
297
+ const { data } = await pub.from(T.stockLocations).insert({ ...camelToSnake(input as any), tenant_id: tenantId }).select().single()
297
298
  return snakeToCamel(data!) as unknown as StockLocation
298
299
  },
299
300
 
300
301
  // --- Recipes ---
301
302
  async getRecipes(): Promise<Recipe[]> {
302
303
  const { pub } = getClients()
303
- const { data } = await pub.from('recipes').select('*').eq('is_active', true).order('name')
304
+ const { data } = await pub.from(T.recipes).select('*').eq('is_active', true).order('name')
304
305
  return (data ?? []).map((r: any) => snakeToCamel(r) as unknown as Recipe)
305
306
  },
306
307
 
307
308
  async getRecipeById(id: string): Promise<Recipe | null> {
308
309
  const { pub } = getClients()
309
- const { data } = await pub.from('recipes').select('*').eq('id', id).single()
310
+ const { data } = await pub.from(T.recipes).select('*').eq('id', id).single()
310
311
  return data ? snakeToCamel(data) as unknown as Recipe : null
311
312
  },
312
313
 
313
314
  async getRecipeIngredients(recipeId: string): Promise<RecipeIngredient[]> {
314
315
  const { pub } = getClients()
315
- const { data } = await pub.from('recipe_ingredients').select('*').eq('recipe_id', recipeId).order('display_order')
316
+ const { data } = await pub.from(T.recipeIngredients).select('*').eq('recipe_id', recipeId).order('display_order')
316
317
  return (data ?? []).map((r: any) => snakeToCamel(r) as unknown as RecipeIngredient)
317
318
  },
318
319
 
@@ -320,9 +321,9 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
320
321
  const { pub } = getClients()
321
322
  const tenantId = getTenantId()
322
323
  const { ingredients, ...recipeData } = input
323
- const { data: recipe } = await pub.from('recipes').insert({ ...camelToSnake(recipeData as any), tenant_id: tenantId }).select().single()
324
+ const { data: recipe } = await pub.from(T.recipes).insert({ ...camelToSnake(recipeData as any), tenant_id: tenantId }).select().single()
324
325
  if (recipe && ingredients.length > 0) {
325
- await pub.from('recipe_ingredients').insert(
326
+ await pub.from(T.recipeIngredients).insert(
326
327
  ingredients.map((ing) => ({ ...camelToSnake(ing as any), recipe_id: recipe.id, tenant_id: tenantId }))
327
328
  )
328
329
  }
@@ -340,7 +341,7 @@ export function createSupabaseInventoryProvider(): InventoryDataProvider {
340
341
 
341
342
  const weekAgo = new Date()
342
343
  weekAgo.setDate(weekAgo.getDate() - 7)
343
- const { data: movements } = await pub.from('stock_movements').select('movement_type').gte('movement_date', weekAgo.toISOString().slice(0, 10))
344
+ const { data: movements } = await pub.from(T.stockMovements).select('movement_type').gte('movement_date', weekAgo.toISOString().slice(0, 10))
344
345
  const movs = movements ?? []
345
346
  const movementsByType: Record<MovementType, number> = { entry: 0, exit: 0, adjustment: 0, transfer: 0, loss: 0 }
346
347
  for (const m of movs) movementsByType[m.movement_type as MovementType]++
@@ -0,0 +1,10 @@
1
+ // Central physical-table-name registry for plugin-inventory.
2
+ export const T = {
3
+ stockLocations: 'plg_inventory_stock_locations',
4
+ stockMovements: 'plg_inventory_stock_movements',
5
+ stockPositions: 'plg_inventory_stock_positions',
6
+ recipes: 'plg_inventory_recipes',
7
+ recipeIngredients: 'plg_inventory_recipe_ingredients',
8
+ measurementUnits: 'plg_inventory_measurement_units',
9
+ productCategories: 'plg_inventory_product_categories',
10
+ } as const
@@ -0,0 +1,27 @@
1
+ -- 000_plg_rename.sql — rename legacy inventory tables to plg_inventory_* for pools
2
+ -- provisioned before the industry-pool rename. Guarded: fires only when the legacy
3
+ -- name exists and the target does not, so fresh pools skip every branch.
4
+ DO $$
5
+ BEGIN
6
+ IF to_regclass('public.stock_locations') IS NOT NULL AND to_regclass('public.plg_inventory_stock_locations') IS NULL THEN
7
+ ALTER TABLE public.stock_locations RENAME TO plg_inventory_stock_locations;
8
+ END IF;
9
+ IF to_regclass('public.stock_movements') IS NOT NULL AND to_regclass('public.plg_inventory_stock_movements') IS NULL THEN
10
+ ALTER TABLE public.stock_movements RENAME TO plg_inventory_stock_movements;
11
+ END IF;
12
+ IF to_regclass('public.stock_positions') IS NOT NULL AND to_regclass('public.plg_inventory_stock_positions') IS NULL THEN
13
+ ALTER TABLE public.stock_positions RENAME TO plg_inventory_stock_positions;
14
+ END IF;
15
+ IF to_regclass('public.recipes') IS NOT NULL AND to_regclass('public.plg_inventory_recipes') IS NULL THEN
16
+ ALTER TABLE public.recipes RENAME TO plg_inventory_recipes;
17
+ END IF;
18
+ IF to_regclass('public.recipe_ingredients') IS NOT NULL AND to_regclass('public.plg_inventory_recipe_ingredients') IS NULL THEN
19
+ ALTER TABLE public.recipe_ingredients RENAME TO plg_inventory_recipe_ingredients;
20
+ END IF;
21
+ IF to_regclass('public.measurement_units') IS NOT NULL AND to_regclass('public.plg_inventory_measurement_units') IS NULL THEN
22
+ ALTER TABLE public.measurement_units RENAME TO plg_inventory_measurement_units;
23
+ END IF;
24
+ IF to_regclass('public.product_categories') IS NOT NULL AND to_regclass('public.plg_inventory_product_categories') IS NULL THEN
25
+ ALTER TABLE public.product_categories RENAME TO plg_inventory_product_categories;
26
+ END IF;
27
+ END $$;
@@ -1,22 +1,22 @@
1
1
  -- Inventory Plugin: Base Tables
2
- -- Products use saas_core.products archetype directly
2
+ -- Products use public.products archetype directly
3
3
  -- These are plugin-specific extension tables
4
4
 
5
- CREATE TABLE IF NOT EXISTS public.product_categories (
5
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_product_categories (
6
6
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
7
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
7
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
8
8
  name text NOT NULL,
9
- parent_id uuid REFERENCES public.product_categories(id),
9
+ parent_id uuid REFERENCES public.plg_inventory_product_categories(id),
10
10
  is_active boolean NOT NULL DEFAULT true,
11
11
  created_at timestamptz NOT NULL DEFAULT now(),
12
12
  updated_at timestamptz NOT NULL DEFAULT now()
13
13
  );
14
- ALTER TABLE public.product_categories ENABLE ROW LEVEL SECURITY;
15
- CREATE INDEX IF NOT EXISTS idx_product_categories_tenant ON public.product_categories(tenant_id);
14
+ ALTER TABLE public.plg_inventory_product_categories ENABLE ROW LEVEL SECURITY;
15
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_product_categories_tenant ON public.plg_inventory_product_categories(tenant_id);
16
16
 
17
- CREATE TABLE IF NOT EXISTS public.stock_locations (
17
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_stock_locations (
18
18
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
19
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
19
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
20
20
  name text NOT NULL,
21
21
  description text,
22
22
  is_active boolean NOT NULL DEFAULT true,
@@ -24,20 +24,20 @@ CREATE TABLE IF NOT EXISTS public.stock_locations (
24
24
  created_at timestamptz NOT NULL DEFAULT now(),
25
25
  updated_at timestamptz NOT NULL DEFAULT now()
26
26
  );
27
- ALTER TABLE public.stock_locations ENABLE ROW LEVEL SECURITY;
28
- CREATE INDEX IF NOT EXISTS idx_stock_locations_tenant ON public.stock_locations(tenant_id);
27
+ ALTER TABLE public.plg_inventory_stock_locations ENABLE ROW LEVEL SECURITY;
28
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_locations_tenant ON public.plg_inventory_stock_locations(tenant_id);
29
29
 
30
- CREATE TABLE IF NOT EXISTS public.stock_movements (
30
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_stock_movements (
31
31
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
32
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
33
- product_id uuid NOT NULL REFERENCES saas_core.products(id),
32
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
33
+ product_id uuid NOT NULL REFERENCES public.products(id),
34
34
  quantity numeric(14,4) NOT NULL,
35
35
  movement_type text NOT NULL,
36
36
  unit_cost numeric(14,2) DEFAULT 0,
37
37
  total_cost numeric(14,2) DEFAULT 0,
38
- stock_location_id uuid REFERENCES public.stock_locations(id),
39
- destination_location_id uuid REFERENCES public.stock_locations(id),
40
- supplier_id uuid REFERENCES saas_core.persons(id),
38
+ stock_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),
39
+ destination_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),
40
+ supplier_id uuid REFERENCES public.people(id),
41
41
  document_number text,
42
42
  reason text,
43
43
  notes text,
@@ -48,22 +48,22 @@ CREATE TABLE IF NOT EXISTS public.stock_movements (
48
48
  metadata jsonb DEFAULT '{}'::jsonb,
49
49
  created_at timestamptz NOT NULL DEFAULT now()
50
50
  );
51
- ALTER TABLE public.stock_movements ENABLE ROW LEVEL SECURITY;
52
- CREATE INDEX IF NOT EXISTS idx_stock_movements_tenant ON public.stock_movements(tenant_id);
53
- CREATE INDEX IF NOT EXISTS idx_stock_movements_product ON public.stock_movements(product_id);
54
- CREATE INDEX IF NOT EXISTS idx_stock_movements_date ON public.stock_movements(tenant_id, movement_date);
51
+ ALTER TABLE public.plg_inventory_stock_movements ENABLE ROW LEVEL SECURITY;
52
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_tenant ON public.plg_inventory_stock_movements(tenant_id);
53
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_product ON public.plg_inventory_stock_movements(product_id);
54
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_date ON public.plg_inventory_stock_movements(tenant_id, movement_date);
55
55
 
56
- CREATE TABLE IF NOT EXISTS public.stock_positions (
56
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_stock_positions (
57
57
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
58
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
59
- product_id uuid NOT NULL REFERENCES saas_core.products(id),
58
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
59
+ product_id uuid NOT NULL REFERENCES public.products(id),
60
60
  quantity numeric(14,4) NOT NULL,
61
61
  unit_cost numeric(14,2) DEFAULT 0,
62
- stock_location_id uuid REFERENCES public.stock_locations(id),
62
+ stock_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),
63
63
  batch_number text,
64
64
  expiration_date date,
65
65
  created_at timestamptz NOT NULL DEFAULT now()
66
66
  );
67
- ALTER TABLE public.stock_positions ENABLE ROW LEVEL SECURITY;
68
- CREATE INDEX IF NOT EXISTS idx_stock_positions_tenant ON public.stock_positions(tenant_id);
69
- CREATE INDEX IF NOT EXISTS idx_stock_positions_product ON public.stock_positions(product_id);
67
+ ALTER TABLE public.plg_inventory_stock_positions ENABLE ROW LEVEL SECURITY;
68
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_positions_tenant ON public.plg_inventory_stock_positions(tenant_id);
69
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_positions_product ON public.plg_inventory_stock_positions(product_id);
@@ -1,11 +1,11 @@
1
1
  -- Inventory Plugin: Recipes & Technical Specs
2
2
 
3
- CREATE TABLE IF NOT EXISTS public.recipes (
3
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_recipes (
4
4
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
5
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
5
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
6
6
  name text NOT NULL,
7
7
  description text,
8
- product_id uuid REFERENCES saas_core.products(id),
8
+ product_id uuid REFERENCES public.products(id),
9
9
  yield_quantity numeric(14,4) DEFAULT 1,
10
10
  yield_unit_id uuid,
11
11
  preparation_time_minutes integer,
@@ -14,21 +14,21 @@ CREATE TABLE IF NOT EXISTS public.recipes (
14
14
  created_at timestamptz NOT NULL DEFAULT now(),
15
15
  updated_at timestamptz NOT NULL DEFAULT now()
16
16
  );
17
- ALTER TABLE public.recipes ENABLE ROW LEVEL SECURITY;
18
- CREATE INDEX IF NOT EXISTS idx_recipes_tenant ON public.recipes(tenant_id);
19
- CREATE INDEX IF NOT EXISTS idx_recipes_product ON public.recipes(product_id);
17
+ ALTER TABLE public.plg_inventory_recipes ENABLE ROW LEVEL SECURITY;
18
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipes_tenant ON public.plg_inventory_recipes(tenant_id);
19
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipes_product ON public.plg_inventory_recipes(product_id);
20
20
 
21
- CREATE TABLE IF NOT EXISTS public.recipe_ingredients (
21
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_recipe_ingredients (
22
22
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
23
- recipe_id uuid NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
24
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
25
- product_id uuid NOT NULL REFERENCES saas_core.products(id),
23
+ recipe_id uuid NOT NULL REFERENCES public.plg_inventory_recipes(id) ON DELETE CASCADE,
24
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
25
+ product_id uuid NOT NULL REFERENCES public.products(id),
26
26
  quantity numeric(14,4) NOT NULL,
27
27
  unit_id uuid,
28
28
  display_order integer DEFAULT 0,
29
29
  notes text,
30
30
  created_at timestamptz NOT NULL DEFAULT now()
31
31
  );
32
- ALTER TABLE public.recipe_ingredients ENABLE ROW LEVEL SECURITY;
33
- CREATE INDEX IF NOT EXISTS idx_recipe_ingredients_tenant ON public.recipe_ingredients(tenant_id);
34
- CREATE INDEX IF NOT EXISTS idx_recipe_ingredients_recipe ON public.recipe_ingredients(recipe_id);
32
+ ALTER TABLE public.plg_inventory_recipe_ingredients ENABLE ROW LEVEL SECURITY;
33
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_ingredients_tenant ON public.plg_inventory_recipe_ingredients(tenant_id);
34
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_ingredients_recipe ON public.plg_inventory_recipe_ingredients(recipe_id);
@@ -1,13 +1,13 @@
1
1
  -- Inventory Plugin: Measurement Units
2
2
 
3
- CREATE TABLE IF NOT EXISTS public.measurement_units (
3
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_measurement_units (
4
4
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
5
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
5
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
6
6
  name text NOT NULL,
7
7
  abbreviation text NOT NULL,
8
8
  is_active boolean NOT NULL DEFAULT true,
9
9
  created_at timestamptz NOT NULL DEFAULT now(),
10
10
  updated_at timestamptz NOT NULL DEFAULT now()
11
11
  );
12
- ALTER TABLE public.measurement_units ENABLE ROW LEVEL SECURITY;
13
- CREATE INDEX IF NOT EXISTS idx_measurement_units_tenant ON public.measurement_units(tenant_id);
12
+ ALTER TABLE public.plg_inventory_measurement_units ENABLE ROW LEVEL SECURITY;
13
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_measurement_units_tenant ON public.plg_inventory_measurement_units(tenant_id);
@@ -0,0 +1,161 @@
1
+ // AUTO-GENERATED from 000_plg_rename.sql, 001_inventory_base.sql, 002_recipes.sql, 003_measurement_units.sql — regenerate with scripts/embed-migrations.mjs
2
+ // SQL files are the source of truth; this inline copy lets the manifest declare
3
+ // migrations as data. Do not edit by hand — run the embed script instead.
4
+
5
+ export const MIGRATION_000_PLG_RENAME = `-- 000_plg_rename.sql — rename legacy inventory tables to plg_inventory_* for pools
6
+ -- provisioned before the industry-pool rename. Guarded: fires only when the legacy
7
+ -- name exists and the target does not, so fresh pools skip every branch.
8
+ DO $$
9
+ BEGIN
10
+ IF to_regclass('public.stock_locations') IS NOT NULL AND to_regclass('public.plg_inventory_stock_locations') IS NULL THEN
11
+ ALTER TABLE public.stock_locations RENAME TO plg_inventory_stock_locations;
12
+ END IF;
13
+ IF to_regclass('public.stock_movements') IS NOT NULL AND to_regclass('public.plg_inventory_stock_movements') IS NULL THEN
14
+ ALTER TABLE public.stock_movements RENAME TO plg_inventory_stock_movements;
15
+ END IF;
16
+ IF to_regclass('public.stock_positions') IS NOT NULL AND to_regclass('public.plg_inventory_stock_positions') IS NULL THEN
17
+ ALTER TABLE public.stock_positions RENAME TO plg_inventory_stock_positions;
18
+ END IF;
19
+ IF to_regclass('public.recipes') IS NOT NULL AND to_regclass('public.plg_inventory_recipes') IS NULL THEN
20
+ ALTER TABLE public.recipes RENAME TO plg_inventory_recipes;
21
+ END IF;
22
+ IF to_regclass('public.recipe_ingredients') IS NOT NULL AND to_regclass('public.plg_inventory_recipe_ingredients') IS NULL THEN
23
+ ALTER TABLE public.recipe_ingredients RENAME TO plg_inventory_recipe_ingredients;
24
+ END IF;
25
+ IF to_regclass('public.measurement_units') IS NOT NULL AND to_regclass('public.plg_inventory_measurement_units') IS NULL THEN
26
+ ALTER TABLE public.measurement_units RENAME TO plg_inventory_measurement_units;
27
+ END IF;
28
+ IF to_regclass('public.product_categories') IS NOT NULL AND to_regclass('public.plg_inventory_product_categories') IS NULL THEN
29
+ ALTER TABLE public.product_categories RENAME TO plg_inventory_product_categories;
30
+ END IF;
31
+ END $$;
32
+ `
33
+
34
+ export const MIGRATION_001_INVENTORY_BASE = `-- Inventory Plugin: Base Tables
35
+ -- Products use public.products archetype directly
36
+ -- These are plugin-specific extension tables
37
+
38
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_product_categories (
39
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
40
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
41
+ name text NOT NULL,
42
+ parent_id uuid REFERENCES public.plg_inventory_product_categories(id),
43
+ is_active boolean NOT NULL DEFAULT true,
44
+ created_at timestamptz NOT NULL DEFAULT now(),
45
+ updated_at timestamptz NOT NULL DEFAULT now()
46
+ );
47
+ ALTER TABLE public.plg_inventory_product_categories ENABLE ROW LEVEL SECURITY;
48
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_product_categories_tenant ON public.plg_inventory_product_categories(tenant_id);
49
+
50
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_stock_locations (
51
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
52
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
53
+ name text NOT NULL,
54
+ description text,
55
+ is_active boolean NOT NULL DEFAULT true,
56
+ unit_id uuid,
57
+ created_at timestamptz NOT NULL DEFAULT now(),
58
+ updated_at timestamptz NOT NULL DEFAULT now()
59
+ );
60
+ ALTER TABLE public.plg_inventory_stock_locations ENABLE ROW LEVEL SECURITY;
61
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_locations_tenant ON public.plg_inventory_stock_locations(tenant_id);
62
+
63
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_stock_movements (
64
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
65
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
66
+ product_id uuid NOT NULL REFERENCES public.products(id),
67
+ quantity numeric(14,4) NOT NULL,
68
+ movement_type text NOT NULL,
69
+ unit_cost numeric(14,2) DEFAULT 0,
70
+ total_cost numeric(14,2) DEFAULT 0,
71
+ stock_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),
72
+ destination_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),
73
+ supplier_id uuid REFERENCES public.people(id),
74
+ document_number text,
75
+ reason text,
76
+ notes text,
77
+ movement_date date NOT NULL DEFAULT CURRENT_DATE,
78
+ user_id uuid,
79
+ batch_number text,
80
+ expiration_date date,
81
+ metadata jsonb DEFAULT '{}'::jsonb,
82
+ created_at timestamptz NOT NULL DEFAULT now()
83
+ );
84
+ ALTER TABLE public.plg_inventory_stock_movements ENABLE ROW LEVEL SECURITY;
85
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_tenant ON public.plg_inventory_stock_movements(tenant_id);
86
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_product ON public.plg_inventory_stock_movements(product_id);
87
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_movements_date ON public.plg_inventory_stock_movements(tenant_id, movement_date);
88
+
89
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_stock_positions (
90
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
91
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
92
+ product_id uuid NOT NULL REFERENCES public.products(id),
93
+ quantity numeric(14,4) NOT NULL,
94
+ unit_cost numeric(14,2) DEFAULT 0,
95
+ stock_location_id uuid REFERENCES public.plg_inventory_stock_locations(id),
96
+ batch_number text,
97
+ expiration_date date,
98
+ created_at timestamptz NOT NULL DEFAULT now()
99
+ );
100
+ ALTER TABLE public.plg_inventory_stock_positions ENABLE ROW LEVEL SECURITY;
101
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_positions_tenant ON public.plg_inventory_stock_positions(tenant_id);
102
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_stock_positions_product ON public.plg_inventory_stock_positions(product_id);
103
+ `
104
+
105
+ export const MIGRATION_002_RECIPES = `-- Inventory Plugin: Recipes & Technical Specs
106
+
107
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_recipes (
108
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
109
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
110
+ name text NOT NULL,
111
+ description text,
112
+ product_id uuid REFERENCES public.products(id),
113
+ yield_quantity numeric(14,4) DEFAULT 1,
114
+ yield_unit_id uuid,
115
+ preparation_time_minutes integer,
116
+ instructions text,
117
+ is_active boolean NOT NULL DEFAULT true,
118
+ created_at timestamptz NOT NULL DEFAULT now(),
119
+ updated_at timestamptz NOT NULL DEFAULT now()
120
+ );
121
+ ALTER TABLE public.plg_inventory_recipes ENABLE ROW LEVEL SECURITY;
122
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipes_tenant ON public.plg_inventory_recipes(tenant_id);
123
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipes_product ON public.plg_inventory_recipes(product_id);
124
+
125
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_recipe_ingredients (
126
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
127
+ recipe_id uuid NOT NULL REFERENCES public.plg_inventory_recipes(id) ON DELETE CASCADE,
128
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
129
+ product_id uuid NOT NULL REFERENCES public.products(id),
130
+ quantity numeric(14,4) NOT NULL,
131
+ unit_id uuid,
132
+ display_order integer DEFAULT 0,
133
+ notes text,
134
+ created_at timestamptz NOT NULL DEFAULT now()
135
+ );
136
+ ALTER TABLE public.plg_inventory_recipe_ingredients ENABLE ROW LEVEL SECURITY;
137
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_ingredients_tenant ON public.plg_inventory_recipe_ingredients(tenant_id);
138
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_ingredients_recipe ON public.plg_inventory_recipe_ingredients(recipe_id);
139
+ `
140
+
141
+ export const MIGRATION_003_MEASUREMENT_UNITS = `-- Inventory Plugin: Measurement Units
142
+
143
+ CREATE TABLE IF NOT EXISTS public.plg_inventory_measurement_units (
144
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
145
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
146
+ name text NOT NULL,
147
+ abbreviation text NOT NULL,
148
+ is_active boolean NOT NULL DEFAULT true,
149
+ created_at timestamptz NOT NULL DEFAULT now(),
150
+ updated_at timestamptz NOT NULL DEFAULT now()
151
+ );
152
+ ALTER TABLE public.plg_inventory_measurement_units ENABLE ROW LEVEL SECURITY;
153
+ CREATE INDEX IF NOT EXISTS idx_plg_inventory_measurement_units_tenant ON public.plg_inventory_measurement_units(tenant_id);
154
+ `
155
+
156
+ export const MIGRATIONS: Array<{ id: string; sql: string }> = [
157
+ { id: "000_plg_rename", sql: MIGRATION_000_PLG_RENAME },
158
+ { id: "001_inventory_base", sql: MIGRATION_001_INVENTORY_BASE },
159
+ { id: "002_recipes", sql: MIGRATION_002_RECIPES },
160
+ { id: "003_measurement_units", sql: MIGRATION_003_MEASUREMENT_UNITS },
161
+ ]