@fayz-ai/plugin-fiscal-br 0.9.3 → 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.
package/dist/index.js CHANGED
@@ -3938,6 +3938,11 @@ var MIGRATION_022_A_NOTA_DO_V1_TRAZ_O_XML = `-- --------------------------------
3938
3938
  -- to rebuild them. Only the mapping changes; rows already copied are not touched.
3939
3939
  -- ---------------------------------------------------------------------------
3940
3940
 
3941
+ -- The row is already frozen, and trg_allowlist_frozen refuses a plain write;
3942
+ -- from zero this file stopped the rest of fiscal_br. A migration file is the
3943
+ -- one writer the hatch exists for, and SET LOCAL ends with the transaction.
3944
+ SET LOCAL migration.allowlist_unlock = 'on';
3945
+
3941
3946
  UPDATE migration.allowlist
3942
3947
  SET columns = columns || jsonb_build_object('xml_content', 'xml_content')
3943
3948
  WHERE source_table = 'dfe_inbox'
@@ -4438,6 +4443,176 @@ BEGIN
4438
4443
  END IF;
4439
4444
  END $$;
4440
4445
  `;
4446
+ var MIGRATION_030_THE_SERVICE_ONBOARDS_THE_EMITTER = `-- ============================================================================
4447
+ -- 030 \u2014 the integration service onboards the emitter
4448
+ --
4449
+ -- The integration service registers the company at PlugNotas (certificate,
4450
+ -- company, NF-e and NFC-e configuration, webhook) from the integration screen
4451
+ -- (control-sdk-backend ADR 0008). The emitter's facts stay here: the legal
4452
+ -- entity (public.company_legal_entities) and its settings
4453
+ -- (plg_fiscalbr_emitter_settings, one row per CNPJ). These are the doors the
4454
+ -- service reads and writes them through, one function per action, the
4455
+ -- tenant explicit, open only to \`fayz_integration\`.
4456
+ --
4457
+ -- NF-e gets what NFC-e already had: environment, series and the next number
4458
+ -- (numbering continues from the previous system, so no number is skipped or
4459
+ -- used twice). The CSC token stays write-only for the screen; the service
4460
+ -- reads it here because PlugNotas needs it to sign NFC-e.
4461
+ -- ============================================================================
4462
+
4463
+ ALTER TABLE public.plg_fiscalbr_emitter_settings
4464
+ ADD COLUMN IF NOT EXISTS nfe_production boolean NOT NULL DEFAULT false,
4465
+ ADD COLUMN IF NOT EXISTS nfe_series integer CHECK (nfe_series IS NULL OR nfe_series > 0),
4466
+ ADD COLUMN IF NOT EXISTS nfe_next_number bigint CHECK (nfe_next_number IS NULL OR nfe_next_number > 0),
4467
+ ADD COLUMN IF NOT EXISTS nfce_next_number bigint CHECK (nfce_next_number IS NULL OR nfce_next_number > 0),
4468
+ ADD COLUMN IF NOT EXISTS provider_webhook_at timestamptz,
4469
+ -- PlugNotas' codes. The legal entity's tax_regime is free text copied from
4470
+ -- the V1 ('simples_nacional', 'other'); guessing a regime from 'other' would
4471
+ -- register the company wrong, so the store states it once.
4472
+ ADD COLUMN IF NOT EXISTS tax_regime_code smallint CHECK (tax_regime_code IS NULL OR tax_regime_code BETWEEN 1 AND 3),
4473
+ ADD COLUMN IF NOT EXISTS special_tax_regime_code smallint NOT NULL DEFAULT 0 CHECK (special_tax_regime_code BETWEEN 0 AND 11);
4474
+
4475
+ COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.nfe_next_number IS
4476
+ 'The next NF-e number PlugNotas will use for nfe_series; set when the store moves from another system.';
4477
+ COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.tax_regime_code IS
4478
+ '1 Simples Nacional, 2 Simples Nacional over the sublimit, 3 normal regime (presumed or real profit).';
4479
+ COMMENT ON COLUMN public.plg_fiscalbr_emitter_settings.provider_webhook_at IS
4480
+ 'When the integration service registered its webhook for this CNPJ at PlugNotas.';
4481
+
4482
+ -- One emitter as the service needs it: the legal entity and its settings,
4483
+ -- CSC token included. NULL when the legal entity is not the tenant's.
4484
+ CREATE OR REPLACE FUNCTION public.fiscalbr_emitter_for_service(p_tenant uuid, p_legal_entity uuid)
4485
+ RETURNS jsonb
4486
+ LANGUAGE sql STABLE SECURITY DEFINER
4487
+ SET search_path TO ''
4488
+ AS $$
4489
+ SELECT jsonb_build_object(
4490
+ 'legalEntityId', l.id,
4491
+ 'tenantId', l.tenant_id,
4492
+ 'cnpj', regexp_replace(coalesce(l.document_number, ''), '\\D', '', 'g'),
4493
+ 'legalName', l.legal_name,
4494
+ 'tradeName', l.trade_name,
4495
+ 'stateRegistration', l.state_registration,
4496
+ 'municipalRegistration', l.municipal_registration,
4497
+ 'taxRegime', l.tax_regime,
4498
+ -- Stated on the emitter, else read from a regime the text names unambiguously.
4499
+ 'taxRegimeCode', coalesce(s.tax_regime_code, CASE WHEN l.tax_regime = 'simples_nacional' THEN 1 END),
4500
+ 'specialTaxRegimeCode', coalesce(s.special_tax_regime_code, 0),
4501
+ 'cnae', l.cnae,
4502
+ 'email', coalesce(nullif(l.fiscal_email, ''), l.email),
4503
+ 'phone', l.phone,
4504
+ -- The address is a row of public.addresses (142); the legal entity's own
4505
+ -- columns are the retired shape, read only when no address row exists.
4506
+ 'address', CASE WHEN a.id IS NOT NULL THEN jsonb_build_object(
4507
+ 'street', a.street, 'number', a.number, 'complement', a.complement,
4508
+ 'district', a.district, 'city', a.city, 'state', a.state, 'postalCode', a.postal_code,
4509
+ 'ibgeCityCode', l.ibge_city_code, 'country', a.country
4510
+ ) ELSE jsonb_build_object(
4511
+ 'street', l.address, 'number', l.address_number, 'complement', l.address_complement,
4512
+ 'district', l.district, 'city', l.city, 'state', l.state, 'postalCode', l.postal_code,
4513
+ 'ibgeCityCode', l.ibge_city_code, 'country', l.country
4514
+ ) END,
4515
+ 'settings', CASE WHEN s.id IS NULL THEN NULL ELSE jsonb_build_object(
4516
+ 'nfe', jsonb_build_object('enabled', s.nfe_enabled, 'production', s.nfe_production, 'series', s.nfe_series, 'nextNumber', s.nfe_next_number),
4517
+ 'nfce', jsonb_build_object('enabled', s.nfce_enabled, 'production', s.nfce_production, 'series', s.nfce_series, 'nextNumber', s.nfce_next_number,
4518
+ 'cscId', s.nfce_csc_id, 'cscToken', s.nfce_csc_token, 'genericItem', s.nfce_generic_item),
4519
+ 'certificate', jsonb_build_object('providerId', s.certificate_provider_id, 'holder', s.certificate_cn,
4520
+ 'expiresAt', s.certificate_expires_at, 'uploadedAt', s.certificate_uploaded_at),
4521
+ 'providerSyncedAt', s.provider_synced_at,
4522
+ 'providerWebhookAt', s.provider_webhook_at
4523
+ ) END
4524
+ )
4525
+ FROM public.company_legal_entities l
4526
+ LEFT JOIN public.plg_fiscalbr_emitter_settings s ON s.legal_entity_id = l.id AND s.tenant_id = l.tenant_id
4527
+ LEFT JOIN LATERAL (
4528
+ SELECT * FROM public.addresses x
4529
+ WHERE x.tenant_id = l.tenant_id AND x.owner_type = 'company_legal_entity' AND x.owner_id = l.id
4530
+ ORDER BY (x.kind = 'fiscal') DESC, x.is_default DESC, x.created_at
4531
+ LIMIT 1
4532
+ ) a ON true
4533
+ WHERE l.id = p_legal_entity AND l.tenant_id = p_tenant
4534
+ $$;
4535
+
4536
+ -- Every legal entity of the tenant that a unit points at: the emitters a
4537
+ -- screen can set up.
4538
+ CREATE OR REPLACE FUNCTION public.fiscalbr_emitters_for_service(p_tenant uuid)
4539
+ RETURNS SETOF jsonb
4540
+ LANGUAGE sql STABLE SECURITY DEFINER
4541
+ SET search_path TO ''
4542
+ AS $$
4543
+ SELECT public.fiscalbr_emitter_for_service(p_tenant, l.id)
4544
+ FROM public.company_legal_entities l
4545
+ WHERE l.tenant_id = p_tenant
4546
+ AND EXISTS (SELECT 1 FROM app.units u WHERE u.tenant_id = p_tenant AND u.company_legal_entity_id = l.id)
4547
+ ORDER BY l.legal_name
4548
+ $$;
4549
+
4550
+ -- Records what the service set at PlugNotas. Only the keys below move; the
4551
+ -- row is created on first write.
4552
+ CREATE OR REPLACE FUNCTION public.fiscalbr_emitter_record(p_tenant uuid, p_legal_entity uuid, p_patch jsonb)
4553
+ RETURNS void
4554
+ LANGUAGE plpgsql SECURITY DEFINER
4555
+ SET search_path TO ''
4556
+ AS $$
4557
+ DECLARE
4558
+ v_unknown text;
4559
+ BEGIN
4560
+ IF NOT EXISTS (SELECT 1 FROM public.company_legal_entities WHERE id = p_legal_entity AND tenant_id = p_tenant) THEN
4561
+ RAISE EXCEPTION 'fiscalbr_emitter_record: legal entity % is not of tenant %', p_legal_entity, p_tenant USING ERRCODE = 'P0002';
4562
+ END IF;
4563
+ SELECT k INTO v_unknown FROM jsonb_object_keys(p_patch) k
4564
+ WHERE k NOT IN ('nfe_enabled', 'nfe_production', 'nfe_series', 'nfe_next_number',
4565
+ 'nfce_enabled', 'nfce_production', 'nfce_series', 'nfce_next_number', 'nfce_csc_id', 'nfce_csc_token',
4566
+ 'certificate_provider_id', 'certificate_cn', 'certificate_expires_at', 'certificate_uploaded_at',
4567
+ 'provider_synced_at', 'provider_webhook_at', 'tax_regime_code', 'special_tax_regime_code')
4568
+ LIMIT 1;
4569
+ IF v_unknown IS NOT NULL THEN
4570
+ RAISE EXCEPTION 'fiscalbr_emitter_record: % is not recorded here', v_unknown USING ERRCODE = '22023';
4571
+ END IF;
4572
+
4573
+ INSERT INTO public.plg_fiscalbr_emitter_settings (tenant_id, legal_entity_id)
4574
+ VALUES (p_tenant, p_legal_entity)
4575
+ ON CONFLICT (legal_entity_id) DO NOTHING;
4576
+
4577
+ UPDATE public.plg_fiscalbr_emitter_settings s SET
4578
+ nfe_enabled = CASE WHEN p_patch ? 'nfe_enabled' THEN (p_patch ->> 'nfe_enabled')::boolean ELSE s.nfe_enabled END,
4579
+ nfe_production = CASE WHEN p_patch ? 'nfe_production' THEN (p_patch ->> 'nfe_production')::boolean ELSE s.nfe_production END,
4580
+ nfe_series = CASE WHEN p_patch ? 'nfe_series' THEN (p_patch ->> 'nfe_series')::integer ELSE s.nfe_series END,
4581
+ nfe_next_number = CASE WHEN p_patch ? 'nfe_next_number' THEN (p_patch ->> 'nfe_next_number')::bigint ELSE s.nfe_next_number END,
4582
+ nfce_enabled = CASE WHEN p_patch ? 'nfce_enabled' THEN (p_patch ->> 'nfce_enabled')::boolean ELSE s.nfce_enabled END,
4583
+ nfce_production = CASE WHEN p_patch ? 'nfce_production' THEN (p_patch ->> 'nfce_production')::boolean ELSE s.nfce_production END,
4584
+ nfce_series = CASE WHEN p_patch ? 'nfce_series' THEN (p_patch ->> 'nfce_series')::integer ELSE s.nfce_series END,
4585
+ nfce_next_number = CASE WHEN p_patch ? 'nfce_next_number' THEN (p_patch ->> 'nfce_next_number')::bigint ELSE s.nfce_next_number END,
4586
+ nfce_csc_id = CASE WHEN p_patch ? 'nfce_csc_id' THEN p_patch ->> 'nfce_csc_id' ELSE s.nfce_csc_id END,
4587
+ nfce_csc_token = CASE WHEN p_patch ? 'nfce_csc_token' THEN p_patch ->> 'nfce_csc_token' ELSE s.nfce_csc_token END,
4588
+ certificate_provider_id = CASE WHEN p_patch ? 'certificate_provider_id' THEN p_patch ->> 'certificate_provider_id' ELSE s.certificate_provider_id END,
4589
+ certificate_cn = CASE WHEN p_patch ? 'certificate_cn' THEN p_patch ->> 'certificate_cn' ELSE s.certificate_cn END,
4590
+ certificate_expires_at = CASE WHEN p_patch ? 'certificate_expires_at' THEN (p_patch ->> 'certificate_expires_at')::timestamptz ELSE s.certificate_expires_at END,
4591
+ certificate_uploaded_at = CASE WHEN p_patch ? 'certificate_uploaded_at' THEN (p_patch ->> 'certificate_uploaded_at')::timestamptz ELSE s.certificate_uploaded_at END,
4592
+ provider_synced_at = CASE WHEN p_patch ? 'provider_synced_at' THEN (p_patch ->> 'provider_synced_at')::timestamptz ELSE s.provider_synced_at END,
4593
+ provider_webhook_at = CASE WHEN p_patch ? 'provider_webhook_at' THEN (p_patch ->> 'provider_webhook_at')::timestamptz ELSE s.provider_webhook_at END,
4594
+ tax_regime_code = CASE WHEN p_patch ? 'tax_regime_code' THEN (p_patch ->> 'tax_regime_code')::smallint ELSE s.tax_regime_code END,
4595
+ 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,
4596
+ updated_at = now()
4597
+ WHERE s.legal_entity_id = p_legal_entity AND s.tenant_id = p_tenant;
4598
+ END
4599
+ $$;
4600
+
4601
+ REVOKE ALL ON FUNCTION public.fiscalbr_emitter_for_service(uuid, uuid) FROM PUBLIC, anon, authenticated;
4602
+ REVOKE ALL ON FUNCTION public.fiscalbr_emitters_for_service(uuid) FROM PUBLIC, anon, authenticated;
4603
+ REVOKE ALL ON FUNCTION public.fiscalbr_emitter_record(uuid, uuid, jsonb) FROM PUBLIC, anon, authenticated;
4604
+
4605
+ DO $$
4606
+ BEGIN
4607
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN
4608
+ GRANT USAGE ON SCHEMA public TO fayz_integration;
4609
+ GRANT EXECUTE ON FUNCTION public.fiscalbr_emitter_for_service(uuid, uuid) TO fayz_integration;
4610
+ GRANT EXECUTE ON FUNCTION public.fiscalbr_emitters_for_service(uuid) TO fayz_integration;
4611
+ GRANT EXECUTE ON FUNCTION public.fiscalbr_emitter_record(uuid, uuid, jsonb) TO fayz_integration;
4612
+ END IF;
4613
+ END
4614
+ $$;
4615
+ `;
4441
4616
  var MIGRATIONS = [
4442
4617
  { id: "000_baseline", sql: MIGRATION_000_BASELINE },
4443
4618
  { id: "001_preserve_terminal_results_and_recover_leases", sql: MIGRATION_001_PRESERVE_TERMINAL_RESULTS_AND_RECOVER_LEASES },
@@ -4468,7 +4643,8 @@ var MIGRATIONS = [
4468
4643
  { id: "026_o_recebimento_parado_diz_por_que", sql: MIGRATION_026_O_RECEBIMENTO_PARADO_DIZ_POR_QUE },
4469
4644
  { id: "027_a_nota_com_conta_e_sem_estoque_termina_no_v2", sql: MIGRATION_027_A_NOTA_COM_CONTA_E_SEM_ESTOQUE_TERMINA_NO_V2 },
4470
4645
  { id: "028_a_linha_de_estoque_tem_unidade", sql: MIGRATION_028_A_LINHA_DE_ESTOQUE_TEM_UNIDADE },
4471
- { id: "029_the_service_writes_the_intake_health", sql: MIGRATION_029_THE_SERVICE_WRITES_THE_INTAKE_HEALTH }
4646
+ { id: "029_the_service_writes_the_intake_health", sql: MIGRATION_029_THE_SERVICE_WRITES_THE_INTAKE_HEALTH },
4647
+ { id: "030_the_service_onboards_the_emitter", sql: MIGRATION_030_THE_SERVICE_ONBOARDS_THE_EMITTER }
4472
4648
  ];
4473
4649
  var taxGroupEntity = {
4474
4650
  name: "Grupo fiscal",