@fayz-ai/plugin-inventory 0.9.3 → 0.10.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/dist/components/RecipeGapsPanel.d.ts +14 -0
- package/dist/components/RecipeGapsPanel.d.ts.map +1 -0
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +6 -0
- package/dist/data/tables.d.ts.map +1 -1
- package/dist/data/types.d.ts +23 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2459 -237
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-tools.d.ts +4 -0
- package/dist/lib/agent-tools.d.ts.map +1 -0
- package/dist/lib/movement-vocabulary.d.ts +30 -0
- package/dist/lib/movement-vocabulary.d.ts.map +1 -0
- package/dist/lib/recipe-cost.d.ts +22 -0
- package/dist/lib/recipe-cost.d.ts.map +1 -0
- package/dist/lib/stock-count.d.ts +19 -0
- package/dist/lib/stock-count.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +7 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/types.d.ts +114 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/InventoryPage.d.ts.map +1 -1
- package/dist/views/MovementHistoryView.d.ts +4 -1
- package/dist/views/MovementHistoryView.d.ts.map +1 -1
- package/dist/views/ProductCrudForm.d.ts.map +1 -1
- package/dist/views/ProductDetailView.d.ts +9 -0
- package/dist/views/ProductDetailView.d.ts.map +1 -0
- package/dist/views/ProductListView.d.ts +3 -2
- package/dist/views/ProductListView.d.ts.map +1 -1
- package/dist/views/ProductStockTab.d.ts +11 -0
- package/dist/views/ProductStockTab.d.ts.map +1 -0
- package/dist/views/RecipeDetailView.d.ts.map +1 -1
- package/dist/views/RecipeFormView.d.ts +3 -1
- package/dist/views/RecipeFormView.d.ts.map +1 -1
- package/dist/views/RecipesView.d.ts +3 -1
- package/dist/views/RecipesView.d.ts.map +1 -1
- package/dist/views/StockCountFormView.d.ts +7 -0
- package/dist/views/StockCountFormView.d.ts.map +1 -0
- package/dist/views/StockCountSessionView.d.ts +15 -0
- package/dist/views/StockCountSessionView.d.ts.map +1 -0
- package/dist/views/StockCountsView.d.ts +7 -0
- package/dist/views/StockCountsView.d.ts.map +1 -0
- package/dist/views/StockMovementView.d.ts.map +1 -1
- package/dist/views/productEntity.d.ts +4 -1
- package/dist/views/productEntity.d.ts.map +1 -1
- package/package.json +8 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/lib/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAuBjD,eAAO,MAAM,gBAAgB,EAAE,YAuC9B,CAAA;AAED,eAAO,MAAM,2BAA2B,QAK7B,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MovementType } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* The one list of stock movement types.
|
|
4
|
+
*
|
|
5
|
+
* This used to be spelled out in three places — the MovementType union, the
|
|
6
|
+
* form's picker and the history's icon/colour maps — which is three chances to
|
|
7
|
+
* disagree. Importers make it four. Everything reads from here.
|
|
8
|
+
*/
|
|
9
|
+
export interface MovementTypeSpec {
|
|
10
|
+
value: MovementType;
|
|
11
|
+
/** i18n key under the plugin's `inventory.movementType` namespace. */
|
|
12
|
+
labelKey: string;
|
|
13
|
+
/** lucide icon name, resolved by the host's icon map. */
|
|
14
|
+
icon: string;
|
|
15
|
+
/** Direction on stock: what the quantity does to the on-hand total. */
|
|
16
|
+
effect: 'increase' | 'decrease' | 'set' | 'move';
|
|
17
|
+
}
|
|
18
|
+
export declare const MOVEMENT_TYPES: readonly MovementTypeSpec[];
|
|
19
|
+
export declare const MOVEMENT_TYPE_VALUES: readonly MovementType[];
|
|
20
|
+
export declare function movementTypeSpec(value: string): MovementTypeSpec | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Portuguese movement vocabulary used by pre-SDK apps (beautyplace and its
|
|
23
|
+
* descendants). Lives next to the canonical list so a data import cannot drift
|
|
24
|
+
* from what the UI can render — that is the whole reason it is here and not in
|
|
25
|
+
* the importer.
|
|
26
|
+
*/
|
|
27
|
+
export declare const LEGACY_PT_MOVEMENT_TYPE: Readonly<Record<string, MovementType>>;
|
|
28
|
+
/** Normalise any known spelling to a MovementType; undefined when unrecognised. */
|
|
29
|
+
export declare function normalizeMovementType(raw: string): MovementType | undefined;
|
|
30
|
+
//# sourceMappingURL=movement-vocabulary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"movement-vocabulary.d.ts","sourceRoot":"","sources":["../../src/lib/movement-vocabulary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAA;IACnB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,KAAK,GAAG,MAAM,CAAA;CACjD;AAED,eAAO,MAAM,cAAc,EAAE,SAAS,gBAAgB,EAM5C,CAAA;AAEV,eAAO,MAAM,oBAAoB,EAAE,SAAS,YAAY,EAAuC,CAAA;AAE/F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE5E;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAQjE,CAAA;AAEV,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAI3E"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { IngredientCostBasis } from '../types';
|
|
2
|
+
/** Above this the dish eats the margin — the line the legacy app drew. */
|
|
3
|
+
export declare const CMV_ALERT_THRESHOLD = 35;
|
|
4
|
+
export interface CostableLine {
|
|
5
|
+
quantity: number;
|
|
6
|
+
costBasis?: IngredientCostBasis | null;
|
|
7
|
+
}
|
|
8
|
+
export interface CostedLine<T> {
|
|
9
|
+
line: T;
|
|
10
|
+
/** Cost of one unit as the recipe counts it. */
|
|
11
|
+
unitCost: number;
|
|
12
|
+
/** unitCost × quantity. */
|
|
13
|
+
cost: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function ingredientUnitCost(basis?: IngredientCostBasis | null): number;
|
|
16
|
+
export declare function lineCost(line: CostableLine): number;
|
|
17
|
+
export declare function costLines<T extends CostableLine>(lines: T[]): CostedLine<T>[];
|
|
18
|
+
export declare function recipeCost(lines: CostableLine[]): number;
|
|
19
|
+
/** Cost as a percentage of the sale price. Null when there is no price to divide by. */
|
|
20
|
+
export declare function cmvPercent(cost: number, salePrice?: number | null): number | null;
|
|
21
|
+
export declare function isCmvHigh(cmv: number | null | undefined): boolean;
|
|
22
|
+
//# sourceMappingURL=recipe-cost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe-cost.d.ts","sourceRoot":"","sources":["../../src/lib/recipe-cost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAwBnD,0EAA0E;AAC1E,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAA;IACP,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAChB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI,GAAG,MAAM,CAM7E;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAEnD;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAK7E;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAExD;AAED,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAIjF;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEjE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StockCountItem } from '../types';
|
|
2
|
+
export declare function isCounted(item: Pick<StockCountItem, 'countedQuantity'>): boolean;
|
|
3
|
+
/** counted − system, or null when the line was never counted. */
|
|
4
|
+
export declare function lineVariance(item: Pick<StockCountItem, 'systemQuantity' | 'countedQuantity'>): number | null;
|
|
5
|
+
export declare function isDivergent(item: Pick<StockCountItem, 'systemQuantity' | 'countedQuantity'>): boolean;
|
|
6
|
+
export interface CountSummary {
|
|
7
|
+
total: number;
|
|
8
|
+
counted: number;
|
|
9
|
+
uncounted: number;
|
|
10
|
+
divergent: number;
|
|
11
|
+
/** Net units gained or lost across the divergent lines. */
|
|
12
|
+
variance: number;
|
|
13
|
+
/** The same, valued at each line's unit cost — what the close costs. */
|
|
14
|
+
varianceValue: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function summarizeCount(items: StockCountItem[]): CountSummary;
|
|
17
|
+
/** The lines the close will act on, in the order it will act on them. */
|
|
18
|
+
export declare function divergentLines(items: StockCountItem[]): StockCountItem[];
|
|
19
|
+
//# sourceMappingURL=stock-count.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stock-count.d.ts","sourceRoot":"","sources":["../../src/lib/stock-count.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAW9C,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,OAAO,CAEhF;AAED,iEAAiE;AACjE,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,gBAAgB,GAAG,iBAAiB,CAAC,GAC/D,MAAM,GAAG,IAAI,CAGf;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,gBAAgB,GAAG,iBAAiB,CAAC,GAC/D,OAAO,CAGT;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,YAAY,CAsBpE;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAExE"}
|
package/dist/locales/en.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA8YrC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA8YvC,CAAA"}
|
|
@@ -2,6 +2,13 @@ export declare const MIGRATION_000_PLG_RENAME = "-- 000_plg_rename.sql \u2014 re
|
|
|
2
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
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
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 MIGRATION_004_STOCK_MOVEMENT_VIEW = "-- ============================================================================\n-- 004_stock_movement_view.sql \u2014 v_stock_movements was reading every tenant.\n--\n-- `plugin-inventory/src/data/supabase.ts` reads `public.v_stock_movements` for\n-- the whole movement history \u2014 it is what the Hist\u00F3rico screen renders. That\n-- view is defined NOWHERE in this repo. It exists on cluster-restaurant-br-01\n-- because a legacy Supabase-CLI chain created it by hand, and it was created\n-- without `security_invoker`.\n--\n-- A view runs with the privileges of its OWNER unless created\n-- `WITH (security_invoker = true)`. The hand-applied view is owned by\n-- `postgres`, who also owns plg_inventory_stock_movements, and a table owner is\n-- exempt from that table's RLS unless the table carries FORCE ROW LEVEL\n-- SECURITY \u2014 which it does not. `authenticated` holds SELECT on the view.\n--\n-- So on a shared industry pool, any signed-in user of ANY tenant could\n--\n-- SELECT * FROM public.v_stock_movements;\n--\n-- and read every tenant's stock ledger \u2014 quantities, unit costs, suppliers.\n--\n-- packages/db/migrations/043_view_invoker.sql exists to close exactly this\n-- class of hole and names nine views. It could not name this one: the view is\n-- in no migration, so nobody auditing the tree knew it existed. This file is\n-- the plugin carrying the fix forward, as 043's header asks each plugin to do.\n--\n-- WHY DROP AND RECREATE RATHER THAN CREATE OR REPLACE. `CREATE OR REPLACE VIEW`\n-- refuses to change a column's name, type or position. The hand-applied\n-- definitions differ across pools \u2014 resto-saas's quarantined\n-- `supabase/migrations.legacy/20260201000018_v_stock_movements_locations.sql`\n-- still selects from `saas_core.products` and the pre-rename\n-- `public.stock_movements`. A replace would fail on whichever pool disagrees.\n-- DROP without CASCADE is deliberate: if something depends on this view we want\n-- to hear about it, not to silently drop it too.\n-- ============================================================================\n\nDROP VIEW IF EXISTS public.v_stock_movements;\n\nCREATE VIEW public.v_stock_movements\nWITH (security_invoker = true) AS\n SELECT\n sm.id,\n sm.tenant_id,\n sm.product_id,\n sm.quantity,\n sm.movement_type,\n sm.unit_cost,\n sm.total_cost,\n sm.stock_location_id,\n sm.destination_location_id,\n sm.supplier_id,\n sm.document_number,\n sm.reason,\n sm.notes,\n sm.movement_date,\n sm.user_id,\n sm.batch_number,\n sm.expiration_date,\n sm.metadata,\n sm.created_at,\n p.name AS product_name,\n p.sku AS product_sku,\n sl.name AS stock_location_name,\n dl.name AS destination_location_name\n FROM public.plg_inventory_stock_movements sm\n LEFT JOIN public.products p ON p.id = sm.product_id\n LEFT JOIN public.plg_inventory_stock_locations sl ON sl.id = sm.stock_location_id\n LEFT JOIN public.plg_inventory_stock_locations dl ON dl.id = sm.destination_location_id;\n\nGRANT SELECT ON public.v_stock_movements TO authenticated;\n\nNOTIFY pgrst, 'reload schema';\n\n-- What changes on screen: a Hist\u00F3rico that was quietly listing other tenants'\n-- movements now lists only its own. That is a shorter list and it is the\n-- correct one.\n";
|
|
6
|
+
export declare const MIGRATION_005_PRODUCT_DETAILS = "-- ============================================================================\n-- 005_product_details.sql \u2014 the product attributes the plugin kept dropping.\n--\n-- A Product in this plugin is the spine archetype `public.products`. The\n-- provider maps name/sku/price/cost/stock onto real columns and stashes\n-- productType/barcode/brand in `products.metadata` \u2014 but categoryId,\n-- measurementUnitId, supplierId and defaultLocationId were accepted by\n-- CreateProductInput and then silently discarded. Nothing persisted them.\n--\n-- WHY A SATELLITE AND NOT COLUMNS ON public.products.\n--\n-- 1. `products.category_id` already exists and already FKs `public.categories`.\n-- It CANNOT hold a plg_inventory_product_categories id \u2014 the same collision\n-- packages/shop/migrations/0009_products_canonical.sql documents for shop.\n-- The inventory category is a different taxonomy living in a different table.\n--\n-- 2. A spine column must not reference a plugin-prefixed table. `products` is\n-- shared by every plugin and by the archetype layer; uninstall inventory and\n-- a `products.measurement_unit_id` FK dangles. Every FK here points from the\n-- plugin's own table into whatever it likes, which is the direction that\n-- survives.\n--\n-- 3. `ARCHETYPE_COLUMNS.product` in packages/core/src/data/archetype.ts is a\n-- whitelist. splitFields() does not drop unknown keys, it routes them to an\n-- extension table \u2014 so adding a column to products without updating that set\n-- fails the write at runtime rather than at review.\n--\n-- plg_shop_products.product_id -> products.id is the shipped house pattern for\n-- exactly this; this follows it.\n--\n-- ON conversion_factor / purchase_unit_id. Artorius uses neither today\n-- (purchase_unit_id NULL on all 43 rows, conversion_factor 1.0 on all 43), and\n-- a plg_inventory_unit_conversions table would ship empty with no UI able to\n-- fill it. These two columns are the cheap hedge for the one case that does\n-- recur in a kitchen \u2014 buy in boxes, consume in grams \u2014 and cost two columns\n-- instead of a table and a screen.\n-- ============================================================================\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_details (\n product_id uuid PRIMARY KEY REFERENCES public.products(id) ON DELETE CASCADE,\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n category_id uuid REFERENCES public.plg_inventory_product_categories(id) ON DELETE SET NULL,\n measurement_unit_id uuid REFERENCES public.plg_inventory_measurement_units(id) ON DELETE SET NULL,\n purchase_unit_id uuid REFERENCES public.plg_inventory_measurement_units(id) ON DELETE SET NULL,\n conversion_factor numeric(14,4) NOT NULL DEFAULT 1,\n supplier_id uuid REFERENCES public.people(id) ON DELETE SET NULL,\n default_location_id uuid REFERENCES public.plg_inventory_stock_locations(id) ON DELETE SET NULL,\n purpose text,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_product_details ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_product_details_tenant ON public.plg_inventory_product_details(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_product_details_category ON public.plg_inventory_product_details(category_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_product_details_supplier ON public.plg_inventory_product_details(supplier_id);\n\nCOMMENT ON COLUMN public.plg_inventory_product_details.purpose IS 'purchase | sale | both | menu. Unconstrained: the vocabulary is per-app labels, not a schema fact.';\nCOMMENT ON COLUMN public.plg_inventory_product_details.conversion_factor IS 'How many measurement_unit_id fit in one purchase_unit_id. 1 when the product is bought and consumed in the same unit.';\n\nNOTIFY pgrst, 'reload schema';\n";
|
|
7
|
+
export declare const MIGRATION_006_STOCK_POSITION_SLOTS = "-- ============================================================================\n-- 006_stock_position_slots.sql \u2014 make a stock position a slot, not a pile.\n--\n-- A row in plg_inventory_stock_positions is meant to be the on-hand quantity\n-- for one (product, location, batch, expiry) slot. Nothing enforced that. The\n-- provider's createMovement() does a read-modify-write through maybeSingle(),\n-- which means two rows for the same slot make maybeSingle() throw, and a\n-- concurrent write creates the duplicate in the first place.\n--\n-- This is not hypothetical: the Artorius source data carries 46 position rows\n-- across 37 distinct slots \u2014 the same defect, already realised, in the system\n-- we are migrating from.\n--\n-- The unique index is the half that matters. It turns the read-modify-write\n-- into an upsert with an inferrable conflict target.\n--\n-- WHY `NULLS NOT DISTINCT` AND NOT COALESCE EXPRESSIONS. batch_number and\n-- expiration_date are NULL on every row that does not track batches \u2014 which is\n-- most of them \u2014 and under default NULL semantics a plain unique index would\n-- consider each of those rows distinct and dedupe nothing at all. The portable\n-- alternative is an index over COALESCE expressions, but then ON CONFLICT can\n-- only be inferred by repeating those expressions verbatim, which supabase-js's\n-- `onConflict: 'col,col'` cannot express. Requires PostgreSQL 15+; Supabase has\n-- been 15+ since long before this plugin existed.\n--\n-- The fold below runs BEFORE the index because this file must also survive\n-- being applied to a pool that already accumulated duplicates.\n-- ============================================================================\n\nALTER TABLE public.plg_inventory_stock_positions\n ADD COLUMN IF NOT EXISTS measurement_unit_id uuid REFERENCES public.plg_inventory_measurement_units(id) ON DELETE SET NULL,\n ADD COLUMN IF NOT EXISTS unit_type text NOT NULL DEFAULT 'base',\n ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();\n\nCOMMENT ON COLUMN public.plg_inventory_stock_positions.unit_type IS 'base | purchase | content \u2014 which of the product''s units this quantity is counted in.';\n\n-- Fold pre-existing duplicate slots: quantities add, unit_cost becomes the\n-- weighted average of what it replaces. The survivor is picked by lowest id so\n-- that a re-run folds the same way; min(id::text) rather than min(id) because\n-- uuid has no ordering aggregate on every version we target.\nDO $$\nDECLARE folded integer;\nBEGIN\n WITH grouped AS (\n SELECT\n min(id::text)::uuid AS keep_id,\n tenant_id, product_id, stock_location_id, batch_number, expiration_date,\n sum(quantity) AS total_quantity,\n CASE WHEN sum(quantity) <> 0\n THEN sum(quantity * coalesce(unit_cost, 0)) / sum(quantity)\n ELSE max(unit_cost) END AS avg_unit_cost,\n count(*) AS n\n FROM public.plg_inventory_stock_positions\n GROUP BY tenant_id, product_id, stock_location_id, batch_number, expiration_date\n HAVING count(*) > 1\n ), updated AS (\n UPDATE public.plg_inventory_stock_positions p\n SET quantity = g.total_quantity,\n unit_cost = round(g.avg_unit_cost, 2),\n updated_at = now()\n FROM grouped g\n WHERE p.id = g.keep_id\n RETURNING p.id\n )\n DELETE FROM public.plg_inventory_stock_positions p\n USING grouped g\n WHERE p.tenant_id = g.tenant_id\n AND p.product_id = g.product_id\n AND p.stock_location_id IS NOT DISTINCT FROM g.stock_location_id\n AND p.batch_number IS NOT DISTINCT FROM g.batch_number\n AND p.expiration_date IS NOT DISTINCT FROM g.expiration_date\n AND p.id <> g.keep_id;\n\n GET DIAGNOSTICS folded = ROW_COUNT;\n IF folded > 0 THEN\n RAISE NOTICE 'plg_inventory_stock_positions: folded % duplicate row(s) into their slot', folded;\n END IF;\nEND $$;\n\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_inventory_stock_positions_slot\n ON public.plg_inventory_stock_positions\n (tenant_id, product_id, stock_location_id, batch_number, expiration_date)\n NULLS NOT DISTINCT;\n\nNOTIFY pgrst, 'reload schema';\n";
|
|
8
|
+
export declare const MIGRATION_007_RECIPE_GROUPS = "-- ============================================================================\n-- 007_recipe_groups.sql \u2014 a recipe's steps, not just its ingredient list.\n--\n-- plg_inventory_recipes + plg_inventory_recipe_ingredients model a flat bill of\n-- materials. A kitchen recipe is staged: \"massa\", \"recheio\", \"montagem\", each\n-- with its own ingredients, order and prep note. Metadata cannot express that \u2014\n-- the group is a shared parent that several ingredient rows point at, and a\n-- jsonb blob per ingredient would duplicate the name and lose the ordering.\n--\n-- group_id is nullable on purpose: an ungrouped ingredient is the common case\n-- and stays valid. ON DELETE SET NULL so deleting a group flattens its\n-- ingredients back into the recipe rather than destroying them.\n-- ============================================================================\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_recipe_groups (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n recipe_id uuid NOT NULL REFERENCES public.plg_inventory_recipes(id) ON DELETE CASCADE,\n name text NOT NULL,\n preparation_notes text,\n display_order integer NOT NULL DEFAULT 0,\n estimated_minutes integer,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\nALTER TABLE public.plg_inventory_recipe_groups ENABLE ROW LEVEL SECURITY;\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_groups_tenant ON public.plg_inventory_recipe_groups(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_groups_recipe ON public.plg_inventory_recipe_groups(recipe_id);\n\nALTER TABLE public.plg_inventory_recipe_ingredients\n ADD COLUMN IF NOT EXISTS group_id uuid REFERENCES public.plg_inventory_recipe_groups(id) ON DELETE SET NULL;\n\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_recipe_ingredients_group ON public.plg_inventory_recipe_ingredients(group_id);\n\nNOTIFY pgrst, 'reload schema';\n";
|
|
9
|
+
export declare const MIGRATION_008_RLS_POLICIES = "-- ============================================================================\n-- 008_rls_policies.sql \u2014 inventory stops borrowing the spine's sweep.\n--\n-- 001/002/003 ENABLE row-level security and create no policies. The policies\n-- that exist on live pools come from packages/db/migrations/002's auto-sweep,\n-- which discovers every public BASE TABLE carrying a tenant_id. That works, but\n-- only on the second pass: on a first-ever provision the spine step runs before\n-- the plugin step, so at sweep time these tables do not exist yet and come up\n-- RLS-enabled with NO policy \u2014 every insert denied. packages/db/test's replay\n-- pass exists partly to paper over exactly this.\n--\n-- plugin-financial solved it with its own 006_rls_policies.sql. This is the\n-- same file for inventory, and it also covers the two tables added by 005 and\n-- 007, which no sweep has ever seen.\n--\n-- NOTE, so it is not rediscovered as a bug: on pools that already ran the spine\n-- sweep, these tables will now carry TWO permissive policy families \u2014\n-- `tenant_isolation_*` from the sweep and `<table>_*` from here. Permissive\n-- policies OR together and both quals are the identical canonical form, so the\n-- result is unchanged. It is noise in \\d output, not a hole.\n--\n-- Canonical RLS form: tenant_id IN (SELECT public.user_tenant_ids()).\n-- ============================================================================\n\nDO $$\nDECLARE t text;\nBEGIN\n FOR t IN SELECT unnest(ARRAY[\n 'plg_inventory_product_categories','plg_inventory_stock_locations',\n 'plg_inventory_stock_movements','plg_inventory_stock_positions',\n 'plg_inventory_recipes','plg_inventory_recipe_ingredients',\n 'plg_inventory_measurement_units','plg_inventory_product_details',\n 'plg_inventory_recipe_groups'\n ])\n LOOP\n CONTINUE WHEN to_regclass('public.' || t) IS NULL;\n EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);\n EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO authenticated', t);\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_select') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_select', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_insert') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_insert', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_update') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_update', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_delete') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_delete', t);\n END IF;\n END LOOP;\nEND $$;\n\n-- plg_inventory_recipe_groups is tenant-scoped like the rest, so the loop above\n-- covers it. plg_inventory_product_details is keyed by product_id but carries\n-- tenant_id for exactly this reason.\n";
|
|
10
|
+
export declare const MIGRATION_009_AGENT_RPCS = "-- ============================================================================\n-- plugin-inventory 009: server-plane agent write RPC.\n--\n-- public.agent_inventory_upsert_recipe \u2014 the assistant BUILDS or EDITS a ficha\n-- t\u00E9cnica from any surface or channel (in-app FAB, WhatsApp, MCP). Same\n-- contract as every agent_* RPC (agenda 005, financial 009, forms 003):\n-- (p_tenant_id, p_actor_user_id, p_payload jsonb) \u2192 jsonb\n-- {ok:true, id, record:{...}} | {ok:false, denial:{...}} | {ok:false, error}\n--\n-- Ingredient NAMES are resolved to products.id HERE, so the model can say\n-- \"costela su\u00EDna\" without holding an id. What it names and we cannot match is\n-- not silently dropped and not force-inserted (product_id is NOT NULL on the\n-- ingredient table): it lands in recipes.metadata->'unmatchedIngredients' and\n-- comes back in the result, so the screen can ask the user to pick or create\n-- it. That recovery loop is what made the legacy feature usable.\n--\n-- GRANT authenticated+service_role \u2014 NEVER anon (the broker calls with the pool\n-- service key and injects tenant/actor; in-browser the signed-in user calls it\n-- directly and spine RLS/actor guards bind them to their own tenant).\n-- ============================================================================\n\nALTER TABLE public.plg_inventory_recipes\n ADD COLUMN IF NOT EXISTS metadata jsonb;\n\nCOMMENT ON COLUMN public.plg_inventory_recipes.metadata IS 'What the assistant could not resolve: {unmatchedIngredients:[{name,quantity,notes}], productHint}.';\n\nCREATE OR REPLACE FUNCTION public.agent_inventory_upsert_recipe(\n p_tenant_id uuid,\n p_actor_user_id uuid,\n p_payload jsonb\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER\nSET search_path = public\nAS $$\nDECLARE\n v_denial jsonb;\n v_used int;\n v_recipe_id uuid;\n v_is_update boolean;\n v_name text;\n v_description text;\n v_instructions text;\n v_yield numeric;\n v_prep int;\n v_product_id uuid;\n v_product_hint text;\n v_item jsonb;\n v_iname text;\n v_qty numeric;\n v_pid uuid;\n v_order int := 0;\n v_resolved jsonb := '[]'::jsonb;\n v_unmatched jsonb := '[]'::jsonb;\n v_metadata jsonb;\n v_final_id uuid;\n v_final_name text;\nBEGIN\n -- \u2500\u2500 payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n BEGIN\n v_recipe_id := NULLIF(p_payload->>'recipe_id','')::uuid;\n EXCEPTION WHEN OTHERS THEN\n RETURN jsonb_build_object('ok', false, 'error', 'invalid recipe_id');\n END;\n v_is_update := v_recipe_id IS NOT NULL;\n v_name := btrim(p_payload->>'name');\n v_description := left(p_payload->>'description', 1000);\n v_instructions := left(p_payload->>'instructions', 8000);\n v_yield := COALESCE(NULLIF(p_payload->>'yield_quantity','')::numeric, 1);\n v_prep := NULLIF(p_payload->>'preparation_time_minutes','')::int;\n v_product_hint := btrim(COALESCE(p_payload->>'product_name',''));\n BEGIN\n v_product_id := NULLIF(p_payload->>'product_id','')::uuid;\n EXCEPTION WHEN OTHERS THEN\n v_product_id := NULL;\n END;\n\n IF NOT v_is_update AND (v_name IS NULL OR v_name = '') THEN\n RETURN jsonb_build_object('ok', false, 'error', 'name is required to create a recipe');\n END IF;\n IF v_yield <= 0 THEN\n v_yield := 1;\n END IF;\n\n -- \u2500\u2500 authorization: role \u2192 plan \u2192 recipes cap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n SELECT count(*) INTO v_used FROM plg_inventory_recipes\n WHERE tenant_id = p_tenant_id AND is_active = true;\n v_denial := agent_guard(p_tenant_id, p_actor_user_id, 'inventory.recipes',\n CASE WHEN v_is_update THEN 'update' ELSE 'create' END,\n 'recipes', v_used, CASE WHEN v_is_update THEN 0 ELSE 1 END);\n IF v_denial IS NOT NULL THEN\n RETURN jsonb_build_object('ok', false, 'denial', v_denial);\n END IF;\n\n IF v_is_update THEN\n PERFORM 1 FROM plg_inventory_recipes\n WHERE id = v_recipe_id AND tenant_id = p_tenant_id;\n IF NOT FOUND THEN\n RETURN jsonb_build_object('ok', false, 'error', 'unknown recipe for this tenant');\n END IF;\n END IF;\n\n -- \u2500\u2500 produced product: id must be this tenant's, else resolve by name \u2500\u2500\u2500\u2500\u2500\u2500\n IF v_product_id IS NOT NULL THEN\n PERFORM 1 FROM products WHERE id = v_product_id AND tenant_id = p_tenant_id;\n IF NOT FOUND THEN v_product_id := NULL; END IF;\n END IF;\n IF v_product_id IS NULL AND v_product_hint <> '' THEN\n SELECT p.id INTO v_product_id FROM products p\n WHERE p.tenant_id = p_tenant_id\n AND lower(btrim(p.name)) = lower(v_product_hint)\n ORDER BY p.is_active DESC LIMIT 1;\n IF v_product_id IS NULL THEN\n -- Containment rather than ILIKE: the name is user text and % / _ in it\n -- would be read as wildcards.\n SELECT p.id INTO v_product_id FROM products p\n WHERE p.tenant_id = p_tenant_id\n AND position(lower(v_product_hint) in lower(p.name)) > 0\n ORDER BY length(p.name) LIMIT 1;\n END IF;\n END IF;\n\n -- \u2500\u2500 ingredients: name \u2192 products.id, unresolved ones set aside \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n FOR v_item IN SELECT value FROM jsonb_array_elements(COALESCE(p_payload->'ingredients','[]'::jsonb))\n LOOP\n v_iname := btrim(COALESCE(v_item->>'name', v_item->>'product_name', ''));\n v_qty := COALESCE(NULLIF(v_item->>'quantity','')::numeric, 0);\n BEGIN\n v_pid := NULLIF(v_item->>'product_id','')::uuid;\n EXCEPTION WHEN OTHERS THEN\n v_pid := NULL;\n END;\n\n IF v_pid IS NOT NULL THEN\n PERFORM 1 FROM products WHERE id = v_pid AND tenant_id = p_tenant_id;\n IF NOT FOUND THEN v_pid := NULL; END IF;\n END IF;\n\n IF v_pid IS NULL AND v_iname <> '' THEN\n SELECT p.id INTO v_pid FROM products p\n WHERE p.tenant_id = p_tenant_id\n AND lower(btrim(p.name)) = lower(v_iname)\n ORDER BY p.is_active DESC LIMIT 1;\n IF v_pid IS NULL THEN\n SELECT p.id INTO v_pid FROM products p\n WHERE p.tenant_id = p_tenant_id\n AND position(lower(v_iname) in lower(p.name)) > 0\n ORDER BY length(p.name) LIMIT 1;\n END IF;\n END IF;\n\n IF v_pid IS NULL OR v_qty <= 0 THEN\n v_unmatched := v_unmatched || jsonb_build_object(\n 'name', v_iname, 'quantity', v_qty, 'notes', NULLIF(v_item->>'notes',''));\n ELSE\n v_resolved := v_resolved || jsonb_build_object(\n 'product_id', v_pid, 'quantity', v_qty,\n 'notes', NULLIF(v_item->>'notes',''), 'display_order', v_order);\n v_order := v_order + 1;\n END IF;\n END LOOP;\n\n v_metadata := jsonb_strip_nulls(jsonb_build_object(\n 'unmatchedIngredients',\n CASE WHEN jsonb_array_length(v_unmatched) > 0 THEN v_unmatched ELSE NULL END,\n 'productHint',\n CASE WHEN v_product_id IS NULL AND v_product_hint <> '' THEN v_product_hint ELSE NULL END\n ));\n\n -- \u2500\u2500 write + audit \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n IF v_is_update THEN\n UPDATE plg_inventory_recipes SET\n name = COALESCE(NULLIF(v_name,''), name),\n description = COALESCE(v_description, description),\n product_id = COALESCE(v_product_id, product_id),\n yield_quantity = COALESCE(NULLIF(NULLIF(p_payload->>'yield_quantity','')::numeric, 0), yield_quantity),\n preparation_time_minutes = COALESCE(v_prep, preparation_time_minutes),\n instructions = COALESCE(v_instructions, instructions),\n -- An edit that says nothing about ingredients must not erase the gaps the\n -- user still has to resolve.\n metadata = CASE WHEN p_payload ? 'ingredients' OR v_product_hint <> ''\n THEN v_metadata ELSE metadata END,\n updated_at = now()\n WHERE id = v_recipe_id AND tenant_id = p_tenant_id\n RETURNING id, name INTO v_final_id, v_final_name;\n ELSE\n INSERT INTO plg_inventory_recipes (\n tenant_id, name, description, product_id, yield_quantity,\n preparation_time_minutes, instructions, metadata\n ) VALUES (\n p_tenant_id, v_name, v_description, v_product_id, v_yield,\n v_prep, v_instructions, v_metadata\n )\n RETURNING id, name INTO v_final_id, v_final_name;\n END IF;\n\n -- `ingredients` REPLACES the list; omitting the key leaves the recipe's own.\n IF p_payload ? 'ingredients' THEN\n DELETE FROM plg_inventory_recipe_ingredients\n WHERE recipe_id = v_final_id AND tenant_id = p_tenant_id;\n INSERT INTO plg_inventory_recipe_ingredients (\n tenant_id, recipe_id, product_id, quantity, notes, display_order\n )\n SELECT p_tenant_id, v_final_id, (e->>'product_id')::uuid,\n (e->>'quantity')::numeric, NULLIF(e->>'notes',''), (e->>'display_order')::int\n FROM jsonb_array_elements(v_resolved) e;\n END IF;\n\n INSERT INTO audit_logs (tenant_id, user_id, action, entity_type, entity_id, metadata)\n VALUES (p_tenant_id, p_actor_user_id,\n CASE WHEN v_is_update THEN 'agent.updateRecipe' ELSE 'agent.createRecipe' END,\n 'inventory_recipe', v_final_id::text,\n jsonb_build_object('payload', p_payload));\n\n RETURN jsonb_build_object(\n 'ok', true,\n 'id', v_final_id,\n 'record', jsonb_build_object(\n 'ref', jsonb_build_object('id', v_final_id, 'resource', 'plg_inventory_recipes',\n 'archetype', 'inventory:recipe'),\n 'name', v_final_name,\n 'productId', v_product_id,\n 'ingredientCount', jsonb_array_length(v_resolved),\n 'unmatchedIngredients', v_unmatched,\n 'productHint', CASE WHEN v_product_id IS NULL THEN NULLIF(v_product_hint,'') ELSE NULL END\n )\n );\nEND;\n$$;\n\nREVOKE ALL ON FUNCTION public.agent_inventory_upsert_recipe(uuid, uuid, jsonb) FROM public;\nREVOKE EXECUTE ON FUNCTION public.agent_inventory_upsert_recipe(uuid, uuid, jsonb) FROM anon;\nGRANT EXECUTE ON FUNCTION public.agent_inventory_upsert_recipe(uuid, uuid, jsonb)\n TO authenticated, service_role;\n\nNOTIFY pgrst, 'reload schema';\n";
|
|
11
|
+
export declare const MIGRATION_010_STOCK_COUNTS = "-- ============================================================================\n-- 010_stock_counts.sql \u2014 the physical count, as a session.\n--\n-- Until now the only correction this module offered was a per-product\n-- `adjustment` movement with a free-text reason. One product at a time, and no\n-- record of the count that motivated it: the number changed and nobody could\n-- say why, or against what. A dark kitchen closes its month against physical\n-- stock, so the count itself has to be a first-class record.\n--\n-- A session scopes to ONE stock location and optionally to one category, and\n-- lives through open \u2192 counting \u2192 closed (or cancelled).\n--\n-- DESIGN DECISIONS, and why:\n--\n-- 1. SNAPSHOT AT OPEN, NOT AT CLOSE. A count takes an hour, and the system\n-- total moves while it happens \u2014 a delivery arrives, a dish is produced.\n-- Comparing what was counted at 09:20 against what the system believes at\n-- 10:30 produces a variance that describes the clock, not the shelf. So\n-- `system_quantity` is written onto the item row when the session opens,\n-- inside inventory_open_count_session, in one consistent read.\n--\n-- 2. UNCOUNTED IS NOT ZERO. `counted_quantity` is NULLABLE, and NULL is the\n-- whole point: it means nobody looked. The close emits nothing for those\n-- lines. Treating them as zero would wipe the stock of every product the\n-- counter skipped \u2014 the single most destructive thing this feature could do.\n-- \"Counted zero\" is a real, different answer, and it is stored as 0.\n--\n-- 3. BLIND BY DEFAULT. `blind` hides the system quantity while counting, so\n-- the counter writes what is on the shelf instead of confirming what is on\n-- the screen. A count that shows you the answer first is a rubber stamp. It\n-- is a per-session flag because a targeted recount of a known discrepancy is\n-- a legitimate reason to turn it off.\n--\n-- 4. LOCATION IS REQUIRED. The close has to write a stock POSITION, and a\n-- position is (product, location, batch, expiry). A session without a\n-- location would have nowhere unambiguous to put the correction.\n--\n-- 5. THE COUNT IS AT PRODUCT \u00D7 LOCATION, NOT PER BATCH. `system_quantity` sums\n-- every batch slot in the location, and the close applies the variance to\n-- the untracked (batch NULL) slot, leaving batch-tracked slots alone.\n-- Counting per batch would mean asking the counter to read lot codes off\n-- frozen packaging; deliberately out of scope.\n--\n-- 6. THE CLOSE IS ONE TRANSACTION AND IS IDEMPOTENT. It is a SECURITY DEFINER\n-- function so that the movement, the position and products.stock cannot end\n-- up half-written, and re-closing emits nothing: the session row is locked\n-- FOR UPDATE, a session already `closed` returns immediately, and each item\n-- remembers the movement it produced in `movement_id`, so a line that\n-- already adjusted can never adjust twice.\n--\n-- 7. THE ADJUSTMENT'S QUANTITY IS THE COUNTED (ABSOLUTE) FIGURE. The module's\n-- own vocabulary declares `adjustment` as effect `set` \u2014 the quantity is the\n-- new truth, not a delta. The variance is preserved on the item row and in\n-- the movement's metadata, and the position and products.stock move by the\n-- delta.\n--\n-- SECURITY: both functions are SECURITY DEFINER and therefore fence the tenant\n-- themselves \u2014 `tenant_id IN (SELECT public.user_tenant_ids())`, which resolves\n-- from auth.uid(). A service-role caller with no JWT is denied by that fence\n-- rather than being handed a cross-tenant write.\n--\n-- Canonical RLS form: tenant_id IN (SELECT public.user_tenant_ids()).\n-- Idempotent throughout.\n-- ============================================================================\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_count_sessions (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n reference text,\n status text NOT NULL DEFAULT 'open',\n stock_location_id uuid NOT NULL REFERENCES public.plg_inventory_stock_locations(id),\n category_id uuid REFERENCES public.plg_inventory_product_categories(id) ON DELETE SET NULL,\n blind boolean NOT NULL DEFAULT true,\n notes text,\n opened_at timestamptz NOT NULL DEFAULT now(),\n opened_by uuid,\n closed_at timestamptz,\n closed_by uuid,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'plg_inventory_count_sessions_status_check'\n AND conrelid = 'public.plg_inventory_count_sessions'::regclass\n ) THEN\n ALTER TABLE public.plg_inventory_count_sessions\n ADD CONSTRAINT plg_inventory_count_sessions_status_check\n CHECK (status IN ('open','counting','closed','cancelled'));\n END IF;\nEND $$;\n\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_count_sessions_tenant\n ON public.plg_inventory_count_sessions(tenant_id, opened_at DESC);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_count_sessions_status\n ON public.plg_inventory_count_sessions(tenant_id, status);\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_count_items (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,\n session_id uuid NOT NULL REFERENCES public.plg_inventory_count_sessions(id) ON DELETE CASCADE,\n product_id uuid NOT NULL REFERENCES public.products(id),\n system_quantity numeric(14,4) NOT NULL DEFAULT 0,\n counted_quantity numeric(14,4),\n unit_cost numeric(14,2) NOT NULL DEFAULT 0,\n notes text,\n counted_at timestamptz,\n counted_by uuid,\n movement_id uuid REFERENCES public.plg_inventory_stock_movements(id) ON DELETE SET NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCOMMENT ON COLUMN public.plg_inventory_count_items.system_quantity IS 'What the system believed when the session OPENED \u2014 never re-read at close.';\nCOMMENT ON COLUMN public.plg_inventory_count_items.counted_quantity IS 'NULL means nobody counted this line. It is not zero and never emits an adjustment.';\nCOMMENT ON COLUMN public.plg_inventory_count_items.movement_id IS 'The adjustment this line already emitted. Set exactly once \u2014 the per-line idempotency key of the close.';\n\nCREATE UNIQUE INDEX IF NOT EXISTS uq_plg_inventory_count_items_line\n ON public.plg_inventory_count_items(session_id, product_id);\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_count_items_tenant\n ON public.plg_inventory_count_items(tenant_id);\n\n-- \u2500\u2500 RLS, same shape as 008 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDO $$\nDECLARE t text;\nBEGIN\n FOR t IN SELECT unnest(ARRAY[\n 'plg_inventory_count_sessions','plg_inventory_count_items'\n ])\n LOOP\n CONTINUE WHEN to_regclass('public.' || t) IS NULL;\n EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);\n EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO authenticated', t);\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_select') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_select', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_insert') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_insert', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_update') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_update', t);\n END IF;\n IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_delete') THEN\n EXECUTE format('CREATE POLICY %I ON public.%I FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))', t||'_delete', t);\n END IF;\n END LOOP;\nEND $$;\n\n-- \u2500\u2500 open: create the session AND snapshot it, in one read \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.inventory_open_count_session(\n p_stock_location_id uuid,\n p_category_id uuid DEFAULT NULL,\n p_reference text DEFAULT NULL,\n p_blind boolean DEFAULT true,\n p_notes text DEFAULT NULL\n) RETURNS uuid\nLANGUAGE plpgsql SECURITY DEFINER\nSET search_path = public\nAS $$\nDECLARE\n v_tenant uuid;\n v_session_id uuid;\nBEGIN\n SELECT tenant_id INTO v_tenant\n FROM plg_inventory_stock_locations WHERE id = p_stock_location_id;\n IF v_tenant IS NULL THEN\n RAISE EXCEPTION 'inventory_open_count_session: unknown stock location %', p_stock_location_id;\n END IF;\n IF v_tenant NOT IN (SELECT public.user_tenant_ids()) THEN\n RAISE EXCEPTION 'inventory_open_count_session: not a member of this tenant';\n END IF;\n\n INSERT INTO plg_inventory_count_sessions (\n tenant_id, reference, status, stock_location_id, category_id, blind, notes, opened_by\n ) VALUES (\n v_tenant, NULLIF(btrim(COALESCE(p_reference, '')), ''), 'open',\n p_stock_location_id, p_category_id, COALESCE(p_blind, true),\n NULLIF(btrim(COALESCE(p_notes, '')), ''), auth.uid()\n )\n RETURNING id INTO v_session_id;\n\n -- Assets are patrimony, not stock, so they are never on a count sheet.\n INSERT INTO plg_inventory_count_items (\n tenant_id, session_id, product_id, system_quantity, unit_cost\n )\n SELECT\n v_tenant, v_session_id, p.id,\n COALESCE(pos.quantity, 0),\n COALESCE(pos.unit_cost, p.cost, 0)\n FROM products p\n LEFT JOIN plg_inventory_product_details d ON d.product_id = p.id\n LEFT JOIN LATERAL (\n SELECT sum(sp.quantity) AS quantity, max(sp.unit_cost) AS unit_cost\n FROM plg_inventory_stock_positions sp\n WHERE sp.tenant_id = v_tenant\n AND sp.product_id = p.id\n AND sp.stock_location_id = p_stock_location_id\n ) pos ON true\n WHERE p.tenant_id = v_tenant\n AND p.is_active = true\n AND COALESCE(p.metadata->>'productType', 'sale') <> 'asset'\n AND (p_category_id IS NULL OR d.category_id = p_category_id);\n\n RETURN v_session_id;\nEND;\n$$;\n\n-- \u2500\u2500 close: one transaction, one adjustment per divergent counted line \u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCREATE OR REPLACE FUNCTION public.inventory_close_count_session(\n p_session_id uuid,\n p_reason text DEFAULT NULL\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER\nSET search_path = public\nAS $$\nDECLARE\n v_session plg_inventory_count_sessions%ROWTYPE;\n v_item plg_inventory_count_items%ROWTYPE;\n v_delta numeric;\n v_movement_id uuid;\n v_reason text;\n v_emitted int := 0;\nBEGIN\n -- The lock is what makes two simultaneous closes serialise instead of both\n -- reading `counting` and both emitting.\n SELECT * INTO v_session FROM plg_inventory_count_sessions\n WHERE id = p_session_id FOR UPDATE;\n IF NOT FOUND THEN\n RETURN jsonb_build_object('ok', false, 'error', 'unknown count session');\n END IF;\n IF v_session.tenant_id NOT IN (SELECT public.user_tenant_ids()) THEN\n RAISE EXCEPTION 'inventory_close_count_session: not a member of this tenant';\n END IF;\n\n IF v_session.status = 'closed' THEN\n RETURN jsonb_build_object('ok', true, 'session_id', p_session_id,\n 'status', 'closed', 'already_closed', true, 'adjustments_created', 0);\n END IF;\n IF v_session.status = 'cancelled' THEN\n RETURN jsonb_build_object('ok', false, 'error', 'this count was cancelled');\n END IF;\n\n v_reason := COALESCE(NULLIF(btrim(COALESCE(p_reason, '')), ''), 'Stock count');\n\n FOR v_item IN\n SELECT * FROM plg_inventory_count_items\n WHERE session_id = p_session_id\n AND counted_quantity IS NOT NULL\n AND counted_quantity <> system_quantity\n AND movement_id IS NULL\n ORDER BY id\n FOR UPDATE\n LOOP\n v_delta := v_item.counted_quantity - v_item.system_quantity;\n\n INSERT INTO plg_inventory_stock_movements (\n tenant_id, product_id, quantity, movement_type, unit_cost, total_cost,\n stock_location_id, reason, movement_date, user_id, metadata\n ) VALUES (\n v_session.tenant_id, v_item.product_id, v_item.counted_quantity, 'adjustment',\n v_item.unit_cost, v_item.unit_cost * v_item.counted_quantity,\n v_session.stock_location_id, v_reason, CURRENT_DATE, auth.uid(),\n jsonb_build_object(\n 'countSessionId', p_session_id,\n 'countItemId', v_item.id,\n 'systemQuantity', v_item.system_quantity,\n 'countedQuantity', v_item.counted_quantity,\n 'variance', v_delta\n )\n )\n RETURNING id INTO v_movement_id;\n\n UPDATE plg_inventory_count_items\n SET movement_id = v_movement_id, updated_at = now()\n WHERE id = v_item.id;\n\n -- The variance lands on the untracked slot; batch slots keep their lots.\n INSERT INTO plg_inventory_stock_positions (\n tenant_id, product_id, stock_location_id, quantity, unit_cost\n ) VALUES (\n v_session.tenant_id, v_item.product_id, v_session.stock_location_id,\n v_delta, v_item.unit_cost\n )\n ON CONFLICT (tenant_id, product_id, stock_location_id, batch_number, expiration_date)\n DO UPDATE SET\n quantity = plg_inventory_stock_positions.quantity + EXCLUDED.quantity,\n updated_at = now();\n\n UPDATE products SET stock = COALESCE(stock, 0) + v_delta, updated_at = now()\n WHERE id = v_item.product_id AND tenant_id = v_session.tenant_id;\n\n v_emitted := v_emitted + 1;\n END LOOP;\n\n UPDATE plg_inventory_count_sessions\n SET status = 'closed', closed_at = now(), closed_by = auth.uid(), updated_at = now()\n WHERE id = p_session_id;\n\n RETURN jsonb_build_object('ok', true, 'session_id', p_session_id,\n 'status', 'closed', 'already_closed', false, 'adjustments_created', v_emitted);\nEND;\n$$;\n\nREVOKE ALL ON FUNCTION public.inventory_open_count_session(uuid, uuid, text, boolean, text) FROM public;\nREVOKE EXECUTE ON FUNCTION public.inventory_open_count_session(uuid, uuid, text, boolean, text) FROM anon;\nGRANT EXECUTE ON FUNCTION public.inventory_open_count_session(uuid, uuid, text, boolean, text)\n TO authenticated, service_role;\n\nREVOKE ALL ON FUNCTION public.inventory_close_count_session(uuid, text) FROM public;\nREVOKE EXECUTE ON FUNCTION public.inventory_close_count_session(uuid, text) FROM anon;\nGRANT EXECUTE ON FUNCTION public.inventory_close_count_session(uuid, text)\n TO authenticated, service_role;\n\nNOTIFY pgrst, 'reload schema';\n";
|
|
5
12
|
export declare const MIGRATIONS: Array<{
|
|
6
13
|
id: string;
|
|
7
14
|
sql: string;
|
|
@@ -1 +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,
|
|
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,iCAAiC,kwGA4E7C,CAAA;AAED,eAAO,MAAM,6BAA6B,i3HA4DzC,CAAA;AAED,eAAO,MAAM,kCAAkC,ylIAqF9C,CAAA;AAED,eAAO,MAAM,2BAA2B,k/DAmCvC,CAAA;AAED,eAAO,MAAM,0BAA0B,w4GAwDtC,CAAA;AAED,eAAO,MAAM,wBAAwB,2tVA2OpC,CAAA;AAED,eAAO,MAAM,0BAA0B,24dA6TtC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAYzD,CAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type ProductType = 'ingredient' | 'sale' | 'intermediate' | 'asset';
|
|
|
4
4
|
export type MovementType = 'entry' | 'exit' | 'adjustment' | 'transfer' | 'loss';
|
|
5
5
|
/** Product purpose for filtering */
|
|
6
6
|
export type ProductPurpose = 'purchase' | 'sale' | 'both' | 'internal';
|
|
7
|
+
/** Life of a physical count. `counting` starts at the first entry typed. */
|
|
8
|
+
export type CountSessionStatus = 'open' | 'counting' | 'closed' | 'cancelled';
|
|
7
9
|
export interface Product {
|
|
8
10
|
id: string;
|
|
9
11
|
name: string;
|
|
@@ -22,6 +24,10 @@ export interface Product {
|
|
|
22
24
|
salePrice?: number;
|
|
23
25
|
measurementUnitId?: string;
|
|
24
26
|
measurementUnitName?: string;
|
|
27
|
+
/** Unit the product is bought in, when it differs from the one it is counted in. */
|
|
28
|
+
purchaseUnitId?: string;
|
|
29
|
+
/** How many measurement units fit in one purchase unit. 1 when they are the same. */
|
|
30
|
+
conversionFactor?: number;
|
|
25
31
|
isActive: boolean;
|
|
26
32
|
supplierId?: string;
|
|
27
33
|
supplierName?: string;
|
|
@@ -49,6 +55,8 @@ export interface StockMovement {
|
|
|
49
55
|
documentNumber?: string;
|
|
50
56
|
reason?: string;
|
|
51
57
|
notes?: string;
|
|
58
|
+
batchNumber?: string;
|
|
59
|
+
expirationDate?: string;
|
|
52
60
|
movementDate: string;
|
|
53
61
|
userId?: string;
|
|
54
62
|
userName?: string;
|
|
@@ -66,8 +74,12 @@ export interface StockPosition {
|
|
|
66
74
|
stockLocationName?: string;
|
|
67
75
|
batchNumber?: string;
|
|
68
76
|
expirationDate?: string;
|
|
77
|
+
measurementUnitId?: string;
|
|
78
|
+
/** Which of the product's units this quantity is counted in. */
|
|
79
|
+
unitType?: 'base' | 'purchase' | 'content';
|
|
69
80
|
tenantId: string;
|
|
70
81
|
createdAt: string;
|
|
82
|
+
updatedAt?: string;
|
|
71
83
|
}
|
|
72
84
|
export interface StockLocation {
|
|
73
85
|
id: string;
|
|
@@ -79,12 +91,54 @@ export interface StockLocation {
|
|
|
79
91
|
createdAt: string;
|
|
80
92
|
updatedAt: string;
|
|
81
93
|
}
|
|
94
|
+
/** A physical count of one stock location, open through closed. */
|
|
95
|
+
export interface StockCountSession {
|
|
96
|
+
id: string;
|
|
97
|
+
reference?: string;
|
|
98
|
+
status: CountSessionStatus;
|
|
99
|
+
stockLocationId: string;
|
|
100
|
+
stockLocationName?: string;
|
|
101
|
+
categoryId?: string;
|
|
102
|
+
categoryName?: string;
|
|
103
|
+
/** Hides the system quantity while counting. */
|
|
104
|
+
blind: boolean;
|
|
105
|
+
notes?: string;
|
|
106
|
+
itemCount?: number;
|
|
107
|
+
countedCount?: number;
|
|
108
|
+
divergentCount?: number;
|
|
109
|
+
openedAt: string;
|
|
110
|
+
closedAt?: string;
|
|
111
|
+
tenantId: string;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
updatedAt: string;
|
|
114
|
+
}
|
|
115
|
+
export interface StockCountItem {
|
|
116
|
+
id: string;
|
|
117
|
+
sessionId: string;
|
|
118
|
+
productId: string;
|
|
119
|
+
productName?: string;
|
|
120
|
+
productSku?: string;
|
|
121
|
+
/** What the system believed when the session opened — never re-read. */
|
|
122
|
+
systemQuantity: number;
|
|
123
|
+
/** undefined means nobody counted this line. It is not zero. */
|
|
124
|
+
countedQuantity?: number;
|
|
125
|
+
unitCost: number;
|
|
126
|
+
notes?: string;
|
|
127
|
+
countedAt?: string;
|
|
128
|
+
/** The adjustment this line already emitted, if the session is closed. */
|
|
129
|
+
movementId?: string;
|
|
130
|
+
tenantId: string;
|
|
131
|
+
createdAt: string;
|
|
132
|
+
updatedAt: string;
|
|
133
|
+
}
|
|
82
134
|
export interface Recipe {
|
|
83
135
|
id: string;
|
|
84
136
|
name: string;
|
|
85
137
|
description?: string;
|
|
86
138
|
productId: string;
|
|
87
139
|
productName?: string;
|
|
140
|
+
/** Sale price of the produced product — the denominator of CMV%. */
|
|
141
|
+
productSalePrice?: number;
|
|
88
142
|
yieldQuantity: number;
|
|
89
143
|
yieldUnitId?: string;
|
|
90
144
|
yieldUnitName?: string;
|
|
@@ -92,10 +146,31 @@ export interface Recipe {
|
|
|
92
146
|
instructions?: string;
|
|
93
147
|
isActive: boolean;
|
|
94
148
|
ingredientCount?: number;
|
|
149
|
+
/** Carries what the assistant could not resolve — see RecipeGaps. */
|
|
150
|
+
metadata?: Record<string, unknown>;
|
|
95
151
|
tenantId: string;
|
|
96
152
|
createdAt: string;
|
|
97
153
|
updatedAt: string;
|
|
98
154
|
}
|
|
155
|
+
/** What an ingredient product contributes to a recipe line's cost. */
|
|
156
|
+
export interface IngredientCostBasis {
|
|
157
|
+
/** `products.cost` — the catalogue's cost for the product. */
|
|
158
|
+
cost: number;
|
|
159
|
+
/** Base units per purchase unit, from plg_inventory_product_details. */
|
|
160
|
+
conversionFactor?: number;
|
|
161
|
+
/** Set only when the product is bought in a unit other than the counted one. */
|
|
162
|
+
purchaseUnitId?: string;
|
|
163
|
+
}
|
|
164
|
+
/** An ingredient the assistant proposed but could not match to the catalogue. */
|
|
165
|
+
export interface RecipeGaps {
|
|
166
|
+
unmatchedIngredients?: Array<{
|
|
167
|
+
name?: string;
|
|
168
|
+
quantity?: number;
|
|
169
|
+
notes?: string;
|
|
170
|
+
}>;
|
|
171
|
+
/** Name of the produced product when it did not resolve to a catalogue row. */
|
|
172
|
+
productHint?: string;
|
|
173
|
+
}
|
|
99
174
|
export interface RecipeIngredient {
|
|
100
175
|
id: string;
|
|
101
176
|
recipeId: string;
|
|
@@ -106,7 +181,30 @@ export interface RecipeIngredient {
|
|
|
106
181
|
unitName?: string;
|
|
107
182
|
displayOrder: number;
|
|
108
183
|
notes?: string;
|
|
184
|
+
/** Optional stage of the recipe this ingredient belongs to. */
|
|
185
|
+
groupId?: string;
|
|
186
|
+
groupName?: string;
|
|
187
|
+
costBasis?: IngredientCostBasis;
|
|
188
|
+
createdAt: string;
|
|
189
|
+
}
|
|
190
|
+
/** A stage of a recipe — "massa", "recheio", "montagem". */
|
|
191
|
+
export interface RecipeGroup {
|
|
192
|
+
id: string;
|
|
193
|
+
recipeId: string;
|
|
194
|
+
name: string;
|
|
195
|
+
preparationNotes?: string;
|
|
196
|
+
displayOrder: number;
|
|
197
|
+
estimatedMinutes?: number;
|
|
198
|
+
tenantId: string;
|
|
109
199
|
createdAt: string;
|
|
200
|
+
updatedAt: string;
|
|
201
|
+
}
|
|
202
|
+
/** Who the operation buys from — the read shape of the supplier registry. */
|
|
203
|
+
export interface Supplier {
|
|
204
|
+
id: string;
|
|
205
|
+
name: string;
|
|
206
|
+
documentNumber?: string;
|
|
207
|
+
phone?: string;
|
|
110
208
|
}
|
|
111
209
|
export interface MeasurementUnit {
|
|
112
210
|
id: string;
|
|
@@ -139,6 +237,8 @@ export interface CreateProductInput {
|
|
|
139
237
|
costPrice?: number;
|
|
140
238
|
salePrice?: number;
|
|
141
239
|
measurementUnitId?: string;
|
|
240
|
+
purchaseUnitId?: string;
|
|
241
|
+
conversionFactor?: number;
|
|
142
242
|
supplierId?: string;
|
|
143
243
|
defaultLocationId?: string;
|
|
144
244
|
imageUrl?: string;
|
|
@@ -159,6 +259,19 @@ export interface CreateStockMovementInput {
|
|
|
159
259
|
batchNumber?: string;
|
|
160
260
|
expirationDate?: string;
|
|
161
261
|
}
|
|
262
|
+
export interface OpenStockCountInput {
|
|
263
|
+
stockLocationId: string;
|
|
264
|
+
categoryId?: string;
|
|
265
|
+
reference?: string;
|
|
266
|
+
blind?: boolean;
|
|
267
|
+
notes?: string;
|
|
268
|
+
}
|
|
269
|
+
export interface CloseStockCountResult {
|
|
270
|
+
sessionId: string;
|
|
271
|
+
adjustmentsCreated: number;
|
|
272
|
+
/** True when the session was already closed — nothing was emitted again. */
|
|
273
|
+
alreadyClosed: boolean;
|
|
274
|
+
}
|
|
162
275
|
export interface CreateRecipeInput {
|
|
163
276
|
name: string;
|
|
164
277
|
description?: string;
|
|
@@ -175,6 +288,7 @@ export interface CreateRecipeIngredientInput {
|
|
|
175
288
|
unitId?: string;
|
|
176
289
|
displayOrder?: number;
|
|
177
290
|
notes?: string;
|
|
291
|
+
groupId?: string;
|
|
178
292
|
}
|
|
179
293
|
export interface DateRange {
|
|
180
294
|
from: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AASA,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,CAAA;AAE1E,sCAAsC;AACtC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAA;AAEhF,oCAAoC;AACpC,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InventoryPage.d.ts","sourceRoot":"","sources":["../../src/views/InventoryPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAqB,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"InventoryPage.d.ts","sourceRoot":"","sources":["../../src/views/InventoryPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE/C,OAAO,EAA4B,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAqB,MAAM,eAAe,CAAA;AAsDzE,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IACrE,MAAM,EAAE,uBAAuB,CAAA;IAC/B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IACjC,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAA;CACjC,qBAuIA"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export declare function MovementHistoryView({ onViewDetail }?: {
|
|
2
|
+
export declare function MovementHistoryView({ onViewDetail, onNew, onNewExit }?: {
|
|
3
3
|
onViewDetail?: (id: string) => void;
|
|
4
|
+
/** Record an entry. This screen is where you already are when the goods arrive. */
|
|
5
|
+
onNew?: () => void;
|
|
6
|
+
onNewExit?: () => void;
|
|
4
7
|
}): React.JSX.Element;
|
|
5
8
|
//# sourceMappingURL=MovementHistoryView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MovementHistoryView.d.ts","sourceRoot":"","sources":["../../src/views/MovementHistoryView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"MovementHistoryView.d.ts","sourceRoot":"","sources":["../../src/views/MovementHistoryView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAA;AAgC3D,wBAAgB,mBAAmB,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,GAAE;IACtE,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CAClB,qBAwFL"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductCrudForm.d.ts","sourceRoot":"","sources":["../../src/views/ProductCrudForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAgBzB,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"ProductCrudForm.d.ts","sourceRoot":"","sources":["../../src/views/ProductCrudForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAgBzB,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,qBAqG7F"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/** The product's own page — overview plus the tabs the entity declares. */
|
|
3
|
+
export declare function ProductDetailView({ productId, initialTab, onBack, onEdit }: {
|
|
4
|
+
productId: string;
|
|
5
|
+
initialTab?: string;
|
|
6
|
+
onBack: () => void;
|
|
7
|
+
onEdit?: () => void;
|
|
8
|
+
}): React.JSX.Element;
|
|
9
|
+
//# sourceMappingURL=ProductDetailView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductDetailView.d.ts","sourceRoot":"","sources":["../../src/views/ProductDetailView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAA;AA0BxE,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IAC3E,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACpB,qBA8DA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export declare function ProductListView({ onNew,
|
|
2
|
+
export declare function ProductListView({ onNew, onOpen }: {
|
|
3
3
|
onNew?: () => void;
|
|
4
|
-
|
|
4
|
+
/** Opens the product's own page. Editing starts there, not from the row. */
|
|
5
|
+
onOpen?: (id: string) => void;
|
|
5
6
|
}): React.JSX.Element;
|
|
6
7
|
//# sourceMappingURL=ProductListView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductListView.d.ts","sourceRoot":"","sources":["../../src/views/ProductListView.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ProductListView.d.ts","sourceRoot":"","sources":["../../src/views/ProductListView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAaxE,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IACjD,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;IAClB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B,qBAoIA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Product } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* "How much costela do I have, where, and where did it go" — answered on the
|
|
5
|
+
* product itself instead of by filtering the global ledger.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ProductStockTab({ item, onChanged }: {
|
|
8
|
+
item: Product;
|
|
9
|
+
onChanged?: () => void;
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
//# sourceMappingURL=ProductStockTab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductStockTab.d.ts","sourceRoot":"","sources":["../../src/views/ProductStockTab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAMxE,OAAO,KAAK,EAAE,OAAO,EAAgC,MAAM,UAAU,CAAA;AAqBrE;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,qBA+K7F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecipeDetailView.d.ts","sourceRoot":"","sources":["../../src/views/RecipeDetailView.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"RecipeDetailView.d.ts","sourceRoot":"","sources":["../../src/views/RecipeDetailView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAA;AAmD3D,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,qBAmP9F"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export declare function RecipeFormView({ onSaved }: {
|
|
2
|
+
export declare function RecipeFormView({ onSaved, productId: initialProductId }: {
|
|
3
3
|
onSaved?: (id?: string) => void;
|
|
4
|
+
/** Menu item this ficha is for, when the list started it from a missing row. */
|
|
5
|
+
productId?: string;
|
|
4
6
|
}): React.JSX.Element;
|
|
5
7
|
//# sourceMappingURL=RecipeFormView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecipeFormView.d.ts","sourceRoot":"","sources":["../../src/views/RecipeFormView.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"RecipeFormView.d.ts","sourceRoot":"","sources":["../../src/views/RecipeFormView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAuBlD,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE;IACvE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,qBAuOA"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export declare function RecipesView({ onNew, onView }: {
|
|
2
|
+
export declare function RecipesView({ onNew, onView, onNewForProduct }: {
|
|
3
3
|
onNew?: () => void;
|
|
4
4
|
onView?: (id: string) => void;
|
|
5
|
+
/** Starts a ficha for a menu item that has none. */
|
|
6
|
+
onNewForProduct?: (productId: string) => void;
|
|
5
7
|
}): React.JSX.Element;
|
|
6
8
|
//# sourceMappingURL=RecipesView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecipesView.d.ts","sourceRoot":"","sources":["../../src/views/RecipesView.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"RecipesView.d.ts","sourceRoot":"","sources":["../../src/views/RecipesView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAqGhF,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE;IAC9D,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,oDAAoD;IACpD,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9C,qBAsLA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/** Opening a count is four decisions: where, what, blind, and what to call it. */
|
|
3
|
+
export declare function StockCountFormView({ onOpened, onCancel }: {
|
|
4
|
+
onOpened: (sessionId: string) => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
}): React.JSX.Element;
|
|
7
|
+
//# sourceMappingURL=StockCountFormView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StockCountFormView.d.ts","sourceRoot":"","sources":["../../src/views/StockCountFormView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AASlD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IACzD,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB,qBAyGA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The counting screen — used standing up, on a phone, in a cold room. One row
|
|
4
|
+
* per product, one big number field, Enter moves to the next line.
|
|
5
|
+
*
|
|
6
|
+
* A blind session hides the system quantity AND the per-line variance until the
|
|
7
|
+
* count is closed. Showing the variance the instant a number is typed would let
|
|
8
|
+
* the counter retype until it reads zero, which is the same rubber stamp blind
|
|
9
|
+
* mode exists to prevent.
|
|
10
|
+
*/
|
|
11
|
+
export declare function StockCountSessionView({ sessionId, onBack }: {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
onBack: () => void;
|
|
14
|
+
}): React.JSX.Element;
|
|
15
|
+
//# sourceMappingURL=StockCountSessionView.d.ts.map
|