@fayz-ai/plugin-fiscal-br 0.9.4 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,7 +20,7 @@ export declare const MIGRATION_018_A_NOTA_DO_V1_ALCANCA_A_CONTA = "-- A nota que
20
20
  export declare const MIGRATION_019_A_NOTA_EMITIDA_SE_CANCELA_PELO_V2 = "-- A nota emitida se consulta e se cancela pelo V2.\n--\n-- Quem fala com a PlugNotas \u00E9 a fun\u00E7\u00E3o `fiscalbr-plugnotas` (a chave s\u00F3 existe\n-- no servidor), mas ela roda com o JWT de quem clicou e n\u00E3o grava nada direto:\n-- pergunta aqui se a pessoa pode, e devolve o resultado aqui. Uma porta de\n-- leitura, uma de escrita, a permiss\u00E3o conferida nas duas.\n--\n-- consultar e baixar o PDF \u2192 financial.read na unidade da nota\n-- cancelar \u2192 financial.edit na unidade da nota\n--\n-- A espelho em `documents.status` anda junto: as duas tabelas s\u00E3o a mesma nota\n-- vista de dois lugares (005), e um status divergente entre elas \u00E9 a nota\n-- dizendo duas coisas. Idempotente.\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_for_provider(p_document_id uuid, p_action text)\nRETURNS jsonb\nLANGUAGE plpgsql\nSTABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v public.plg_fiscalbr_documents%ROWTYPE;\nBEGIN\n SELECT * INTO v FROM public.plg_fiscalbr_documents\n WHERE document_id = p_document_id AND tenant_id = app.current_tenant_id();\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscal: note not found' USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission(\n CASE WHEN p_action = 'cancel' THEN 'financial.edit' ELSE 'financial.read' END, v.unit_id);\n RETURN jsonb_build_object(\n 'document_id', v.document_id, 'model', v.model, 'status', v.status,\n 'environment', v.environment, 'provider_document_id', v.provider_document_id,\n 'cancelled_at', v.cancelled_at, 'access_key', v.access_key, 'number', v.number);\nEND $$;\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_apply(\n p_document_id uuid,\n p_action text,\n p_patch jsonb,\n p_event jsonb DEFAULT NULL\n)\nRETURNS void\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v public.plg_fiscalbr_documents%ROWTYPE;\n v_status text := p_patch ->> 'status';\nBEGIN\n SELECT * INTO v FROM public.plg_fiscalbr_documents\n WHERE document_id = p_document_id AND tenant_id = app.current_tenant_id()\n FOR UPDATE;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscal: note not found' USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission(\n CASE WHEN p_action = 'cancel' THEN 'financial.edit' ELSE 'financial.read' END, v.unit_id);\n IF v_status IS NOT NULL AND v_status NOT IN ('processing','authorized','rejected','error','cancelled','substituted') THEN\n RAISE EXCEPTION 'fiscal: unknown status %', v_status USING ERRCODE = '22023';\n END IF;\n\n -- Only what the provider answers; identity (model, unit, order) never moves.\n UPDATE public.plg_fiscalbr_documents SET\n status = COALESCE(v_status, status),\n number = COALESCE(p_patch ->> 'number', number),\n series = COALESCE(p_patch ->> 'series', series),\n access_key = COALESCE(p_patch ->> 'access_key', access_key),\n verification_code = COALESCE(p_patch ->> 'verification_code', verification_code),\n status_payload = COALESCE(p_patch -> 'status_payload', status_payload),\n rejection_payload = CASE WHEN p_patch ? 'rejection_payload' THEN p_patch -> 'rejection_payload' ELSE rejection_payload END,\n authorized_at = COALESCE((p_patch ->> 'authorized_at')::timestamptz, authorized_at),\n last_polled_at = CASE WHEN p_action = 'poll' THEN now() ELSE last_polled_at END,\n cancelled_at = CASE WHEN p_patch ? 'cancelled_at' THEN (p_patch ->> 'cancelled_at')::timestamptz ELSE cancelled_at END,\n cancel_reason = CASE WHEN p_patch ? 'cancel_reason' THEN p_patch ->> 'cancel_reason' ELSE cancel_reason END,\n cancelled_by_user_id = CASE WHEN p_action = 'cancel' THEN auth.uid()\n WHEN p_patch ? 'cancelled_at' AND p_patch -> 'cancelled_at' = 'null'::jsonb THEN NULL\n ELSE cancelled_by_user_id END\n WHERE document_id = p_document_id;\n\n IF v_status IS NOT NULL THEN\n UPDATE public.documents SET status = v_status WHERE id = p_document_id AND status IS DISTINCT FROM v_status;\n END IF;\n\n IF p_event IS NOT NULL THEN\n INSERT INTO public.plg_fiscalbr_document_events\n (tenant_id, unit_id, document_id, event_type, status, justification, response_payload, created_by_user_id)\n VALUES (v.tenant_id, v.unit_id, p_document_id, p_event ->> 'event_type', p_event ->> 'status',\n p_event ->> 'justification', p_event -> 'response_payload', auth.uid());\n END IF;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_note_for_provider(uuid, text) FROM PUBLIC, anon;\nREVOKE ALL ON FUNCTION public.fiscalbr_note_apply(uuid, text, jsonb, jsonb) FROM PUBLIC, anon;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_note_for_provider(uuid, text) TO authenticated;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_note_apply(uuid, text, jsonb, jsonb) TO authenticated;\n";
21
21
  export declare const MIGRATION_020_A_NOTA_ACHA_O_FORNECEDOR_PELO_CNPJ = "-- A nota acha o fornecedor pelo CNPJ \u2014 e, se n\u00E3o achar, cadastra.\n--\n-- O est\u00E1gio (009) s\u00F3 casava o CNPJ no momento do import. As notas que vieram\n-- do V1 atravessaram sem o v\u00EDnculo (003 deixou para \"a costura\"), e um\n-- fornecedor cadastrado depois nunca era religado: 1029 notas com o fornecedor\n-- no cadastro e a tela dizendo \"n\u00E3o est\u00E1 no cadastro\", e o t\u00EDtulo aceito no V2\n-- nascendo sem contraparte (o tra\u00E7o em Contas a Pagar).\n--\n-- O V1 nunca pediu cadastro: ao aceitar, `resolveSupplierId` procura o\n-- fornecedor pelo CNPJ e, sem achar, cria com o nome e o CNPJ da nota. Esta\n-- fun\u00E7\u00E3o \u00E9 esse gesto, com duas pontas:\n--\n-- p_create = false s\u00F3 casa (ao abrir a nota)\n-- p_create = true casa ou cria (antes de aceitar)\n--\n-- O t\u00EDtulo da nota, se j\u00E1 existe e est\u00E1 SEM contraparte, recebe a mesma pessoa.\n-- Nunca troca um v\u00EDnculo gravado. Idempotente.\n\nCREATE OR REPLACE FUNCTION public.plg_fiscalbr_supplier_for_cnpj(p_tenant uuid, p_cnpj text)\nRETURNS uuid\nLANGUAGE sql\nSTABLE\nSET search_path TO ''\nAS $$\n SELECT p.id FROM public.people p\n WHERE p.tenant_id = p_tenant\n AND p_cnpj <> ''\n AND public.plg_fiscalbr_digits(p.document_number) = p_cnpj\n ORDER BY (p.kind = 'supplier') DESC, p.created_at\n LIMIT 1\n$$;\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_link_inbox_supplier(p_document_id uuid, p_create boolean DEFAULT false)\nRETURNS jsonb\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_cnpj text;\n v_party uuid;\n v_created boolean := false;\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE id = p_document_id AND tenant_id = v_tenant;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscal: inbox note not found' USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission(\n CASE WHEN p_create THEN 'financial.create' ELSE 'financial.read' END, v_doc.unit_id);\n\n v_party := v_doc.supplier_party_id;\n v_cnpj := public.plg_fiscalbr_digits(v_doc.supplier_cnpj);\n\n IF v_party IS NULL THEN\n v_party := public.plg_fiscalbr_supplier_for_cnpj(v_tenant, v_cnpj);\n END IF;\n\n -- Without a CNPJ there is nothing to match on; a name-only person would be a\n -- duplicate waiting to happen, so creation needs the document.\n IF v_party IS NULL AND p_create AND v_cnpj <> '' THEN\n INSERT INTO public.people (tenant_id, kind, name, document_number)\n VALUES (v_tenant, 'supplier', coalesce(nullif(trim(v_doc.supplier_name), ''), 'Fornecedor ' || v_cnpj), v_cnpj)\n RETURNING id INTO v_party;\n v_created := true;\n END IF;\n\n IF v_party IS NOT NULL AND v_doc.supplier_party_id IS NULL THEN\n UPDATE public.plg_fiscalbr_inbox_documents\n SET supplier_party_id = v_party\n WHERE id = v_doc.id AND supplier_party_id IS NULL;\n END IF;\n\n IF v_party IS NOT NULL AND v_doc.invoice_id IS NOT NULL THEN\n UPDATE public.plg_financial_invoices\n SET counterparty_person_id = v_party\n WHERE id = v_doc.invoice_id AND tenant_id = v_tenant AND counterparty_person_id IS NULL;\n END IF;\n\n RETURN jsonb_build_object('supplier_party_id', v_party, 'created', v_created);\nEND;\n$$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_link_inbox_supplier(uuid, boolean) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_link_inbox_supplier(uuid, boolean) TO authenticated;\n\n-- Backfill: match only, NULLs only. Nothing is created here \u2014 creating a person\n-- is the operator's gesture (accepting), not a migration's.\nWITH p AS (\n SELECT DISTINCT ON (tenant_id, dg) tenant_id, dg, id\n FROM (SELECT tenant_id, id, kind, created_at, public.plg_fiscalbr_digits(document_number) AS dg\n FROM public.people\n WHERE document_number IS NOT NULL) x\n WHERE length(dg) = 14\n ORDER BY tenant_id, dg, (kind = 'supplier') DESC, created_at\n)\nUPDATE public.plg_fiscalbr_inbox_documents d\n SET supplier_party_id = p.id\n FROM p\n WHERE d.supplier_party_id IS NULL\n AND p.tenant_id = d.tenant_id\n AND p.dg = public.plg_fiscalbr_digits(d.supplier_cnpj);\n\nUPDATE public.plg_financial_invoices i\n SET counterparty_person_id = d.supplier_party_id\n FROM public.plg_fiscalbr_inbox_documents d\n WHERE d.invoice_id = i.id\n AND d.tenant_id = i.tenant_id\n AND d.supplier_party_id IS NOT NULL\n AND i.counterparty_person_id IS NULL;\n";
22
22
  export declare const MIGRATION_021_USO_E_CONSUMO_ENTRA_NO_ESTOQUE = "-- \"Materiais de Uso e Consumo\" vira um tipo de registro da nota recebida.\n--\n-- \u00C9 o que a casa gasta sem vender nem transformar \u2014 luva, descart\u00E1vel, material\n-- de limpeza \u2014 e que ainda assim se conta no estoque. Entra no estoque como o\n-- insumo: precisa de produto vinculado e gera movimento ao aceitar.\n--\n-- Aditivo e idempotente. A fun\u00E7\u00E3o de aceite \u00E9 REMENDADA, n\u00E3o reescrita (mesmo\n-- padr\u00E3o da 015): copiar o corpo da 010 apagaria o `reason` que a 015 enxertou.\n\nALTER TABLE public.plg_fiscalbr_inbox_items\n DROP CONSTRAINT IF EXISTS plg_fiscalbr_inbox_items_entry_type_chk;\nALTER TABLE public.plg_fiscalbr_inbox_items\n ADD CONSTRAINT plg_fiscalbr_inbox_items_entry_type_chk\n CHECK (entry_type IS NULL OR entry_type IN ('insumo','revenda','patrimonio','uso_consumo','despesa','servico'));\n\nALTER TABLE public.plg_fiscalbr_supplier_product_map\n DROP CONSTRAINT IF EXISTS plg_fiscalbr_supplier_product_map_entry_type_chk;\nALTER TABLE public.plg_fiscalbr_supplier_product_map\n ADD CONSTRAINT plg_fiscalbr_supplier_product_map_entry_type_chk\n CHECK (entry_type IS NULL OR entry_type IN ('insumo','revenda','patrimonio','uso_consumo','despesa','servico'));\n\nDO $m$\nDECLARE\n v_def text;\n v_antes text := $q$entry_type IN ('insumo','revenda','patrimonio')$q$;\n v_depois text := $q$entry_type IN ('insumo','revenda','patrimonio','uso_consumo')$q$;\nBEGIN\n SELECT pg_get_functiondef(p.oid) INTO v_def\n FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_accept_inbox_document';\n\n IF v_def IS NULL THEN\n RAISE EXCEPTION 'fiscalbr/021: fiscalbr_accept_inbox_document n\u00E3o existe \u2014 aplicar 009/010 antes';\n END IF;\n\n IF position('uso_consumo' in v_def) > 0 THEN RETURN; END IF;\n\n IF position(v_antes in v_def) = 0 THEN\n RAISE EXCEPTION 'fiscalbr/021: o corpo de fiscalbr_accept_inbox_document n\u00E3o \u00E9 o esperado \u2014 confira \u00E0 m\u00E3o em vez de deixar passar calado';\n END IF;\n\n EXECUTE replace(v_def, v_antes, v_depois);\nEND\n$m$;\n";
23
- export declare const MIGRATION_022_A_NOTA_DO_V1_TRAZ_O_XML = "-- ---------------------------------------------------------------------------\n-- The V1 bridge carries the note's XML.\n-- ---------------------------------------------------------------------------\n-- 003 dropped `xml_content` because, back then, V2 had no column for it (016\n-- added one). Without the file a note copied from the V1 cannot be downloaded,\n-- and a note the V1 never broke into lines arrives with zero items and no way\n-- to rebuild them. Only the mapping changes; rows already copied are not touched.\n-- ---------------------------------------------------------------------------\n\nUPDATE migration.allowlist\n SET columns = columns || jsonb_build_object('xml_content', 'xml_content')\n WHERE source_table = 'dfe_inbox'\n AND columns->>'xml_content' IS DISTINCT FROM 'xml_content';\n";
23
+ export declare const MIGRATION_022_A_NOTA_DO_V1_TRAZ_O_XML = "-- ---------------------------------------------------------------------------\n-- The V1 bridge carries the note's XML.\n-- ---------------------------------------------------------------------------\n-- 003 dropped `xml_content` because, back then, V2 had no column for it (016\n-- added one). Without the file a note copied from the V1 cannot be downloaded,\n-- and a note the V1 never broke into lines arrives with zero items and no way\n-- to rebuild them. Only the mapping changes; rows already copied are not touched.\n-- ---------------------------------------------------------------------------\n\n-- The row is already frozen, and trg_allowlist_frozen refuses a plain write;\n-- from zero this file stopped the rest of fiscal_br. A migration file is the\n-- one writer the hatch exists for, and SET LOCAL ends with the transaction.\nSET LOCAL migration.allowlist_unlock = 'on';\n\nUPDATE migration.allowlist\n SET columns = columns || jsonb_build_object('xml_content', 'xml_content')\n WHERE source_table = 'dfe_inbox'\n AND columns->>'xml_content' IS DISTINCT FROM 'xml_content';\n";
24
24
  export declare const MIGRATION_023_A_NOTA_ACEITA_SEM_LINHAS_RECEBE_AS_LINHAS = "-- ---------------------------------------------------------------------------\n-- An accepted note that has no lines gets its lines, and nothing else.\n-- ---------------------------------------------------------------------------\n-- Notes copied from the V1 can carry a payable (or stock) and zero lines: the\n-- V1 never split their XML. Restaging is refused for them on purpose (it would\n-- rewrite the decision that became stock and payable), so they showed 0/0\n-- forever. This writes ONLY the missing lines, from the stored XML, and only\n-- while the note has none; the document, its payable and its instalments stay.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_fill_inbox_lines(p_document_id uuid, p_items jsonb)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_cnpj text;\n v_item jsonb;\n v_map public.plg_fiscalbr_supplier_product_map%ROWTYPE;\n v_n integer := 0;\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = v_tenant AND id = p_document_id\n FOR UPDATE; -- two opens of the same note must not both see \"no lines\"\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr: nota n\u00E3o encontrada' USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.create', v_doc.unit_id);\n\n IF EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items\n WHERE tenant_id = v_tenant AND inbox_document_id = v_doc.id) THEN\n RETURN jsonb_build_object('document_id', v_doc.id, 'items', 0, 'already_had_lines', true);\n END IF;\n\n v_cnpj := public.plg_fiscalbr_digits(v_doc.supplier_cnpj);\n\n FOR v_item IN SELECT * FROM jsonb_array_elements(coalesce(p_items, '[]'::jsonb)) LOOP\n v_n := v_n + 1;\n v_map := NULL;\n SELECT * INTO v_map FROM public.plg_fiscalbr_supplier_product_map\n WHERE tenant_id = v_tenant AND supplier_cnpj = v_cnpj\n AND supplier_product_code = coalesce(v_item->>'supplier_product_code', '');\n\n INSERT INTO public.plg_fiscalbr_inbox_items (\n tenant_id, inbox_document_id, item_number, supplier_product_code, ean_barcode,\n description, ncm, cest, cfop, unit_commercial, quantity_commercial,\n unit_price_commercial, total_price, discount, freight, other_expenses,\n icms_cst, icms_csosn, icms_base, icms_rate, icms_value,\n pis_value, cofins_value, ipi_value, raw_xml_item,\n product_id, entry_type, chart_account_id, stock_unit_id, conversion_factor,\n cost_mode, reconciled)\n VALUES (\n v_tenant, v_doc.id, coalesce((v_item->>'item_number')::int, v_n),\n v_item->>'supplier_product_code', v_item->>'ean_barcode',\n v_item->>'description', v_item->>'ncm', v_item->>'cest', v_item->>'cfop',\n v_item->>'unit', (v_item->>'quantity')::numeric,\n (v_item->>'unit_price')::numeric, (v_item->>'total')::numeric,\n (v_item->>'discount')::numeric, (v_item->>'freight')::numeric,\n (v_item->>'other')::numeric,\n v_item->>'icms_cst', v_item->>'icms_csosn', (v_item->>'icms_base')::numeric,\n (v_item->>'icms_rate')::numeric, (v_item->>'icms_value')::numeric,\n (v_item->>'pis_value')::numeric, (v_item->>'cofins_value')::numeric,\n (v_item->>'ipi_value')::numeric, v_item,\n v_map.product_id, v_map.entry_type, v_map.chart_account_id, v_map.stock_unit_id,\n coalesce(v_map.conversion_factor, 1), coalesce(v_map.cost_mode, 'novo'),\n v_map.product_id IS NOT NULL);\n END LOOP;\n\n RETURN jsonb_build_object('document_id', v_doc.id, 'items', v_n, 'already_had_lines', false);\nEND;\n$function$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_fill_inbox_lines(uuid, jsonb) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_fill_inbox_lines(uuid, jsonb) TO authenticated;\n";
25
25
  export declare const MIGRATION_024_A_NOTA_NAO_APONTA_PARA_CONTA_QUE_SUMIU = "-- ---------------------------------------------------------------------------\n-- A note never points at a payable (or stock movement) that no longer exists.\n-- ---------------------------------------------------------------------------\n-- invoice_id and stock_movement_id cross into other plugins, so they carry no\n-- foreign key. When a re-copy rebuilt a tenant's payables under new ids, the\n-- notes kept the old ones: the screen read \"already accepted\" and hid every\n-- action, while Contas a Pagar had nothing. This heals a note: a dead payable\n-- link is re-pointed to the live payable of the same NF-e when exactly one\n-- exists, otherwise cleared; a dead movement link is cleared, and the note\n-- stops counting as stocked only when that dead movement was all it had. A\n-- cleared note can be accepted again, which writes the payable and the stock.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_fiscalbr_heal_note_links(p_tenant uuid, p_document_id uuid)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_invoice uuid;\n v_matches integer;\n v_dead_mv integer := 0;\n v_relinked boolean := false;\n v_released boolean := false;\n v_unstocked boolean := false;\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = p_tenant AND id = p_document_id\n FOR UPDATE;\n IF NOT FOUND THEN\n RETURN jsonb_build_object('document_id', p_document_id, 'changed', false);\n END IF;\n\n IF v_doc.invoice_id IS NOT NULL AND NOT EXISTS (\n SELECT 1 FROM public.plg_financial_invoices f WHERE f.id = v_doc.invoice_id) THEN\n -- Same NF-e: the key this plugin writes, or the V1 conciliation's text for the same supplier.\n SELECT count(*), min(f.id::text)::uuid INTO v_matches, v_invoice\n FROM public.plg_financial_invoices f\n WHERE f.tenant_id = p_tenant AND f.direction = 'payable' AND f.cancelled_at IS NULL\n AND (f.idempotency_key = 'danfe:' || v_doc.access_key\n OR (v_doc.supplier_party_id IS NOT NULL\n AND f.counterparty_person_id = v_doc.supplier_party_id\n AND f.description ~ ('NF-e 0*' || ltrim(v_doc.nfe_number, '0') || '(\\D|$)')))\n AND NOT EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_documents o\n WHERE o.tenant_id = p_tenant AND o.invoice_id = f.id AND o.id <> v_doc.id);\n IF v_matches = 1 THEN\n v_relinked := true;\n ELSE\n v_invoice := NULL;\n v_released := true;\n END IF;\n UPDATE public.plg_fiscalbr_inbox_documents SET invoice_id = v_invoice, updated_at = now()\n WHERE id = v_doc.id;\n END IF;\n\n UPDATE public.plg_fiscalbr_inbox_items i SET stock_movement_id = NULL, updated_at = now()\n WHERE i.tenant_id = p_tenant AND i.inbox_document_id = v_doc.id\n AND i.stock_movement_id IS NOT NULL\n AND NOT EXISTS (SELECT 1 FROM public.plg_inventory_stock_movements m WHERE m.id = i.stock_movement_id);\n GET DIAGNOSTICS v_dead_mv = ROW_COUNT;\n\n IF v_dead_mv > 0 AND v_doc.stocked_at IS NOT NULL\n AND v_doc.inventory_operation_id IS NULL\n AND NOT EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items i\n WHERE i.tenant_id = p_tenant AND i.inbox_document_id = v_doc.id\n AND i.stock_movement_id IS NOT NULL) THEN\n UPDATE public.plg_fiscalbr_inbox_documents SET stocked_at = NULL, updated_at = now()\n WHERE id = v_doc.id;\n v_unstocked := true;\n END IF;\n\n RETURN jsonb_build_object('document_id', v_doc.id,\n 'changed', v_relinked OR v_released OR v_dead_mv > 0,\n 'relinked', v_relinked, 'released', v_released,\n 'dead_movements', v_dead_mv, 'unstocked', v_unstocked);\nEND;\n$function$;\n\nREVOKE ALL ON FUNCTION public.plg_fiscalbr_heal_note_links(uuid, uuid) FROM PUBLIC, anon, authenticated;\n\n-- The screen heals the note it opens, so a future re-copy cannot strand one again.\nCREATE OR REPLACE FUNCTION public.fiscalbr_heal_inbox_note(p_document_id uuid)\nRETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_tenant uuid := public.plg_financial_require_tenant();\n v_unit uuid;\nBEGIN\n SELECT unit_id INTO v_unit FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = v_tenant AND id = p_document_id;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr: nota n\u00E3o encontrada' USING ERRCODE = 'P0002';\n END IF;\n PERFORM public.plg_financial_require_permission('financial.create', v_unit);\n RETURN public.plg_fiscalbr_heal_note_links(v_tenant, p_document_id);\nEND;\n$function$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_heal_inbox_note(uuid) FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.fiscalbr_heal_inbox_note(uuid) TO authenticated;\n\n-- Heal what is already stranded.\nDO $backfill$\nDECLARE r record;\nBEGIN\n FOR r IN\n SELECT DISTINCT d.tenant_id, d.id\n FROM public.plg_fiscalbr_inbox_documents d\n LEFT JOIN public.plg_fiscalbr_inbox_items i\n ON i.tenant_id = d.tenant_id AND i.inbox_document_id = d.id\n WHERE (d.invoice_id IS NOT NULL\n AND NOT EXISTS (SELECT 1 FROM public.plg_financial_invoices f WHERE f.id = d.invoice_id))\n OR (i.stock_movement_id IS NOT NULL\n AND NOT EXISTS (SELECT 1 FROM public.plg_inventory_stock_movements m WHERE m.id = i.stock_movement_id))\n LOOP\n PERFORM public.plg_fiscalbr_heal_note_links(r.tenant_id, r.id);\n END LOOP;\nEND\n$backfill$;\n";
26
26
  export declare const MIGRATION_025_A_CONTA_DA_NOTA_FECHA_COM_O_TOTAL = "-- ---------------------------------------------------------------------------\n-- The payable a note opens lists products as products, and adds up to its total.\n-- ---------------------------------------------------------------------------\n-- Two defects in the lines fiscalbr_accept_inbox_document wrote:\n-- 1. `itemKind` carried the fiscal entry type ('insumo', 'revenda'\u2026), which the\n-- financial screens do not know, so a linked product showed as \"Insumo\"\n-- with no product. The kind is 'product' when the line has one; the entry\n-- type moves to `entryType`, and product/chart account go to their columns.\n-- 2. Only the product lines were written, so freight, insurance, other\n-- expenses, IPI, ICMS-ST and discount were in the total but in no line\n-- (NF-e 1430116: R$ 480,47 owed, R$ 478,47 listed). Each becomes a line,\n-- and whatever still differs from the instalments becomes one more.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_fiscalbr_note_charge_lines(\n p_tenant uuid, p_document_id uuid, p_invoice_id uuid, p_total numeric)\nRETURNS integer LANGUAGE plpgsql SECURITY DEFINER SET search_path TO '' AS $function$\nDECLARE\n v_doc public.plg_fiscalbr_inbox_documents%ROWTYPE;\n v_sum numeric;\n v_rest numeric;\n v_n integer := 0;\n v_c record;\nBEGIN\n SELECT * INTO v_doc FROM public.plg_fiscalbr_inbox_documents\n WHERE tenant_id = p_tenant AND id = p_document_id;\n IF NOT FOUND OR p_invoice_id IS NULL THEN RETURN 0; END IF;\n IF EXISTS (SELECT 1 FROM public.items\n WHERE tenant_id = p_tenant AND invoice_id = p_invoice_id\n AND metadata ? 'noteCharge') THEN\n RETURN 0;\n END IF;\n -- Only a payable that already lists the note's products: a V1 copy with no lines\n -- would get its whole total as one \"difference\" line.\n IF NOT EXISTS (SELECT 1 FROM public.items\n WHERE tenant_id = p_tenant AND invoice_id = p_invoice_id\n AND metadata ? 'cfop') THEN\n RETURN 0;\n END IF;\n\n FOR v_c IN\n SELECT * FROM (VALUES\n (1, 'freight', 'Frete', round(coalesce(v_doc.total_freight, 0), 2)),\n (2, 'insurance', 'Seguro', round(coalesce(v_doc.total_insurance, 0), 2)),\n (3, 'other', 'Outras despesas da nota', round(coalesce(v_doc.total_other, 0), 2)),\n (4, 'ipi', 'IPI', round(coalesce(v_doc.total_ipi_value, 0), 2)),\n (5, 'icms_st', 'ICMS-ST', round(coalesce(v_doc.total_icms_st_value, 0), 2)),\n (6, 'discount', 'Desconto da nota', -round(coalesce(v_doc.total_discount, 0), 2))\n ) AS c(ord, code, label, amount)\n WHERE amount <> 0\n ORDER BY ord\n LOOP\n INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)\n VALUES (p_tenant, p_invoice_id, v_c.label, 1, v_c.amount, v_c.amount, 1000 + v_c.ord,\n jsonb_build_object('itemKind', 'other', 'noteCharge', v_c.code));\n v_n := v_n + 1;\n END LOOP;\n\n SELECT coalesce(sum(total), 0) INTO v_sum FROM public.items\n WHERE tenant_id = p_tenant AND invoice_id = p_invoice_id AND cancelled_at IS NULL;\n v_rest := round(coalesce(p_total, 0) - v_sum, 2);\n IF v_rest <> 0 THEN\n INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)\n VALUES (p_tenant, p_invoice_id, 'Diferen\u00E7a entre a nota e as parcelas', 1, v_rest, v_rest, 1099,\n jsonb_build_object('itemKind', 'other', 'noteCharge', 'instalment_difference'));\n v_n := v_n + 1;\n END IF;\n RETURN v_n;\nEND;\n$function$;\n\nREVOKE ALL ON FUNCTION public.plg_fiscalbr_note_charge_lines(uuid, uuid, uuid, numeric) FROM PUBLIC, anon, authenticated;\n\nDO $m$\nDECLARE\n v_def text;\n v_start integer;\n v_end integer;\n v_marker text := 'INSERT INTO public.items (tenant_id, invoice_id, name, quantity, unit_price, total, sort_order, metadata)';\n v_close text := 'ON CONFLICT DO NOTHING;';\n v_new text := $q$INSERT INTO public.items (tenant_id, invoice_id, product_id, chart_account_id, name, quantity, unit_price, total, sort_order, metadata)\n SELECT v_tenant, v_invoice, i.product_id, coalesce(i.chart_account_id, v_doc.default_chart_account_id),\n i.description, i.quantity_commercial, i.unit_price_commercial,\n round(coalesce(i.total_price, 0), 2), i.item_number,\n jsonb_strip_nulls(jsonb_build_object(\n 'itemKind', CASE WHEN i.product_id IS NOT NULL THEN 'product' ELSE 'other' END,\n 'referenceId', i.product_id, 'entryType', i.entry_type,\n 'chartAccountId', coalesce(i.chart_account_id, v_doc.default_chart_account_id),\n 'ncm', i.ncm, 'cfop', i.cfop,\n 'supplierProductCode', i.supplier_product_code))\n FROM public.plg_fiscalbr_inbox_items i\n WHERE i.tenant_id = v_tenant AND i.inbox_document_id = v_doc.id\n ORDER BY i.item_number\n ON CONFLICT DO NOTHING;\n PERFORM public.plg_fiscalbr_note_charge_lines(v_tenant, v_doc.id, v_invoice, v_total);$q$;\nBEGIN\n SELECT pg_get_functiondef(p.oid) INTO v_def\n FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_accept_inbox_document';\n IF v_def IS NULL THEN\n RAISE EXCEPTION 'fiscalbr/025: fiscalbr_accept_inbox_document does not exist';\n END IF;\n IF position('plg_fiscalbr_note_charge_lines' in v_def) > 0 THEN RETURN; END IF;\n\n v_start := position(v_marker in v_def);\n IF v_start = 0 THEN\n RAISE EXCEPTION 'fiscalbr/025: unexpected body of fiscalbr_accept_inbox_document \u2014 check by hand';\n END IF;\n v_end := v_start + position(v_close in substr(v_def, v_start)) - 1 + length(v_close);\n EXECUTE substr(v_def, 1, v_start - 1) || v_new || substr(v_def, v_end);\nEND\n$m$;\n\n-- The payables already opened by notes: same kind/product fix, and the missing charge lines.\nALTER TABLE public.items DISABLE TRIGGER items_invoice_line_immutable;\n\nUPDATE public.items it\n SET product_id = coalesce(it.product_id, (it.metadata->>'referenceId')::uuid),\n metadata = jsonb_set(it.metadata || jsonb_build_object('entryType', it.metadata->>'itemKind'),\n '{itemKind}', '\"product\"'::jsonb, true),\n updated_at = now()\n FROM public.plg_financial_invoices f\n WHERE f.id = it.invoice_id\n AND f.metadata ? 'danfe'\n AND it.metadata->>'itemKind' IN ('insumo','revenda','patrimonio','uso_consumo','despesa','servico')\n AND it.metadata->>'referenceId' IS NOT NULL\n AND EXISTS (SELECT 1 FROM public.products p WHERE p.id = (it.metadata->>'referenceId')::uuid);\n\nUPDATE public.items it\n SET metadata = jsonb_set(it.metadata || jsonb_build_object('entryType', it.metadata->>'itemKind'),\n '{itemKind}', '\"other\"'::jsonb, true),\n updated_at = now()\n FROM public.plg_financial_invoices f\n WHERE f.id = it.invoice_id\n AND f.metadata ? 'danfe'\n AND it.metadata->>'itemKind' IN ('insumo','revenda','patrimonio','uso_consumo','despesa','servico');\n\nALTER TABLE public.items ENABLE TRIGGER items_invoice_line_immutable;\n\nDO $backfill$\nDECLARE r record;\nBEGIN\n FOR r IN\n SELECT d.tenant_id, d.id, f.id AS invoice_id, f.total\n FROM public.plg_fiscalbr_inbox_documents d\n JOIN public.plg_financial_invoices f ON f.id = d.invoice_id AND f.tenant_id = d.tenant_id\n WHERE f.metadata ? 'danfe' AND f.cancelled_at IS NULL\n LOOP\n PERFORM public.plg_fiscalbr_note_charge_lines(r.tenant_id, r.id, r.invoice_id, r.total);\n END LOOP;\nEND\n$backfill$;\n";
@@ -28,6 +28,7 @@ export declare const MIGRATION_026_O_RECEBIMENTO_PARADO_DIZ_POR_QUE = "-- ------
28
28
  export declare const MIGRATION_027_A_NOTA_COM_CONTA_E_SEM_ESTOQUE_TERMINA_NO_V2 = "-- ---------------------------------------------------------------------------\n-- A note that has its payable but never reached stock can be finished in V2.\n-- ---------------------------------------------------------------------------\n-- In the V1, saving the payment plan of a DANFE import opened the payable\n-- before anyone reconciled the lines; many imports stopped there. Copied over,\n-- those notes carry invoice_id and no stock, and both RPCs read invoice_id as\n-- \"accepted\": nothing could be reconciled and the goods never entered stock\n-- (Artorius 18, Espa\u00E7o Facial 72, Pertinho 17 on 14/10/2026).\n--\n-- \"Settled\" is now: stocked, or a payable with no stock line left to receive.\n-- Saving decisions is allowed until then, and accepting a note that already has\n-- its payable receives the stock against it instead of opening a second one.\n-- ---------------------------------------------------------------------------\n\nDO $m$\nDECLARE\n v_def text;\n v_old text;\n v_new text;\n v_start integer;\n v_end integer;\n v_pending constant text := $q$NOT EXISTS (SELECT 1 FROM public.plg_fiscalbr_inbox_items x\n WHERE x.tenant_id = v_tenant AND x.inbox_document_id = v_doc.id\n AND x.stock_movement_id IS NULL\n AND (x.entry_type IS NULL OR x.entry_type = '' OR x.entry_type IN ('insumo','revenda','patrimonio','uso_consumo')))$q$;\nBEGIN\n -- 1. Saving decisions.\n SELECT pg_get_functiondef(p.oid) INTO v_def FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_save_inbox_decision';\n v_old := 'IF v_doc.stocked_at IS NOT NULL OR v_doc.invoice_id IS NOT NULL THEN';\n IF v_def IS NULL THEN RAISE EXCEPTION 'fiscalbr/027: fiscalbr_save_inbox_decision does not exist'; END IF;\n IF position(v_old in v_def) > 0 THEN\n v_def := replace(v_def, v_old, 'IF v_doc.stocked_at IS NOT NULL OR (v_doc.invoice_id IS NOT NULL AND ' || v_pending || ') THEN');\n -- v_tenant is that function's own variable name too.\n EXECUTE v_def;\n ELSIF position('x.stock_movement_id IS NULL' in v_def) = 0 THEN\n RAISE EXCEPTION 'fiscalbr/027: unexpected body of fiscalbr_save_inbox_decision \u2014 check by hand';\n END IF;\n\n -- 2. Accepting.\n SELECT pg_get_functiondef(p.oid) INTO v_def FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_accept_inbox_document';\n IF v_def IS NULL THEN RAISE EXCEPTION 'fiscalbr/027: fiscalbr_accept_inbox_document does not exist'; END IF;\n IF position('Half-done in the V1' in v_def) > 0 THEN RETURN; END IF;\n\n IF position(v_old in v_def) = 0 THEN\n RAISE EXCEPTION 'fiscalbr/027: unexpected guard in fiscalbr_accept_inbox_document \u2014 check by hand';\n END IF;\n v_def := replace(v_def, v_old, 'IF v_doc.stocked_at IS NOT NULL OR (v_doc.invoice_id IS NOT NULL AND ' || v_pending || ') THEN');\n\n v_start := position(' SELECT coalesce(sum(value), 0),' in v_def);\n v_new := 'PERFORM public.plg_fiscalbr_note_charge_lines(v_tenant, v_doc.id, v_invoice, v_total);';\n v_end := position(v_new in v_def);\n IF v_start = 0 OR v_end = 0 OR v_end < v_start THEN\n RAISE EXCEPTION 'fiscalbr/027: unexpected payable block in fiscalbr_accept_inbox_document \u2014 check by hand';\n END IF;\n v_end := v_end + length(v_new);\n v_def := substr(v_def, 1, v_start - 1)\n || $q$ IF v_doc.invoice_id IS NOT NULL THEN\n -- Half-done in the V1: the payable exists; only the stock is missing.\n v_invoice := v_doc.invoice_id;\n SELECT total INTO v_total FROM public.plg_financial_invoices WHERE id = v_invoice;\n v_insts := '[]'::jsonb;\n ELSE\n$q$ || substr(v_def, v_start, v_end - v_start) || $q$\n END IF;$q$\n || substr(v_def, v_end);\n EXECUTE v_def;\nEND\n$m$;\n";
29
29
  export declare const MIGRATION_028_A_LINHA_DE_ESTOQUE_TEM_UNIDADE = "-- ---------------------------------------------------------------------------\n-- A stock line enters with its unit.\n-- ---------------------------------------------------------------------------\n-- Accepting let a stock line through with no stock unit; the movement then\n-- carried a bare quantity that every screen reads as \"unidade\" (Artorius, NF-e\n-- 222879: 20 kg of copa lombo entered as 20 units). The screen now suggests the\n-- unit and refuses without one; this is the same rule where it cannot be\n-- skipped.\n-- ---------------------------------------------------------------------------\n\nDO $m$\nDECLARE\n v_def text;\n v_old constant text := $q$ IF v_it.product_id IS NULL THEN$q$;\n v_new constant text := $q$ IF v_it.stock_unit_id IS NULL THEN\n RAISE EXCEPTION 'fiscalbr: item % entra no estoque sem unidade \u2014 escolha a unidade de estoque', v_it.item_number\n USING ERRCODE = '23514';\n END IF;\n IF v_it.product_id IS NULL THEN$q$;\nBEGIN\n SELECT pg_get_functiondef(p.oid) INTO v_def FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'public' AND p.proname = 'fiscalbr_accept_inbox_document';\n IF v_def IS NULL THEN RAISE EXCEPTION 'fiscalbr/028: fiscalbr_accept_inbox_document does not exist'; END IF;\n IF position('entra no estoque sem unidade' in v_def) > 0 THEN RETURN; END IF;\n IF position(v_old in v_def) = 0 THEN\n RAISE EXCEPTION 'fiscalbr/028: unexpected body of fiscalbr_accept_inbox_document \u2014 check by hand';\n END IF;\n EXECUTE replace(v_def, v_old, v_new);\nEND\n$m$;\n";
30
30
  export declare const MIGRATION_029_THE_SERVICE_WRITES_THE_INTAKE_HEALTH = "-- ============================================================================\n-- 029_the_service_writes_the_intake_health \u2014 the DF-e pull moves to the\n-- integration service (ADR 0003 phase 2).\n--\n-- The pull reads a tenant's emitter rows AS the caller (RLS decides which), and\n-- then records each CNPJ's outcome in three health columns, even for a reader\n-- without settings.manage, so the notes list can say \"intake stopped\". The\n-- edge function did that with service_role; the service gets exactly those\n-- three columns, by row id, and nothing else on the table.\n--\n-- Guarded: the role is an operator step. Replay-safe.\n-- ============================================================================\n\nDO $$\nBEGIN\n IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration')\n AND to_regclass('public.plg_fiscalbr_emitter_settings') IS NOT NULL THEN\n GRANT SELECT (id) ON public.plg_fiscalbr_emitter_settings TO fayz_integration;\n GRANT UPDATE (dfe_last_sync_at, dfe_last_error, dfe_last_error_at) ON public.plg_fiscalbr_emitter_settings TO fayz_integration;\n DROP POLICY IF EXISTS plg_fiscalbr_emitter_settings_service_health ON public.plg_fiscalbr_emitter_settings;\n CREATE POLICY plg_fiscalbr_emitter_settings_service_health ON public.plg_fiscalbr_emitter_settings\n FOR UPDATE TO fayz_integration USING (true) WITH CHECK (true);\n DROP POLICY IF EXISTS plg_fiscalbr_emitter_settings_service_read ON public.plg_fiscalbr_emitter_settings;\n CREATE POLICY plg_fiscalbr_emitter_settings_service_read ON public.plg_fiscalbr_emitter_settings\n FOR SELECT TO fayz_integration USING (true);\n END IF;\nEND $$;\n";
31
+ export declare const MIGRATION_030_THE_SERVICE_ONBOARDS_THE_EMITTER = "-- ============================================================================\n-- 030 \u2014 the integration service onboards the emitter\n--\n-- The integration service registers the company at PlugNotas (certificate,\n-- company, NF-e and NFC-e configuration, webhook) from the integration screen\n-- (control-sdk-backend ADR 0008). The emitter's facts stay here: the legal\n-- entity (public.company_legal_entities) and its settings\n-- (plg_fiscalbr_emitter_settings, one row per CNPJ). These are the doors the\n-- service reads and writes them through, one function per action, the\n-- tenant explicit, open only to `fayz_integration`.\n--\n-- NF-e gets what NFC-e already had: environment, series and the next number\n-- (numbering continues from the previous system, so no number is skipped or\n-- used twice). The CSC token stays write-only for the screen; the service\n-- reads it here because PlugNotas needs it to sign NFC-e.\n-- ============================================================================\n\nALTER TABLE public.plg_fiscalbr_emitter_settings\n ADD COLUMN IF NOT EXISTS nfe_production boolean NOT NULL DEFAULT false,\n ADD COLUMN IF NOT EXISTS nfe_series integer CHECK (nfe_series IS NULL OR nfe_series > 0),\n ADD COLUMN IF NOT EXISTS nfe_next_number bigint CHECK (nfe_next_number IS NULL OR nfe_next_number > 0),\n ADD COLUMN IF NOT EXISTS nfce_next_number bigint CHECK (nfce_next_number IS NULL OR nfce_next_number > 0),\n ADD COLUMN IF NOT EXISTS provider_webhook_at timestamptz,\n -- PlugNotas' codes. The legal entity's tax_regime is free text copied from\n -- the V1 ('simples_nacional', 'other'); guessing a regime from 'other' would\n -- register the company wrong, so the store states it once.\n ADD COLUMN IF NOT EXISTS tax_regime_code smallint CHECK (tax_regime_code IS NULL OR tax_regime_code BETWEEN 1 AND 3),\n ADD COLUMN IF NOT EXISTS special_tax_regime_code smallint NOT NULL DEFAULT 0 CHECK (special_tax_regime_code BETWEEN 0 AND 11);\n\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfe_next_number IS\n 'The next NF-e number PlugNotas will use for nfe_series; set when the store moves from another system.';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.tax_regime_code IS\n '1 Simples Nacional, 2 Simples Nacional over the sublimit, 3 normal regime (presumed or real profit).';\nCOMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.provider_webhook_at IS\n 'When the integration service registered its webhook for this CNPJ at PlugNotas.';\n\n-- One emitter as the service needs it: the legal entity and its settings,\n-- CSC token included. NULL when the legal entity is not the tenant's.\nCREATE OR REPLACE FUNCTION public.fiscalbr_emitter_for_service(p_tenant uuid, p_legal_entity uuid)\nRETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT jsonb_build_object(\n 'legalEntityId', l.id,\n 'tenantId', l.tenant_id,\n 'cnpj', regexp_replace(coalesce(l.document_number, ''), '\\D', '', 'g'),\n 'legalName', l.legal_name,\n 'tradeName', l.trade_name,\n 'stateRegistration', l.state_registration,\n 'municipalRegistration', l.municipal_registration,\n 'taxRegime', l.tax_regime,\n -- Stated on the emitter, else read from a regime the text names unambiguously.\n 'taxRegimeCode', coalesce(s.tax_regime_code, CASE WHEN l.tax_regime = 'simples_nacional' THEN 1 END),\n 'specialTaxRegimeCode', coalesce(s.special_tax_regime_code, 0),\n 'cnae', l.cnae,\n 'email', coalesce(nullif(l.fiscal_email, ''), l.email),\n 'phone', l.phone,\n -- The address is a row of public.addresses (142); the legal entity's own\n -- columns are the retired shape, read only when no address row exists.\n 'address', CASE WHEN a.id IS NOT NULL THEN jsonb_build_object(\n 'street', a.street, 'number', a.number, 'complement', a.complement,\n 'district', a.district, 'city', a.city, 'state', a.state, 'postalCode', a.postal_code,\n 'ibgeCityCode', l.ibge_city_code, 'country', a.country\n ) ELSE jsonb_build_object(\n 'street', l.address, 'number', l.address_number, 'complement', l.address_complement,\n 'district', l.district, 'city', l.city, 'state', l.state, 'postalCode', l.postal_code,\n 'ibgeCityCode', l.ibge_city_code, 'country', l.country\n ) END,\n 'settings', CASE WHEN s.id IS NULL THEN NULL ELSE jsonb_build_object(\n 'nfe', jsonb_build_object('enabled', s.nfe_enabled, 'production', s.nfe_production, 'series', s.nfe_series, 'nextNumber', s.nfe_next_number),\n 'nfce', jsonb_build_object('enabled', s.nfce_enabled, 'production', s.nfce_production, 'series', s.nfce_series, 'nextNumber', s.nfce_next_number,\n 'cscId', s.nfce_csc_id, 'cscToken', s.nfce_csc_token, 'genericItem', s.nfce_generic_item),\n 'certificate', jsonb_build_object('providerId', s.certificate_provider_id, 'holder', s.certificate_cn,\n 'expiresAt', s.certificate_expires_at, 'uploadedAt', s.certificate_uploaded_at),\n 'providerSyncedAt', s.provider_synced_at,\n 'providerWebhookAt', s.provider_webhook_at\n ) END\n )\n FROM public.company_legal_entities l\n LEFT JOIN public.plg_fiscalbr_emitter_settings s ON s.legal_entity_id = l.id AND s.tenant_id = l.tenant_id\n LEFT JOIN LATERAL (\n SELECT * FROM public.addresses x\n WHERE x.tenant_id = l.tenant_id AND x.owner_type = 'company_legal_entity' AND x.owner_id = l.id\n ORDER BY (x.kind = 'fiscal') DESC, x.is_default DESC, x.created_at\n LIMIT 1\n ) a ON true\n WHERE l.id = p_legal_entity AND l.tenant_id = p_tenant\n$$;\n\n-- Every legal entity of the tenant that a unit points at: the emitters a\n-- screen can set up.\nCREATE OR REPLACE FUNCTION public.fiscalbr_emitters_for_service(p_tenant uuid)\nRETURNS SETOF jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT public.fiscalbr_emitter_for_service(p_tenant, l.id)\n FROM public.company_legal_entities l\n WHERE l.tenant_id = p_tenant\n AND EXISTS (SELECT 1 FROM app.units u WHERE u.tenant_id = p_tenant AND u.company_legal_entity_id = l.id)\n ORDER BY l.legal_name\n$$;\n\n-- Records what the service set at PlugNotas. Only the keys below move; the\n-- row is created on first write.\nCREATE OR REPLACE FUNCTION public.fiscalbr_emitter_record(p_tenant uuid, p_legal_entity uuid, p_patch jsonb)\nRETURNS void\nLANGUAGE plpgsql SECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_unknown text;\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM public.company_legal_entities WHERE id = p_legal_entity AND tenant_id = p_tenant) THEN\n RAISE EXCEPTION 'fiscalbr_emitter_record: legal entity % is not of tenant %', p_legal_entity, p_tenant USING ERRCODE = 'P0002';\n END IF;\n SELECT k INTO v_unknown FROM jsonb_object_keys(p_patch) k\n WHERE k NOT IN ('nfe_enabled', 'nfe_production', 'nfe_series', 'nfe_next_number',\n 'nfce_enabled', 'nfce_production', 'nfce_series', 'nfce_next_number', 'nfce_csc_id', 'nfce_csc_token',\n 'certificate_provider_id', 'certificate_cn', 'certificate_expires_at', 'certificate_uploaded_at',\n 'provider_synced_at', 'provider_webhook_at', 'tax_regime_code', 'special_tax_regime_code')\n LIMIT 1;\n IF v_unknown IS NOT NULL THEN\n RAISE EXCEPTION 'fiscalbr_emitter_record: % is not recorded here', v_unknown USING ERRCODE = '22023';\n END IF;\n\n INSERT INTO public.plg_fiscalbr_emitter_settings (tenant_id, legal_entity_id)\n VALUES (p_tenant, p_legal_entity)\n ON CONFLICT (legal_entity_id) DO NOTHING;\n\n UPDATE public.plg_fiscalbr_emitter_settings s SET\n nfe_enabled = CASE WHEN p_patch ? 'nfe_enabled' THEN (p_patch ->> 'nfe_enabled')::boolean ELSE s.nfe_enabled END,\n nfe_production = CASE WHEN p_patch ? 'nfe_production' THEN (p_patch ->> 'nfe_production')::boolean ELSE s.nfe_production END,\n nfe_series = CASE WHEN p_patch ? 'nfe_series' THEN (p_patch ->> 'nfe_series')::integer ELSE s.nfe_series END,\n nfe_next_number = CASE WHEN p_patch ? 'nfe_next_number' THEN (p_patch ->> 'nfe_next_number')::bigint ELSE s.nfe_next_number END,\n nfce_enabled = CASE WHEN p_patch ? 'nfce_enabled' THEN (p_patch ->> 'nfce_enabled')::boolean ELSE s.nfce_enabled END,\n nfce_production = CASE WHEN p_patch ? 'nfce_production' THEN (p_patch ->> 'nfce_production')::boolean ELSE s.nfce_production END,\n nfce_series = CASE WHEN p_patch ? 'nfce_series' THEN (p_patch ->> 'nfce_series')::integer ELSE s.nfce_series END,\n nfce_next_number = CASE WHEN p_patch ? 'nfce_next_number' THEN (p_patch ->> 'nfce_next_number')::bigint ELSE s.nfce_next_number END,\n nfce_csc_id = CASE WHEN p_patch ? 'nfce_csc_id' THEN p_patch ->> 'nfce_csc_id' ELSE s.nfce_csc_id END,\n nfce_csc_token = CASE WHEN p_patch ? 'nfce_csc_token' THEN p_patch ->> 'nfce_csc_token' ELSE s.nfce_csc_token END,\n certificate_provider_id = CASE WHEN p_patch ? 'certificate_provider_id' THEN p_patch ->> 'certificate_provider_id' ELSE s.certificate_provider_id END,\n certificate_cn = CASE WHEN p_patch ? 'certificate_cn' THEN p_patch ->> 'certificate_cn' ELSE s.certificate_cn END,\n certificate_expires_at = CASE WHEN p_patch ? 'certificate_expires_at' THEN (p_patch ->> 'certificate_expires_at')::timestamptz ELSE s.certificate_expires_at END,\n certificate_uploaded_at = CASE WHEN p_patch ? 'certificate_uploaded_at' THEN (p_patch ->> 'certificate_uploaded_at')::timestamptz ELSE s.certificate_uploaded_at END,\n provider_synced_at = CASE WHEN p_patch ? 'provider_synced_at' THEN (p_patch ->> 'provider_synced_at')::timestamptz ELSE s.provider_synced_at END,\n provider_webhook_at = CASE WHEN p_patch ? 'provider_webhook_at' THEN (p_patch ->> 'provider_webhook_at')::timestamptz ELSE s.provider_webhook_at END,\n tax_regime_code = CASE WHEN p_patch ? 'tax_regime_code' THEN (p_patch ->> 'tax_regime_code')::smallint ELSE s.tax_regime_code END,\n special_tax_regime_code = CASE WHEN p_patch ? 'special_tax_regime_code' THEN coalesce((p_patch ->> 'special_tax_regime_code')::smallint, 0) ELSE s.special_tax_regime_code END,\n updated_at = now()\n WHERE s.legal_entity_id = p_legal_entity AND s.tenant_id = p_tenant;\nEND\n$$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_emitter_for_service(uuid, uuid) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.fiscalbr_emitters_for_service(uuid) FROM PUBLIC, anon, authenticated;\nREVOKE ALL ON FUNCTION public.fiscalbr_emitter_record(uuid, uuid, jsonb) FROM PUBLIC, anon, authenticated;\n\nDO $$\nBEGIN\n IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN\n GRANT USAGE ON SCHEMA public TO fayz_integration;\n GRANT EXECUTE ON FUNCTION public.fiscalbr_emitter_for_service(uuid, uuid) TO fayz_integration;\n GRANT EXECUTE ON FUNCTION public.fiscalbr_emitters_for_service(uuid) TO fayz_integration;\n GRANT EXECUTE ON FUNCTION public.fiscalbr_emitter_record(uuid, uuid, jsonb) TO fayz_integration;\n END IF;\nEND\n$$;\n";
31
32
  export declare const MIGRATIONS: Array<{
32
33
  id: string;
33
34
  sql: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,2g0BA2jBlC,CAAA;AAED,eAAO,MAAM,0DAA0D,uzRAmMtE,CAAA;AAED,eAAO,MAAM,+CAA+C,m5oBAyW3D,CAAA;AAED,eAAO,MAAM,0CAA0C,o1XAgPtD,CAAA;AAED,eAAO,MAAM,8CAA8C,6qCAsB1D,CAAA;AAED,eAAO,MAAM,2CAA2C,w8dA6MvD,CAAA;AAED,eAAO,MAAM,yCAAyC,42BAkBrD,CAAA;AAED,eAAO,MAAM,oDAAoD,4oDA+BhE,CAAA;AAED,eAAO,MAAM,yDAAyD,otbA0NrE,CAAA;AAED,eAAO,MAAM,gEAAgE,q1+BAoiB5E,CAAA;AAED,eAAO,MAAM,gEAAgE,mqVAgO5E,CAAA;AAED,eAAO,MAAM,8CAA8C,k/HAiF1D,CAAA;AAED,eAAO,MAAM,oDAAoD,w2OA4JhE,CAAA;AAED,eAAO,MAAM,oDAAoD,gvRAwJhE,CAAA;AAED,eAAO,MAAM,kDAAkD,shPAoK9D,CAAA;AAED,eAAO,MAAM,8CAA8C,8/EA0C1D,CAAA;AAED,eAAO,MAAM,yCAAyC,2mJA2ErD,CAAA;AAED,eAAO,MAAM,4CAA4C,68QA0IxD,CAAA;AAED,eAAO,MAAM,0CAA0C,83CAwBtD,CAAA;AAED,eAAO,MAAM,+CAA+C,81JAiG3D,CAAA;AAED,eAAO,MAAM,gDAAgD,y+IAgH5D,CAAA;AAED,eAAO,MAAM,4CAA4C,ylEA4CxD,CAAA;AAED,eAAO,MAAM,qCAAqC,kzBAajD,CAAA;AAED,eAAO,MAAM,uDAAuD,u6HAyEnE,CAAA;AAED,eAAO,MAAM,oDAAoD,6/KAoHhE,CAAA;AAED,eAAO,MAAM,+CAA+C,uqPAwJ3D,CAAA;AAED,eAAO,MAAM,8CAA8C,myCAoB1D,CAAA;AAED,eAAO,MAAM,0DAA0D,yyHAsEtE,CAAA;AAED,eAAO,MAAM,4CAA4C,+jDA8BxD,CAAA;AAED,eAAO,MAAM,kDAAkD,0pDA2B9D,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA+BzD,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,2g0BA2jBlC,CAAA;AAED,eAAO,MAAM,0DAA0D,uzRAmMtE,CAAA;AAED,eAAO,MAAM,+CAA+C,m5oBAyW3D,CAAA;AAED,eAAO,MAAM,0CAA0C,o1XAgPtD,CAAA;AAED,eAAO,MAAM,8CAA8C,6qCAsB1D,CAAA;AAED,eAAO,MAAM,2CAA2C,w8dA6MvD,CAAA;AAED,eAAO,MAAM,yCAAyC,42BAkBrD,CAAA;AAED,eAAO,MAAM,oDAAoD,4oDA+BhE,CAAA;AAED,eAAO,MAAM,yDAAyD,otbA0NrE,CAAA;AAED,eAAO,MAAM,gEAAgE,q1+BAoiB5E,CAAA;AAED,eAAO,MAAM,gEAAgE,mqVAgO5E,CAAA;AAED,eAAO,MAAM,8CAA8C,k/HAiF1D,CAAA;AAED,eAAO,MAAM,oDAAoD,w2OA4JhE,CAAA;AAED,eAAO,MAAM,oDAAoD,gvRAwJhE,CAAA;AAED,eAAO,MAAM,kDAAkD,shPAoK9D,CAAA;AAED,eAAO,MAAM,8CAA8C,8/EA0C1D,CAAA;AAED,eAAO,MAAM,yCAAyC,2mJA2ErD,CAAA;AAED,eAAO,MAAM,4CAA4C,68QA0IxD,CAAA;AAED,eAAO,MAAM,0CAA0C,83CAwBtD,CAAA;AAED,eAAO,MAAM,+CAA+C,81JAiG3D,CAAA;AAED,eAAO,MAAM,gDAAgD,y+IAgH5D,CAAA;AAED,eAAO,MAAM,4CAA4C,ylEA4CxD,CAAA;AAED,eAAO,MAAM,qCAAqC,8kCAkBjD,CAAA;AAED,eAAO,MAAM,uDAAuD,u6HAyEnE,CAAA;AAED,eAAO,MAAM,oDAAoD,6/KAoHhE,CAAA;AAED,eAAO,MAAM,+CAA+C,uqPAwJ3D,CAAA;AAED,eAAO,MAAM,8CAA8C,myCAoB1D,CAAA;AAED,eAAO,MAAM,0DAA0D,yyHAsEtE,CAAA;AAED,eAAO,MAAM,4CAA4C,+jDA8BxD,CAAA;AAED,eAAO,MAAM,kDAAkD,0pDA2B9D,CAAA;AAED,eAAO,MAAM,8CAA8C,m3VAyK1D,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAgCzD,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fayz-ai/plugin-fiscal-br",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "description": "[preview] Brazilian fiscal addon for asynchronous NFC-e emission through PlugNotas",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@tanstack/react-table": "^8.20.0",
31
- "@fayz-ai/admin": "^0.54.0",
32
- "@fayz-ai/core": "^0.53.0",
31
+ "@fayz-ai/admin": "^0.54.2",
33
32
  "@fayz-ai/financial": "^0.1.4",
33
+ "@fayz-ai/core": "^0.54.3",
34
34
  "@fayz-ai/ui": "^0.53.0"
35
35
  },
36
36
  "devDependencies": {