Files
jonatanritter 032151af34 feat: extract pure fiscal core from sowai-auto (parity-preserving copy)
Copies the 6 pure fiscal modules (domains, chave_acesso, tpag, presets,
resolver, xml_builder) from sowai-auto's backend/app/modules/fiscal/ into
this standalone lib, with only the two mechanical changes required to drop
the app.* dependency: import prefix rename, and TaxRule (ORM) -> TaxRuleLike
(structural Protocol) in the resolver. No other line changes -- byte-level
parity with the auto is the point.

Ports the pure test suites (domains, chave_acesso, resolver with a local
FakeTaxRule satisfying TaxRuleLike, xml_builder) plus a new test_presets_data
covering the PRESETS dict directly (the auto's equivalent test exercises the
apply-preset HTTP/DB flow, which isn't part of the extracted pure core).

55 tests pass locally via `uv run pytest`, no Postgres/k8s required.
2026-07-22 15:05:10 -03:00

74 lines
3.1 KiB
Python

"""Bloco B (spec 2026-07-14): catálogo de domínios tributários.
`tax_domain` é String no banco de propósito (imposto-como-dado): um domínio
novo (IBS/CBS/futuro) é um membro novo NO ENUM PYTHON, sem ALTER TYPE, sem
migration, sem o gotcha enum-NAME-vs-value. Estes testes travam o contrato
que o frontend consome via GET /fiscal/tax-domains e a tabela de pesos/
transposição que o resolvedor usa.
"""
from sowai_fiscal.domains import (
DOMAIN_FIELDS,
MATCHER_WEIGHTS,
TaxDomain,
transpose_cfop,
)
def test_tax_domain_has_the_reform_domains_without_any_migration():
# A prova literal do "imposto-como-dado": IBS/CBS já são domínios aceitos
# hoje, e não existe NENHUM tipo enum Postgres para tax_domain.
assert TaxDomain.IBS.value == "ibs"
assert TaxDomain.CBS.value == "cbs"
assert {d.value for d in TaxDomain} >= {
"icms", "icmsst", "ipi", "pis", "cofins", "difal", "fcp", "ibs", "cbs", "iss",
}
def test_domain_fields_catalog_covers_every_domain():
# O frontend monta o form por domínio a partir deste catálogo — cada
# domínio precisa declarar campos aplicáveis e obrigatórios.
for domain in TaxDomain:
spec = DOMAIN_FIELDS[domain.value]
assert spec.label
assert set(spec.campos_obrigatorios) <= set(spec.campos_aplicaveis)
for grupo in spec.campos_um_de:
assert set(grupo) <= set(spec.campos_aplicaveis)
assert not set(grupo) & set(spec.campos_obrigatorios) # um-de nunca também obrigatório
def test_icms_declares_the_exclusive_situacao_group():
# A UI exige "situação tributária" (cst OU csosn) a partir DAQUI — sem
# este grupo no catálogo, o form dinâmico deixaria passar regra de ICMS
# sem situação e o usuário comeria um 422 imprevisto (review frontend).
assert ("cst", "csosn") in DOMAIN_FIELDS["icms"].campos_um_de
assert ("cst", "csosn") in DOMAIN_FIELDS["icmsst"].campos_um_de
def test_matcher_weights_are_strict_powers_of_two():
# Cada peso supera a SOMA de todos os mais fracos (64 > 63): nenhuma
# combinação de matchers genéricos vence um mais seletivo. É a correção
# do review do frontend sobre "contagem de matchers".
ordered = sorted(MATCHER_WEIGHTS.values())
for i, w in enumerate(ordered):
assert w > sum(ordered[:i])
assert MATCHER_WEIGHTS["ncm_prefix"] == 64
assert MATCHER_WEIGHTS["cest"] == 32
assert MATCHER_WEIGHTS["consumidor_final"] == 16
assert MATCHER_WEIGHTS["indicador_ie"] == 8
assert MATCHER_WEIGHTS["uf_destino_tipo"] == 4
assert MATCHER_WEIGHTS["tipo_operacao"] == 2
assert MATCHER_WEIGHTS["crt"] == 1
def test_transpose_cfop_uses_the_explicit_table_for_st():
# 5405 → 6403, NÃO 6405: é por isso que a tabela explícita existe.
assert transpose_cfop("5405") == "6403"
assert transpose_cfop("5102") == "6102"
assert transpose_cfop("1202") == "2202"
def test_transpose_cfop_generic_fallback_swaps_the_first_digit():
# CFOP fora da tabela: regra geral 5xxx→6xxx / 1xxx→2xxx.
assert transpose_cfop("5949") == "6949"
assert transpose_cfop("1949") == "2949"