@fayz-ai/plugin-fiscal-br 0.13.2 → 0.13.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.
- package/dist/index.js +142 -2
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -29,7 +29,8 @@ export declare const MIGRATION_027_A_NOTA_COM_CONTA_E_SEM_ESTOQUE_TERMINA_NO_V2
|
|
|
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
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";
|
|
32
|
-
export declare const MIGRATION_031_THE_SERVICE_ISSUES_THE_NOTE = "-- ============================================================================\n-- 031 \u2014 the integration service issues the order's note\n--\n-- control-sdk-backend ADR 0008, step 2: an operator clicks \"Emitir NFC-e\" on\n-- an order and the integration service builds the note with the V1's rules,\n-- sends it to PlugNotas and records what comes back. The note is the record\n-- 005 already defined (public.documents + plg_fiscalbr_documents, linked to\n-- the order by plg_fiscalbr_document_orders); these are the service's doors to\n-- it, one function per action, the tenant explicit, open only to\n-- `fayz_integration`.\n--\n-- fiscalbr_order_for_note_for_service what the note is made of\n-- fiscalbr_note_open_for_service the note, opened before the call\n-- fiscalbr_note_apply_for_service what PlugNotas answered\n-- fiscalbr_notes_for_service an order's notes\n-- fiscalbr_note_for_service one note\n-- fiscalbr_notes_to_follow_for_service notes still waiting on PlugNotas\n--\n-- A note is opened BEFORE PlugNotas is called, with the idIntegracao it will\n-- carry, so a call whose answer is lost is found again by that id instead of\n-- being sent twice (the V1's FISCAL_PROVIDER_RESULT_UNCERTAIN rule).\n-- ============================================================================\n\n-- What the note of an order is made of: the order, its lines with their tax\n-- data, what was paid and how, who bought and where it goes. The service\n-- applies the rules; this only gathers.\nCREATE OR REPLACE FUNCTION public.fiscalbr_order_for_note_for_service(p_tenant uuid, p_order uuid)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_checkout jsonb;\nBEGIN\n -- The shop is optional: a site order says how it was paid on its checkout,\n -- read only where the shop's table exists.\n IF to_regclass('public.plg_shop_checkout_sessions') IS NOT NULL THEN\n EXECUTE $q$\n SELECT jsonb_build_object(\n 'paymentMethod', s.payment_method, 'provider', s.provider, 'total', s.total, 'status', s.status,\n 'customerName', s.customer_name, 'customerDocument', s.customer_document,\n 'destinationState', s.shipping_address ->> 'state', 'destinationCity', s.shipping_address ->> 'city')\n FROM public.plg_shop_checkout_sessions s\n WHERE s.order_id = $1 AND s.tenant_id = $2 AND s.status = 'paid'\n ORDER BY s.updated_at DESC LIMIT 1 $q$\n INTO v_checkout USING p_order, p_tenant;\n END IF;\n\n RETURN (SELECT jsonb_build_object(\n 'orderId', o.id,\n 'tenantId', o.tenant_id,\n 'unitId', o.unit_id,\n 'kind', o.kind,\n 'status', o.status,\n 'channel', o.channel,\n 'createdAt', o.created_at,\n 'subtotal', o.subtotal,\n 'discount', o.discount,\n 'shipping', o.shipping,\n 'total', o.total,\n 'legalEntityId', u.company_legal_entity_id,\n 'items', coalesce((\n SELECT jsonb_agg(jsonb_build_object(\n 'itemId', i.id,\n 'productId', i.product_id,\n 'code', coalesce(nullif(p.sku, ''), i.product_id::text, i.id::text),\n 'name', i.name,\n 'quantity', i.quantity,\n 'unitPrice', i.unit_price,\n 'discount', i.discount,\n 'total', i.total,\n 'cancelled', i.cancelled_at IS NOT NULL,\n -- The product's own value wins, field by field, over its group's (V1).\n 'tax', CASE WHEN t.product_id IS NULL AND g.id IS NULL THEN NULL ELSE jsonb_build_object(\n 'ncm', t.ncm,\n 'cest', coalesce(t.cest, g.cest),\n 'cfop', coalesce(nullif(t.cfop, ''), g.cfop),\n 'cst', coalesce(nullif(t.cst, ''), g.cst),\n 'csosn', g.csosn,\n 'origin', coalesce(nullif(t.icms_origin, ''), g.icms_origin),\n 'icmsRate', CASE WHEN coalesce(t.icms_rate, 0) > 0 THEN t.icms_rate ELSE g.icms_rate END,\n 'icmsBaseMode', g.icms_bc_mode,\n 'pisSituation', coalesce(nullif(t.pis_situation, ''), g.pis_situation),\n 'pisRate', CASE WHEN coalesce(t.pis_rate, 0) > 0 THEN t.pis_rate ELSE g.pis_rate END,\n 'cofinsSituation', coalesce(nullif(t.cofins_situation, ''), g.cofins_situation),\n 'cofinsRate', CASE WHEN coalesce(t.cofins_rate, 0) > 0 THEN t.cofins_rate ELSE g.cofins_rate END,\n 'ibsCbsSituation', coalesce(nullif(t.ibs_cbs_situation, ''), g.ibs_cbs_situation),\n 'ibsCbsClassification', coalesce(nullif(t.ibs_cbs_classification, ''), g.ibs_cbs_classification),\n 'group', g.name\n ) END\n ) ORDER BY i.sort_order, i.created_at)\n FROM public.items i\n LEFT JOIN public.products p ON p.id = i.product_id AND p.tenant_id = i.tenant_id\n LEFT JOIN public.plg_fiscalbr_product_tax_info t ON t.product_id = i.product_id AND t.tenant_id = i.tenant_id\n LEFT JOIN public.plg_fiscalbr_tax_groups g ON g.id = t.tax_group_id AND g.tenant_id = i.tenant_id\n WHERE i.order_id = o.id AND i.tenant_id = o.tenant_id\n ), '[]'::jsonb),\n -- Anything that is not a site order is paid through the movements the\n -- order received, net of reversals, by method.\n 'checkout', v_checkout,\n 'payments', coalesce((\n SELECT jsonb_agg(jsonb_build_object('method', x.method, 'methodType', x.method_type, 'amount', x.amount))\n FROM (\n SELECT pm.name AS method, pt.name AS method_type,\n sum(CASE WHEN m.direction = 'credit' THEN m.paid_amount ELSE -m.paid_amount END) AS amount\n FROM public.plg_financial_movements m\n LEFT JOIN public.plg_financial_payment_methods pm ON pm.id = m.payment_method_id\n LEFT JOIN public.plg_financial_payment_method_types pt ON pt.id = pm.payment_method_type_id\n WHERE m.tenant_id = o.tenant_id AND m.movement_kind = 'payment' AND m.status = 'paid'\n AND (m.invoice_id = o.id OR m.financial_invoice_id IN (\n SELECT fi.id FROM public.plg_financial_invoices fi WHERE fi.order_id = o.id AND fi.tenant_id = o.tenant_id))\n GROUP BY pm.name, pt.name\n ) x WHERE x.amount <> 0\n ), '[]'::jsonb),\n 'notes', coalesce((\n SELECT jsonb_agg(jsonb_build_object('documentId', d.document_id, 'model', d.model, 'status', d.status))\n FROM public.plg_fiscalbr_documents d\n WHERE d.tenant_id = o.tenant_id AND d.order_id = o.id\n ), '[]'::jsonb)\n )\n FROM public.orders o\n LEFT JOIN app.units u ON u.id = o.unit_id AND u.tenant_id = o.tenant_id\n WHERE o.id = p_order AND o.tenant_id = p_tenant);\nEND\n$$;\n\n-- The note, opened before PlugNotas is called. One live note per order and\n-- model: a second \"Emitir\" while one is processing or authorized is refused\n-- (P0001 note_exists), so a double click never issues twice.\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_open_for_service(\n p_tenant uuid, p_order uuid, p_model text, p_input jsonb\n)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_order public.orders%ROWTYPE;\n v_doc uuid := gen_random_uuid();\n v_integration text;\n v_existing uuid;\nBEGIN\n IF p_model NOT IN ('nfce', 'nfe') THEN\n RAISE EXCEPTION 'fiscalbr_note_open: model % is not issued here', p_model USING ERRCODE = '22023';\n END IF;\n SELECT * INTO v_order FROM public.orders WHERE id = p_order AND tenant_id = p_tenant FOR UPDATE;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr_note_open: order % is not of tenant %', p_order, p_tenant USING ERRCODE = 'P0002';\n END IF;\n SELECT document_id INTO v_existing FROM public.plg_fiscalbr_documents\n WHERE tenant_id = p_tenant AND order_id = p_order AND model = p_model AND status IN ('processing', 'authorized')\n LIMIT 1;\n IF v_existing IS NOT NULL THEN\n RAISE EXCEPTION 'note_exists' USING ERRCODE = 'P0001', DETAIL = v_existing::text;\n END IF;\n\n -- Ours, and unique per company at PlugNotas: how a lost answer is found again.\n v_integration := 'csb-' || v_doc::text;\n\n INSERT INTO public.documents (id, tenant_id, kind, title, status, unit_id, subject_type, subject_id, created_by, metadata)\n VALUES (v_doc, p_tenant, 'fiscal_document', upper(p_model) || ' do pedido', 'processing', v_order.unit_id,\n 'order', p_order, nullif(p_input ->> 'userId', '')::uuid, jsonb_build_object('source', 'integration-service'));\n\n INSERT INTO public.plg_fiscalbr_documents (\n document_id, tenant_id, unit_id, order_id, model, environment, status, series, provider,\n provider_integration_id, total_value, recipient_snapshot, request_payload, source_snapshot, emitted_by_user_id, metadata)\n VALUES (\n v_doc, p_tenant, v_order.unit_id, p_order, p_model, p_input ->> 'environment', 'processing', p_input ->> 'series', 'plugnotas',\n v_integration, (p_input ->> 'total')::numeric, p_input -> 'recipient', p_input -> 'request', p_input -> 'source',\n nullif(p_input ->> 'userId', '')::uuid, jsonb_build_object('emitterCnpj', p_input ->> 'emitterCnpj'));\n\n INSERT INTO public.plg_fiscalbr_document_orders (tenant_id, document_id, order_id)\n VALUES (p_tenant, v_doc, p_order);\n\n INSERT INTO public.plg_fiscalbr_document_events (tenant_id, unit_id, document_id, event_type, status, created_by_user_id)\n VALUES (p_tenant, v_order.unit_id, v_doc, 'issue_requested', 'processing', nullif(p_input ->> 'userId', '')::uuid);\n\n RETURN jsonb_build_object('documentId', v_doc, 'integrationId', v_integration);\nEND\n$$;\n\n-- What PlugNotas answered, on the note and on its public.documents row (the\n-- same note seen from two places, 005). Identity (model, unit, order) never moves.\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_apply_for_service(\n p_tenant uuid, p_document uuid, p_patch jsonb, p_event jsonb DEFAULT NULL\n)\nRETURNS void\nLANGUAGE plpgsql SECURITY 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 AND tenant_id = p_tenant FOR UPDATE;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr_note_apply: note % is not of tenant %', p_document, p_tenant USING ERRCODE = 'P0002';\n END IF;\n IF v_status IS NOT NULL AND v_status NOT IN ('processing', 'authorized', 'rejected', 'error', 'cancelled', 'substituted') THEN\n RAISE EXCEPTION 'fiscalbr_note_apply: unknown status %', v_status USING ERRCODE = '22023';\n END IF;\n\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 protocol = coalesce(p_patch ->> 'protocol', protocol),\n provider_document_id = coalesce(p_patch ->> 'provider_document_id', provider_document_id),\n response_payload = coalesce(p_patch -> 'response_payload', response_payload),\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 xml = coalesce(p_patch ->> 'xml', xml),\n authorized_at = coalesce((p_patch ->> 'authorized_at')::timestamptz, authorized_at),\n last_polled_at = CASE WHEN p_patch ? 'polled' 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_patch ? 'cancelled_by_user_id' THEN nullif(p_patch ->> 'cancelled_by_user_id', '')::uuid ELSE cancelled_by_user_id END,\n metadata = metadata || coalesce(p_patch -> 'metadata', '{}'::jsonb)\n WHERE document_id = p_document;\n\n IF v_status IS NOT NULL THEN\n UPDATE public.documents SET status = v_status WHERE id = p_document 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, p_event ->> 'event_type', p_event ->> 'status',\n p_event ->> 'justification', p_event -> 'response_payload', nullif(p_event ->> 'userId', '')::uuid);\n END IF;\nEND\n$$;\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_for_service(p_tenant uuid, p_document uuid)\nRETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT jsonb_build_object(\n 'documentId', d.document_id, 'tenantId', d.tenant_id, 'unitId', d.unit_id, 'orderId', d.order_id,\n 'model', d.model, 'environment', d.environment, 'status', d.status, 'series', d.series, 'number', d.number,\n 'accessKey', d.access_key, 'protocol', d.protocol, 'provider', d.provider,\n 'providerDocumentId', d.provider_document_id, 'integrationId', d.provider_integration_id,\n 'total', d.total_value, 'recipient', d.recipient_snapshot, 'rejection', d.rejection_payload,\n 'hasXml', d.xml IS NOT NULL, 'authorizedAt', d.authorized_at, 'lastPolledAt', d.last_polled_at,\n 'cancelledAt', d.cancelled_at, 'cancelReason', d.cancel_reason, 'createdAt', d.created_at,\n 'emitterCnpj', d.metadata ->> 'emitterCnpj', 'metadata', d.metadata)\n FROM public.plg_fiscalbr_documents d\n WHERE d.document_id = p_document AND d.tenant_id = p_tenant\n$$;\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_notes_for_service(p_tenant uuid, p_order uuid)\nRETURNS SETOF jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT public.fiscalbr_note_for_service(p_tenant, d.document_id)\n FROM public.plg_fiscalbr_documents d\n WHERE d.tenant_id = p_tenant AND d.order_id = p_order\n ORDER BY d.created_at DESC\n$$;\n\n-- The XML is large and read only on download.\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_xml_for_service(p_tenant uuid, p_document uuid)\nRETURNS text\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT d.xml FROM public.plg_fiscalbr_documents d WHERE d.document_id = p_document AND d.tenant_id = p_tenant\n$$;\n\n-- Notes this service sent that PlugNotas has not settled: the fallback when a\n-- webhook does not arrive. Oldest poll first.\nCREATE OR REPLACE FUNCTION public.fiscalbr_notes_to_follow_for_service(p_limit integer DEFAULT 25)\nRETURNS SETOF jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT public.fiscalbr_note_for_service(d.tenant_id, d.document_id)\n FROM public.plg_fiscalbr_documents d\n WHERE d.provider = 'plugnotas' AND d.status = 'processing' AND d.provider_integration_id LIKE 'csb-%'\n AND d.created_at > now() - interval '7 days'\n ORDER BY coalesce(d.last_polled_at, d.created_at)\n LIMIT greatest(1, least(p_limit, 100))\n$$;\n\nDO $$\nDECLARE\n f text;\nBEGIN\n FOREACH f IN ARRAY ARRAY[\n 'public.fiscalbr_order_for_note_for_service(uuid, uuid)',\n 'public.fiscalbr_note_open_for_service(uuid, uuid, text, jsonb)',\n 'public.fiscalbr_note_apply_for_service(uuid, uuid, jsonb, jsonb)',\n 'public.fiscalbr_note_for_service(uuid, uuid)',\n 'public.fiscalbr_notes_for_service(uuid, uuid)',\n 'public.fiscalbr_note_xml_for_service(uuid, uuid)',\n 'public.fiscalbr_notes_to_follow_for_service(integer)'\n ] LOOP\n EXECUTE format('REVOKE ALL ON FUNCTION %s FROM PUBLIC, anon, authenticated', f);\n IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN\n EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO fayz_integration', f);\n END IF;\n END LOOP;\nEND\n$$;\n";
|
|
32
|
+
export declare const MIGRATION_031_THE_SERVICE_ISSUES_THE_NOTE = "-- ============================================================================\n-- 031 \u2014 the integration service issues the order's note\n--\n-- control-sdk-backend ADR 0008, step 2: an operator clicks \"Emitir NFC-e\" on\n-- an order and the integration service builds the note with the V1's rules,\n-- sends it to PlugNotas and records what comes back. The note is the record\n-- 005 already defined (public.documents + plg_fiscalbr_documents, linked to\n-- the order by plg_fiscalbr_document_orders); these are the service's doors to\n-- it, one function per action, the tenant explicit, open only to\n-- `fayz_integration`.\n--\n-- fiscalbr_order_for_note_for_service what the note is made of\n-- fiscalbr_note_open_for_service the note, opened before the call\n-- fiscalbr_note_apply_for_service what PlugNotas answered\n-- fiscalbr_notes_for_service an order's notes\n-- fiscalbr_note_for_service one note\n-- fiscalbr_notes_to_follow_for_service notes still waiting on PlugNotas\n-- fiscalbr_note_by_integration_for_service where a webhook belongs\n--\n-- A note is opened BEFORE PlugNotas is called, with the idIntegracao it will\n-- carry, so a call whose answer is lost is found again by that id instead of\n-- being sent twice (the V1's FISCAL_PROVIDER_RESULT_UNCERTAIN rule).\n-- ============================================================================\n\n-- What the note of an order is made of: the order, its lines with their tax\n-- data, what was paid and how, who bought and where it goes. The service\n-- applies the rules; this only gathers.\nCREATE OR REPLACE FUNCTION public.fiscalbr_order_for_note_for_service(p_tenant uuid, p_order uuid)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_checkout jsonb;\nBEGIN\n -- The shop is optional: a site order says how it was paid on its checkout,\n -- read only where the shop's table exists.\n IF to_regclass('public.plg_shop_checkout_sessions') IS NOT NULL THEN\n EXECUTE $q$\n SELECT jsonb_build_object(\n 'paymentMethod', s.payment_method, 'provider', s.provider, 'total', s.total, 'status', s.status,\n 'customerName', s.customer_name, 'customerDocument', s.customer_document,\n 'destinationState', s.shipping_address ->> 'state', 'destinationCity', s.shipping_address ->> 'city')\n FROM public.plg_shop_checkout_sessions s\n WHERE s.order_id = $1 AND s.tenant_id = $2 AND s.status = 'paid'\n ORDER BY s.updated_at DESC LIMIT 1 $q$\n INTO v_checkout USING p_order, p_tenant;\n END IF;\n\n RETURN (SELECT jsonb_build_object(\n 'orderId', o.id,\n 'tenantId', o.tenant_id,\n 'unitId', o.unit_id,\n 'kind', o.kind,\n 'status', o.status,\n 'channel', o.channel,\n 'createdAt', o.created_at,\n 'subtotal', o.subtotal,\n 'discount', o.discount,\n 'shipping', o.shipping,\n 'total', o.total,\n 'legalEntityId', u.company_legal_entity_id,\n 'items', coalesce((\n SELECT jsonb_agg(jsonb_build_object(\n 'itemId', i.id,\n 'productId', i.product_id,\n 'code', coalesce(nullif(p.sku, ''), i.product_id::text, i.id::text),\n 'name', i.name,\n 'quantity', i.quantity,\n 'unitPrice', i.unit_price,\n 'discount', i.discount,\n 'total', i.total,\n 'cancelled', i.cancelled_at IS NOT NULL,\n -- The product's own value wins, field by field, over its group's (V1).\n 'tax', CASE WHEN t.product_id IS NULL AND g.id IS NULL THEN NULL ELSE jsonb_build_object(\n 'ncm', t.ncm,\n 'cest', coalesce(t.cest, g.cest),\n 'cfop', coalesce(nullif(t.cfop, ''), g.cfop),\n 'cst', coalesce(nullif(t.cst, ''), g.cst),\n 'csosn', g.csosn,\n 'origin', coalesce(nullif(t.icms_origin, ''), g.icms_origin),\n 'icmsRate', CASE WHEN coalesce(t.icms_rate, 0) > 0 THEN t.icms_rate ELSE g.icms_rate END,\n 'icmsBaseMode', g.icms_bc_mode,\n 'pisSituation', coalesce(nullif(t.pis_situation, ''), g.pis_situation),\n 'pisRate', CASE WHEN coalesce(t.pis_rate, 0) > 0 THEN t.pis_rate ELSE g.pis_rate END,\n 'cofinsSituation', coalesce(nullif(t.cofins_situation, ''), g.cofins_situation),\n 'cofinsRate', CASE WHEN coalesce(t.cofins_rate, 0) > 0 THEN t.cofins_rate ELSE g.cofins_rate END,\n 'ibsCbsSituation', coalesce(nullif(t.ibs_cbs_situation, ''), g.ibs_cbs_situation),\n 'ibsCbsClassification', coalesce(nullif(t.ibs_cbs_classification, ''), g.ibs_cbs_classification),\n 'group', g.name\n ) END\n ) ORDER BY i.sort_order, i.created_at)\n FROM public.items i\n LEFT JOIN public.products p ON p.id = i.product_id AND p.tenant_id = i.tenant_id\n LEFT JOIN public.plg_fiscalbr_product_tax_info t ON t.product_id = i.product_id AND t.tenant_id = i.tenant_id\n LEFT JOIN public.plg_fiscalbr_tax_groups g ON g.id = t.tax_group_id AND g.tenant_id = i.tenant_id\n WHERE i.order_id = o.id AND i.tenant_id = o.tenant_id\n ), '[]'::jsonb),\n -- Anything that is not a site order is paid through the movements the\n -- order received, net of reversals, by method.\n 'checkout', v_checkout,\n 'payments', coalesce((\n SELECT jsonb_agg(jsonb_build_object('method', x.method, 'methodType', x.method_type, 'amount', x.amount))\n FROM (\n SELECT pm.name AS method, pt.name AS method_type,\n sum(CASE WHEN m.direction = 'credit' THEN m.paid_amount ELSE -m.paid_amount END) AS amount\n FROM public.plg_financial_movements m\n LEFT JOIN public.plg_financial_payment_methods pm ON pm.id = m.payment_method_id\n LEFT JOIN public.plg_financial_payment_method_types pt ON pt.id = pm.payment_method_type_id\n WHERE m.tenant_id = o.tenant_id AND m.movement_kind = 'payment' AND m.status = 'paid'\n AND (m.invoice_id = o.id OR m.financial_invoice_id IN (\n SELECT fi.id FROM public.plg_financial_invoices fi WHERE fi.order_id = o.id AND fi.tenant_id = o.tenant_id))\n GROUP BY pm.name, pt.name\n ) x WHERE x.amount <> 0\n ), '[]'::jsonb),\n 'notes', coalesce((\n SELECT jsonb_agg(jsonb_build_object('documentId', d.document_id, 'model', d.model, 'status', d.status))\n FROM public.plg_fiscalbr_documents d\n WHERE d.tenant_id = o.tenant_id AND d.order_id = o.id\n ), '[]'::jsonb)\n )\n FROM public.orders o\n LEFT JOIN app.units u ON u.id = o.unit_id AND u.tenant_id = o.tenant_id\n WHERE o.id = p_order AND o.tenant_id = p_tenant);\nEND\n$$;\n\n-- The note, opened before PlugNotas is called. One live note per order and\n-- model: a second \"Emitir\" while one is processing or authorized is refused\n-- (P0001 note_exists), so a double click never issues twice.\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_open_for_service(\n p_tenant uuid, p_order uuid, p_model text, p_input jsonb\n)\nRETURNS jsonb\nLANGUAGE plpgsql SECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_order public.orders%ROWTYPE;\n v_doc uuid := gen_random_uuid();\n v_integration text;\n v_existing uuid;\nBEGIN\n IF p_model NOT IN ('nfce', 'nfe') THEN\n RAISE EXCEPTION 'fiscalbr_note_open: model % is not issued here', p_model USING ERRCODE = '22023';\n END IF;\n SELECT * INTO v_order FROM public.orders WHERE id = p_order AND tenant_id = p_tenant FOR UPDATE;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr_note_open: order % is not of tenant %', p_order, p_tenant USING ERRCODE = 'P0002';\n END IF;\n SELECT document_id INTO v_existing FROM public.plg_fiscalbr_documents\n WHERE tenant_id = p_tenant AND order_id = p_order AND model = p_model AND status IN ('processing', 'authorized')\n LIMIT 1;\n IF v_existing IS NOT NULL THEN\n RAISE EXCEPTION 'note_exists' USING ERRCODE = 'P0001', DETAIL = v_existing::text;\n END IF;\n\n -- Ours, and unique per company at PlugNotas: how a lost answer is found again.\n v_integration := 'csb-' || v_doc::text;\n\n INSERT INTO public.documents (id, tenant_id, kind, title, status, unit_id, subject_type, subject_id, created_by, metadata)\n VALUES (v_doc, p_tenant, 'fiscal_document', upper(p_model) || ' do pedido', 'processing', v_order.unit_id,\n 'order', p_order, nullif(p_input ->> 'userId', '')::uuid, jsonb_build_object('source', 'integration-service'));\n\n INSERT INTO public.plg_fiscalbr_documents (\n document_id, tenant_id, unit_id, order_id, model, environment, status, series, provider,\n provider_integration_id, total_value, recipient_snapshot, request_payload, source_snapshot, emitted_by_user_id, metadata)\n VALUES (\n v_doc, p_tenant, v_order.unit_id, p_order, p_model, p_input ->> 'environment', 'processing', p_input ->> 'series', 'plugnotas',\n v_integration, (p_input ->> 'total')::numeric, p_input -> 'recipient', p_input -> 'request', p_input -> 'source',\n nullif(p_input ->> 'userId', '')::uuid, jsonb_build_object('emitterCnpj', p_input ->> 'emitterCnpj'));\n\n INSERT INTO public.plg_fiscalbr_document_orders (tenant_id, document_id, order_id)\n VALUES (p_tenant, v_doc, p_order);\n\n INSERT INTO public.plg_fiscalbr_document_events (tenant_id, unit_id, document_id, event_type, status, created_by_user_id)\n VALUES (p_tenant, v_order.unit_id, v_doc, 'issue_requested', 'processing', nullif(p_input ->> 'userId', '')::uuid);\n\n RETURN jsonb_build_object('documentId', v_doc, 'integrationId', v_integration);\nEND\n$$;\n\n-- What PlugNotas answered, on the note and on its public.documents row (the\n-- same note seen from two places, 005). Identity (model, unit, order) never moves.\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_apply_for_service(\n p_tenant uuid, p_document uuid, p_patch jsonb, p_event jsonb DEFAULT NULL\n)\nRETURNS void\nLANGUAGE plpgsql SECURITY 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 AND tenant_id = p_tenant FOR UPDATE;\n IF NOT FOUND THEN\n RAISE EXCEPTION 'fiscalbr_note_apply: note % is not of tenant %', p_document, p_tenant USING ERRCODE = 'P0002';\n END IF;\n IF v_status IS NOT NULL AND v_status NOT IN ('processing', 'authorized', 'rejected', 'error', 'cancelled', 'substituted') THEN\n RAISE EXCEPTION 'fiscalbr_note_apply: unknown status %', v_status USING ERRCODE = '22023';\n END IF;\n\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 protocol = coalesce(p_patch ->> 'protocol', protocol),\n provider_document_id = coalesce(p_patch ->> 'provider_document_id', provider_document_id),\n response_payload = coalesce(p_patch -> 'response_payload', response_payload),\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 xml = coalesce(p_patch ->> 'xml', xml),\n authorized_at = coalesce((p_patch ->> 'authorized_at')::timestamptz, authorized_at),\n last_polled_at = CASE WHEN p_patch ? 'polled' 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_patch ? 'cancelled_by_user_id' THEN nullif(p_patch ->> 'cancelled_by_user_id', '')::uuid ELSE cancelled_by_user_id END,\n metadata = metadata || coalesce(p_patch -> 'metadata', '{}'::jsonb)\n WHERE document_id = p_document;\n\n IF v_status IS NOT NULL THEN\n UPDATE public.documents SET status = v_status WHERE id = p_document 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, p_event ->> 'event_type', p_event ->> 'status',\n p_event ->> 'justification', p_event -> 'response_payload', nullif(p_event ->> 'userId', '')::uuid);\n END IF;\nEND\n$$;\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_for_service(p_tenant uuid, p_document uuid)\nRETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT jsonb_build_object(\n 'documentId', d.document_id, 'tenantId', d.tenant_id, 'unitId', d.unit_id, 'orderId', d.order_id,\n 'model', d.model, 'environment', d.environment, 'status', d.status, 'series', d.series, 'number', d.number,\n 'accessKey', d.access_key, 'protocol', d.protocol, 'provider', d.provider,\n 'providerDocumentId', d.provider_document_id, 'integrationId', d.provider_integration_id,\n 'total', d.total_value, 'recipient', d.recipient_snapshot, 'rejection', d.rejection_payload,\n 'hasXml', d.xml IS NOT NULL, 'authorizedAt', d.authorized_at, 'lastPolledAt', d.last_polled_at,\n 'cancelledAt', d.cancelled_at, 'cancelReason', d.cancel_reason, 'createdAt', d.created_at,\n 'emitterCnpj', d.metadata ->> 'emitterCnpj', 'metadata', d.metadata)\n FROM public.plg_fiscalbr_documents d\n WHERE d.document_id = p_document AND d.tenant_id = p_tenant\n$$;\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_notes_for_service(p_tenant uuid, p_order uuid)\nRETURNS SETOF jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT public.fiscalbr_note_for_service(p_tenant, d.document_id)\n FROM public.plg_fiscalbr_documents d\n WHERE d.tenant_id = p_tenant AND d.order_id = p_order\n ORDER BY d.created_at DESC\n$$;\n\n-- The XML is large and read only on download.\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_xml_for_service(p_tenant uuid, p_document uuid)\nRETURNS text\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT d.xml FROM public.plg_fiscalbr_documents d WHERE d.document_id = p_document AND d.tenant_id = p_tenant\n$$;\n\n-- Where a PlugNotas webhook belongs: our idIntegracao names the note.\nCREATE OR REPLACE FUNCTION public.fiscalbr_note_by_integration_for_service(p_integration text)\nRETURNS jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT jsonb_build_object('tenantId', d.tenant_id, 'documentId', d.document_id)\n FROM public.plg_fiscalbr_documents d\n WHERE d.provider_integration_id = p_integration AND d.provider = 'plugnotas'\n LIMIT 1\n$$;\n\n-- Notes this service sent that PlugNotas has not settled: the fallback when a\n-- webhook does not arrive. Oldest poll first.\nCREATE OR REPLACE FUNCTION public.fiscalbr_notes_to_follow_for_service(p_limit integer DEFAULT 25)\nRETURNS SETOF jsonb\nLANGUAGE sql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\n SELECT public.fiscalbr_note_for_service(d.tenant_id, d.document_id)\n FROM public.plg_fiscalbr_documents d\n WHERE d.provider = 'plugnotas' AND d.status = 'processing' AND d.provider_integration_id LIKE 'csb-%'\n AND d.created_at > now() - interval '7 days'\n ORDER BY coalesce(d.last_polled_at, d.created_at)\n LIMIT greatest(1, least(p_limit, 100))\n$$;\n\nDO $$\nDECLARE\n f text;\nBEGIN\n FOREACH f IN ARRAY ARRAY[\n 'public.fiscalbr_order_for_note_for_service(uuid, uuid)',\n 'public.fiscalbr_note_open_for_service(uuid, uuid, text, jsonb)',\n 'public.fiscalbr_note_apply_for_service(uuid, uuid, jsonb, jsonb)',\n 'public.fiscalbr_note_for_service(uuid, uuid)',\n 'public.fiscalbr_notes_for_service(uuid, uuid)',\n 'public.fiscalbr_note_xml_for_service(uuid, uuid)',\n 'public.fiscalbr_notes_to_follow_for_service(integer)',\n 'public.fiscalbr_note_by_integration_for_service(text)'\n ] LOOP\n EXECUTE format('REVOKE ALL ON FUNCTION %s FROM PUBLIC, anon, authenticated', f);\n IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN\n EXECUTE format('GRANT EXECUTE ON FUNCTION %s TO fayz_integration', f);\n END IF;\n END LOOP;\nEND\n$$;\n";
|
|
33
|
+
export declare const MIGRATION_032_A_DELIVERED_NOTE_CARRIES_WHERE_IT_GOES = "-- ============================================================================\n-- 032 \u2014 a home-delivery NFC-e carries where it goes\n--\n-- The SEFAZ rejected Artorius' NFC-e delivered at home with \"NFC-e de entrega\n-- a domicilio sem o endereco do destinatario\": indPres 4 requires the\n-- recipient's address. The order's source for its note now carries the\n-- checkout's delivery address and the customer's email; the integration\n-- service adds the city's IBGE code (control-sdk-backend ADR 0008).\n-- ============================================================================\n\nCREATE OR REPLACE FUNCTION public.fiscalbr_order_for_note_for_service(p_tenant uuid, p_order uuid)\nRETURNS jsonb\nLANGUAGE plpgsql STABLE SECURITY DEFINER\nSET search_path TO ''\nAS $$\nDECLARE\n v_checkout jsonb;\nBEGIN\n -- The shop is optional: a site order says how it was paid on its checkout,\n -- read only where the shop's table exists.\n IF to_regclass('public.plg_shop_checkout_sessions') IS NOT NULL THEN\n EXECUTE $q$\n SELECT jsonb_build_object(\n 'paymentMethod', s.payment_method, 'provider', s.provider, 'total', s.total, 'status', s.status,\n 'customerName', s.customer_name, 'customerDocument', s.customer_document,\n 'destinationState', s.shipping_address ->> 'state', 'destinationCity', s.shipping_address ->> 'city',\n 'customerEmail', s.customer_email,\n 'shippingAddress', CASE WHEN s.shipping_address IS NULL THEN NULL ELSE jsonb_build_object(\n 'street', s.shipping_address ->> 'street', 'number', s.shipping_address ->> 'number',\n 'complement', s.shipping_address ->> 'complement', 'district', s.shipping_address ->> 'district',\n 'city', s.shipping_address ->> 'city', 'state', s.shipping_address ->> 'state',\n 'postalCode', coalesce(s.shipping_address ->> 'postalCode', s.shipping_address ->> 'postal_code')) END)\n FROM public.plg_shop_checkout_sessions s\n WHERE s.order_id = $1 AND s.tenant_id = $2 AND s.status = 'paid'\n ORDER BY s.updated_at DESC LIMIT 1 $q$\n INTO v_checkout USING p_order, p_tenant;\n END IF;\n\n RETURN (SELECT jsonb_build_object(\n 'orderId', o.id,\n 'tenantId', o.tenant_id,\n 'unitId', o.unit_id,\n 'kind', o.kind,\n 'status', o.status,\n 'channel', o.channel,\n 'createdAt', o.created_at,\n 'subtotal', o.subtotal,\n 'discount', o.discount,\n 'shipping', o.shipping,\n 'total', o.total,\n 'legalEntityId', u.company_legal_entity_id,\n 'items', coalesce((\n SELECT jsonb_agg(jsonb_build_object(\n 'itemId', i.id,\n 'productId', i.product_id,\n 'code', coalesce(nullif(p.sku, ''), i.product_id::text, i.id::text),\n 'name', i.name,\n 'quantity', i.quantity,\n 'unitPrice', i.unit_price,\n 'discount', i.discount,\n 'total', i.total,\n 'cancelled', i.cancelled_at IS NOT NULL,\n -- The product's own value wins, field by field, over its group's (V1).\n 'tax', CASE WHEN t.product_id IS NULL AND g.id IS NULL THEN NULL ELSE jsonb_build_object(\n 'ncm', t.ncm,\n 'cest', coalesce(t.cest, g.cest),\n 'cfop', coalesce(nullif(t.cfop, ''), g.cfop),\n 'cst', coalesce(nullif(t.cst, ''), g.cst),\n 'csosn', g.csosn,\n 'origin', coalesce(nullif(t.icms_origin, ''), g.icms_origin),\n 'icmsRate', CASE WHEN coalesce(t.icms_rate, 0) > 0 THEN t.icms_rate ELSE g.icms_rate END,\n 'icmsBaseMode', g.icms_bc_mode,\n 'pisSituation', coalesce(nullif(t.pis_situation, ''), g.pis_situation),\n 'pisRate', CASE WHEN coalesce(t.pis_rate, 0) > 0 THEN t.pis_rate ELSE g.pis_rate END,\n 'cofinsSituation', coalesce(nullif(t.cofins_situation, ''), g.cofins_situation),\n 'cofinsRate', CASE WHEN coalesce(t.cofins_rate, 0) > 0 THEN t.cofins_rate ELSE g.cofins_rate END,\n 'ibsCbsSituation', coalesce(nullif(t.ibs_cbs_situation, ''), g.ibs_cbs_situation),\n 'ibsCbsClassification', coalesce(nullif(t.ibs_cbs_classification, ''), g.ibs_cbs_classification),\n 'group', g.name\n ) END\n ) ORDER BY i.sort_order, i.created_at)\n FROM public.items i\n LEFT JOIN public.products p ON p.id = i.product_id AND p.tenant_id = i.tenant_id\n LEFT JOIN public.plg_fiscalbr_product_tax_info t ON t.product_id = i.product_id AND t.tenant_id = i.tenant_id\n LEFT JOIN public.plg_fiscalbr_tax_groups g ON g.id = t.tax_group_id AND g.tenant_id = i.tenant_id\n WHERE i.order_id = o.id AND i.tenant_id = o.tenant_id\n ), '[]'::jsonb),\n -- Anything that is not a site order is paid through the movements the\n -- order received, net of reversals, by method.\n 'checkout', v_checkout,\n 'payments', coalesce((\n SELECT jsonb_agg(jsonb_build_object('method', x.method, 'methodType', x.method_type, 'amount', x.amount))\n FROM (\n SELECT pm.name AS method, pt.name AS method_type,\n sum(CASE WHEN m.direction = 'credit' THEN m.paid_amount ELSE -m.paid_amount END) AS amount\n FROM public.plg_financial_movements m\n LEFT JOIN public.plg_financial_payment_methods pm ON pm.id = m.payment_method_id\n LEFT JOIN public.plg_financial_payment_method_types pt ON pt.id = pm.payment_method_type_id\n WHERE m.tenant_id = o.tenant_id AND m.movement_kind = 'payment' AND m.status = 'paid'\n AND (m.invoice_id = o.id OR m.financial_invoice_id IN (\n SELECT fi.id FROM public.plg_financial_invoices fi WHERE fi.order_id = o.id AND fi.tenant_id = o.tenant_id))\n GROUP BY pm.name, pt.name\n ) x WHERE x.amount <> 0\n ), '[]'::jsonb),\n 'notes', coalesce((\n SELECT jsonb_agg(jsonb_build_object('documentId', d.document_id, 'model', d.model, 'status', d.status))\n FROM public.plg_fiscalbr_documents d\n WHERE d.tenant_id = o.tenant_id AND d.order_id = o.id\n ), '[]'::jsonb)\n )\n FROM public.orders o\n LEFT JOIN app.units u ON u.id = o.unit_id AND u.tenant_id = o.tenant_id\n WHERE o.id = p_order AND o.tenant_id = p_tenant);\nEND\n$$;\n\nREVOKE ALL ON FUNCTION public.fiscalbr_order_for_note_for_service(uuid, uuid) FROM PUBLIC, anon, authenticated;\nDO $$\nBEGIN\n IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN\n GRANT EXECUTE ON FUNCTION public.fiscalbr_order_for_note_for_service(uuid, uuid) TO fayz_integration;\n END IF;\nEND\n$$;\n";
|
|
33
34
|
export declare const MIGRATIONS: Array<{
|
|
34
35
|
id: string;
|
|
35
36
|
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,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,yCAAyC,
|
|
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,yCAAyC,kogBAkUrD,CAAA;AAED,eAAO,MAAM,oDAAoD,y2MA4HhE,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAkCzD,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fayz-ai/plugin-fiscal-br",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"description": "[preview] Brazilian fiscal addon for asynchronous NFC-e emission through PlugNotas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@tanstack/react-table": "^8.20.0",
|
|
31
|
-
"@fayz-ai/admin": "^0.
|
|
32
|
-
"@fayz-ai/core": "^0.
|
|
33
|
-
"@fayz-ai/financial": "^0.1.
|
|
34
|
-
"@fayz-ai/ui": "^0.
|
|
31
|
+
"@fayz-ai/admin": "^0.62.0",
|
|
32
|
+
"@fayz-ai/core": "^0.61.0",
|
|
33
|
+
"@fayz-ai/financial": "^0.1.10",
|
|
34
|
+
"@fayz-ai/ui": "^0.61.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^20.0.0",
|