@fayz-ai/plugin-inventory 0.14.0 → 0.14.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.
@@ -21,6 +21,7 @@ export declare const MIGRATION_019_SEM_SALDO_NAO_SE_BAIXA = "-- Sem saldo n\u00E
21
21
  export declare const MIGRATION_020_O_PRODUTO_E_VARIAS_COISAS_AO_MESMO_TEMPO = "-- Um produto \u00E9 V\u00C1RIAS coisas ao mesmo tempo, e cada uma tem a sua ficha.\n--\n-- O V2 guardava o tipo do produto como UM valor dentro de `products.metadata`\n-- (\u00ABingredient\u00BB | \u00ABsale\u00BB | \u00ABintermediate\u00BB | \u00ABasset\u00BB). O V1 n\u00E3o: ele tem SETE\n-- interruptores independentes, e cada um liga uma ficha pr\u00F3pria no cadastro.\n-- Um shampoo de sal\u00E3o \u00E9 o caso que quebra o valor \u00FAnico \u2014 a casa o usa no\n-- atendimento (insumo) E o revende ao cliente (venda), e com um valor s\u00F3\n-- algu\u00E9m tem de escolher qual das duas fichas o produto pode ter.\n--\n-- Ent\u00E3o as flags viram COLUNAS, e cada ficha vira uma tabela com a chave no\n-- produto. `metadata->>productType` continua escrito pelo provider porque \u00E9\n-- dele que a faceta da lista (\u00ABInsumos / Venda / Intermedi\u00E1rios / Patrim\u00F4nio\u00BB,\n-- igual \u00E0 do V1) se serve; ele passa a ser DERIVADO das flags, n\u00E3o a origem.\n--\n-- As fichas s\u00E3o tabelas separadas, e n\u00E3o trinta colunas em\n-- `plg_inventory_product_details`, porque nenhuma casa usa as quatro: um sal\u00E3o\n-- nunca abre a de patrim\u00F4nio, e um restaurante nunca abre a de revenda\n-- profissional. Linha que n\u00E3o existe n\u00E3o custa leitura, e a ficha inteira some\n-- junto com o produto (`ON DELETE CASCADE`).\n\n-- ---------------------------------------------------------------------------\n-- 1. As flags e o resto dos Dados Gerais\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_inventory_product_details\n ADD COLUMN IF NOT EXISTS is_ingredient boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS is_for_sale boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS is_intermediate boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS is_asset boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS is_produced boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS is_resale boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS track_by_unit_label boolean NOT NULL DEFAULT false,\n -- Rastreabilidade na ENTRADA. No V1 vive na Classifica\u00E7\u00E3o e vale para\n -- qualquer produto: revenda tamb\u00E9m tem lote e validade.\n ADD COLUMN IF NOT EXISTS require_batch_for_stock boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS require_expiry_for_stock boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS commission_percent numeric(7,4) NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS professional_resale_price numeric(16,4),\n ADD COLUMN IF NOT EXISTS chart_account_id uuid,\n ADD COLUMN IF NOT EXISTS content_per_unit numeric(14,4),\n ADD COLUMN IF NOT EXISTS content_unit_id uuid,\n ADD COLUMN IF NOT EXISTS avg_purchase_price numeric(16,4),\n ADD COLUMN IF NOT EXISTS avg_purchase_price_updated_at timestamp with time zone;\n\nCOMMENT ON COLUMN public.plg_inventory_product_details.chart_account_id IS\n 'Conta de despesa em plg_financial_chart_of_accounts. Sem chave estrangeira de prop\u00F3sito: a conta de estoque existe em tenant que n\u00E3o instalou o financeiro.';\nCOMMENT ON COLUMN public.plg_inventory_product_details.content_per_unit IS\n 'Quanto de content_unit_id cabe em UMA measurement_unit_id (1 frasco cont\u00E9m 300 ml).';\nCOMMENT ON COLUMN public.plg_inventory_product_details.avg_purchase_price IS\n 'Custo m\u00E9dio, reescrito a cada entrada de estoque. avg_purchase_price_updated_at carimba a \u00FAltima.';\nCOMMENT ON COLUMN public.plg_inventory_product_details.track_by_unit_label IS\n 'Alto custo / etiquetado: cada unidade \u00E9 acompanhada individualmente.';\n\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_constraint\n WHERE conname = 'plg_inventory_product_details_content_unit_id_fkey'\n ) THEN\n ALTER TABLE public.plg_inventory_product_details\n ADD CONSTRAINT plg_inventory_product_details_content_unit_id_fkey\n FOREIGN KEY (content_unit_id)\n REFERENCES public.plg_inventory_measurement_units(id) ON DELETE SET NULL;\n END IF;\nEND $$;\n\n-- O cat\u00E1logo que j\u00E1 existe nasceu com UM tipo. Acender a flag correspondente\n-- mant\u00E9m a tela igual ao que a pessoa cadastrou \u2014 sem isto, 169 produtos\n-- abririam o formul\u00E1rio sem nenhuma ficha e pareceriam ter perdido o tipo.\nUPDATE public.plg_inventory_product_details d\n SET is_ingredient = (p.metadata ->> 'productType') = 'ingredient',\n is_for_sale = (p.metadata ->> 'productType') = 'sale',\n is_intermediate = (p.metadata ->> 'productType') = 'intermediate',\n is_asset = (p.metadata ->> 'productType') = 'asset'\n FROM public.products p\n WHERE p.id = d.product_id\n AND p.metadata ->> 'productType' IS NOT NULL\n AND NOT (d.is_ingredient OR d.is_for_sale OR d.is_intermediate OR d.is_asset);\n\n-- Produto que existe sem linha de detalhe nenhuma tamb\u00E9m precisa da flag, ou\n-- o formul\u00E1rio abre sem ficha e a lista perde a faceta.\nINSERT INTO public.plg_inventory_product_details\n (product_id, tenant_id, is_ingredient, is_for_sale, is_intermediate, is_asset)\nSELECT p.id, p.tenant_id,\n (p.metadata ->> 'productType') = 'ingredient',\n (p.metadata ->> 'productType') = 'sale',\n (p.metadata ->> 'productType') = 'intermediate',\n (p.metadata ->> 'productType') = 'asset'\n FROM public.products p\n WHERE p.metadata ->> 'productType' IS NOT NULL\n AND NOT EXISTS (\n SELECT 1 FROM public.plg_inventory_product_details d WHERE d.product_id = p.id\n )\nON CONFLICT (product_id) DO NOTHING;\n\n-- ---------------------------------------------------------------------------\n-- 2. C\u00F3digos de barras \u2014 v\u00E1rios por produto, um principal\n-- ---------------------------------------------------------------------------\n--\n-- O produto j\u00E1 tinha UM c\u00F3digo em `products.metadata->>barcode`. Um s\u00F3 n\u00E3o\n-- serve ao balc\u00E3o: a mesma mercadoria chega em embalagem de 500 ml e de 1 L,\n-- com c\u00F3digos diferentes, e quem bipa n\u00E3o escolhe qual.\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_barcodes (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n product_id uuid NOT NULL,\n barcode text NOT NULL,\n barcode_type text DEFAULT 'EAN-13' NOT NULL,\n description text,\n is_primary boolean DEFAULT false NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_inventory_product_barcodes_pkey PRIMARY KEY (id),\n CONSTRAINT plg_inventory_product_barcodes_product_id_fkey\n FOREIGN KEY (product_id) REFERENCES public.products(id) ON DELETE CASCADE,\n CONSTRAINT plg_inventory_product_barcodes_tenant_id_fkey\n FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_plg_inventory_product_barcodes_product\n ON public.plg_inventory_product_barcodes (product_id);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_plg_inventory_product_barcodes_code\n ON public.plg_inventory_product_barcodes (tenant_id, barcode);\n-- Principal \u00E9 UM. Sem isto dois \u00ABprincipais\u00BB convivem e quem l\u00EA a etiqueta\n-- pega o que vier primeiro.\nCREATE UNIQUE INDEX IF NOT EXISTS idx_plg_inventory_product_barcodes_primary\n ON public.plg_inventory_product_barcodes (product_id) WHERE is_primary;\n\n-- ---------------------------------------------------------------------------\n-- 3. Ficha de insumo\n-- ---------------------------------------------------------------------------\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_ingredient_info (\n product_id uuid NOT NULL,\n tenant_id uuid NOT NULL,\n minimum_quantity numeric(16,4) DEFAULT 0 NOT NULL,\n maximum_quantity numeric(16,4),\n reorder_point numeric(16,4),\n lead_time_days integer,\n shelf_life_days integer,\n storage_temperature text,\n allowed_supplier_ids uuid[],\n notes text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_inventory_product_ingredient_info_pkey PRIMARY KEY (product_id),\n CONSTRAINT plg_inventory_product_ingredient_info_product_id_fkey\n FOREIGN KEY (product_id) REFERENCES public.products(id) ON DELETE CASCADE,\n CONSTRAINT plg_inventory_product_ingredient_info_tenant_id_fkey\n FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE\n);\n\nCOMMENT ON COLUMN public.plg_inventory_product_ingredient_info.reorder_point IS\n 'Saldo em que a compra deve ser disparada. Fica ACIMA do m\u00EDnimo: o m\u00EDnimo \u00E9 o que n\u00E3o se pode furar, este \u00E9 o aviso que chega a tempo.';\n\n-- ---------------------------------------------------------------------------\n-- 4. Ficha de venda e os pre\u00E7os por canal\n-- ---------------------------------------------------------------------------\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_sale_info (\n product_id uuid NOT NULL,\n tenant_id uuid NOT NULL,\n menu_description text,\n allergens text,\n preparation_time_minutes integer,\n serves_quantity integer,\n calories integer,\n sold_by_weight boolean DEFAULT false NOT NULL,\n highlight_delivery boolean DEFAULT false NOT NULL,\n available_for_delivery boolean DEFAULT true NOT NULL,\n available_for_counter boolean DEFAULT true NOT NULL,\n notes text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_inventory_product_sale_info_pkey PRIMARY KEY (product_id),\n CONSTRAINT plg_inventory_product_sale_info_product_id_fkey\n FOREIGN KEY (product_id) REFERENCES public.products(id) ON DELETE CASCADE,\n CONSTRAINT plg_inventory_product_sale_info_tenant_id_fkey\n FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE\n);\n\n-- O canal \u00E9 texto livre, e n\u00E3o um CHECK: a lista do V1 (balc\u00E3o, delivery\n-- pr\u00F3prio, iFood, 99Food, Keeta) \u00E9 o card\u00E1pio de HOJE, e cada pra\u00E7a tem o seu.\n-- Um CHECK aqui obrigaria uma migration para aceitar o pr\u00F3ximo aplicativo.\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_sale_channels (\n id uuid DEFAULT gen_random_uuid() NOT NULL,\n tenant_id uuid NOT NULL,\n product_id uuid NOT NULL,\n channel text NOT NULL,\n price numeric(16,4) DEFAULT 0 NOT NULL,\n active boolean DEFAULT true NOT NULL,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_inventory_product_sale_channels_pkey PRIMARY KEY (id),\n CONSTRAINT plg_inventory_product_sale_channels_product_id_fkey\n FOREIGN KEY (product_id) REFERENCES public.products(id) ON DELETE CASCADE,\n CONSTRAINT plg_inventory_product_sale_channels_tenant_id_fkey\n FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_plg_inventory_product_sale_channels_one\n ON public.plg_inventory_product_sale_channels (product_id, channel);\n\n-- ---------------------------------------------------------------------------\n-- 5. Ficha de intermedi\u00E1rio\n-- ---------------------------------------------------------------------------\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_intermediate_info (\n product_id uuid NOT NULL,\n tenant_id uuid NOT NULL,\n yield_percentage numeric(7,2),\n preparation_time_minutes integer,\n shelf_life_hours integer,\n batch_size numeric(16,4),\n batch_unit_id uuid,\n storage_instructions text,\n notes text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_inventory_product_intermediate_info_pkey PRIMARY KEY (product_id),\n CONSTRAINT plg_inventory_product_intermediate_info_product_id_fkey\n FOREIGN KEY (product_id) REFERENCES public.products(id) ON DELETE CASCADE,\n CONSTRAINT plg_inventory_product_intermediate_info_batch_unit_id_fkey\n FOREIGN KEY (batch_unit_id) REFERENCES public.plg_inventory_measurement_units(id) ON DELETE SET NULL,\n CONSTRAINT plg_inventory_product_intermediate_info_tenant_id_fkey\n FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE\n);\n\n-- ---------------------------------------------------------------------------\n-- 6. Ficha de patrim\u00F4nio\n-- ---------------------------------------------------------------------------\n--\n-- O respons\u00E1vel aponta para `people` e n\u00E3o para uma tabela de funcion\u00E1rios:\n-- no V2 quem trabalha na casa \u00E9 uma pessoa como as outras.\n\nCREATE TABLE IF NOT EXISTS public.plg_inventory_product_asset_info (\n product_id uuid NOT NULL,\n tenant_id uuid NOT NULL,\n asset_value numeric(16,4) DEFAULT 0 NOT NULL,\n residual_value numeric(16,4) DEFAULT 0 NOT NULL,\n useful_life_months integer,\n depreciation_rate numeric(7,4),\n serial_number text,\n acquisition_date date,\n warranty_expiry_date date,\n asset_status text DEFAULT 'active' NOT NULL,\n asset_location text,\n responsible_person_id uuid,\n insurance_policy text,\n last_maintenance_date date,\n next_maintenance_date date,\n notes text,\n created_at timestamp with time zone DEFAULT now() NOT NULL,\n updated_at timestamp with time zone DEFAULT now() NOT NULL,\n CONSTRAINT plg_inventory_product_asset_info_pkey PRIMARY KEY (product_id),\n CONSTRAINT plg_inventory_product_asset_info_product_id_fkey\n FOREIGN KEY (product_id) REFERENCES public.products(id) ON DELETE CASCADE,\n CONSTRAINT plg_inventory_product_asset_info_responsible_person_id_fkey\n FOREIGN KEY (responsible_person_id) REFERENCES public.people(id) ON DELETE SET NULL,\n CONSTRAINT plg_inventory_product_asset_info_tenant_id_fkey\n FOREIGN KEY (tenant_id) REFERENCES public.tenants(id) ON DELETE CASCADE\n);\n\n-- ---------------------------------------------------------------------------\n-- 7. A cerca de cada ficha\n-- ---------------------------------------------------------------------------\n--\n-- Mesma regra do resto do plugin: a linha \u00E9 da casa que a escreveu, e a\n-- pergunta \u00E9 uma s\u00F3 (`user_tenant_ids()`), feita uma vez por consulta.\n\nDO $$\nDECLARE\n t text;\nBEGIN\n FOREACH t IN ARRAY ARRAY[\n 'plg_inventory_product_barcodes',\n 'plg_inventory_product_ingredient_info',\n 'plg_inventory_product_sale_info',\n 'plg_inventory_product_sale_channels',\n 'plg_inventory_product_intermediate_info',\n 'plg_inventory_product_asset_info'\n ]\n LOOP\n EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', t);\n EXECUTE format('ALTER TABLE public.%I FORCE ROW LEVEL SECURITY', t);\n EXECUTE format('CREATE INDEX IF NOT EXISTS idx_%s_tenant ON public.%I (tenant_id)', t, t);\n\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_select', t);\n EXECUTE format(\n 'CREATE POLICY %I ON public.%I FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))',\n t || '_select', t);\n\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_insert', t);\n EXECUTE format(\n 'CREATE POLICY %I ON public.%I FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()))',\n t || '_insert', t);\n\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_update', t);\n EXECUTE format(\n 'CREATE POLICY %I ON public.%I FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))',\n t || '_update', t);\n\n EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', t || '_delete', t);\n EXECUTE format(\n 'CREATE POLICY %I ON public.%I FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()))',\n t || '_delete', t);\n\n EXECUTE format('GRANT ALL ON TABLE public.%I TO authenticated', t);\n EXECUTE format('GRANT ALL ON TABLE public.%I TO service_role', t);\n END LOOP;\nEND $$;\n";
22
22
  export declare const MIGRATION_021_O_RAZAO_DIZ_DE_ONDE_VEIO = "-- ---------------------------------------------------------------------------\n-- O raz\u00E3o de estoque diz de onde veio, e a tela consegue abrir a nota.\n-- ---------------------------------------------------------------------------\n-- Comparando uma entrada migrada do V1 com uma que a V2 acabou de gravar:\n--\n-- V1 reason = \u00ABEntrada via DANFE NF-e 5411455\u00BB document_number = NULL\n-- V2 reason = NULL document_number = \u00ABNF-e 4693\u00BB\n--\n-- A tela de Movimenta\u00E7\u00F5es mostra o MOTIVO como legenda debaixo do produto, e \u00E9 a\n-- resposta para a pergunta n\u00BA 1 do FLOW.md do V1 \u2014 \u00ABde onde vieram essas entradas\n-- que eu n\u00E3o lancei?\u00BB. Nas linhas novas ela estava escrevendo \u00ABSem motivo\n-- registrado\u00BB sobre uma entrada que tinha motivo: a nota fiscal.\n--\n-- Duas causas, as duas aqui:\n--\n-- \u2460 `inventory_receive` NUNCA GRAVOU `reason`. A coluna n\u00E3o estava na lista do\n-- INSERT. O chamador at\u00E9 mandava a frase no cabe\u00E7alho (`notes`), mas o\n-- `coalesce(v_ln.notes, v_doc->>'notes')` d\u00E1 a vez \u00E0 anota\u00E7\u00E3o DA LINHA, que\n-- toda entrada de nota preenche com a descri\u00E7\u00E3o do item. A frase nunca\n-- chegava. Agora existe uma chave pr\u00F3pria: `reason` no cabe\u00E7alho, e nada muda\n-- para quem n\u00E3o a manda \u2014 continua NULL, como hoje.\n--\n-- \u2461 A VIEW ESCONDIA O CAMINHO DE VOLTA. `v_stock_movements` n\u00E3o expunha\n-- `document_type` nem `operation_id`, que s\u00E3o o que diz \u00ABisto \u00E9 uma NF-e\u00BB e\n-- \u00ABfoi esta opera\u00E7\u00E3o\u00BB. Sem eles a tela tem o n\u00FAmero da nota escrito e nenhum\n-- jeito de abri-la: `plg_fiscalbr_inbox_documents.inventory_operation_id`\n-- aponta para a mesma opera\u00E7\u00E3o, e \u00E9 por ali que o link se faz.\n--\n-- De quebra, tr\u00EAs colunas que o V1 mostra e a V2 n\u00E3o tinha como mostrar: a\n-- UNIDADE junto da quantidade (o V1 escreve \u00AB12 SR\u00BB, \u00AB100 UN\u00BB, e a V2 escrevia s\u00F3\n-- \u00AB12\u00BB), e o NOME do fornecedor em vez do uuid.\n--\n-- `security_invoker=true` fica: sem ele a view l\u00EA com os direitos de quem a\n-- criou, e uma view de movimento sem invoker \u00E9 vazamento entre contas.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE VIEW public.v_stock_movements\nWITH (security_invoker = true) AS\n SELECT sm.id,\n sm.tenant_id,\n sm.product_id,\n sm.quantity,\n sm.kind AS movement_type,\n sm.unit_cost,\n sm.total_cost,\n sm.source_location_id AS stock_location_id,\n sm.destination_location_id,\n sm.supplier_id,\n sm.document_number,\n sm.reason,\n sm.notes,\n sm.movement_date,\n sm.user_id,\n sm.batch_number,\n sm.expiration_date,\n sm.metadata,\n sm.created_at,\n p.name AS product_name,\n p.sku AS product_sku,\n sl.name AS stock_location_name,\n dl.name AS destination_location_name,\n -- NO FIM, e n\u00E3o no meio: `CREATE OR REPLACE VIEW` s\u00F3 sabe ACRESCENTAR coluna.\n -- Inserir uma antes de `product_name` faz o Postgres entender que a coluna\n -- est\u00E1 sendo RENOMEADA, e ele recusa \u2014 \u00ABcannot change name of view column\u00BB.\n sm.document_type,\n sm.operation_id,\n sm.measurement_unit_id,\n mu.abbreviation AS measurement_unit_abbreviation,\n sup.name AS supplier_name\n FROM plg_inventory_stock_movements sm\n LEFT JOIN products p ON p.id = sm.product_id\n LEFT JOIN plg_inventory_stock_locations sl ON sl.id = sm.source_location_id\n LEFT JOIN plg_inventory_stock_locations dl ON dl.id = sm.destination_location_id\n LEFT JOIN plg_inventory_measurement_units mu ON mu.id = sm.measurement_unit_id\n LEFT JOIN people sup ON sup.id = sm.supplier_id;\n\nCOMMENT ON VIEW public.v_stock_movements IS\n 'O raz\u00E3o de estoque para a tela: movimento + nome do produto, do local, da unidade e do fornecedor, mais document_type/operation_id para a linha conseguir abrir o documento que a originou.';\n\n-- \u2500\u2500 `inventory_receive` passa a gravar o motivo \u2500\u2500\u2500\u2500\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-- Corpo igual ao que est\u00E1 no cluster, com `reason` a mais. Reescrever de mem\u00F3ria\n-- \u00E9 como se perde uma regra; isto \u00E9 o texto real com duas linhas trocadas.\n\nCREATE OR REPLACE FUNCTION public.inventory_receive(p_location uuid, p_lines jsonb, p_document jsonb DEFAULT '{}'::jsonb, p_idempotency_key text DEFAULT NULL::text)\n RETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n SET search_path TO ''\nAS $function$\nDECLARE\n v_tenant uuid := app.inventory_require_tenant();\n v_key text := coalesce(nullif(btrim(p_idempotency_key), ''), 'receive:' || gen_random_uuid()::text);\n v_op record; v_unit uuid; v_line jsonb; v_ln record; v_no integer := 0; v_id uuid;\n v_movements jsonb := '[]'::jsonb; v_positions jsonb := '[]'::jsonb;\n v_doc jsonb := coalesce(p_document, '{}'::jsonb); v_supplier uuid; v_date date;\nBEGIN\n PERFORM app.inventory_check_lines(p_lines);\n v_unit := app.inventory_authorize_location(v_tenant, p_location, 'inventory.create');\n BEGIN\n v_supplier := (v_doc->>'supplier_id')::uuid;\n v_date := (v_doc->>'date')::date;\n EXCEPTION WHEN OTHERS THEN\n RAISE EXCEPTION 'inventory: invalid document: %', SQLERRM USING ERRCODE = '22023';\n END;\n IF v_supplier IS NOT NULL AND NOT EXISTS (SELECT 1 FROM public.people s WHERE s.id = v_supplier AND s.tenant_id = v_tenant) THEN\n RAISE EXCEPTION 'inventory: supplier % not found in this tenant', v_supplier USING ERRCODE = '22023';\n END IF;\n\n SELECT * INTO v_op FROM app.inventory_begin_operation(v_tenant, 'receive', v_key,\n jsonb_build_object('location', p_location, 'lines', p_lines, 'document', v_doc));\n IF v_op.existing IS NOT NULL THEN RETURN v_op.existing; END IF;\n\n FOR v_line IN SELECT * FROM jsonb_array_elements(p_lines) LOOP\n v_no := v_no + 1;\n SELECT * INTO v_ln FROM app.inventory_parse_line(v_tenant, v_line, 'positive');\n INSERT INTO public.plg_inventory_stock_movements\n (tenant_id, product_id, variant_id, kind, quantity, unit_cost, source_location_id, batch_number, expiration_date, measurement_unit_id,\n supplier_id, document_type, document_number, notes, reason, movement_date, idempotency_key, line_no, operation_id, metadata)\n VALUES\n (v_tenant, v_ln.product_id, v_ln.variant_id, 'in', v_ln.quantity, coalesce(v_ln.unit_cost, 0), p_location, v_ln.batch_number, v_ln.expiration_date, v_ln.measurement_unit_id,\n v_supplier, coalesce(v_doc->>'type', 'manual'), v_doc->>'ref', coalesce(v_ln.notes, v_doc->>'notes'),\n -- O MOTIVO, que \u00E9 do chamador e n\u00E3o do raz\u00E3o. Ver a migration.\n nullif(v_doc->>'reason', ''), coalesce(v_date, current_date),\n v_key, v_no, v_op.op_id, v_ln.declared)\n RETURNING id INTO v_id;\n v_movements := v_movements || app.inventory_movement_json(v_id);\n v_positions := v_positions || app.inventory_position_json(v_tenant, v_ln.product_id, p_location, v_ln.batch_number, v_ln.expiration_date);\n END LOOP;\n\n RETURN app.inventory_finish_operation(v_tenant, v_op.op_id, 'receive', v_key,\n jsonb_build_object('operation_id', v_op.op_id, 'kind', 'receive', 'idempotency_key', v_key,\n 'location_id', p_location, 'unit_id', v_unit, 'movements', v_movements, 'positions', v_positions),\n jsonb_build_object('location_id', p_location, 'unit_id', v_unit, 'lines', v_no, 'document_type', coalesce(v_doc->>'type', 'manual')));\nEND $function$;\n";
23
23
  export declare const MIGRATION_022_RECEBER_ESTOQUE_VOLTA_A_FUNCIONAR = "-- ---------------------------------------------------------------------------\n-- 022 \u2014 receber estoque volta a funcionar.\n--\n-- A 021 passou a gravar o `reason` em `inventory_receive` \u2014 e, na mesma linha,\n-- acrescentou `variant_id`. Esse campo n\u00E3o existe de dois jeitos:\n--\n-- \u00B7 `plg_inventory_stock_movements` N\u00C3O tem a coluna `variant_id` na cadeia\n-- deste plugin (s\u00F3 `reason`, desde a baseline). Num banco do zero o INSERT\n-- nomeia coluna inexistente.\n-- \u00B7 `app.inventory_parse_line` N\u00C3O devolve `variant_id` \u2014 os OUT dela s\u00E3o\n-- product_id, quantity, unit_cost, batch_number, expiration_date,\n-- measurement_unit_id, declared e notes. Ent\u00E3o `v_ln.variant_id` tamb\u00E9m\n-- n\u00E3o resolve nos clusters onde algu\u00E9m criou a coluna por fora.\n--\n-- Nenhum dos dois erros aparece ao CRIAR a fun\u00E7\u00E3o: o corpo de uma plpgsql s\u00F3 \u00E9\n-- analisado na primeira chamada. O resultado \u00E9 que RECEBER ESTOQUE estava\n-- quebrado \u2014 em produ\u00E7\u00E3o, onde a 021 j\u00E1 foi aplicada, e no banco de testes.\n-- Quem pegou foi a su\u00EDte pgTAP do invent\u00E1rio, com 14 asser\u00E7\u00F5es vermelhas; e\n-- ningu\u00E9m tinha visto porque a cadeia morria l\u00E1 na db/162, antes de chegar nos\n-- testes.\n--\n-- Esta migration rep\u00F5e a fun\u00E7\u00E3o SEM `variant_id`. O `reason`, que \u00E9 o motivo\n-- de existir da 021, fica. Variante de produto \u00E9 outra feature, e quando\n-- chegar vai precisar de coluna na cadeia e de sa\u00EDda na `parse_line` \u2014 as duas\n-- coisas, n\u00E3o meia.\n--\n-- N\u00E3o reescreve a 021: ela j\u00E1 est\u00E1 no ledger dos clusters, e arquivo aplicado\n-- n\u00E3o se edita. Idempotente.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.inventory_receive(p_location uuid, p_lines jsonb, p_document jsonb DEFAULT '{}'::jsonb, p_idempotency_key text DEFAULT NULL::text)\n RETURNS jsonb\n LANGUAGE plpgsql\n SECURITY DEFINER\n SET search_path TO ''\nAS $function$\nDECLARE\n v_tenant uuid := app.inventory_require_tenant();\n v_key text := coalesce(nullif(btrim(p_idempotency_key), ''), 'receive:' || gen_random_uuid()::text);\n v_op record; v_unit uuid; v_line jsonb; v_ln record; v_no integer := 0; v_id uuid;\n v_movements jsonb := '[]'::jsonb; v_positions jsonb := '[]'::jsonb;\n v_doc jsonb := coalesce(p_document, '{}'::jsonb); v_supplier uuid; v_date date;\nBEGIN\n PERFORM app.inventory_check_lines(p_lines);\n v_unit := app.inventory_authorize_location(v_tenant, p_location, 'inventory.create');\n BEGIN\n v_supplier := (v_doc->>'supplier_id')::uuid;\n v_date := (v_doc->>'date')::date;\n EXCEPTION WHEN OTHERS THEN\n RAISE EXCEPTION 'inventory: invalid document: %', SQLERRM USING ERRCODE = '22023';\n END;\n IF v_supplier IS NOT NULL AND NOT EXISTS (SELECT 1 FROM public.people s WHERE s.id = v_supplier AND s.tenant_id = v_tenant) THEN\n RAISE EXCEPTION 'inventory: supplier % not found in this tenant', v_supplier USING ERRCODE = '22023';\n END IF;\n\n SELECT * INTO v_op FROM app.inventory_begin_operation(v_tenant, 'receive', v_key,\n jsonb_build_object('location', p_location, 'lines', p_lines, 'document', v_doc));\n IF v_op.existing IS NOT NULL THEN RETURN v_op.existing; END IF;\n\n FOR v_line IN SELECT * FROM jsonb_array_elements(p_lines) LOOP\n v_no := v_no + 1;\n SELECT * INTO v_ln FROM app.inventory_parse_line(v_tenant, v_line, 'positive');\n INSERT INTO public.plg_inventory_stock_movements\n (tenant_id, product_id, kind, quantity, unit_cost, source_location_id, batch_number, expiration_date, measurement_unit_id,\n supplier_id, document_type, document_number, notes, reason, movement_date, idempotency_key, line_no, operation_id, metadata)\n VALUES\n (v_tenant, v_ln.product_id, 'in', v_ln.quantity, coalesce(v_ln.unit_cost, 0), p_location, v_ln.batch_number, v_ln.expiration_date, v_ln.measurement_unit_id,\n v_supplier, coalesce(v_doc->>'type', 'manual'), v_doc->>'ref', coalesce(v_ln.notes, v_doc->>'notes'),\n -- O MOTIVO, que \u00E9 do chamador e n\u00E3o do raz\u00E3o. Ver a migration.\n nullif(v_doc->>'reason', ''), coalesce(v_date, current_date),\n v_key, v_no, v_op.op_id, v_ln.declared)\n RETURNING id INTO v_id;\n v_movements := v_movements || app.inventory_movement_json(v_id);\n v_positions := v_positions || app.inventory_position_json(v_tenant, v_ln.product_id, p_location, v_ln.batch_number, v_ln.expiration_date);\n END LOOP;\n\n RETURN app.inventory_finish_operation(v_tenant, v_op.op_id, 'receive', v_key,\n jsonb_build_object('operation_id', v_op.op_id, 'kind', 'receive', 'idempotency_key', v_key,\n 'location_id', p_location, 'unit_id', v_unit, 'movements', v_movements, 'positions', v_positions),\n jsonb_build_object('location_id', p_location, 'unit_id', v_unit, 'lines', v_no, 'document_type', coalesce(v_doc->>'type', 'manual')));\nEND $function$;\n";
24
+ export declare const MIGRATION_023_A_RECEITA_SE_ESCREVE_COM_PERMISSAO = "-- ---------------------------------------------------------------------------\n-- 023 \u2014 a receita se escreve com permiss\u00E3o.\n--\n-- The baseline kept the pre-authz insert/update/delete policies on recipes and\n-- product categories next to the *_authz_* ones. Permissive policies are ORed,\n-- so `tenant_id IN user_tenant_ids()` let any member (viewer, staff, kitchen)\n-- write what inventory.create/edit/delete is supposed to gate.\n--\n-- The old SELECT policies stay on purpose: agenda's service kit\n-- (getServiceKit) reads recipes as reception/professional, who hold no\n-- inventory.read. Narrowing reads is a separate decision.\n-- ---------------------------------------------------------------------------\n\nDROP POLICY IF EXISTS plg_inventory_recipes_insert ON public.plg_inventory_recipes;\nDROP POLICY IF EXISTS plg_inventory_recipes_update ON public.plg_inventory_recipes;\nDROP POLICY IF EXISTS plg_inventory_recipes_delete ON public.plg_inventory_recipes;\n\nDROP POLICY IF EXISTS plg_inventory_product_categories_insert ON public.plg_inventory_product_categories;\nDROP POLICY IF EXISTS plg_inventory_product_categories_update ON public.plg_inventory_product_categories;\nDROP POLICY IF EXISTS plg_inventory_product_categories_delete ON public.plg_inventory_product_categories;\n";
24
25
  export declare const MIGRATIONS: Array<{
25
26
  id: string;
26
27
  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,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAwBzD,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,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAyBzD,CAAA"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "status": "beta",
5
5
  "dependencies": []
6
6
  },
7
- "version": "0.14.0",
7
+ "version": "0.14.1",
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.36.0",
32
- "@fayz-ai/ui": "^0.36.0",
33
- "@fayz-ai/admin": "^0.36.0"
31
+ "@fayz-ai/core": "^0.36.1",
32
+ "@fayz-ai/ui": "^0.37.0",
33
+ "@fayz-ai/admin": "^0.37.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/react": "^18.3.0",