@fayz-ai/plugin-inventory 0.12.3 → 0.12.4

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.
@@ -2,6 +2,8 @@ export declare const MIGRATION_000_BASELINE = "-- ==============================
2
2
  export declare const MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED = "-- 001_the_usage_that_left_stock_can_be_audited.sql\n--\n-- AUDITORIA DE USO. No V1 o consumo declarado no atendimento fica numa FILA e\n-- s\u00F3 baixa o estoque depois que algu\u00E9m aprova. O V2 n\u00E3o tem essa fila e n\u00E3o\n-- deveria ter: `order.completed` consome a receita no mesmo instante, pelo log\n-- de eventos, e \u00E9 isso que faz o saldo e a venda contarem a mesma hist\u00F3ria.\n--\n-- O QUE A TELA DO V1 REALMENTE ENTREGA n\u00E3o \u00E9 o represamento \u2014 \u00E9 a PERGUNTA:\n-- \"o que o profissional disse ter usado bate com o padr\u00E3o do servi\u00E7o?\". No V1\n-- ela era feita antes da baixa; aqui \u00E9 feita depois. A resposta \u00E9 a mesma, e a\n-- corre\u00E7\u00E3o continua poss\u00EDvel porque o raz\u00E3o \u00E9 append-only: corrigir \u00E9 lan\u00E7ar o\n-- ajuste da diferen\u00E7a, nunca reescrever o que j\u00E1 saiu.\n--\n-- POR QUE UMA VISTA, E N\u00C3O UMA TABELA DE AUDITORIA. Uma fila pr\u00F3pria seria um\n-- segundo lugar guardando a mesma quantidade, e os dois divergem no primeiro\n-- estorno. Tudo de que a auditoria precisa j\u00E1 est\u00E1 escrito:\n--\n-- `plg_inventory_stock_movements` guarda `source_item_type='order_item'` e\n-- `source_item_id` apontando para a linha vendida, com `unit_cost`,\n-- `total_cost` e a localiza\u00E7\u00E3o de onde saiu.\n--\n-- `plg_inventory_recipes` + `_recipe_ingredients` guardam o PADR\u00C3O \u2014 o mesmo\n-- n\u00FAmero que `app.inventory_consume_order_item` usou para calcular a baixa.\n--\n-- `items.assignee_id` guarda QUEM executou (financeiro 019 j\u00E1 provou o elo),\n-- e `orders.party_id` guarda para quem.\n--\n-- A GRANULARIDADE \u00C9 (linha vendida \u00D7 produto), n\u00E3o o movimento. Um consumo se\n-- parte em v\u00E1rios movimentos quando o FIFO atravessa lotes: tr\u00EAs linhas de\n-- raz\u00E3o para um frasco s\u00F3 confundiriam quem confere.\n--\n-- O PADR\u00C3O \u00C9 O DE HOJE. A receita pode ter mudado desde o atendimento, e a\n-- vista mostra a receita vigente \u2014 igual ao V1, que tamb\u00E9m lia o cadastro atual.\n-- Diverg\u00EAncia antiga que sumiu porque a receita mudou \u00E9 ru\u00EDdo, n\u00E3o achado.\n\nCREATE OR REPLACE VIEW public.v_inventory_usage_audit\nWITH (security_invoker = 'true') AS\nWITH consumed AS (\n SELECT m.tenant_id,\n m.unit_id,\n m.source_item_id AS item_id,\n m.product_id,\n -- o raz\u00E3o guarda sa\u00EDda como negativo; quem confere pensa em positivo\n sum(- m.quantity) AS posted_quantity,\n sum(m.total_cost) AS total_cost,\n min(m.created_at) AS used_at,\n min(m.movement_date) AS movement_date,\n (array_agg(m.source_location_id ORDER BY m.created_at, m.id))[1] AS stock_location_id,\n count(DISTINCT m.source_location_id)::int AS location_count,\n (array_agg(m.measurement_unit_id ORDER BY m.created_at, m.id))[1] AS measurement_unit_id,\n (array_agg(m.metadata ->> 'recipe_id' ORDER BY m.created_at, m.id))[1] AS recipe_id\n FROM public.plg_inventory_stock_movements m\n WHERE m.source_item_type = 'order_item'\n AND m.source_item_id IS NOT NULL\n AND m.kind = 'out'\n -- estornado n\u00E3o \u00E9 consumo: devolver ao estoque apaga a linha da fila\n AND NOT EXISTS (\n SELECT 1 FROM public.plg_inventory_stock_movements r\n WHERE r.reverses_movement_id = m.id\n )\n GROUP BY m.tenant_id, m.unit_id, m.source_item_id, m.product_id\n), corrected AS (\n SELECT c.tenant_id,\n c.source_item_id AS item_id,\n c.product_id,\n -- devolver ao estoque \u00E9 ajuste positivo, e reduz o consumido\n sum(- c.quantity) AS correction_delta,\n -- o raz\u00E3o guarda custo sempre positivo; o sinal vem da quantidade\n sum(- sign(c.quantity) * c.total_cost) AS correction_cost,\n max(c.created_at) AS corrected_at,\n (array_agg(c.reason ORDER BY c.created_at DESC, c.id DESC))[1] AS correction_reason\n FROM public.plg_inventory_stock_movements c\n WHERE c.source_item_type = 'usage_audit'\n AND c.source_item_id IS NOT NULL\n GROUP BY c.tenant_id, c.source_item_id, c.product_id\n)\nSELECT c.tenant_id,\n c.unit_id,\n c.item_id,\n c.product_id,\n p.name AS product_name,\n p.sku AS product_sku,\n c.used_at,\n c.movement_date,\n c.posted_quantity::numeric(14,4) AS posted_quantity,\n (c.posted_quantity + coalesce(k.correction_delta, 0))::numeric(14,4) AS net_quantity,\n std.standard_quantity::numeric(14,4) AS standard_quantity,\n CASE WHEN std.standard_quantity IS NULL THEN NULL\n ELSE ((c.posted_quantity + coalesce(k.correction_delta, 0))\n - std.standard_quantity)::numeric(14,4)\n END AS variance,\n u.abbreviation AS unit_abbreviation,\n -- o custo acompanha a conclus\u00E3o da auditoria, n\u00E3o o que foi lan\u00E7ado:\n -- devolver um frasco devolve o custo dele junto\n (c.total_cost + coalesce(k.correction_cost, 0))::numeric(16,4) AS total_cost,\n c.stock_location_id,\n sl.name AS stock_location_name,\n -- mais de uma posi\u00E7\u00E3o quer dizer que o FIFO atravessou lotes\n c.location_count,\n r.id AS recipe_id,\n r.name AS recipe_name,\n i.order_id,\n i.name AS item_name,\n o.reference_number AS order_reference,\n i.assignee_id AS performed_by_id,\n perf.name AS performed_by_name,\n o.party_id AS customer_id,\n cust.name AS customer_name,\n k.corrected_at,\n k.correction_reason,\n (k.item_id IS NOT NULL) AS is_corrected\n FROM consumed c\n LEFT JOIN corrected k\n ON k.tenant_id = c.tenant_id AND k.item_id = c.item_id AND k.product_id = c.product_id\n LEFT JOIN public.products p ON p.id = c.product_id\n LEFT JOIN public.plg_inventory_stock_locations sl ON sl.id = c.stock_location_id\n LEFT JOIN public.plg_inventory_measurement_units u ON u.id = c.measurement_unit_id\n -- A linha vendida e seu pedido chegam por LEFT JOIN de prop\u00F3sito: quem l\u00EA o\n -- estoque pode n\u00E3o ler a venda, e nesse caso a linha aparece sem o nome do\n -- profissional em vez de desaparecer da confer\u00EAncia.\n LEFT JOIN public.items i ON i.id = c.item_id AND i.tenant_id = c.tenant_id\n LEFT JOIN public.orders o ON o.id = i.order_id\n LEFT JOIN public.people perf ON perf.id = i.assignee_id\n LEFT JOIN public.people cust ON cust.id = o.party_id\n LEFT JOIN public.plg_inventory_recipes r\n ON r.id = nullif(c.recipe_id, '')::uuid AND r.tenant_id = c.tenant_id\n LEFT JOIN LATERAL (\n SELECT CASE\n WHEN r.id IS NOT NULL AND coalesce(r.yield_quantity, 0) > 0 THEN (\n SELECT round(sum(ri.quantity) * coalesce(i.quantity, 1) / r.yield_quantity, 4)\n FROM public.plg_inventory_recipe_ingredients ri\n WHERE ri.tenant_id = c.tenant_id\n AND ri.recipe_id = r.id\n AND ri.product_id = c.product_id\n )\n -- sem receita o produto vendido \u00E9 o pr\u00F3prio consumido: o padr\u00E3o \u00E9\n -- a quantidade da linha, e divergir a\u00ED seria erro de sistema\n WHEN r.id IS NULL THEN i.quantity\n ELSE NULL\n END AS standard_quantity\n ) std ON true;\n\nCOMMENT ON VIEW public.v_inventory_usage_audit IS\n 'O consumo que j\u00E1 saiu do estoque, por linha vendida e produto, ao lado do padr\u00E3o da receita vigente e da corre\u00E7\u00E3o que a auditoria lan\u00E7ou. N\u00E3o cria fila: o raz\u00E3o j\u00E1 sabia tudo isso (001).';\n\nGRANT SELECT ON public.v_inventory_usage_audit TO authenticated;\nGRANT SELECT ON public.v_inventory_usage_audit TO service_role;\n\n-- \u2500\u2500 corrigir \u00E9 lan\u00E7ar a diferen\u00E7a, n\u00E3o reescrever o passado \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- No V1 \"Corrigir\" e \"Aprovar\" s\u00E3o um ato s\u00F3 porque a baixa ainda n\u00E3o tinha\n-- acontecido. Aqui ela aconteceu, ent\u00E3o corrigir para 40 o que saiu como 50\n-- devolve 10 ao saldo \u2014 e o raz\u00E3o fica com as duas linhas, que \u00E9 o que permite\n-- perguntar depois quem corrigiu o qu\u00EA.\n--\n-- Um motivo \u00E9 obrigat\u00F3rio, como no V1. Ajuste de estoque sem motivo \u00E9 o que\n-- transforma uma confer\u00EAncia em um buraco novo.\nCREATE OR REPLACE FUNCTION public.inventory_correct_usage(\n p_item_id uuid,\n p_product_id uuid,\n p_quantity numeric,\n p_reason text\n) RETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n SET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.inventory_require_tenant();\n v_reason text := nullif(btrim(p_reason), '');\n v_row record;\n v_delta numeric;\n v_unit uuid;\n v_key text;\n v_op record;\n v_id uuid;\nBEGIN\n IF v_reason IS NULL THEN\n RAISE EXCEPTION 'inventory: a usage correction needs a reason' USING ERRCODE = '22023';\n END IF;\n IF p_quantity IS NULL OR p_quantity < 0 THEN\n RAISE EXCEPTION 'inventory: the corrected quantity cannot be negative' USING ERRCODE = '22023';\n END IF;\n\n -- o consumido de hoje: a baixa original mais o que auditorias j\u00E1 corrigiram\n SELECT sum(- m.quantity) AS net,\n (array_agg(m.source_location_id ORDER BY m.created_at DESC, m.id DESC))[1] AS location_id,\n (array_agg(m.measurement_unit_id ORDER BY m.created_at DESC, m.id DESC))[1] AS measurement_unit_id,\n (array_agg(m.batch_number ORDER BY m.created_at DESC, m.id DESC))[1] AS batch_number,\n (array_agg(m.expiration_date ORDER BY m.created_at DESC, m.id DESC))[1] AS expiration_date,\n (array_agg(m.unit_cost ORDER BY m.created_at DESC, m.id DESC))[1] AS unit_cost\n INTO v_row\n FROM public.plg_inventory_stock_movements m\n WHERE m.tenant_id = v_tenant\n AND m.source_item_id = p_item_id\n AND m.product_id = p_product_id\n AND ((m.source_item_type = 'order_item' AND m.kind = 'out')\n OR (m.source_item_type = 'usage_audit' AND m.kind = 'adjust'))\n AND NOT EXISTS (\n SELECT 1 FROM public.plg_inventory_stock_movements r\n WHERE r.reverses_movement_id = m.id\n );\n\n IF v_row.net IS NULL THEN\n RAISE EXCEPTION 'inventory: no usage of product % on item % to correct', p_product_id, p_item_id\n USING ERRCODE = '22023';\n END IF;\n\n -- positivo devolve ao estoque, negativo tira mais\n v_delta := v_row.net - p_quantity;\n IF v_delta = 0 THEN\n RETURN jsonb_build_object('kind', 'usage_audit', 'status', 'unchanged',\n 'item_id', p_item_id, 'product_id', p_product_id,\n 'quantity', p_quantity);\n END IF;\n\n IF v_delta < 0\n AND app.inventory_available(v_tenant, p_product_id, v_row.location_id,\n v_row.batch_number, v_row.expiration_date) < - v_delta THEN\n RAISE EXCEPTION 'inventory: correcting to % would take product % below its available quantity at location %',\n p_quantity, p_product_id, v_row.location_id USING ERRCODE = '23514';\n END IF;\n\n v_unit := app.inventory_authorize_location(v_tenant, v_row.location_id, 'inventory.edit');\n\n -- corrigir duas vezes para o MESMO n\u00FAmero \u00E9 o mesmo ato, e a segunda chamada\n -- devolve o resultado da primeira em vez de mexer no saldo de novo\n v_key := 'usage_audit:' || p_item_id::text || ':' || p_product_id::text || ':' || p_quantity::text;\n\n SELECT * INTO v_op FROM app.inventory_begin_operation(\n v_tenant, 'adjust', v_key,\n jsonb_build_object('source', 'usage_audit', 'item_id', p_item_id,\n 'product_id', p_product_id, 'quantity', p_quantity, 'reason', v_reason));\n IF v_op.existing IS NOT NULL THEN\n RETURN v_op.existing;\n END IF;\n\n INSERT INTO public.plg_inventory_stock_movements\n (tenant_id, product_id, kind, quantity, unit_cost, source_location_id,\n batch_number, expiration_date, measurement_unit_id, document_type,\n reason, source_item_type, source_item_id, idempotency_key, line_no,\n operation_id, metadata)\n VALUES\n (v_tenant, p_product_id, 'adjust', v_delta, coalesce(v_row.unit_cost, 0), v_row.location_id,\n v_row.batch_number, v_row.expiration_date, v_row.measurement_unit_id, 'adjustment',\n v_reason, 'usage_audit', p_item_id, v_key, 1, v_op.op_id,\n jsonb_build_object('source', 'usage_audit', 'order_item_id', p_item_id,\n 'posted_quantity', v_row.net, 'corrected_quantity', p_quantity))\n RETURNING id INTO v_id;\n\n RETURN app.inventory_finish_operation(\n v_tenant, v_op.op_id, 'adjust', v_key,\n jsonb_build_object('operation_id', v_op.op_id, 'kind', 'usage_audit',\n 'idempotency_key', v_key, 'status', 'corrected',\n 'item_id', p_item_id, 'product_id', p_product_id,\n 'location_id', v_row.location_id, 'unit_id', v_unit,\n 'previous_quantity', v_row.net, 'quantity', p_quantity,\n 'delta', v_delta, 'reason', v_reason,\n 'movements', app.inventory_movement_json(v_id)),\n jsonb_build_object('source', 'usage_audit', 'item_id', p_item_id,\n 'product_id', p_product_id, 'unit_id', v_unit,\n 'previous_quantity', v_row.net, 'quantity', p_quantity,\n 'reason', v_reason));\nEND $$;\n\nCOMMENT ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) IS\n 'Corrige o que o atendimento disse ter usado: lan\u00E7a o ajuste da diferen\u00E7a no raz\u00E3o, com motivo, e deixa o rastro ligado \u00E0 linha vendida por source_item_type=usage_audit (001).';\n\nREVOKE ALL ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) TO authenticated;\nGRANT ALL ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) TO service_role;\n";
3
3
  export declare const MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE = "-- 002_a_auditoria_fecha_em_lote.sql\n--\n-- A AUDITORIA VIRA UMA CONTAGEM. Corrigir linha a linha, cada uma com o seu\n-- motivo, \u00E9 o desenho de quem conserta UM engano. Quem confere um dia inteiro\n-- desce a lista digitando o que realmente saiu e explica o conjunto UMA vez \u2014\n-- \u00E9 o mesmo gesto do fechamento de contagem (`inventory_close_count_session`),\n-- que emite N ajustes sob um motivo s\u00F3.\n--\n-- POR QUE ISSO PRECISA DE UMA RPC NOVA, E N\u00C3O DE UM LA\u00C7O NO CLIENTE. Um la\u00E7o\n-- de N chamadas produz N opera\u00E7\u00F5es, N motivos iguais repetidos e N transa\u00E7\u00F5es:\n-- a terceira pode falhar depois de as duas primeiras terem gravado, e o\n-- operador fica sem saber o que aplicou. Aqui o lote \u00E9 UM\n-- `plg_inventory_operations` \u2014 um evento, com as linhas dentro \u2014 e o\n-- `operation_id` de cada movimento \u00E9 o que devolve o lote inteiro depois.\n--\n-- O QUE **N\u00C3O** MUDA: continua sendo o raz\u00E3o que manda. Aplicar o lote LAN\u00C7A a\n-- diferen\u00E7a de cada linha como ajuste; nada \u00E9 reescrito, nada vira UPDATE de\n-- saldo, e uma linha cuja quantidade n\u00E3o mudou n\u00E3o gera movimento nenhum. Por\n-- isso o rascunho pode viver na tela: n\u00E3o existe segundo lugar guardando a\n-- mesma quantidade, que \u00E9 o mesmo motivo pelo qual a 001 recusou uma fila.\n\n-- \u2500\u2500 uma linha do lote, sem abrir opera\u00E7\u00E3o pr\u00F3pria \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--\n-- Extra\u00EDdo de `inventory_correct_usage` (001) para que o ato de uma linha e o\n-- de um lote sejam literalmente o mesmo c\u00F3digo. N\u00E3o abre nem fecha opera\u00E7\u00E3o:\n-- quem chama j\u00E1 \u00E9 dono de uma.\nCREATE OR REPLACE FUNCTION app.inventory_correct_usage_line(\n p_tenant uuid,\n p_op uuid,\n p_key text,\n p_line_no integer,\n p_item_id uuid,\n p_product_id uuid,\n p_quantity numeric,\n p_reason text\n) RETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n SET search_path TO ''\nAS $$\nDECLARE\n v_row record;\n v_delta numeric;\n v_unit uuid;\n v_id uuid;\nBEGIN\n IF p_quantity IS NULL OR p_quantity < 0 THEN\n RAISE EXCEPTION 'inventory: the corrected quantity cannot be negative' USING ERRCODE = '22023';\n END IF;\n\n -- o consumido de hoje: a baixa original mais o que auditorias j\u00E1 corrigiram\n SELECT sum(- m.quantity) AS net,\n (array_agg(m.source_location_id ORDER BY m.created_at DESC, m.id DESC))[1] AS location_id,\n (array_agg(m.measurement_unit_id ORDER BY m.created_at DESC, m.id DESC))[1] AS measurement_unit_id,\n (array_agg(m.batch_number ORDER BY m.created_at DESC, m.id DESC))[1] AS batch_number,\n (array_agg(m.expiration_date ORDER BY m.created_at DESC, m.id DESC))[1] AS expiration_date,\n (array_agg(m.unit_cost ORDER BY m.created_at DESC, m.id DESC))[1] AS unit_cost\n INTO v_row\n FROM public.plg_inventory_stock_movements m\n WHERE m.tenant_id = p_tenant\n AND m.source_item_id = p_item_id\n AND m.product_id = p_product_id\n AND ((m.source_item_type = 'order_item' AND m.kind = 'out')\n OR (m.source_item_type = 'usage_audit' AND m.kind = 'adjust'))\n AND NOT EXISTS (\n SELECT 1 FROM public.plg_inventory_stock_movements r\n WHERE r.reverses_movement_id = m.id\n );\n\n IF v_row.net IS NULL THEN\n RAISE EXCEPTION 'inventory: no usage of product % on item % to correct', p_product_id, p_item_id\n USING ERRCODE = '22023';\n END IF;\n\n -- positivo devolve ao estoque, negativo tira mais\n v_delta := v_row.net - p_quantity;\n IF v_delta = 0 THEN\n RETURN jsonb_build_object('status', 'unchanged', 'item_id', p_item_id,\n 'product_id', p_product_id, 'previous_quantity', v_row.net,\n 'quantity', p_quantity, 'delta', 0);\n END IF;\n\n IF v_delta < 0\n AND app.inventory_available(p_tenant, p_product_id, v_row.location_id,\n v_row.batch_number, v_row.expiration_date) < - v_delta THEN\n RAISE EXCEPTION 'inventory: correcting to % would take product % below its available quantity at location %',\n p_quantity, p_product_id, v_row.location_id USING ERRCODE = '23514';\n END IF;\n\n v_unit := app.inventory_authorize_location(p_tenant, v_row.location_id, 'inventory.edit');\n\n INSERT INTO public.plg_inventory_stock_movements\n (tenant_id, product_id, kind, quantity, unit_cost, source_location_id,\n batch_number, expiration_date, measurement_unit_id, document_type,\n reason, source_item_type, source_item_id, idempotency_key, line_no,\n operation_id, metadata)\n VALUES\n (p_tenant, p_product_id, 'adjust', v_delta, coalesce(v_row.unit_cost, 0), v_row.location_id,\n v_row.batch_number, v_row.expiration_date, v_row.measurement_unit_id, 'adjustment',\n p_reason, 'usage_audit', p_item_id, p_key, p_line_no, p_op,\n jsonb_build_object('source', 'usage_audit', 'order_item_id', p_item_id,\n 'posted_quantity', v_row.net, 'corrected_quantity', p_quantity))\n RETURNING id INTO v_id;\n\n RETURN jsonb_build_object('status', 'corrected', 'item_id', p_item_id,\n 'product_id', p_product_id, 'location_id', v_row.location_id,\n 'unit_id', v_unit, 'previous_quantity', v_row.net,\n 'quantity', p_quantity, 'delta', v_delta,\n 'movements', app.inventory_movement_json(v_id));\nEND $$;\n\nCOMMENT ON FUNCTION app.inventory_correct_usage_line(uuid, uuid, text, integer, uuid, uuid, numeric, text) IS\n 'Uma linha de corre\u00E7\u00E3o de consumo dentro de uma opera\u00E7\u00E3o j\u00E1 aberta: lan\u00E7a a diferen\u00E7a no raz\u00E3o. Compartilhada pela corre\u00E7\u00E3o avulsa e pelo lote (002).';\n\nREVOKE ALL ON FUNCTION app.inventory_correct_usage_line(uuid, uuid, text, integer, uuid, uuid, numeric, text) FROM PUBLIC;\n\n-- \u2500\u2500 a corre\u00E7\u00E3o avulsa passa a ser um lote de uma linha \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Mesma assinatura, mesma chave de idempot\u00EAncia, mesmo retorno: nada que j\u00E1\n-- chamava esta fun\u00E7\u00E3o precisa saber que o miolo mudou de casa.\nCREATE OR REPLACE FUNCTION public.inventory_correct_usage(\n p_item_id uuid,\n p_product_id uuid,\n p_quantity numeric,\n p_reason text\n) RETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n SET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.inventory_require_tenant();\n v_reason text := nullif(btrim(p_reason), '');\n v_key text;\n v_op record;\n v_line jsonb;\nBEGIN\n IF v_reason IS NULL THEN\n RAISE EXCEPTION 'inventory: a usage correction needs a reason' USING ERRCODE = '22023';\n END IF;\n\n -- corrigir duas vezes para o MESMO n\u00FAmero \u00E9 o mesmo ato, e a segunda chamada\n -- devolve o resultado da primeira em vez de mexer no saldo de novo\n v_key := 'usage_audit:' || p_item_id::text || ':' || p_product_id::text || ':' || p_quantity::text;\n\n SELECT * INTO v_op FROM app.inventory_begin_operation(\n v_tenant, 'adjust', v_key,\n jsonb_build_object('source', 'usage_audit', 'item_id', p_item_id,\n 'product_id', p_product_id, 'quantity', p_quantity, 'reason', v_reason));\n IF v_op.existing IS NOT NULL THEN\n RETURN v_op.existing;\n END IF;\n\n v_line := app.inventory_correct_usage_line(\n v_tenant, v_op.op_id, v_key, 1, p_item_id, p_product_id, p_quantity, v_reason);\n\n IF v_line->>'status' = 'unchanged' THEN\n RETURN jsonb_build_object('kind', 'usage_audit', 'status', 'unchanged',\n 'item_id', p_item_id, 'product_id', p_product_id,\n 'quantity', p_quantity);\n END IF;\n\n RETURN app.inventory_finish_operation(\n v_tenant, v_op.op_id, 'adjust', v_key,\n jsonb_build_object('operation_id', v_op.op_id, 'kind', 'usage_audit',\n 'idempotency_key', v_key, 'status', 'corrected',\n 'item_id', p_item_id, 'product_id', p_product_id,\n 'location_id', v_line->'location_id', 'unit_id', v_line->'unit_id',\n 'previous_quantity', v_line->'previous_quantity',\n 'quantity', p_quantity, 'delta', v_line->'delta', 'reason', v_reason,\n 'movements', v_line->'movements'),\n jsonb_build_object('source', 'usage_audit', 'item_id', p_item_id,\n 'product_id', p_product_id, 'unit_id', v_line->'unit_id',\n 'previous_quantity', v_line->'previous_quantity',\n 'quantity', p_quantity, 'reason', v_reason));\nEND $$;\n\nCOMMENT ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) IS\n 'Corrige o que o atendimento disse ter usado: lan\u00E7a o ajuste da diferen\u00E7a no raz\u00E3o, com motivo, e deixa o rastro ligado \u00E0 linha vendida por source_item_type=usage_audit (001, reescrita sobre o miolo compartilhado em 002).';\n\nREVOKE ALL ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) TO authenticated;\nGRANT ALL ON FUNCTION public.inventory_correct_usage(uuid, uuid, numeric, text) TO service_role;\n\n-- \u2500\u2500 o lote: um motivo, N linhas, UM evento \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--\n-- `p_lines` \u00E9 `[{\"item_id\": uuid, \"product_id\": uuid, \"quantity\": numeric}, \u2026]`\n-- \u2014 a linha vendida, o produto e o que a confer\u00EAncia diz que realmente saiu.\n--\n-- A chave de idempot\u00EAncia sai do CONTE\u00DADO do lote (linhas ordenadas + motivo),\n-- n\u00E3o de um rel\u00F3gio: reenviar o mesmo lote \u2014 o duplo clique, a rede que caiu\n-- entre gravar e responder \u2014 devolve o resultado do primeiro envio. E mesmo\n-- sem isso o dano seria zero: a segunda passagem calcula delta 0 e n\u00E3o emite.\nCREATE OR REPLACE FUNCTION public.inventory_correct_usage_batch(\n p_reason text,\n p_lines jsonb\n) RETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n SET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := app.inventory_require_tenant();\n v_reason text := nullif(btrim(p_reason), '');\n v_key text;\n v_op record;\n v_line jsonb;\n v_result jsonb;\n r record;\n v_no integer := 0;\n v_applied integer := 0;\n v_unchanged integer := 0;\n v_lines jsonb := '[]'::jsonb;\n v_movements jsonb := '[]'::jsonb;\nBEGIN\n IF v_reason IS NULL THEN\n RAISE EXCEPTION 'inventory: a usage correction needs a reason' USING ERRCODE = '22023';\n END IF;\n PERFORM app.inventory_check_lines(p_lines);\n\n SELECT 'usage_audit_batch:' || md5(v_reason || '|' || string_agg(\n (l->>'item_id') || ':' || (l->>'product_id') || ':' || (l->>'quantity'), ',' ORDER BY\n (l->>'item_id'), (l->>'product_id')))\n INTO v_key\n FROM jsonb_array_elements(p_lines) AS l;\n\n SELECT * INTO v_op FROM app.inventory_begin_operation(\n v_tenant, 'adjust', v_key,\n jsonb_build_object('source', 'usage_audit_batch', 'reason', v_reason, 'lines', p_lines));\n IF v_op.existing IS NOT NULL THEN\n RETURN v_op.existing;\n END IF;\n\n -- a ordem do array \u00E9 a ordem em que a pessoa digitou, e \u00E9 ela que o\n -- `line_no` guarda: reler o lote depois devolve a confer\u00EAncia como ela foi\n -- feita, n\u00E3o reordenada por id\n FOR r IN\n SELECT (l->>'item_id')::uuid AS item_id,\n (l->>'product_id')::uuid AS product_id,\n (l->>'quantity')::numeric AS quantity,\n ord\n FROM jsonb_array_elements(p_lines) WITH ORDINALITY AS t(l, ord)\n ORDER BY ord\n LOOP\n IF r.item_id IS NULL OR r.product_id IS NULL OR r.quantity IS NULL THEN\n RAISE EXCEPTION 'inventory: every audit line needs item_id, product_id and quantity'\n USING ERRCODE = '22023';\n END IF;\n v_no := v_no + 1;\n v_line := app.inventory_correct_usage_line(\n v_tenant, v_op.op_id, v_key, v_no, r.item_id, r.product_id, r.quantity, v_reason);\n IF v_line->>'status' = 'unchanged' THEN\n v_unchanged := v_unchanged + 1;\n ELSE\n v_applied := v_applied + 1;\n v_movements := v_movements || coalesce(v_line->'movements', '[]'::jsonb);\n END IF;\n v_lines := v_lines || jsonb_build_array(v_line - 'movements');\n END LOOP;\n\n v_result := jsonb_build_object(\n 'operation_id', v_op.op_id, 'kind', 'usage_audit_batch',\n 'idempotency_key', v_key, 'status', 'corrected', 'reason', v_reason,\n 'lines_applied', v_applied, 'lines_unchanged', v_unchanged,\n 'lines', v_lines, 'movements', v_movements);\n\n RETURN app.inventory_finish_operation(\n v_tenant, v_op.op_id, 'adjust', v_key, v_result,\n jsonb_build_object('source', 'usage_audit_batch', 'reason', v_reason,\n 'lines_applied', v_applied, 'lines_unchanged', v_unchanged));\nEND $$;\n\nCOMMENT ON FUNCTION public.inventory_correct_usage_batch(text, jsonb) IS\n 'Fecha uma confer\u00EAncia de consumo inteira: N linhas corrigidas sob UM motivo e UMA opera\u00E7\u00E3o, cada diferen\u00E7a lan\u00E7ada como ajuste no raz\u00E3o. O operation_id devolve o lote depois (002).';\n\nREVOKE ALL ON FUNCTION public.inventory_correct_usage_batch(text, jsonb) FROM PUBLIC;\nGRANT ALL ON FUNCTION public.inventory_correct_usage_batch(text, jsonb) TO authenticated;\nGRANT ALL ON FUNCTION public.inventory_correct_usage_batch(text, jsonb) TO service_role;\n";
4
4
  export declare const MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER = "-- 003_stock_count_closes_through_ledger.sql\n--\n-- A physical count is a reconciliation, not a second stock writer. The old\n-- function inserted values into GENERATED columns, recorded the counted total\n-- instead of the variance and then updated the position a second time outside\n-- the ledger trigger. This replacement emits one canonical `adjust` movement\n-- per divergent line and lets the existing movement trigger update the balance.\n--\n-- Safety gates: every line must be counted (zero is valid); any stock movement\n-- after the opening snapshot aborts the close; lot stock requires a future\n-- batch-aware count instead of guessing which lot changed.\n\nCREATE OR REPLACE FUNCTION public.inventory_close_count_session(p_session_id uuid, p_reason text DEFAULT NULL::text) RETURNS jsonb\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid := app.inventory_require_tenant();\n v_session public.plg_inventory_count_sessions%ROWTYPE;\n v_item public.plg_inventory_count_items%ROWTYPE;\n v_op record;\n v_delta numeric;\n v_current numeric;\n v_current_cost numeric;\n v_has_lots boolean;\n v_movement_id uuid;\n v_reason text;\n v_key text := 'count:' || p_session_id::text || ':close:v1';\n v_line_no int := 0;\n v_uncounted int;\n v_conflicts int;\n v_emitted int := 0;\n v_movements jsonb := '[]'::jsonb;\n v_positions jsonb := '[]'::jsonb;\n v_result jsonb;\nBEGIN\n SELECT * INTO v_session FROM public.plg_inventory_count_sessions\n WHERE id = p_session_id AND tenant_id = v_tenant FOR UPDATE;\n IF NOT FOUND THEN\n RETURN jsonb_build_object('ok', false, 'error', 'unknown count session');\n END IF;\n PERFORM app.inventory_authorize_location(v_tenant, v_session.stock_location_id, 'inventory.edit');\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 SELECT count(*) INTO v_uncounted\n FROM public.plg_inventory_count_items i\n WHERE i.session_id = p_session_id AND i.counted_quantity IS NULL;\n IF v_uncounted > 0 THEN\n RAISE EXCEPTION 'inventory: count session has % uncounted line(s)', v_uncounted\n USING ERRCODE = '22023', HINT = 'Count every line, including physical zero, before closing.';\n END IF;\n\n PERFORM p.id\n FROM public.plg_inventory_stock_positions p\n JOIN public.plg_inventory_count_items i\n ON i.tenant_id = p.tenant_id AND i.product_id = p.product_id\n WHERE i.session_id = p_session_id\n AND p.stock_location_id = v_session.stock_location_id\n ORDER BY p.id\n FOR UPDATE OF p;\n\n SELECT count(DISTINCT i.id) INTO v_conflicts\n FROM public.plg_inventory_count_items i\n JOIN public.plg_inventory_stock_movements m\n ON m.tenant_id = i.tenant_id\n AND m.product_id = i.product_id\n AND (m.source_location_id = v_session.stock_location_id\n OR m.destination_location_id = v_session.stock_location_id)\n WHERE i.session_id = p_session_id\n AND m.created_at > v_session.opened_at\n AND (i.movement_id IS NULL OR m.id <> i.movement_id);\n IF v_conflicts > 0 THEN\n RAISE EXCEPTION 'inventory: stock changed after this count opened (% line(s)); review and recount', v_conflicts\n USING ERRCODE = '40001';\n END IF;\n\n SELECT * INTO v_op FROM app.inventory_begin_operation(\n v_tenant, 'adjust', v_key,\n jsonb_build_object('count_session_id', p_session_id, 'reason', v_reason));\n IF v_op.existing IS NOT NULL THEN\n UPDATE public.plg_inventory_count_sessions\n SET status = 'closed', closed_at = coalesce(closed_at, now()),\n closed_by = coalesce(closed_by, auth.uid()), updated_at = now()\n WHERE id = p_session_id;\n RETURN v_op.existing || jsonb_build_object('already_closed', true, 'adjustments_created', 0);\n END IF;\n\n FOR v_item IN\n SELECT * FROM public.plg_inventory_count_items\n WHERE session_id = p_session_id AND movement_id IS NULL\n ORDER BY id\n FOR UPDATE\n LOOP\n SELECT coalesce(sum(p.quantity), 0),\n coalesce(max(p.unit_cost), v_item.unit_cost, 0),\n coalesce(bool_or((p.batch_number IS NOT NULL OR p.expiration_date IS NOT NULL) AND p.quantity <> 0), false)\n INTO v_current, v_current_cost, v_has_lots\n FROM public.plg_inventory_stock_positions p\n WHERE p.tenant_id = v_tenant\n AND p.product_id = v_item.product_id\n AND p.stock_location_id = v_session.stock_location_id;\n\n v_delta := v_item.counted_quantity - v_current;\n IF v_delta = 0 THEN CONTINUE; END IF;\n IF v_has_lots THEN\n RAISE EXCEPTION 'inventory: product % has batch stock; close it with a batch-aware count', v_item.product_id\n USING ERRCODE = '22023';\n END IF;\n\n v_line_no := v_line_no + 1;\n INSERT INTO public.plg_inventory_stock_movements (\n tenant_id, product_id, kind, quantity, unit_cost,\n source_location_id, document_type, reason,\n idempotency_key, line_no, operation_id,\n source_item_type, source_item_id, metadata\n ) VALUES (\n v_tenant, v_item.product_id, 'adjust', v_delta, v_current_cost,\n v_session.stock_location_id, 'stock_count', v_reason,\n v_key, v_line_no, v_op.op_id,\n 'stock_count_item', v_item.id,\n jsonb_build_object(\n 'countSessionId', p_session_id,\n 'countItemId', v_item.id,\n 'systemQuantity', v_item.system_quantity,\n 'currentQuantity', v_current,\n 'countedQuantity', v_item.counted_quantity,\n 'variance', v_delta\n )\n )\n RETURNING id INTO v_movement_id;\n\n UPDATE public.plg_inventory_count_items\n SET movement_id = v_movement_id, updated_at = now()\n WHERE id = v_item.id;\n v_movements := v_movements || app.inventory_movement_json(v_movement_id);\n v_positions := v_positions || app.inventory_position_json(\n v_tenant, v_item.product_id, v_session.stock_location_id, NULL, NULL);\n v_emitted := v_emitted + 1;\n END LOOP;\n\n UPDATE public.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 v_result := jsonb_build_object(\n 'ok', true, 'session_id', p_session_id, 'status', 'closed',\n 'already_closed', false, 'adjustments_created', v_emitted,\n 'operation_id', v_op.op_id, 'idempotency_key', v_key,\n 'movements', v_movements, 'positions', v_positions);\n RETURN app.inventory_finish_operation(\n v_tenant, v_op.op_id, 'count.close', v_key, v_result,\n jsonb_build_object('count_session_id', p_session_id, 'adjustments_created', v_emitted));\nEND;\n$$;\n";
5
+ export declare const MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA = "-- A posi\u00E7\u00E3o de estoque atravessa.\n--\n-- `stock_positions` \u2014 o saldo por produto \u00D7 local \u00D7 lote \u2014 estava no plano de\n-- leitura e sem destino: 1.861 linhas do tenant Pertinho do C\u00E9u ficaram\n-- `pendentes` em toda rodada, nunca ofertadas a writer nenhum. A nota no\n-- extrator dizia \"no V2 a posi\u00E7\u00E3o \u00E9 derivada do movimento\", e essa frase \u00E9\n-- verdadeira e insuficiente.\n--\n-- Derivar o saldo do movimento s\u00F3 d\u00E1 o mesmo n\u00FAmero quando o movimento\n-- atravessa INTEIRO. Neste tenant 2.208 dos 2.697 movimentos estavam em\n-- quarentena, ent\u00E3o o saldo derivado seria o saldo de 18% do hist\u00F3rico \u2014\n-- apresentado com a mesma confian\u00E7a do saldo certo. E lote e validade, que a\n-- posi\u00E7\u00E3o carrega e o movimento n\u00E3o, n\u00E3o se derivam de soma nenhuma.\n--\n-- Ent\u00E3o a posi\u00E7\u00E3o vem do V1 como fato, e n\u00E3o como conta.\n--\n-- \u2500\u2500 O que o destino exige e a origem n\u00E3o d\u00E1 \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--\n-- `plg_inventory_stock_positions` tem tr\u00EAs colunas NOT NULL que precisam de\n-- tradu\u00E7\u00E3o:\n--\n-- product_id \u2192 resolvido pelo ledger de `products`\n-- stock_location_id \u2192 pelo ledger de `stock_locations`\n-- unit_id \u2192 a unidade da EMPRESA (app.units), e o V1 guarda duas\n-- coisas com nome parecido: `unit_id` (a unidade de\n-- MEDIDA do item) e `company_unit_id` (a filial). \u00C9 a\n-- segunda. Confundi-las poria a posi\u00E7\u00E3o na unidade\n-- errada \u2014 e num tenant de 63 filiais isso \u00E9 saldo no\n-- lugar de outro.\n--\n-- Quando a filial n\u00E3o vem na linha, ela vem do LOCAL: um local de estoque\n-- pertence a uma unidade, e o saldo que est\u00E1 nele est\u00E1 nela.\n--\n-- Idempotente.\n\n-- \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- 1. O writer\n-- \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE OR REPLACE FUNCTION migration.upsert_inv_position(\n p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb\n) RETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\nAS $function$\nDECLARE\n v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);\n v_product uuid := (p_row ->> 'product_id')::uuid;\n v_location uuid := (p_row ->> 'stock_location_id')::uuid;\n v_unit uuid := (p_row ->> 'unit_id')::uuid;\n v_qty numeric := round(coalesce((p_row ->> 'quantity')::numeric, 0), 4);\n v_existing_tenant uuid;\n v_created boolean := false;\n v_standalone boolean := migration._standalone();\nBEGIN\n IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;\n IF p_id IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'uma posi\u00E7\u00E3o precisa de id');\n END IF;\n IF v_product IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',\n 'reason', 'posi\u00E7\u00E3o de saldo cujo produto ainda n\u00E3o atravessou');\n END IF;\n IF v_location IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',\n 'reason', 'posi\u00E7\u00E3o de saldo sem local \u2014 saldo sem lugar n\u00E3o \u00E9 saldo');\n END IF;\n\n -- A filial pelo LOCAL quando a linha n\u00E3o a traz: o local pertence a uma\n -- unidade, e o saldo que est\u00E1 nele est\u00E1 nela.\n IF v_unit IS NULL THEN\n SELECT l.unit_id INTO v_unit\n FROM public.plg_inventory_stock_locations l\n WHERE l.id = v_location AND l.tenant_id = p_tenant;\n END IF;\n IF v_unit IS NULL THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',\n 'reason', 'posi\u00E7\u00E3o sem unidade, e o local dela tamb\u00E9m n\u00E3o diz de qual \u00E9');\n END IF;\n\n -- `quantity >= 0` \u00E9 CHECK da tabela. O V1 admite saldo negativo (sa\u00EDda que\n -- n\u00E3o conferiu com a entrada); recusar aqui, com a frase, \u00E9 melhor que o erro\n -- cru do banco no meio do lote \u2014 e melhor que gravar zero, que apagaria a\n -- evid\u00EAncia de que a contagem daquele item est\u00E1 furada.\n IF v_qty < 0 THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',\n 'reason', format('saldo negativo (%s) \u2014 o V1 admite, o raz\u00E3o de estoque do V2 n\u00E3o', v_qty));\n END IF;\n\n SELECT tenant_id INTO v_existing_tenant FROM public.plg_inventory_stock_positions WHERE id = p_id;\n IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN\n RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant',\n 'reason', format('%s already exists in another tenant', p_id));\n END IF;\n\n IF v_existing_tenant IS NULL THEN\n INSERT INTO public.plg_inventory_stock_positions\n (id, tenant_id, unit_id, product_id, stock_location_id, quantity, unit_cost,\n batch_number, expiration_date, unit_type)\n VALUES (p_id, p_tenant, v_unit, v_product, v_location, v_qty,\n round(coalesce((p_row ->> 'unit_cost')::numeric, 0), 4),\n p_row ->> 'batch_number', (p_row ->> 'expiration_date')::date,\n coalesce(nullif(btrim(p_row ->> 'unit_type'), ''), 'base'));\n v_created := true;\n ELSE\n UPDATE public.plg_inventory_stock_positions SET\n unit_id = coalesce(v_unit, unit_id),\n product_id = coalesce(v_product, product_id),\n stock_location_id = coalesce(v_location, stock_location_id),\n quantity = CASE WHEN p_row ? 'quantity' THEN v_qty ELSE quantity END,\n unit_cost = CASE WHEN p_row ? 'unit_cost' THEN round(coalesce((p_row ->> 'unit_cost')::numeric, 0), 4) ELSE unit_cost END,\n batch_number = CASE WHEN p_row ? 'batch_number' THEN p_row ->> 'batch_number' ELSE batch_number END,\n expiration_date = CASE WHEN p_row ? 'expiration_date' THEN (p_row ->> 'expiration_date')::date ELSE expiration_date END,\n unit_type = CASE WHEN p_row ? 'unit_type' THEN coalesce(nullif(btrim(p_row ->> 'unit_type'), ''), 'base') ELSE unit_type END,\n updated_at = now()\n WHERE id = p_id;\n END IF;\n\n IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_inventory_stock_positions', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;\n RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);\nEND $function$;\n\n-- \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- 2. O vocabul\u00E1rio de writers aceita o nome novo\n-- \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nDO $$\nDECLARE v_def text;\nBEGIN\n SELECT pg_get_constraintdef(oid) INTO v_def\n FROM pg_constraint WHERE conname = 'allowlist_writer_check';\n IF v_def IS NULL OR strpos(v_def, 'inv_position') > 0 THEN RETURN; END IF;\n ALTER TABLE migration.allowlist DROP CONSTRAINT allowlist_writer_check;\n EXECUTE replace(\n 'ALTER TABLE migration.allowlist ADD CONSTRAINT allowlist_writer_check ' || v_def,\n '''inv_movement''::text',\n '''inv_movement''::text, ''inv_position''::text');\nEND $$;\n\n-- \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- 3. O de-para\n-- \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `unit_id` do V1 \u00E9 a unidade de MEDIDA e n\u00E3o a filial \u2014 por isso ela vai para\n-- metadata e quem responde pela filial \u00E9 `company_unit_id`.\n\n-- A allowlist \u00E9 CONGELADA (`trg_allowlist_frozen`): mudan\u00E7a de core-A \u00E9\n-- arquivo de migration, n\u00E3o escrita solta \u2014 e esta \u00E9 um arquivo. A escotilha\n-- \u00E9 transacional (`true` no terceiro argumento), ent\u00E3o n\u00E3o fica porta aberta.\nSELECT set_config('migration.allowlist_unlock', 'on', true);\n\nSELECT migration._allow(\n 'stock_positions', 'public.plg_inventory_stock_positions', 'inventory',\n 'inv_position', '{}'::jsonb, ARRAY['quantity', 'unit_cost'],\n jsonb_build_object(\n 'uuid', 'id',\n 'product_id', 'product_id@products',\n 'stock_location_id', 'stock_location_id@stock_locations',\n 'company_unit_id', 'unit_id@companies:app.units',\n 'quantity', 'quantity',\n 'unit_cost', 'unit_cost',\n 'batch_number', 'batch_number',\n 'expiration_date', 'expiration_date',\n 'unit_type', 'unit_type',\n 'location', 'jsonb:v1.location_label',\n 'unit_id', 'jsonb:v1.measurement_unit_ref'\n ),\n 'O SALDO por produto \u00D7 local \u00D7 lote. Vem da origem como FATO e n\u00E3o derivado '\n 'do movimento: o saldo derivado s\u00F3 bate quando o movimento atravessa inteiro, '\n 'e lote e validade a posi\u00E7\u00E3o carrega e o movimento n\u00E3o.'\n);\n\nSELECT set_config('migration.allowlist_unlock', 'off', true);\n\n\n-- \u2500\u2500 quem pode executar \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--\n-- Fun\u00E7\u00E3o nova nasce execut\u00E1vel por PUBLIC, e `anon`/`authenticated` herdam\n-- dali. Estas s\u00E3o de PORTE: SECURITY DEFINER, escrevem dados de qualquer\n-- inquilino, e uma delas alcan\u00E7\u00E1vel pela chave an\u00F4nima \u00E9 a base inteira\n-- alcan\u00E7\u00E1vel pela chave an\u00F4nima. S\u00F3 o service_role, que \u00E9 quem roda a onda.\nREVOKE ALL ON FUNCTION migration.upsert_inv_position(uuid, uuid, jsonb, jsonb) FROM PUBLIC, anon, authenticated;\nGRANT EXECUTE ON FUNCTION migration.upsert_inv_position(uuid, uuid, jsonb, jsonb) TO service_role;\n";
6
+ export declare const MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO = "-- A posi\u00E7\u00E3o \u00E9 consequ\u00EAncia do movimento.\n--\n-- Desfaz a migration 004, que estava errada \u2014 e o registro do porqu\u00EA vale mais\n-- que o c\u00F3digo que sai.\n--\n-- A 004 criou um writer para trazer `stock_positions` do V1 como FATO, com o\n-- argumento de que derivar o saldo do movimento s\u00F3 d\u00E1 o mesmo n\u00FAmero quando o\n-- movimento atravessa inteiro. O argumento era bom e a premissa tinha deixado\n-- de valer no mesmo dia: os 2.697 movimentos passaram a atravessar inteiros\n-- assim que a entrada por DANFE ganhou o local (a ponte no extrator).\n--\n-- E o destino j\u00E1 dizia isso, em voz alta:\n--\n-- plg_inventory_stock_positions is written only through the inventory RPCs\n-- (inventory_receive / _transfer \u2026)\n--\n-- 339 das 1.861 linhas bateram nesse gatilho. O gatilho est\u00E1 certo: no V2 a\n-- posi\u00E7\u00E3o n\u00E3o \u00E9 algo que se escreve, \u00E9 o que sobra depois que o movimento\n-- passou. Escrever por fora seria manter duas verdades sobre o mesmo saldo e\n-- deixar a diverg\u00EAncia para depois.\n--\n-- \u2500\u2500 A prova \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\u2500\u2500\u2500\n--\n-- Medido em 19/09/2026 no tenant Pertinho do C\u00E9u, depois de os movimentos\n-- entrarem:\n--\n-- V1 stock_positions 1.861 linhas 251.561,2484\n-- V2 plg_inventory_stock_positions 990 linhas 251.561,2484\n--\n-- O SALDO \u00E9 o mesmo at\u00E9 o quarto decimal. A contagem de linhas difere porque o\n-- V1 guarda uma posi\u00E7\u00E3o por lote/validade e o V2 consolida por produto \u00D7 local\n-- quando n\u00E3o h\u00E1 lote \u2014 duas formas de escrever o mesmo estoque, e \u00E9 o estoque\n-- que se confere.\n--\n-- \u2500\u2500 O que sai e o que fica \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--\n-- Sai a linha de `stock_positions` da allowlist. Fica `migration.excluded_tables`\n-- com o motivo medido, para que a pr\u00F3xima pessoa que vir 1.861 linhas paradas\n-- no dashboard leia a resposta em vez de refazer a investiga\u00E7\u00E3o.\n--\n-- O writer `upsert_inv_position` FICA. Ele n\u00E3o faz mal parado, e um pool que\n-- porte posi\u00E7\u00E3o sem portar movimento vai precisar dele \u2014 o que muda \u00E9 que este\n-- n\u00E3o \u00E9 esse caso.\n--\n-- Idempotente.\n\n-- A linha est\u00E1 CONGELADA, e o gatilho recusa DELETE nela pelo mesmo motivo que\n-- recusa UPDATE: mudan\u00E7a de core-A \u00E9 arquivo de migration. Este \u00E9 o arquivo, e\n-- a escotilha \u00E9 transacional \u2014 n\u00E3o fica porta aberta depois dele.\nSELECT set_config('migration.allowlist_unlock', 'on', true);\nDELETE FROM migration.allowlist WHERE source_table = 'stock_positions';\nSELECT set_config('migration.allowlist_unlock', 'off', true);\n\n-- `WHERE tenant_id IS NULL` no \u00E1rbitro: a 073 da espinha trocou a PK de\n-- `migration.excluded_tables` por dois \u00EDndices \u00FAnicos PARCIAIS \u2014 um global e\n-- um por inquilino \u2014, porque \"n\u00E3o trazer esta tabela\" passou a ser decis\u00E3o de\n-- cliente e n\u00E3o do cluster. \u00CDndice parcial s\u00F3 serve de \u00E1rbitro de ON CONFLICT\n-- quando o comando repete o mesmo recorte. Esta decis\u00E3o \u00E9 global.\nINSERT INTO migration.excluded_tables (source_table, reason)\nVALUES ('stock_positions',\n 'Consequ\u00EAncia, n\u00E3o fato: no V2 a posi\u00E7\u00E3o s\u00F3 se escreve pelas RPCs de '\n 'invent\u00E1rio (o gatilho recusa INSERT direto), e ela se forma sozinha '\n 'quando o movimento atravessa. Conferido em 19/09/2026 no Pertinho do '\n 'C\u00E9u \u2014 V1 1.861 linhas / 251.561,2484 contra V2 990 linhas / '\n '251.561,2484: mesmo saldo, formas diferentes de agrupar (o V1 guarda '\n 'por lote, o V2 consolida sem lote).')\n-- `WHERE tenant_id IS NULL` porque a PK de `excluded_tables` deixou de existir:\n-- o spine 070 a trocou por dois \u00EDndices \u00FAnicos PARCIAIS \u2014 um por tabela global,\n-- outro por tabela e tenant. `ON CONFLICT (source_table)` sozinho n\u00E3o casa com\n-- \u00EDndice parcial, e a cadeia do zero parou aqui com \"there is no unique or\n-- exclusion constraint matching the ON CONFLICT specification\".\n--\n-- Esta linha \u00E9 global (sem tenant), ent\u00E3o \u00E9 o \u00EDndice global que ela quer.\nON CONFLICT (source_table) WHERE tenant_id IS NULL DO UPDATE SET reason = EXCLUDED.reason;\n";
5
7
  export declare const MIGRATIONS: Array<{
6
8
  id: string;
7
9
  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,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAKzD,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,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAOzD,CAAA"}
@@ -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;AAoEzE,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,qBA8IA"}
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;AAoEzE,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,qBA+IA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductListView.d.ts","sourceRoot":"","sources":["../../src/views/ProductListView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAkBxE,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,qBAwJA"}
1
+ {"version":3,"file":"ProductListView.d.ts","sourceRoot":"","sources":["../../src/views/ProductListView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAkBxE,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,qBAiJA"}
@@ -1 +1 @@
1
- {"version":3,"file":"RecipesView.d.ts","sourceRoot":"","sources":["../../src/views/RecipesView.tsx"],"names":[],"mappings":"AACA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAsGhF,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,qBA8MA"}
1
+ {"version":3,"file":"RecipesView.d.ts","sourceRoot":"","sources":["../../src/views/RecipesView.tsx"],"names":[],"mappings":"AACA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAsGhF,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,qBAyMA"}
@@ -1 +1 @@
1
- {"version":3,"file":"StockCountsView.d.ts","sourceRoot":"","sources":["../../src/views/StockCountsView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAgExE,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IACjD,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B,qBAkDA"}
1
+ {"version":3,"file":"StockCountsView.d.ts","sourceRoot":"","sources":["../../src/views/StockCountsView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAgExE,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IACjD,KAAK,CAAC,EAAE,MAAM,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B,qBA2CA"}
@@ -1 +1 @@
1
- {"version":3,"file":"UsageAuditView.d.ts","sourceRoot":"","sources":["../../src/views/UsageAuditView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AA0MhF,wBAAgB,cAAc,sBA8Q7B"}
1
+ {"version":3,"file":"UsageAuditView.d.ts","sourceRoot":"","sources":["../../src/views/UsageAuditView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAyMhF,wBAAgB,cAAc,sBAuQ7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"productEntity.d.ts","sourceRoot":"","sources":["../../src/views/productEntity.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAA+B,MAAM,eAAe,CAAA;AAC3E,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC3F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,gBAAgB,CAAA;AAiBlC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;AAiB1E,wBAAgB,kBAAkB,CAChC,CAAC,EAAE,CAAC,EACJ,YAAY,EAAE,iBAAiB,EAAE,EACjC,QAAQ,EAAE,iBAAiB;AAC3B,+EAA+E;AAC/E,cAAc,CAAC,EAAE,MAAM,IAAI;AAC3B,oEAAoE;AACpE,QAAQ,CAAC,EAAE,qBAAqB,GAC/B,SAAS,CA0NX"}
1
+ {"version":3,"file":"productEntity.d.ts","sourceRoot":"","sources":["../../src/views/productEntity.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAA+B,MAAM,eAAe,CAAA;AAG3E,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC3F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,gBAAgB,CAAA;AAiBlC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAA;AAiB1E,wBAAgB,kBAAkB,CAChC,CAAC,EAAE,CAAC,EACJ,YAAY,EAAE,iBAAiB,EAAE,EACjC,QAAQ,EAAE,iBAAiB;AAC3B,+EAA+E;AAC/E,cAAc,CAAC,EAAE,MAAM,IAAI;AAC3B,oEAAoE;AACpE,QAAQ,CAAC,EAAE,qBAAqB,GAC/B,SAAS,CAqOX"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "status": "beta",
5
5
  "dependencies": []
6
6
  },
7
- "version": "0.12.3",
7
+ "version": "0.12.4",
8
8
  "description": "Fayz SDK — plugin-inventory plugin",
9
9
  "type": "module",
10
10
  "sideEffects": false,
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@tanstack/react-table": "^8.20.0",
31
- "@fayz-ai/core": "^0.22.0",
32
- "@fayz-ai/ui": "^0.22.0",
33
- "@fayz-ai/admin": "^0.22.0"
31
+ "@fayz-ai/core": "^0.23.0",
32
+ "@fayz-ai/ui": "^0.23.0",
33
+ "@fayz-ai/admin": "^0.23.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/react": "^18.3.0",