@fayz-ai/plugin-inventory 0.19.0 → 0.19.1

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.
@@ -29,7 +29,7 @@ export declare const MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM = "-- =
29
29
  export declare const MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT = "-- ============================================================================\n-- 028 \u2014 a receipt keeps the variant\n--\n-- `inventory_receive` read `variant_id` off each line, checked it belonged to\n-- the product, and then did not write it: every receipt landed on the\n-- product's default variant. A shirt received as 10 M and 9 G was 19 of\n-- \"Padr\u00E3o\". The line's variant now reaches the ledger row, which is what the\n-- per-variant balance is computed from.\n--\n-- Patched in place, as 026 and 027 did.\n--\n-- The column itself was never in this plugin's chain (022 says so): pools got\n-- it by hand, a database built from zero did not. It is declared here, in the\n-- shape the pools have. The value is read off the line's JSON, which exists in\n-- every version of the function; `v_ln.variant_id` only exists where the\n-- parser was widened by hand.\n-- ============================================================================\n\nALTER TABLE public.plg_inventory_stock_movements\n ADD COLUMN IF NOT EXISTS variant_id uuid REFERENCES public.product_variants(id) ON DELETE SET NULL;\nCREATE INDEX IF NOT EXISTS plg_inventory_stock_movements_variant_idx\n ON public.plg_inventory_stock_movements (tenant_id, variant_id) WHERE variant_id IS NOT NULL;\n\nDO $patch$\nDECLARE\n v_fn regprocedure := 'public.inventory_receive(uuid, jsonb, jsonb, text)'::regprocedure;\n v_def text := pg_get_functiondef(v_fn);\n v_cols constant text := 'source_item_type, source_item_id)';\n v_vals constant text := 'nullif(v_line->>''source_item_id'', '''')::uuid)';\nBEGIN\n IF position('source_item_type, source_item_id, variant_id)' in v_def) > 0 THEN\n RETURN; -- already writes it (a replay, or a later rewrite)\n END IF;\n IF position(v_cols in v_def) = 0 OR position(v_vals in v_def) = 0 THEN\n RAISE EXCEPTION '028: inventory_receive no longer has the INSERT this extends; patch it by hand';\n END IF;\n v_def := overlay(v_def PLACING 'source_item_type, source_item_id, variant_id)'\n FROM position(v_cols in v_def) FOR length(v_cols));\n v_def := overlay(v_def PLACING 'nullif(v_line->>''source_item_id'', '''')::uuid, nullif(v_line->>''variant_id'', '''')::uuid)'\n FROM position(v_vals in v_def) FOR length(v_vals));\n EXECUTE v_def;\nEND $patch$;\n";
30
30
  export declare const MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT = "-- ============================================================================\n-- 029 \u2014 a reversal undoes the same variant\n--\n-- 028 made a receipt keep the variant it was for. `inventory_reverse` copies\n-- the row it undoes and left the variant out, so undoing \"6 of M\" tried to\n-- take 6 from the product's default variant \u2014 which has none, and the\n-- reversal was refused (\"would take variant \u2026 below zero\"). A receipt by size\n-- could be made and never corrected.\n--\n-- The reversal now carries the variant of the row it reverses.\n-- Patched in place, like 026\u2013028.\n-- ============================================================================\n\nDO $patch$\nDECLARE\n v_fn regprocedure := 'public.inventory_reverse(text, text)'::regprocedure;\n v_def text := pg_get_functiondef(v_fn);\n v_new text;\n -- The function has two INSERTs (one per branch); both copy the row they undo.\n v_cols constant text := 'reverses_movement_id, idempotency_key, line_no, operation_id, metadata)';\n v_vals1 constant text := 'jsonb_build_object(''reversed_movement_id'', r.id))';\n v_vals2 constant text := '''reversed_kind'', r.kind))';\nBEGIN\n IF position('r.variant_id' in v_def) > 0 THEN\n RETURN; -- already carries it (a replay, or a later rewrite)\n END IF;\n v_new := replace(v_def, v_cols, 'reverses_movement_id, idempotency_key, line_no, operation_id, metadata, variant_id)');\n v_new := replace(v_new, v_vals1, 'jsonb_build_object(''reversed_movement_id'', r.id), r.variant_id)');\n v_new := replace(v_new, v_vals2, '''reversed_kind'', r.kind), r.variant_id)');\n -- Every INSERT that gained the column must have gained the value.\n IF (length(v_new) - length(replace(v_new, 'metadata, variant_id)', ''))) / length('metadata, variant_id)')\n <> (length(v_new) - length(replace(v_new, ', r.variant_id)', ''))) / length(', r.variant_id)')\n OR position('metadata, variant_id)' in v_new) = 0 THEN\n RAISE EXCEPTION '029: inventory_reverse no longer has the INSERTs this extends; patch it by hand';\n END IF;\n EXECUTE v_new;\nEND $patch$;\n";
31
31
  export declare const MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED = "-- ============================================================================\n-- 030 \u2014 the minimum can be saved\n--\n-- The product form saves its minimum with an upsert on\n-- (tenant_id, product_id, unit_id). The only unique index on those columns\n-- was the expression index (tenant_id, product_id, COALESCE(unit_id, zero)),\n-- which ON CONFLICT cannot infer from a column list, so every save came back\n-- 42P10 and the client never looked at the error: \"Quantidade M\u00EDnima\" was\n-- dropped in silence.\n--\n-- A plain column index with NULLS NOT DISTINCT is an arbiter for that\n-- conflict target and treats the tenant-wide row (unit_id NULL) as one row,\n-- which is what the expression index already enforced. Both are kept: they\n-- agree on every row, and dropping the old one is not this fix's business.\n--\n-- Hand-applied on cluster-squash-test on 2026-10-22 under this same name, so\n-- this file must be a no-op there.\n--\n-- Second part: the Insumo tab had its own minimum/maximum, stored on\n-- plg_inventory_product_ingredient_info, which no alert or restock list\n-- reads. The tab no longer offers them. Whatever was typed there is copied\n-- into the tenant-wide settings row when the product has none yet, so the\n-- number the person entered becomes the one that is acted on. The columns\n-- stay; nothing is dropped.\n-- ============================================================================\n\nCREATE UNIQUE INDEX IF NOT EXISTS plg_inventory_product_settings_upsert_key\n ON public.plg_inventory_product_settings (tenant_id, product_id, unit_id) NULLS NOT DISTINCT;\n\nINSERT INTO public.plg_inventory_product_settings (tenant_id, product_id, unit_id, min_quantity, max_quantity)\nSELECT i.tenant_id,\n i.product_id,\n NULL,\n GREATEST(COALESCE(i.minimum_quantity, 0), 0),\n CASE WHEN i.maximum_quantity >= 0 THEN i.maximum_quantity END\n FROM public.plg_inventory_product_ingredient_info i\n JOIN public.products p ON p.id = i.product_id AND p.tenant_id = i.tenant_id\n WHERE (COALESCE(i.minimum_quantity, 0) > 0 OR i.maximum_quantity IS NOT NULL)\n AND NOT EXISTS (\n SELECT 1\n FROM public.plg_inventory_product_settings s\n WHERE s.tenant_id = i.tenant_id\n AND s.product_id = i.product_id\n AND s.unit_id IS NULL)\nON CONFLICT (tenant_id, product_id, unit_id) DO NOTHING;\n";
32
- export declare const MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW = "-- ============================================================================\n-- 031 \u2014 the bell rings when stock runs low\n--\n-- A product's minimum (plg_inventory_product_settings.min_quantity) only ever\n-- fed a count and a filter: \"estoque baixo\" was something you found by opening\n-- the Estoque screen or asking the assistant. Nothing told anyone.\n--\n-- Now the moment a product's balance CROSSES its minimum \u2014 from above it to at\n-- or below it \u2014 every active member who can read the stock gets a line in the\n-- bell (public.notifications, the table the shell's bell reads), the bell is\n-- poked over realtime, and `inventory.stock_low` is recorded in the event log\n-- so an automation can carry it further (WhatsApp, e-mail).\n--\n-- The rules are the ones v_inventory_low_stock already states, so the alert and\n-- the screen never disagree:\n-- - a minimum applies when it is > 0;\n-- - \"low\" is on_hand <= min_quantity;\n-- - a settings row with unit_id set watches that unit's positions; the row\n-- with unit_id NULL watches the product across the whole tenant.\n--\n-- EDGE, not level. The alert fires on the crossing, not on every movement while\n-- the product stays low \u2014 selling the tenth bottle of a product already at its\n-- minimum must not ring ten times. It re-arms by itself: once a receipt lifts\n-- the balance above the minimum, the next fall rings again. The same crossing\n-- is checked when the minimum itself changes: raising it above what is on the\n-- shelf is as much \"this is now low\" as selling down to it.\n--\n-- One notification per user per minimum, re-surfaced. The subject is the\n-- settings row (product + scope), so the unique index of packages/db 130 on\n-- (user_id, subject_type, subject_id, type) keeps one line per product per\n-- person; a new crossing marks it unread and moves it to the top instead of\n-- stacking copies.\n--\n-- PER TRANSACTION, not per statement. Every position write goes through\n-- app.inventory_apply_to_position, one position per call, so a transfer is two\n-- statements \u2014 the source goes down, then the destination goes up \u2014 and a\n-- recipe consumes ingredient by ingredient. Checked per statement, a transfer\n-- between two units of a product sitting near its minimum would dip the tenant\n-- total in between and ring for a balance that never changed. So:\n-- - a BEFORE row trigger records, on the first touch of a transaction, the\n-- balance of each scope it touches (the product across the tenant, and the\n-- unit) in a transaction-local setting \u2014 that is the balance BEFORE;\n-- - a DEFERRED constraint trigger runs at COMMIT, when every statement of\n-- the transaction has landed, compares each minimum's scope against what\n-- was recorded, rings on a crossing, and moves the record forward so the\n-- other rows of the same commit do not ring again.\n\n-- A failure here never takes the stock write with it: everything that rings\n-- runs inside an exception block that only warns, as the order bell does.\n-- ============================================================================\n\n-- The text of a quantity: \"2\", \"2,5\", \"0,125\" \u2014 no trailing zeros, Brazilian\n-- decimal comma. Helper of this file only.\nCREATE OR REPLACE FUNCTION app.inventory_quantity_text(p_value numeric)\nRETURNS text\nLANGUAGE sql\nIMMUTABLE\nSET search_path TO ''\nAS $$\n SELECT replace(trim_scale(coalesce(p_value, 0))::text, '.', ',');\n$$;\n\n-- Ring for one minimum that has just been crossed.\nCREATE OR REPLACE FUNCTION app.inventory_ring_the_bell_for_low_stock(\n p_setting_id uuid,\n p_tenant_id uuid,\n p_product_id uuid,\n p_unit_id uuid,\n p_on_hand numeric,\n p_min numeric\n)\nRETURNS void\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_name text;\n v_measure text;\n v_unit text;\n v_title text;\n v_body text;\nBEGIN\n BEGIN\n SELECT pl.name INTO v_name FROM app.inventory_product_label(p_product_id) pl(name, sku);\n SELECT nullif(btrim(coalesce(p.unit, '')), '') INTO v_measure\n FROM public.products p WHERE p.id = p_product_id;\n IF p_unit_id IS NOT NULL THEN\n SELECT u.name INTO v_unit FROM app.units u WHERE u.id = p_unit_id;\n END IF;\n\n v_title := 'Estoque baixo: ' || coalesce(v_name, 'produto');\n v_body := concat_ws(' \u00B7 ',\n 'Saldo ' || app.inventory_quantity_text(p_on_hand) || coalesce(' ' || v_measure, ''),\n 'm\u00EDnimo ' || app.inventory_quantity_text(p_min) || coalesce(' ' || v_measure, ''),\n v_unit);\n\n -- Whoever can see the stock where it ran low. RLS on notifications shows a\n -- row only to its own user_id, so it is one row per person.\n INSERT INTO public.notifications (tenant_id, user_id, title, body, type, link, subject_type, subject_id)\n SELECT p_tenant_id, m.user_id, v_title, v_body, 'inventory.stock_low',\n '/inventory/products/detail/' || p_product_id::text,\n 'inventory_minimum', p_setting_id\n FROM (SELECT DISTINCT mm.user_id FROM app.memberships mm\n WHERE mm.tenant_id = p_tenant_id AND mm.active AND mm.user_id IS NOT NULL) m\n WHERE EXISTS (\n SELECT 1 FROM app.role_permissions rp\n WHERE rp.tenant_id = p_tenant_id\n AND rp.role_id = app.effective_role_id(p_tenant_id, m.user_id, p_unit_id)\n AND rp.permission IN ('inventory.read', 'inventory.manage'))\n ON CONFLICT (user_id, subject_type, subject_id, type) WHERE subject_id IS NOT NULL\n DO UPDATE SET read = false, created_at = now(), title = EXCLUDED.title, body = EXCLUDED.body;\n\n IF to_regproc('realtime.send') IS NOT NULL THEN\n PERFORM realtime.send(\n jsonb_build_object('type', 'inventory.stock_low', 'subjectId', p_setting_id),\n 'notification',\n -- Contract with `subscribeBell()` in packages/admin/src/lib/bell.ts.\n 'avisos:' || p_tenant_id::text,\n false);\n END IF;\n\n PERFORM public.plg_emit_event(\n 'inventory.stock_low',\n jsonb_build_object(\n 'productId', p_product_id,\n 'productName', v_name,\n 'unitId', p_unit_id,\n 'unitName', v_unit,\n 'onHand', p_on_hand,\n 'minQuantity', p_min,\n 'measure', v_measure),\n 'product', p_product_id::text, p_tenant_id);\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'inventory_ring_the_bell_for_low_stock: %', SQLERRM;\n END;\nEND;\n$$;\n\n-- The balance a minimum watches: the unit's positions, or the tenant's.\nCREATE OR REPLACE FUNCTION app.inventory_scope_on_hand(p_tenant_id uuid, p_product_id uuid, p_unit_id uuid)\nRETURNS numeric\nLANGUAGE sql\nSTABLE\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT coalesce(sum(p.quantity), 0)\n FROM public.plg_inventory_stock_positions p\n WHERE p.tenant_id = p_tenant_id\n AND p.product_id = p_product_id\n AND (p_unit_id IS NULL OR p.unit_id = p_unit_id);\n$$;\n\n-- The transaction-local setting holding a scope's balance before this\n-- transaction. md5 keeps the name a plain identifier of fixed length.\nCREATE OR REPLACE FUNCTION app.inventory_low_stock_key(p_tenant_id uuid, p_product_id uuid, p_unit_id uuid)\nRETURNS text\nLANGUAGE sql\nIMMUTABLE\nSET search_path TO ''\nAS $$\n SELECT 'fayz_low_stock.s' || md5(p_tenant_id::text || '|' || p_product_id::text || '|' || coalesce(p_unit_id::text, '*'));\n$$;\n\n-- BEFORE each position write: remember the balances as they were when this\n-- transaction first touched them. A key already set means \"already touched\".\nCREATE OR REPLACE FUNCTION app.trg_inventory_positions_low_stock_snapshot()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n r record;\n v_k text;\nBEGIN\n BEGIN\n FOR r IN\n SELECT DISTINCT x.tenant_id, x.product_id, x.unit_id\n FROM (SELECT NEW.tenant_id, NEW.product_id, NEW.unit_id WHERE TG_OP <> 'DELETE'\n UNION ALL\n SELECT OLD.tenant_id, OLD.product_id, OLD.unit_id WHERE TG_OP <> 'INSERT') AS x(tenant_id, product_id, unit_id)\n WHERE x.tenant_id IS NOT NULL AND x.product_id IS NOT NULL\n LOOP\n v_k := app.inventory_low_stock_key(r.tenant_id, r.product_id, NULL);\n IF coalesce(current_setting(v_k, true), '') = '' THEN\n PERFORM set_config(v_k, app.inventory_scope_on_hand(r.tenant_id, r.product_id, NULL)::text, true);\n END IF;\n IF r.unit_id IS NOT NULL THEN\n v_k := app.inventory_low_stock_key(r.tenant_id, r.product_id, r.unit_id);\n IF coalesce(current_setting(v_k, true), '') = '' THEN\n PERFORM set_config(v_k, app.inventory_scope_on_hand(r.tenant_id, r.product_id, r.unit_id)::text, true);\n END IF;\n END IF;\n END LOOP;\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'trg_inventory_positions_low_stock_snapshot: %', SQLERRM;\n END;\n RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END;\nEND;\n$$;\n\n-- At COMMIT, once per written row: every minimum of the row's product whose\n-- scope this transaction touched is compared against the recorded balance.\nCREATE OR REPLACE FUNCTION app.trg_inventory_positions_low_stock_check()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid;\n v_product uuid;\n s record;\n v_k text;\n v_before text;\n v_now numeric;\nBEGIN\n -- Everything, the reads of OLD/NEW included, inside the block that only\n -- warns: this runs at COMMIT, and an error here would undo the sale.\n BEGIN\n IF TG_OP = 'DELETE' THEN\n v_tenant := OLD.tenant_id; v_product := OLD.product_id;\n ELSE\n v_tenant := NEW.tenant_id; v_product := NEW.product_id;\n END IF;\n FOR s IN\n SELECT st.id, st.tenant_id, st.product_id, st.unit_id, st.min_quantity\n FROM public.plg_inventory_product_settings st\n WHERE st.tenant_id = v_tenant AND st.product_id = v_product AND st.min_quantity > 0\n LOOP\n v_k := app.inventory_low_stock_key(s.tenant_id, s.product_id, s.unit_id);\n v_before := current_setting(v_k, true);\n CONTINUE WHEN coalesce(v_before, '') = '';\n v_now := app.inventory_scope_on_hand(s.tenant_id, s.product_id, s.unit_id);\n IF v_now <= s.min_quantity AND v_before::numeric > s.min_quantity THEN\n PERFORM app.inventory_ring_the_bell_for_low_stock(\n s.id, s.tenant_id, s.product_id, s.unit_id, v_now, s.min_quantity);\n END IF;\n -- Forward: the next row of this commit compares against now.\n PERFORM set_config(v_k, v_now::text, true);\n END LOOP;\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'trg_inventory_positions_low_stock_check: %', SQLERRM;\n END;\n RETURN NULL;\nEND;\n$$;\n\nDROP TRIGGER IF EXISTS plg_inventory_stock_positions_low_stock_snapshot ON public.plg_inventory_stock_positions;\nCREATE TRIGGER plg_inventory_stock_positions_low_stock_snapshot\n BEFORE INSERT OR UPDATE OR DELETE ON public.plg_inventory_stock_positions\n FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_positions_low_stock_snapshot();\n\nDROP TRIGGER IF EXISTS plg_inventory_stock_positions_low_stock_check ON public.plg_inventory_stock_positions;\nCREATE CONSTRAINT TRIGGER plg_inventory_stock_positions_low_stock_check\n AFTER INSERT OR UPDATE OR DELETE ON public.plg_inventory_stock_positions\n DEFERRABLE INITIALLY DEFERRED\n FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_positions_low_stock_check();\n\n-- The minimum moved: low now and not low under the old minimum is a crossing.\n-- A new minimum on a product that has never been stocked does not ring.\nCREATE OR REPLACE FUNCTION app.trg_inventory_minimum_check_low_stock()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_on_hand numeric;\n v_old_min numeric := 0;\nBEGIN\n BEGIN\n IF TG_OP = 'UPDATE' THEN v_old_min := OLD.min_quantity; END IF;\n IF coalesce(NEW.min_quantity, 0) <= 0 THEN RETURN NULL; END IF;\n -- A minimum set on a product nobody has stocked yet is the product being\n -- registered, not stock running out: the bell would ring for every new\n -- item before its first receipt. Only a product with positions rings.\n IF TG_OP = 'INSERT' AND NOT EXISTS (\n SELECT 1 FROM public.plg_inventory_stock_positions p\n WHERE p.tenant_id = NEW.tenant_id AND p.product_id = NEW.product_id) THEN\n RETURN NULL;\n END IF;\n v_on_hand := app.inventory_scope_on_hand(NEW.tenant_id, NEW.product_id, NEW.unit_id);\n IF v_on_hand <= NEW.min_quantity\n AND NOT (coalesce(v_old_min, 0) > 0 AND v_on_hand <= v_old_min) THEN\n PERFORM app.inventory_ring_the_bell_for_low_stock(\n NEW.id, NEW.tenant_id, NEW.product_id, NEW.unit_id, v_on_hand, NEW.min_quantity);\n END IF;\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'trg_inventory_minimum_check_low_stock: %', SQLERRM;\n END;\n RETURN NULL;\nEND;\n$$;\n\nDROP TRIGGER IF EXISTS plg_inventory_product_settings_low_stock ON public.plg_inventory_product_settings;\nCREATE TRIGGER plg_inventory_product_settings_low_stock\n AFTER INSERT OR UPDATE OF min_quantity ON public.plg_inventory_product_settings\n FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_minimum_check_low_stock();\n\n-- Internal machinery: nobody calls these over PostgREST.\nREVOKE ALL ON FUNCTION app.inventory_quantity_text(numeric) FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.inventory_low_stock_key(uuid, uuid, uuid) FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.inventory_ring_the_bell_for_low_stock(uuid, uuid, uuid, uuid, numeric, numeric) FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.inventory_scope_on_hand(uuid, uuid, uuid) FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.trg_inventory_positions_low_stock_snapshot() FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.trg_inventory_positions_low_stock_check() FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.trg_inventory_minimum_check_low_stock() FROM PUBLIC;\n";
32
+ export declare const MIGRATION_031_THE_STOCK_SAYS_WHEN_IT_RUNS_LOW = "-- ============================================================================\n-- 031 \u2014 the stock says when it runs low\n--\n-- A product's minimum (plg_inventory_product_settings.min_quantity) only ever\n-- fed a count and a filter: \"estoque baixo\" was something you found by opening\n-- the Estoque screen or asking the assistant. Nothing told anyone.\n--\n-- Now the moment a product's balance CROSSES its minimum \u2014 from above it to at\n-- or below it \u2014 the database records the fact `inventory.stock_low` in the\n-- event log, with what anyone acting on it needs: the product, the scope, the\n-- balance and the minimum. That is all it does.\n--\n-- WHO is told and HOW is not decided here (ADR 0003, \"where logic lives\"):\n-- the Integration service reads these facts, rings the bell for whoever can\n-- read the stock \u2014 asking this database who that is \u2014 and is where WhatsApp\n-- or e-mail would be added. The detection stays here because only the\n-- transaction that moves the stock sees the balance before and after it.\n--\n-- The rules are the ones v_inventory_low_stock already states, so the alert and\n-- the screen never disagree:\n-- - a minimum applies when it is > 0;\n-- - \"low\" is on_hand <= min_quantity;\n-- - a settings row with unit_id set watches that unit's positions; the row\n-- with unit_id NULL watches the product across the whole tenant.\n--\n-- EDGE, not level. The fact is the crossing, not every movement while the\n-- product stays low \u2014 selling the tenth bottle of a product already at its\n-- minimum must not tell anyone ten times. It re-arms by itself: once a receipt\n-- lifts the balance above the minimum, the next fall is recorded again. The same crossing\n-- is checked when the minimum itself changes: raising it above what is on the\n-- shelf is as much \"this is now low\" as selling down to it.\n--\n-- PER TRANSACTION, not per statement. Every position write goes through\n-- app.inventory_apply_to_position, one position per call, so a transfer is two\n-- statements \u2014 the source goes down, then the destination goes up \u2014 and a\n-- recipe consumes ingredient by ingredient. Checked per statement, a transfer\n-- between two units of a product sitting near its minimum would dip the tenant\n-- total in between and report a crossing for a balance that never changed. So:\n-- - a BEFORE row trigger records, on the first touch of a transaction, the\n-- balance of each scope it touches (the product across the tenant, and the\n-- unit) in a transaction-local setting \u2014 that is the balance BEFORE;\n-- - a DEFERRED constraint trigger runs at COMMIT, when every statement of\n-- the transaction has landed, compares each minimum's scope against what\n-- was recorded, records a crossing, and moves the record forward so the\n-- other rows of the same commit do not record it again.\n\n-- A failure here never takes the stock write with it: everything runs inside\n-- exception blocks that only warn.\n-- ============================================================================\n\n-- Record one minimum that has just been crossed.\nCREATE OR REPLACE FUNCTION app.inventory_record_stock_low(\n p_setting_id uuid,\n p_tenant_id uuid,\n p_product_id uuid,\n p_unit_id uuid,\n p_on_hand numeric,\n p_min numeric\n)\nRETURNS void\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_name text;\n v_measure text;\n v_unit text;\nBEGIN\n BEGIN\n SELECT pl.name INTO v_name FROM app.inventory_product_label(p_product_id) pl(name, sku);\n SELECT nullif(btrim(coalesce(p.unit, '')), '') INTO v_measure\n FROM public.products p WHERE p.id = p_product_id;\n IF p_unit_id IS NOT NULL THEN\n SELECT u.name INTO v_unit FROM app.units u WHERE u.id = p_unit_id;\n END IF;\n\n PERFORM public.plg_emit_event(\n 'inventory.stock_low',\n jsonb_build_object(\n 'settingId', p_setting_id,\n 'productId', p_product_id,\n 'productName', v_name,\n 'measure', v_measure,\n 'unitId', p_unit_id,\n 'unitName', v_unit,\n 'onHand', p_on_hand,\n 'minQuantity', p_min),\n 'product', p_product_id::text, p_tenant_id);\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'inventory_record_stock_low: %', SQLERRM;\n END;\nEND;\n$$;\n\n-- The balance a minimum watches: the unit's positions, or the tenant's.\nCREATE OR REPLACE FUNCTION app.inventory_scope_on_hand(p_tenant_id uuid, p_product_id uuid, p_unit_id uuid)\nRETURNS numeric\nLANGUAGE sql\nSTABLE\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT coalesce(sum(p.quantity), 0)\n FROM public.plg_inventory_stock_positions p\n WHERE p.tenant_id = p_tenant_id\n AND p.product_id = p_product_id\n AND (p_unit_id IS NULL OR p.unit_id = p_unit_id);\n$$;\n\n-- The transaction-local setting holding a scope's balance before this\n-- transaction. md5 keeps the name a plain identifier of fixed length.\nCREATE OR REPLACE FUNCTION app.inventory_low_stock_key(p_tenant_id uuid, p_product_id uuid, p_unit_id uuid)\nRETURNS text\nLANGUAGE sql\nIMMUTABLE\nSET search_path TO ''\nAS $$\n SELECT 'fayz_low_stock.s' || md5(p_tenant_id::text || '|' || p_product_id::text || '|' || coalesce(p_unit_id::text, '*'));\n$$;\n\n-- BEFORE each position write: remember the balances as they were when this\n-- transaction first touched them. A key already set means \"already touched\".\nCREATE OR REPLACE FUNCTION app.trg_inventory_positions_low_stock_snapshot()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n r record;\n v_k text;\nBEGIN\n BEGIN\n FOR r IN\n SELECT DISTINCT x.tenant_id, x.product_id, x.unit_id\n FROM (SELECT NEW.tenant_id, NEW.product_id, NEW.unit_id WHERE TG_OP <> 'DELETE'\n UNION ALL\n SELECT OLD.tenant_id, OLD.product_id, OLD.unit_id WHERE TG_OP <> 'INSERT') AS x(tenant_id, product_id, unit_id)\n WHERE x.tenant_id IS NOT NULL AND x.product_id IS NOT NULL\n LOOP\n v_k := app.inventory_low_stock_key(r.tenant_id, r.product_id, NULL);\n IF coalesce(current_setting(v_k, true), '') = '' THEN\n PERFORM set_config(v_k, app.inventory_scope_on_hand(r.tenant_id, r.product_id, NULL)::text, true);\n END IF;\n IF r.unit_id IS NOT NULL THEN\n v_k := app.inventory_low_stock_key(r.tenant_id, r.product_id, r.unit_id);\n IF coalesce(current_setting(v_k, true), '') = '' THEN\n PERFORM set_config(v_k, app.inventory_scope_on_hand(r.tenant_id, r.product_id, r.unit_id)::text, true);\n END IF;\n END IF;\n END LOOP;\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'trg_inventory_positions_low_stock_snapshot: %', SQLERRM;\n END;\n RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END;\nEND;\n$$;\n\n-- At COMMIT, once per written row: every minimum of the row's product whose\n-- scope this transaction touched is compared against the recorded balance.\nCREATE OR REPLACE FUNCTION app.trg_inventory_positions_low_stock_check()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid;\n v_product uuid;\n s record;\n v_k text;\n v_before text;\n v_now numeric;\nBEGIN\n -- Everything, the reads of OLD/NEW included, inside the block that only\n -- warns: this runs at COMMIT, and an error here would undo the sale.\n BEGIN\n IF TG_OP = 'DELETE' THEN\n v_tenant := OLD.tenant_id; v_product := OLD.product_id;\n ELSE\n v_tenant := NEW.tenant_id; v_product := NEW.product_id;\n END IF;\n FOR s IN\n SELECT st.id, st.tenant_id, st.product_id, st.unit_id, st.min_quantity\n FROM public.plg_inventory_product_settings st\n WHERE st.tenant_id = v_tenant AND st.product_id = v_product AND st.min_quantity > 0\n LOOP\n v_k := app.inventory_low_stock_key(s.tenant_id, s.product_id, s.unit_id);\n v_before := current_setting(v_k, true);\n CONTINUE WHEN coalesce(v_before, '') = '';\n v_now := app.inventory_scope_on_hand(s.tenant_id, s.product_id, s.unit_id);\n IF v_now <= s.min_quantity AND v_before::numeric > s.min_quantity THEN\n PERFORM app.inventory_record_stock_low(\n s.id, s.tenant_id, s.product_id, s.unit_id, v_now, s.min_quantity);\n END IF;\n -- Forward: the next row of this commit compares against now.\n PERFORM set_config(v_k, v_now::text, true);\n END LOOP;\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'trg_inventory_positions_low_stock_check: %', SQLERRM;\n END;\n RETURN NULL;\nEND;\n$$;\n\nDROP TRIGGER IF EXISTS plg_inventory_stock_positions_low_stock_snapshot ON public.plg_inventory_stock_positions;\nCREATE TRIGGER plg_inventory_stock_positions_low_stock_snapshot\n BEFORE INSERT OR UPDATE OR DELETE ON public.plg_inventory_stock_positions\n FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_positions_low_stock_snapshot();\n\nDROP TRIGGER IF EXISTS plg_inventory_stock_positions_low_stock_check ON public.plg_inventory_stock_positions;\nCREATE CONSTRAINT TRIGGER plg_inventory_stock_positions_low_stock_check\n AFTER INSERT OR UPDATE OR DELETE ON public.plg_inventory_stock_positions\n DEFERRABLE INITIALLY DEFERRED\n FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_positions_low_stock_check();\n\n-- The minimum moved: low now and not low under the old minimum is a crossing.\n-- A new minimum on a product that has never been stocked is not a crossing.\nCREATE OR REPLACE FUNCTION app.trg_inventory_minimum_check_low_stock()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_on_hand numeric;\n v_old_min numeric := 0;\nBEGIN\n BEGIN\n IF TG_OP = 'UPDATE' THEN v_old_min := OLD.min_quantity; END IF;\n IF coalesce(NEW.min_quantity, 0) <= 0 THEN RETURN NULL; END IF;\n -- A minimum set on a product nobody has stocked yet is the product being\n -- registered, not stock running out: every new item would be reported\n -- low before its first receipt. Only a product with positions counts.\n IF TG_OP = 'INSERT' AND NOT EXISTS (\n SELECT 1 FROM public.plg_inventory_stock_positions p\n WHERE p.tenant_id = NEW.tenant_id AND p.product_id = NEW.product_id) THEN\n RETURN NULL;\n END IF;\n v_on_hand := app.inventory_scope_on_hand(NEW.tenant_id, NEW.product_id, NEW.unit_id);\n IF v_on_hand <= NEW.min_quantity\n AND NOT (coalesce(v_old_min, 0) > 0 AND v_on_hand <= v_old_min) THEN\n PERFORM app.inventory_record_stock_low(\n NEW.id, NEW.tenant_id, NEW.product_id, NEW.unit_id, v_on_hand, NEW.min_quantity);\n END IF;\n EXCEPTION WHEN OTHERS THEN\n RAISE WARNING 'trg_inventory_minimum_check_low_stock: %', SQLERRM;\n END;\n RETURN NULL;\nEND;\n$$;\n\nDROP TRIGGER IF EXISTS plg_inventory_product_settings_low_stock ON public.plg_inventory_product_settings;\nCREATE TRIGGER plg_inventory_product_settings_low_stock\n AFTER INSERT OR UPDATE OF min_quantity ON public.plg_inventory_product_settings\n FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_minimum_check_low_stock();\n\n-- Internal machinery: nobody calls these over PostgREST.\nREVOKE ALL ON FUNCTION app.inventory_low_stock_key(uuid, uuid, uuid) FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.inventory_record_stock_low(uuid, uuid, uuid, uuid, numeric, numeric) FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.inventory_scope_on_hand(uuid, uuid, uuid) FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.trg_inventory_positions_low_stock_snapshot() FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.trg_inventory_positions_low_stock_check() FROM PUBLIC;\nREVOKE ALL ON FUNCTION app.trg_inventory_minimum_check_low_stock() FROM PUBLIC;\n";
33
33
  export declare const MIGRATIONS: Array<{
34
34
  id: string;
35
35
  sql: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,yn7NAy+GlC,CAAA;AAED,eAAO,MAAM,sDAAsD,g0bA+QlE,CAAA;AAED,eAAO,MAAM,uCAAuC,ymbAsRnD,CAAA;AAED,eAAO,MAAM,+CAA+C,+zNAmK3D,CAAA;AAED,eAAO,MAAM,4CAA4C,szYAsLxD,CAAA;AAED,eAAO,MAAM,mDAAmD,g5JA0E/D,CAAA;AAED,eAAO,MAAM,4CAA4C,60nBAuUxD,CAAA;AAED,eAAO,MAAM,kCAAkC,oxSA+L9C,CAAA;AAED,eAAO,MAAM,qDAAqD,6yCA0BjE,CAAA;AAED,eAAO,MAAM,uDAAuD,s+XA4KnE,CAAA;AAED,eAAO,MAAM,0CAA0C,gm4BA4atD,CAAA;AAED,eAAO,MAAM,wCAAwC,y43BAsgBpD,CAAA;AAED,eAAO,MAAM,sEAAsE,o1ZAmRlF,CAAA;AAED,eAAO,MAAM,gDAAgD,2uXAmQ5D,CAAA;AAED,eAAO,MAAM,yCAAyC,umXA6PrD,CAAA;AAED,eAAO,MAAM,6CAA6C,w9WA2PzD,CAAA;AAED,eAAO,MAAM,4CAA4C,k8XAoQxD,CAAA;AAED,eAAO,MAAM,8CAA8C,okPA4K1D,CAAA;AAED,eAAO,MAAM,qCAAqC,umWAqPjD,CAAA;AAED,eAAO,MAAM,oCAAoC,skQA0LhD,CAAA;AAED,eAAO,MAAM,sDAAsD,6ufAwTlE,CAAA;AAED,eAAO,MAAM,sCAAsC,onPAsIlD,CAAA;AAED,eAAO,MAAM,+CAA+C,o9JAiF3D,CAAA;AAED,eAAO,MAAM,gDAAgD,myCAoB5D,CAAA;AAED,eAAO,MAAM,kDAAkD,otFAuD9D,CAAA;AAED,eAAO,MAAM,yCAAyC,25eAgTrD,CAAA;AAED,eAAO,MAAM,2CAA2C,2tOAwIvD,CAAA;AAED,eAAO,MAAM,mDAAmD,+5DAoC/D,CAAA;AAED,eAAO,MAAM,yCAAyC,4xEA0CrD,CAAA;AAED,eAAO,MAAM,gDAAgD,ikEAqC5D,CAAA;AAED,eAAO,MAAM,sCAAsC,02EA6ClD,CAAA;AAED,eAAO,MAAM,gDAAgD,6ubAsT5D,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAiCzD,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,yn7NAy+GlC,CAAA;AAED,eAAO,MAAM,sDAAsD,g0bA+QlE,CAAA;AAED,eAAO,MAAM,uCAAuC,ymbAsRnD,CAAA;AAED,eAAO,MAAM,+CAA+C,+zNAmK3D,CAAA;AAED,eAAO,MAAM,4CAA4C,szYAsLxD,CAAA;AAED,eAAO,MAAM,mDAAmD,g5JA0E/D,CAAA;AAED,eAAO,MAAM,4CAA4C,60nBAuUxD,CAAA;AAED,eAAO,MAAM,kCAAkC,oxSA+L9C,CAAA;AAED,eAAO,MAAM,qDAAqD,6yCA0BjE,CAAA;AAED,eAAO,MAAM,uDAAuD,s+XA4KnE,CAAA;AAED,eAAO,MAAM,0CAA0C,gm4BA4atD,CAAA;AAED,eAAO,MAAM,wCAAwC,y43BAsgBpD,CAAA;AAED,eAAO,MAAM,sEAAsE,o1ZAmRlF,CAAA;AAED,eAAO,MAAM,gDAAgD,2uXAmQ5D,CAAA;AAED,eAAO,MAAM,yCAAyC,umXA6PrD,CAAA;AAED,eAAO,MAAM,6CAA6C,w9WA2PzD,CAAA;AAED,eAAO,MAAM,4CAA4C,k8XAoQxD,CAAA;AAED,eAAO,MAAM,8CAA8C,okPA4K1D,CAAA;AAED,eAAO,MAAM,qCAAqC,umWAqPjD,CAAA;AAED,eAAO,MAAM,oCAAoC,skQA0LhD,CAAA;AAED,eAAO,MAAM,sDAAsD,6ufAwTlE,CAAA;AAED,eAAO,MAAM,sCAAsC,onPAsIlD,CAAA;AAED,eAAO,MAAM,+CAA+C,o9JAiF3D,CAAA;AAED,eAAO,MAAM,gDAAgD,myCAoB5D,CAAA;AAED,eAAO,MAAM,kDAAkD,otFAuD9D,CAAA;AAED,eAAO,MAAM,yCAAyC,25eAgTrD,CAAA;AAED,eAAO,MAAM,2CAA2C,2tOAwIvD,CAAA;AAED,eAAO,MAAM,mDAAmD,+5DAoC/D,CAAA;AAED,eAAO,MAAM,yCAAyC,4xEA0CrD,CAAA;AAED,eAAO,MAAM,gDAAgD,ikEAqC5D,CAAA;AAED,eAAO,MAAM,sCAAsC,02EA6ClD,CAAA;AAED,eAAO,MAAM,6CAA6C,ghXAyQzD,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAiCzD,CAAA"}
@@ -30,9 +30,9 @@ var MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM = "";
30
30
  var MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT = "";
31
31
  var MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT = "";
32
32
  var MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED = "";
33
- var MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW = "";
33
+ var MIGRATION_031_THE_STOCK_SAYS_WHEN_IT_RUNS_LOW = "";
34
34
  var MIGRATIONS = [];
35
35
 
36
- export { MIGRATIONS, MIGRATION_000_BASELINE, MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED, MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE, MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER, MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA, MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO, MIGRATION_006_A_PRODUCAO_TEM_ORDEM_E_ESTACAO, MIGRATION_007_A_PRODUCAO_ATRAVESSA, MIGRATION_008_O_GRUPO_DA_RECEITA_TAMBEM_E_DE_UMA_CASA, MIGRATION_009_A_DIRECAO_DO_MOVIMENTO_NAO_ESTA_NO_NUMERO, MIGRATION_010_A_ENTRADA_DE_NOTAS_ATRAVESSA, MIGRATION_011_O_CONSUMO_TEM_DUAS_ORIGENS, MIGRATION_012_O_CONSUMO_CLINICO_NAO_PRECISA_DA_TABELA_DE_ITENS_INTEIRA, MIGRATION_013_A_FILA_DE_AUDITORIA_TEM_UMA_JANELA, MIGRATION_014_A_SESSAO_SE_BUSCA_UMA_A_UMA, MIGRATION_015_LATERAL_SEM_CERCA_NAO_E_LATERAL, MIGRATION_016_A_DATA_DO_CONSUMO_E_UMA_COLUNA, MIGRATION_017_APROVAR_E_BAIXAR_SAO_DOIS_GESTOS, MIGRATION_018_O_NOME_NAO_SAI_DA_VISTA, MIGRATION_019_SEM_SALDO_NAO_SE_BAIXA, MIGRATION_020_O_PRODUTO_E_VARIAS_COISAS_AO_MESMO_TEMPO, MIGRATION_021_O_RAZAO_DIZ_DE_ONDE_VEIO, MIGRATION_022_RECEBER_ESTOQUE_VOLTA_A_FUNCIONAR, MIGRATION_023_A_RECEITA_SE_ESCREVE_COM_PERMISSAO, MIGRATION_024_A_CATEGORIA_DO_PRODUTO_E_DO_CATALOGO, MIGRATION_025_A_FICHA_TECNICA_POR_ESTACAO, MIGRATION_026_STOCK_COST_KNOWS_ITS_CURRENCY, MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM, MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT, MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT, MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED, MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW };
36
+ export { MIGRATIONS, MIGRATION_000_BASELINE, MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED, MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE, MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER, MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA, MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO, MIGRATION_006_A_PRODUCAO_TEM_ORDEM_E_ESTACAO, MIGRATION_007_A_PRODUCAO_ATRAVESSA, MIGRATION_008_O_GRUPO_DA_RECEITA_TAMBEM_E_DE_UMA_CASA, MIGRATION_009_A_DIRECAO_DO_MOVIMENTO_NAO_ESTA_NO_NUMERO, MIGRATION_010_A_ENTRADA_DE_NOTAS_ATRAVESSA, MIGRATION_011_O_CONSUMO_TEM_DUAS_ORIGENS, MIGRATION_012_O_CONSUMO_CLINICO_NAO_PRECISA_DA_TABELA_DE_ITENS_INTEIRA, MIGRATION_013_A_FILA_DE_AUDITORIA_TEM_UMA_JANELA, MIGRATION_014_A_SESSAO_SE_BUSCA_UMA_A_UMA, MIGRATION_015_LATERAL_SEM_CERCA_NAO_E_LATERAL, MIGRATION_016_A_DATA_DO_CONSUMO_E_UMA_COLUNA, MIGRATION_017_APROVAR_E_BAIXAR_SAO_DOIS_GESTOS, MIGRATION_018_O_NOME_NAO_SAI_DA_VISTA, MIGRATION_019_SEM_SALDO_NAO_SE_BAIXA, MIGRATION_020_O_PRODUTO_E_VARIAS_COISAS_AO_MESMO_TEMPO, MIGRATION_021_O_RAZAO_DIZ_DE_ONDE_VEIO, MIGRATION_022_RECEBER_ESTOQUE_VOLTA_A_FUNCIONAR, MIGRATION_023_A_RECEITA_SE_ESCREVE_COM_PERMISSAO, MIGRATION_024_A_CATEGORIA_DO_PRODUTO_E_DO_CATALOGO, MIGRATION_025_A_FICHA_TECNICA_POR_ESTACAO, MIGRATION_026_STOCK_COST_KNOWS_ITS_CURRENCY, MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM, MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT, MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT, MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED, MIGRATION_031_THE_STOCK_SAYS_WHEN_IT_RUNS_LOW };
37
37
  //# sourceMappingURL=main.browser.js.map
38
38
  //# sourceMappingURL=main.browser.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../node_modules/.cache/fayz-migration-stubs/main.ts"],"names":[],"mappings":";AAEO,IAAM,sBAAA,GAAyB;AAC/B,IAAM,sDAAA,GAAyD;AAC/D,IAAM,uCAAA,GAA0C;AAChD,IAAM,+CAAA,GAAkD;AACxD,IAAM,4CAAA,GAA+C;AACrD,IAAM,mDAAA,GAAsD;AAC5D,IAAM,4CAAA,GAA+C;AACrD,IAAM,kCAAA,GAAqC;AAC3C,IAAM,qDAAA,GAAwD;AAC9D,IAAM,uDAAA,GAA0D;AAChE,IAAM,0CAAA,GAA6C;AACnD,IAAM,wCAAA,GAA2C;AACjD,IAAM,sEAAA,GAAyE;AAC/E,IAAM,gDAAA,GAAmD;AACzD,IAAM,yCAAA,GAA4C;AAClD,IAAM,6CAAA,GAAgD;AACtD,IAAM,4CAAA,GAA+C;AACrD,IAAM,8CAAA,GAAiD;AACvD,IAAM,qCAAA,GAAwC;AAC9C,IAAM,oCAAA,GAAuC;AAC7C,IAAM,sDAAA,GAAyD;AAC/D,IAAM,sCAAA,GAAyC;AAC/C,IAAM,+CAAA,GAAkD;AACxD,IAAM,gDAAA,GAAmD;AACzD,IAAM,kDAAA,GAAqD;AAC3D,IAAM,yCAAA,GAA4C;AAClD,IAAM,2CAAA,GAA8C;AACpD,IAAM,mDAAA,GAAsD;AAC5D,IAAM,yCAAA,GAA4C;AAClD,IAAM,gDAAA,GAAmD;AACzD,IAAM,sCAAA,GAAyC;AAC/C,IAAM,gDAAA,GAAmD;AACzD,IAAM,aAAiD","file":"main.browser.js","sourcesContent":["// GENERATED by scripts/tsup-migrations.mjs — the browser build of a\n// migrations barrel: the same names, no SQL. See that script for why.\nexport const MIGRATION_000_BASELINE = ''\nexport const MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED = ''\nexport const MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE = ''\nexport const MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER = ''\nexport const MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA = ''\nexport const MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO = ''\nexport const MIGRATION_006_A_PRODUCAO_TEM_ORDEM_E_ESTACAO = ''\nexport const MIGRATION_007_A_PRODUCAO_ATRAVESSA = ''\nexport const MIGRATION_008_O_GRUPO_DA_RECEITA_TAMBEM_E_DE_UMA_CASA = ''\nexport const MIGRATION_009_A_DIRECAO_DO_MOVIMENTO_NAO_ESTA_NO_NUMERO = ''\nexport const MIGRATION_010_A_ENTRADA_DE_NOTAS_ATRAVESSA = ''\nexport const MIGRATION_011_O_CONSUMO_TEM_DUAS_ORIGENS = ''\nexport const MIGRATION_012_O_CONSUMO_CLINICO_NAO_PRECISA_DA_TABELA_DE_ITENS_INTEIRA = ''\nexport const MIGRATION_013_A_FILA_DE_AUDITORIA_TEM_UMA_JANELA = ''\nexport const MIGRATION_014_A_SESSAO_SE_BUSCA_UMA_A_UMA = ''\nexport const MIGRATION_015_LATERAL_SEM_CERCA_NAO_E_LATERAL = ''\nexport const MIGRATION_016_A_DATA_DO_CONSUMO_E_UMA_COLUNA = ''\nexport const MIGRATION_017_APROVAR_E_BAIXAR_SAO_DOIS_GESTOS = ''\nexport const MIGRATION_018_O_NOME_NAO_SAI_DA_VISTA = ''\nexport const MIGRATION_019_SEM_SALDO_NAO_SE_BAIXA = ''\nexport const MIGRATION_020_O_PRODUTO_E_VARIAS_COISAS_AO_MESMO_TEMPO = ''\nexport const MIGRATION_021_O_RAZAO_DIZ_DE_ONDE_VEIO = ''\nexport const MIGRATION_022_RECEBER_ESTOQUE_VOLTA_A_FUNCIONAR = ''\nexport const MIGRATION_023_A_RECEITA_SE_ESCREVE_COM_PERMISSAO = ''\nexport const MIGRATION_024_A_CATEGORIA_DO_PRODUTO_E_DO_CATALOGO = ''\nexport const MIGRATION_025_A_FICHA_TECNICA_POR_ESTACAO = ''\nexport const MIGRATION_026_STOCK_COST_KNOWS_ITS_CURRENCY = ''\nexport const MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM = ''\nexport const MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT = ''\nexport const MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT = ''\nexport const MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED = ''\nexport const MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW = ''\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = []\n"]}
1
+ {"version":3,"sources":["../../node_modules/.cache/fayz-migration-stubs/main.ts"],"names":[],"mappings":";AAEO,IAAM,sBAAA,GAAyB;AAC/B,IAAM,sDAAA,GAAyD;AAC/D,IAAM,uCAAA,GAA0C;AAChD,IAAM,+CAAA,GAAkD;AACxD,IAAM,4CAAA,GAA+C;AACrD,IAAM,mDAAA,GAAsD;AAC5D,IAAM,4CAAA,GAA+C;AACrD,IAAM,kCAAA,GAAqC;AAC3C,IAAM,qDAAA,GAAwD;AAC9D,IAAM,uDAAA,GAA0D;AAChE,IAAM,0CAAA,GAA6C;AACnD,IAAM,wCAAA,GAA2C;AACjD,IAAM,sEAAA,GAAyE;AAC/E,IAAM,gDAAA,GAAmD;AACzD,IAAM,yCAAA,GAA4C;AAClD,IAAM,6CAAA,GAAgD;AACtD,IAAM,4CAAA,GAA+C;AACrD,IAAM,8CAAA,GAAiD;AACvD,IAAM,qCAAA,GAAwC;AAC9C,IAAM,oCAAA,GAAuC;AAC7C,IAAM,sDAAA,GAAyD;AAC/D,IAAM,sCAAA,GAAyC;AAC/C,IAAM,+CAAA,GAAkD;AACxD,IAAM,gDAAA,GAAmD;AACzD,IAAM,kDAAA,GAAqD;AAC3D,IAAM,yCAAA,GAA4C;AAClD,IAAM,2CAAA,GAA8C;AACpD,IAAM,mDAAA,GAAsD;AAC5D,IAAM,yCAAA,GAA4C;AAClD,IAAM,gDAAA,GAAmD;AACzD,IAAM,sCAAA,GAAyC;AAC/C,IAAM,6CAAA,GAAgD;AACtD,IAAM,aAAiD","file":"main.browser.js","sourcesContent":["// GENERATED by scripts/tsup-migrations.mjs — the browser build of a\n// migrations barrel: the same names, no SQL. See that script for why.\nexport const MIGRATION_000_BASELINE = ''\nexport const MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED = ''\nexport const MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE = ''\nexport const MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER = ''\nexport const MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA = ''\nexport const MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO = ''\nexport const MIGRATION_006_A_PRODUCAO_TEM_ORDEM_E_ESTACAO = ''\nexport const MIGRATION_007_A_PRODUCAO_ATRAVESSA = ''\nexport const MIGRATION_008_O_GRUPO_DA_RECEITA_TAMBEM_E_DE_UMA_CASA = ''\nexport const MIGRATION_009_A_DIRECAO_DO_MOVIMENTO_NAO_ESTA_NO_NUMERO = ''\nexport const MIGRATION_010_A_ENTRADA_DE_NOTAS_ATRAVESSA = ''\nexport const MIGRATION_011_O_CONSUMO_TEM_DUAS_ORIGENS = ''\nexport const MIGRATION_012_O_CONSUMO_CLINICO_NAO_PRECISA_DA_TABELA_DE_ITENS_INTEIRA = ''\nexport const MIGRATION_013_A_FILA_DE_AUDITORIA_TEM_UMA_JANELA = ''\nexport const MIGRATION_014_A_SESSAO_SE_BUSCA_UMA_A_UMA = ''\nexport const MIGRATION_015_LATERAL_SEM_CERCA_NAO_E_LATERAL = ''\nexport const MIGRATION_016_A_DATA_DO_CONSUMO_E_UMA_COLUNA = ''\nexport const MIGRATION_017_APROVAR_E_BAIXAR_SAO_DOIS_GESTOS = ''\nexport const MIGRATION_018_O_NOME_NAO_SAI_DA_VISTA = ''\nexport const MIGRATION_019_SEM_SALDO_NAO_SE_BAIXA = ''\nexport const MIGRATION_020_O_PRODUTO_E_VARIAS_COISAS_AO_MESMO_TEMPO = ''\nexport const MIGRATION_021_O_RAZAO_DIZ_DE_ONDE_VEIO = ''\nexport const MIGRATION_022_RECEBER_ESTOQUE_VOLTA_A_FUNCIONAR = ''\nexport const MIGRATION_023_A_RECEITA_SE_ESCREVE_COM_PERMISSAO = ''\nexport const MIGRATION_024_A_CATEGORIA_DO_PRODUTO_E_DO_CATALOGO = ''\nexport const MIGRATION_025_A_FICHA_TECNICA_POR_ESTACAO = ''\nexport const MIGRATION_026_STOCK_COST_KNOWS_ITS_CURRENCY = ''\nexport const MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM = ''\nexport const MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT = ''\nexport const MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT = ''\nexport const MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED = ''\nexport const MIGRATION_031_THE_STOCK_SAYS_WHEN_IT_RUNS_LOW = ''\nexport const MIGRATIONS: Array<{ id: string; sql: string }> = []\n"]}
@@ -9324,18 +9324,23 @@ SELECT i.tenant_id,
9324
9324
  AND s.unit_id IS NULL)
9325
9325
  ON CONFLICT (tenant_id, product_id, unit_id) DO NOTHING;
9326
9326
  `;
9327
- var MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW = `-- ============================================================================
9328
- -- 031 \u2014 the bell rings when stock runs low
9327
+ var MIGRATION_031_THE_STOCK_SAYS_WHEN_IT_RUNS_LOW = `-- ============================================================================
9328
+ -- 031 \u2014 the stock says when it runs low
9329
9329
  --
9330
9330
  -- A product's minimum (plg_inventory_product_settings.min_quantity) only ever
9331
9331
  -- fed a count and a filter: "estoque baixo" was something you found by opening
9332
9332
  -- the Estoque screen or asking the assistant. Nothing told anyone.
9333
9333
  --
9334
9334
  -- Now the moment a product's balance CROSSES its minimum \u2014 from above it to at
9335
- -- or below it \u2014 every active member who can read the stock gets a line in the
9336
- -- bell (public.notifications, the table the shell's bell reads), the bell is
9337
- -- poked over realtime, and \`inventory.stock_low\` is recorded in the event log
9338
- -- so an automation can carry it further (WhatsApp, e-mail).
9335
+ -- or below it \u2014 the database records the fact \`inventory.stock_low\` in the
9336
+ -- event log, with what anyone acting on it needs: the product, the scope, the
9337
+ -- balance and the minimum. That is all it does.
9338
+ --
9339
+ -- WHO is told and HOW is not decided here (ADR 0003, "where logic lives"):
9340
+ -- the Integration service reads these facts, rings the bell for whoever can
9341
+ -- read the stock \u2014 asking this database who that is \u2014 and is where WhatsApp
9342
+ -- or e-mail would be added. The detection stays here because only the
9343
+ -- transaction that moves the stock sees the balance before and after it.
9339
9344
  --
9340
9345
  -- The rules are the ones v_inventory_low_stock already states, so the alert and
9341
9346
  -- the screen never disagree:
@@ -9344,50 +9349,33 @@ var MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW = `-- =====================
9344
9349
  -- - a settings row with unit_id set watches that unit's positions; the row
9345
9350
  -- with unit_id NULL watches the product across the whole tenant.
9346
9351
  --
9347
- -- EDGE, not level. The alert fires on the crossing, not on every movement while
9348
- -- the product stays low \u2014 selling the tenth bottle of a product already at its
9349
- -- minimum must not ring ten times. It re-arms by itself: once a receipt lifts
9350
- -- the balance above the minimum, the next fall rings again. The same crossing
9352
+ -- EDGE, not level. The fact is the crossing, not every movement while the
9353
+ -- product stays low \u2014 selling the tenth bottle of a product already at its
9354
+ -- minimum must not tell anyone ten times. It re-arms by itself: once a receipt
9355
+ -- lifts the balance above the minimum, the next fall is recorded again. The same crossing
9351
9356
  -- is checked when the minimum itself changes: raising it above what is on the
9352
9357
  -- shelf is as much "this is now low" as selling down to it.
9353
9358
  --
9354
- -- One notification per user per minimum, re-surfaced. The subject is the
9355
- -- settings row (product + scope), so the unique index of packages/db 130 on
9356
- -- (user_id, subject_type, subject_id, type) keeps one line per product per
9357
- -- person; a new crossing marks it unread and moves it to the top instead of
9358
- -- stacking copies.
9359
- --
9360
9359
  -- PER TRANSACTION, not per statement. Every position write goes through
9361
9360
  -- app.inventory_apply_to_position, one position per call, so a transfer is two
9362
9361
  -- statements \u2014 the source goes down, then the destination goes up \u2014 and a
9363
9362
  -- recipe consumes ingredient by ingredient. Checked per statement, a transfer
9364
9363
  -- between two units of a product sitting near its minimum would dip the tenant
9365
- -- total in between and ring for a balance that never changed. So:
9364
+ -- total in between and report a crossing for a balance that never changed. So:
9366
9365
  -- - a BEFORE row trigger records, on the first touch of a transaction, the
9367
9366
  -- balance of each scope it touches (the product across the tenant, and the
9368
9367
  -- unit) in a transaction-local setting \u2014 that is the balance BEFORE;
9369
9368
  -- - a DEFERRED constraint trigger runs at COMMIT, when every statement of
9370
9369
  -- the transaction has landed, compares each minimum's scope against what
9371
- -- was recorded, rings on a crossing, and moves the record forward so the
9372
- -- other rows of the same commit do not ring again.
9370
+ -- was recorded, records a crossing, and moves the record forward so the
9371
+ -- other rows of the same commit do not record it again.
9373
9372
 
9374
- -- A failure here never takes the stock write with it: everything that rings
9375
- -- runs inside an exception block that only warns, as the order bell does.
9373
+ -- A failure here never takes the stock write with it: everything runs inside
9374
+ -- exception blocks that only warn.
9376
9375
  -- ============================================================================
9377
9376
 
9378
- -- The text of a quantity: "2", "2,5", "0,125" \u2014 no trailing zeros, Brazilian
9379
- -- decimal comma. Helper of this file only.
9380
- CREATE OR REPLACE FUNCTION app.inventory_quantity_text(p_value numeric)
9381
- RETURNS text
9382
- LANGUAGE sql
9383
- IMMUTABLE
9384
- SET search_path TO ''
9385
- AS $$
9386
- SELECT replace(trim_scale(coalesce(p_value, 0))::text, '.', ',');
9387
- $$;
9388
-
9389
- -- Ring for one minimum that has just been crossed.
9390
- CREATE OR REPLACE FUNCTION app.inventory_ring_the_bell_for_low_stock(
9377
+ -- Record one minimum that has just been crossed.
9378
+ CREATE OR REPLACE FUNCTION app.inventory_record_stock_low(
9391
9379
  p_setting_id uuid,
9392
9380
  p_tenant_id uuid,
9393
9381
  p_product_id uuid,
@@ -9404,8 +9392,6 @@ DECLARE
9404
9392
  v_name text;
9405
9393
  v_measure text;
9406
9394
  v_unit text;
9407
- v_title text;
9408
- v_body text;
9409
9395
  BEGIN
9410
9396
  BEGIN
9411
9397
  SELECT pl.name INTO v_name FROM app.inventory_product_label(p_product_id) pl(name, sku);
@@ -9415,50 +9401,20 @@ BEGIN
9415
9401
  SELECT u.name INTO v_unit FROM app.units u WHERE u.id = p_unit_id;
9416
9402
  END IF;
9417
9403
 
9418
- v_title := 'Estoque baixo: ' || coalesce(v_name, 'produto');
9419
- v_body := concat_ws(' \xB7 ',
9420
- 'Saldo ' || app.inventory_quantity_text(p_on_hand) || coalesce(' ' || v_measure, ''),
9421
- 'm\xEDnimo ' || app.inventory_quantity_text(p_min) || coalesce(' ' || v_measure, ''),
9422
- v_unit);
9423
-
9424
- -- Whoever can see the stock where it ran low. RLS on notifications shows a
9425
- -- row only to its own user_id, so it is one row per person.
9426
- INSERT INTO public.notifications (tenant_id, user_id, title, body, type, link, subject_type, subject_id)
9427
- SELECT p_tenant_id, m.user_id, v_title, v_body, 'inventory.stock_low',
9428
- '/inventory/products/detail/' || p_product_id::text,
9429
- 'inventory_minimum', p_setting_id
9430
- FROM (SELECT DISTINCT mm.user_id FROM app.memberships mm
9431
- WHERE mm.tenant_id = p_tenant_id AND mm.active AND mm.user_id IS NOT NULL) m
9432
- WHERE EXISTS (
9433
- SELECT 1 FROM app.role_permissions rp
9434
- WHERE rp.tenant_id = p_tenant_id
9435
- AND rp.role_id = app.effective_role_id(p_tenant_id, m.user_id, p_unit_id)
9436
- AND rp.permission IN ('inventory.read', 'inventory.manage'))
9437
- ON CONFLICT (user_id, subject_type, subject_id, type) WHERE subject_id IS NOT NULL
9438
- DO UPDATE SET read = false, created_at = now(), title = EXCLUDED.title, body = EXCLUDED.body;
9439
-
9440
- IF to_regproc('realtime.send') IS NOT NULL THEN
9441
- PERFORM realtime.send(
9442
- jsonb_build_object('type', 'inventory.stock_low', 'subjectId', p_setting_id),
9443
- 'notification',
9444
- -- Contract with \`subscribeBell()\` in packages/admin/src/lib/bell.ts.
9445
- 'avisos:' || p_tenant_id::text,
9446
- false);
9447
- END IF;
9448
-
9449
9404
  PERFORM public.plg_emit_event(
9450
9405
  'inventory.stock_low',
9451
9406
  jsonb_build_object(
9407
+ 'settingId', p_setting_id,
9452
9408
  'productId', p_product_id,
9453
9409
  'productName', v_name,
9410
+ 'measure', v_measure,
9454
9411
  'unitId', p_unit_id,
9455
9412
  'unitName', v_unit,
9456
9413
  'onHand', p_on_hand,
9457
- 'minQuantity', p_min,
9458
- 'measure', v_measure),
9414
+ 'minQuantity', p_min),
9459
9415
  'product', p_product_id::text, p_tenant_id);
9460
9416
  EXCEPTION WHEN OTHERS THEN
9461
- RAISE WARNING 'inventory_ring_the_bell_for_low_stock: %', SQLERRM;
9417
+ RAISE WARNING 'inventory_record_stock_low: %', SQLERRM;
9462
9418
  END;
9463
9419
  END;
9464
9420
  $$;
@@ -9561,7 +9517,7 @@ BEGIN
9561
9517
  CONTINUE WHEN coalesce(v_before, '') = '';
9562
9518
  v_now := app.inventory_scope_on_hand(s.tenant_id, s.product_id, s.unit_id);
9563
9519
  IF v_now <= s.min_quantity AND v_before::numeric > s.min_quantity THEN
9564
- PERFORM app.inventory_ring_the_bell_for_low_stock(
9520
+ PERFORM app.inventory_record_stock_low(
9565
9521
  s.id, s.tenant_id, s.product_id, s.unit_id, v_now, s.min_quantity);
9566
9522
  END IF;
9567
9523
  -- Forward: the next row of this commit compares against now.
@@ -9586,7 +9542,7 @@ CREATE CONSTRAINT TRIGGER plg_inventory_stock_positions_low_stock_check
9586
9542
  FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_positions_low_stock_check();
9587
9543
 
9588
9544
  -- The minimum moved: low now and not low under the old minimum is a crossing.
9589
- -- A new minimum on a product that has never been stocked does not ring.
9545
+ -- A new minimum on a product that has never been stocked is not a crossing.
9590
9546
  CREATE OR REPLACE FUNCTION app.trg_inventory_minimum_check_low_stock()
9591
9547
  RETURNS trigger
9592
9548
  LANGUAGE plpgsql
@@ -9601,8 +9557,8 @@ BEGIN
9601
9557
  IF TG_OP = 'UPDATE' THEN v_old_min := OLD.min_quantity; END IF;
9602
9558
  IF coalesce(NEW.min_quantity, 0) <= 0 THEN RETURN NULL; END IF;
9603
9559
  -- A minimum set on a product nobody has stocked yet is the product being
9604
- -- registered, not stock running out: the bell would ring for every new
9605
- -- item before its first receipt. Only a product with positions rings.
9560
+ -- registered, not stock running out: every new item would be reported
9561
+ -- low before its first receipt. Only a product with positions counts.
9606
9562
  IF TG_OP = 'INSERT' AND NOT EXISTS (
9607
9563
  SELECT 1 FROM public.plg_inventory_stock_positions p
9608
9564
  WHERE p.tenant_id = NEW.tenant_id AND p.product_id = NEW.product_id) THEN
@@ -9611,7 +9567,7 @@ BEGIN
9611
9567
  v_on_hand := app.inventory_scope_on_hand(NEW.tenant_id, NEW.product_id, NEW.unit_id);
9612
9568
  IF v_on_hand <= NEW.min_quantity
9613
9569
  AND NOT (coalesce(v_old_min, 0) > 0 AND v_on_hand <= v_old_min) THEN
9614
- PERFORM app.inventory_ring_the_bell_for_low_stock(
9570
+ PERFORM app.inventory_record_stock_low(
9615
9571
  NEW.id, NEW.tenant_id, NEW.product_id, NEW.unit_id, v_on_hand, NEW.min_quantity);
9616
9572
  END IF;
9617
9573
  EXCEPTION WHEN OTHERS THEN
@@ -9627,9 +9583,8 @@ CREATE TRIGGER plg_inventory_product_settings_low_stock
9627
9583
  FOR EACH ROW EXECUTE FUNCTION app.trg_inventory_minimum_check_low_stock();
9628
9584
 
9629
9585
  -- Internal machinery: nobody calls these over PostgREST.
9630
- REVOKE ALL ON FUNCTION app.inventory_quantity_text(numeric) FROM PUBLIC;
9631
9586
  REVOKE ALL ON FUNCTION app.inventory_low_stock_key(uuid, uuid, uuid) FROM PUBLIC;
9632
- REVOKE ALL ON FUNCTION app.inventory_ring_the_bell_for_low_stock(uuid, uuid, uuid, uuid, numeric, numeric) FROM PUBLIC;
9587
+ REVOKE ALL ON FUNCTION app.inventory_record_stock_low(uuid, uuid, uuid, uuid, numeric, numeric) FROM PUBLIC;
9633
9588
  REVOKE ALL ON FUNCTION app.inventory_scope_on_hand(uuid, uuid, uuid) FROM PUBLIC;
9634
9589
  REVOKE ALL ON FUNCTION app.trg_inventory_positions_low_stock_snapshot() FROM PUBLIC;
9635
9590
  REVOKE ALL ON FUNCTION app.trg_inventory_positions_low_stock_check() FROM PUBLIC;
@@ -9667,9 +9622,9 @@ var MIGRATIONS = [
9667
9622
  { id: "028_a_receipt_keeps_the_variant", sql: MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT },
9668
9623
  { id: "029_a_reversal_undoes_the_same_variant", sql: MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT },
9669
9624
  { id: "030_the_minimum_can_be_saved", sql: MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED },
9670
- { id: "031_the_bell_rings_when_stock_runs_low", sql: MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW }
9625
+ { id: "031_the_stock_says_when_it_runs_low", sql: MIGRATION_031_THE_STOCK_SAYS_WHEN_IT_RUNS_LOW }
9671
9626
  ];
9672
9627
 
9673
- export { MIGRATIONS, MIGRATION_000_BASELINE, MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED, MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE, MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER, MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA, MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO, MIGRATION_006_A_PRODUCAO_TEM_ORDEM_E_ESTACAO, MIGRATION_007_A_PRODUCAO_ATRAVESSA, MIGRATION_008_O_GRUPO_DA_RECEITA_TAMBEM_E_DE_UMA_CASA, MIGRATION_009_A_DIRECAO_DO_MOVIMENTO_NAO_ESTA_NO_NUMERO, MIGRATION_010_A_ENTRADA_DE_NOTAS_ATRAVESSA, MIGRATION_011_O_CONSUMO_TEM_DUAS_ORIGENS, MIGRATION_012_O_CONSUMO_CLINICO_NAO_PRECISA_DA_TABELA_DE_ITENS_INTEIRA, MIGRATION_013_A_FILA_DE_AUDITORIA_TEM_UMA_JANELA, MIGRATION_014_A_SESSAO_SE_BUSCA_UMA_A_UMA, MIGRATION_015_LATERAL_SEM_CERCA_NAO_E_LATERAL, MIGRATION_016_A_DATA_DO_CONSUMO_E_UMA_COLUNA, MIGRATION_017_APROVAR_E_BAIXAR_SAO_DOIS_GESTOS, MIGRATION_018_O_NOME_NAO_SAI_DA_VISTA, MIGRATION_019_SEM_SALDO_NAO_SE_BAIXA, MIGRATION_020_O_PRODUTO_E_VARIAS_COISAS_AO_MESMO_TEMPO, MIGRATION_021_O_RAZAO_DIZ_DE_ONDE_VEIO, MIGRATION_022_RECEBER_ESTOQUE_VOLTA_A_FUNCIONAR, MIGRATION_023_A_RECEITA_SE_ESCREVE_COM_PERMISSAO, MIGRATION_024_A_CATEGORIA_DO_PRODUTO_E_DO_CATALOGO, MIGRATION_025_A_FICHA_TECNICA_POR_ESTACAO, MIGRATION_026_STOCK_COST_KNOWS_ITS_CURRENCY, MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM, MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT, MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT, MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED, MIGRATION_031_THE_BELL_RINGS_WHEN_STOCK_RUNS_LOW };
9628
+ export { MIGRATIONS, MIGRATION_000_BASELINE, MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED, MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE, MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER, MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA, MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO, MIGRATION_006_A_PRODUCAO_TEM_ORDEM_E_ESTACAO, MIGRATION_007_A_PRODUCAO_ATRAVESSA, MIGRATION_008_O_GRUPO_DA_RECEITA_TAMBEM_E_DE_UMA_CASA, MIGRATION_009_A_DIRECAO_DO_MOVIMENTO_NAO_ESTA_NO_NUMERO, MIGRATION_010_A_ENTRADA_DE_NOTAS_ATRAVESSA, MIGRATION_011_O_CONSUMO_TEM_DUAS_ORIGENS, MIGRATION_012_O_CONSUMO_CLINICO_NAO_PRECISA_DA_TABELA_DE_ITENS_INTEIRA, MIGRATION_013_A_FILA_DE_AUDITORIA_TEM_UMA_JANELA, MIGRATION_014_A_SESSAO_SE_BUSCA_UMA_A_UMA, MIGRATION_015_LATERAL_SEM_CERCA_NAO_E_LATERAL, MIGRATION_016_A_DATA_DO_CONSUMO_E_UMA_COLUNA, MIGRATION_017_APROVAR_E_BAIXAR_SAO_DOIS_GESTOS, MIGRATION_018_O_NOME_NAO_SAI_DA_VISTA, MIGRATION_019_SEM_SALDO_NAO_SE_BAIXA, MIGRATION_020_O_PRODUTO_E_VARIAS_COISAS_AO_MESMO_TEMPO, MIGRATION_021_O_RAZAO_DIZ_DE_ONDE_VEIO, MIGRATION_022_RECEBER_ESTOQUE_VOLTA_A_FUNCIONAR, MIGRATION_023_A_RECEITA_SE_ESCREVE_COM_PERMISSAO, MIGRATION_024_A_CATEGORIA_DO_PRODUTO_E_DO_CATALOGO, MIGRATION_025_A_FICHA_TECNICA_POR_ESTACAO, MIGRATION_026_STOCK_COST_KNOWS_ITS_CURRENCY, MIGRATION_027_A_RECEIPT_NAMES_THE_LINE_IT_CAME_FROM, MIGRATION_028_A_RECEIPT_KEEPS_THE_VARIANT, MIGRATION_029_A_REVERSAL_UNDOES_THE_SAME_VARIANT, MIGRATION_030_THE_MINIMUM_CAN_BE_SAVED, MIGRATION_031_THE_STOCK_SAYS_WHEN_IT_RUNS_LOW };
9674
9629
  //# sourceMappingURL=main.js.map
9675
9630
  //# sourceMappingURL=main.js.map