Files
GTI_GERENCIADOR_FISCAL/fiscal/central/app/unified_fiscal_coverage.py
T
gti d21a9d1140 Add 'fiscal/' from commit '102ba84500dbd9655a0471288c3e278652593ca9'
git-subtree-dir: fiscal
git-subtree-mainline: d95165fc8f
git-subtree-split: 102ba84500
2026-09-29 14:43:23 -03:00

274 lines
16 KiB
Python

"""UNIFIED fiscal completeness: ONE definition of "complete" for a product (pure, no I/O).
mandatory(product, company, rtc) = base fiscal fields (decided elsewhere: NCM/CEST/CFOP/ICMS/IPI ...)
+ PIS/COFINS when they APPLY to the product
+ RTC (cClassTrib -> CST IBS/CBS) ONLY when the ERP has the tax reform switched on.
There is no "legacy scope" and no "expanded scope": every finding here becomes a normal blocker in the single queue and
changes the single coverage number. Nothing here writes to the ERP or invents a fiscal value.
Sources (see docs/pis_cofins_applicability.md): SPED EFD-Contribuicoes table 4.3.3/4.3.4 (CST domain), 4.3.10-4.3.15 (special regimes),
LC 123/2006 art. 13/18 (PIS/Cofins inside the Simples DAS), Leis 9.718/98, 10.637/2002, 10.833/2003 (cumulative / non-cumulative),
NT 2025.002-RTC v1.51 (IBS/CBS fields), LC 214/2025 art. 348.
"""
from __future__ import annotations
import re
import unicodedata
from datetime import date
# Official CST PIS / CST COFINS domain (SPED tables 4.3.3 / 4.3.4). Membership only - never used to pick a value.
CST_DOMAIN = frozenset([f"{n:02d}" for n in (*range(1, 10), 49, *range(50, 57), *range(60, 68), *range(70, 76), 98, 99)])
RATE_REQUIRED_CST = frozenset({"01", "02"}) # taxable by percentage: a rate is needed
RATE_NOT_APPLICABLE_CST = frozenset({"03", "04", "05", "06", "07", "08", "09"}) # per-unit amount / monophasic / ST / zero / exempt / no incidence / suspended
INCIDENCE_CST = {"MONOFASICA": frozenset({"02", "03", "04"}), "SUBSTITUICAO": frozenset({"05"})} # 4.3.10/4.3.11 (02,03,04) and 4.3.12 (05)
SIMPLES_FAMILY = frozenset({"1", "2", "4"}) # CRT 1 Simples, 2 Simples excess sublimit, 4 MEI (LC 123/2006): PIS/Cofins are collected inside the DAS
PIS_COFINS_FIELDS = ("COD_ENTRADA_PIS", "COD_SAIDA_PIS", "ALIQ_PIS", "COD_ENTRADA_COFINS", "COD_SAIDA_COFINS", "ALIQ_COFINS")
FIELD_BY_BLOCKER_PREFIX = (("PIS", "PIS"), ("COFINS", "COFINS"), ("CCLASSTRIB", "RTC"), ("RTC", "RTC"))
def _text(value) -> str:
return "" if value is None else str(value).strip()
def blocker_category(blocker_type: str) -> str:
"""Dashboard category of a blocker type: PIS | COFINS | RTC | OTHER."""
for prefix, category in FIELD_BY_BLOCKER_PREFIX:
if blocker_type.startswith(prefix):
return category
return "OTHER"
def incidence_kind(value) -> str:
"""NONE | MONOFASICA | SUBSTITUICAO | UNKNOWN (accent/case-insensitive, encoding tolerant). Anything the SGV does not define is UNKNOWN, never NONE."""
raw = _text(value)
text = re.sub(r"[^A-Z]", "", unicodedata.normalize("NFKD", raw).encode("ascii", "ignore").decode().upper())
if not raw or text == "NENHUMA":
return "NONE"
if text.startswith("MONOF"):
return "MONOFASICA"
if text.startswith("SUBST"):
return "SUBSTITUICAO"
return "UNKNOWN"
def canonical_cst(value) -> str | None:
"""The official 2-digit CST this text stands for, or None. Exactly two official digits, or ONE digit whose zero-padded form is an official code
("4" -> "04"). Nothing else is ever normalised ("049", "000", "2x" stay invalid)."""
code = _text(value)
if code in CST_DOMAIN:
return code
if len(code) == 1 and code.isdigit() and code.zfill(2) in CST_DOMAIN:
return code.zfill(2)
return None
def validate_cst(value) -> str:
"""EMPTY | VALID | VALID_AFTER_NORMALIZATION | INVALID."""
code = _text(value)
if not code:
return "EMPTY"
if code in CST_DOMAIN:
return "VALID"
return "VALID_AFTER_NORMALIZATION" if canonical_cst(code) else "INVALID"
def validate_rate(value) -> str:
text = _text(value)
if not text:
return "EMPTY"
if not re.fullmatch(r"\d{1,3}([.,]\d{1,4})?", text):
return "INVALID"
return "VALID" if float(text.replace(",", ".")) <= 100 else "INVALID"
# ------------------------------------------------------------------------------------------------- PIS / COFINS
def pis_cofins_applicability(product: dict, crt) -> dict:
"""{"state": NOT_APPLICABLE | REQUIRED | OPTIONAL | NEEDS_REVIEW, "basis": ...}.
Decided ONLY from signals that are independent of the six CST/rate fields - an empty field is a data state, never proof of "not applicable":
* the company regime (CRT);
* the product's own declaration: PISCOFINS flag and INCIDENCIA_PISCOFINS;
* official rules (LC 123/2006 art. 13 IV/V and art. 18 par. 4-A I; Leis 9.718/98, 10.637/02, 10.833/03; SPED 4.3.x for the CST domain/semantics).
REQUIRED the product declares a special treatment (flag S, or incidence MONOFASICA / SUBSTITUICAO): the exit CST must be valid, whatever the fields hold.
NOT_APPLICABLE Simples family (CRT 1/2/4) with no special treatment declared: PIS/Cofins are collected inside the DAS (LC 123 art. 13), so the product carries no
obligation. The same verdict holds whether the fields are empty or typed.
OPTIONAL taxed regime (CRT 3 or other) AND the product explicitly declares "no special treatment" (flag N + incidence NENHUMA): the tax applies but the
company default covers it, so the product default is not mandatory; anything typed must still be valid.
NEEDS_REVIEW the declaration cannot be read: incidence text outside the SGV vocabulary, or a taxed regime with a blank flag/incidence.
"""
flag = _text(product.get("PISCOFINS")).upper()
raw_incidence = _text(product.get("INCIDENCIA_PISCOFINS"))
kind = incidence_kind(raw_incidence)
if kind == "UNKNOWN":
return {"state": "NEEDS_REVIEW", "basis": "INCIDENCE_NOT_DEFINED_BY_SGV"}
if flag == "S" or kind in ("MONOFASICA", "SUBSTITUICAO"):
return {"state": "REQUIRED", "basis": "PRODUCT_DECLARES_SPECIAL_TREATMENT"}
if _text(crt) in SIMPLES_FAMILY:
return {"state": "NOT_APPLICABLE", "basis": "SIMPLES_DAS_NO_SPECIAL_TREATMENT"}
if flag == "N" and raw_incidence:
return {"state": "OPTIONAL", "basis": "TAXED_REGIME_COMPANY_DEFAULT_COVERS"}
return {"state": "NEEDS_REVIEW", "basis": "NO_DECLARATION_IN_TAXED_REGIME"}
def evaluate_pis_cofins(product: dict, crt) -> dict:
"""{"applicability": {...}, "blockers": [(type, reason)], "warnings": [(type, reason)]}. Entry and exit are separate; PIS and COFINS independent.
Blockers exist only where the field applies (REQUIRED / OPTIONAL). Non-blocking warnings report data-quality facts (non-canonical or ignored values)."""
app = pis_cofins_applicability(product, crt)
blockers: list[tuple[str, str]] = []
warnings: list[tuple[str, str]] = []
kind = incidence_kind(product.get("INCIDENCIA_PISCOFINS"))
flag = _text(product.get("PISCOFINS")).upper()
applies = app["state"] in ("REQUIRED", "OPTIONAL")
for tax in ("PIS", "COFINS"):
entry, exit_, rate = product.get(f"COD_ENTRADA_{tax}"), product.get(f"COD_SAIDA_{tax}"), product.get(f"ALIQ_{tax}")
entry_state, exit_state, rate_state = validate_cst(entry), validate_cst(exit_), validate_rate(rate)
for slot, value, state in (("ENTRY", entry, entry_state), ("EXIT", exit_, exit_state)):
if state == "VALID_AFTER_NORMALIZATION":
warnings.append((f"CST_{tax}_NON_CANONICAL_FORMAT", f"{slot}:{_text(value)}->{canonical_cst(value)}"))
elif state == "INVALID":
if applies:
blockers.append((f"{tax}_CST_INVALID", slot))
else:
warnings.append((f"CST_{tax}_INVALID_IGNORED", f"{slot}:{_text(value)}"))
if not applies:
continue
if app["state"] == "REQUIRED" and exit_state == "EMPTY":
blockers.append((f"{tax}_CST_MISSING", "EXIT"))
if rate_state == "INVALID":
blockers.append((f"{tax}_RATE_INVALID", "RATE"))
code = canonical_cst(exit_)
if code:
if code in RATE_REQUIRED_CST and rate_state == "EMPTY":
blockers.append((f"{tax}_RATE_MISSING", f"CST_{code}_NEEDS_RATE"))
if kind in INCIDENCE_CST and code not in INCIDENCE_CST[kind]:
blockers.append((f"{tax}_CST_INCIDENCE_MISMATCH", f"{kind}:CST_{code}"))
if kind == "NONE" and app["state"] == "REQUIRED" and code in RATE_NOT_APPLICABLE_CST - {"03"} and flag == "S":
blockers.append((f"{tax}_CST_INCIDENCE_MISMATCH", f"NENHUMA:CST_{code}"))
if applies and flag == "N" and kind != "NONE":
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_N_WITH_SPECIAL_INCIDENCE"))
if applies and flag == "S" and kind == "NONE" and not any(_text(product.get(c)) for c in PIS_COFINS_FIELDS):
blockers.append(("PIS_COFINS_FLAG_INCONSISTENT", "FLAG_S_WITHOUT_INCIDENCE_OR_CODES"))
return {"applicability": app, "blockers": _dedupe(blockers), "warnings": _dedupe(warnings)}
def _dedupe(items):
seen, out = set(), []
for item in items:
if item not in seen:
seen.add(item)
out.append(item)
return out
# ------------------------------------------------------------------------------------------------- RTC
CCLASSTRIB_EFFECTIVE_FROM = {
"3": date(2026, 8, 3),
"1": date(2027, 1, 1),
"2": date(2027, 1, 1),
"4": date(2027, 1, 1),
}
def rtc_enabled(caps: dict) -> bool:
"""RTC is in scope ONLY when the ERP is switched to the reform (PARAMETROS.USAR_REFORMA_TRIBUTARIA truthy) AND its schema can hold it."""
return bool(caps.get("HAS_PRODUCT_CCLASSTRIB") and caps.get("HAS_RTC_RELATIONSHIP") and caps.get("RTC_ENABLED_BY_PARAM"))
def cclasstrib_applicability(crt, rtc_enabled: bool, today: date | None = None) -> dict:
"""Company-level cClassTrib requiredness from live CRT and its effective date.
The ERP RTC switch scopes the feature; it does not make cClassTrib mandatory
for every company/product. Unknown regimes fail closed to NOT_APPLICABLE.
"""
today = today or date.today()
regime = _text(crt)
if not rtc_enabled:
return {"state": "NOT_APPLICABLE", "basis": "RTC_DISABLED_IN_ERP", "effective_from": None, "source": "PARAMETROS.USAR_REFORMA_TRIBUTARIA"}
effective_from = CCLASSTRIB_EFFECTIVE_FROM.get(regime)
if effective_from is None:
return {"state": "NOT_APPLICABLE", "basis": "COMPANY_CRT_UNRESOLVED", "effective_from": None, "source": "C000004.CRT"}
if today < effective_from:
basis = "SIMPLES_CCLASSTRIB_NOT_EFFECTIVE" if regime in SIMPLES_FAMILY else "CCLASSTRIB_NOT_YET_EFFECTIVE"
return {"state": "NOT_APPLICABLE", "basis": basis, "effective_from": effective_from.isoformat(), "source": "C000004.CRT + RTC effective-date rules"}
return {"state": "REQUIRED", "basis": "CCLASSTRIB_EFFECTIVE_FOR_CRT", "effective_from": effective_from.isoformat(), "source": "C000004.CRT + RTC effective-date rules"}
def catalog_integrity(catalog: dict) -> dict:
"""catalog: {"classes": {cClassTrib: cst_or_None}, "cst": {code, ...}, "duplicates": int}."""
classes, cst = catalog.get("classes", {}), set(catalog.get("cst", ()))
bad_format = [c for c in classes if not re.fullmatch(r"\d{6}", str(c))]
bad_cst_format = [c for c in cst if not re.fullmatch(r"\d{3}", str(c))]
orphans = [c for c, s in classes.items() if s is None or str(s) not in cst]
return {"classes": len(classes), "cst": len(cst), "orphan_classes": len(orphans), "bad_class_format": len(bad_format), "bad_cst_format": len(bad_cst_format),
"duplicates": int(catalog.get("duplicates", 0)), "valid": bool(classes and cst and not orphans and not bad_format and not bad_cst_format and not catalog.get("duplicates"))}
def validate_cclasstrib(value, catalog: dict | None, today: date | None = None) -> str:
"""EMPTY | INVALID_FORMAT | NOT_IN_CATALOG | EXPIRED | CST_MISSING | VALID (CST IBS/CBS derived through the catalog, never stored on the product)."""
code = _text(value)
if not code:
return "EMPTY"
if not re.fullmatch(r"\d{6}", code):
return "INVALID_FORMAT"
if not catalog or code not in catalog.get("classes", {}):
return "NOT_IN_CATALOG"
validity = (catalog.get("validity") or {}).get(code)
if validity and today is not None:
start, end = validity
if (start and start > today) or (end and end < today):
return "EXPIRED"
cst = catalog["classes"][code]
return "VALID" if cst is not None and str(cst) in set(catalog.get("cst", ())) else "CST_MISSING"
def evaluate_rtc(product: dict, rtc: dict | None, today: date | None = None, crt=None) -> dict:
"""Validate cClassTrib only when RTC is on and the company's regime/date gate applies."""
app = rtc.get("cclasstrib_applicability") if isinstance(rtc, dict) else None
if not isinstance(app, dict):
app = cclasstrib_applicability(crt, bool(rtc and rtc.get("enabled")), today)
if app["state"] != "REQUIRED":
return {"applicability": app, "blockers": []}
if not rtc.get("catalog_valid"):
return {"applicability": app, "blockers": [("RTC_CATALOG_INVALID", "LOCAL_CATALOG_FAILED_INTEGRITY")]}
state = validate_cclasstrib(product.get("CCLASSTRIB"), rtc.get("catalog"), today)
mapping = {"EMPTY": "CCLASSTRIB_MISSING", "INVALID_FORMAT": "CCLASSTRIB_INVALID_FORMAT", "NOT_IN_CATALOG": "CCLASSTRIB_NOT_IN_CATALOG", "EXPIRED": "CCLASSTRIB_NOT_IN_CATALOG", "CST_MISSING": "RTC_CST_MISSING"}
return {"applicability": app, "blockers": [(mapping[state], state)] if state in mapping else []}
# ------------------------------------------------------------------------------------------------- unified
def evaluate_product(product: dict, company: dict | None, rtc: dict | None, today: date | None = None) -> dict:
"""The single fiscal-completeness verdict for the fields this module owns.
Returns {"blockers": [{"type","category","reason"}], "applicability": {"PIS_COFINS": state, "RTC": state}, "mandatory_fields": [...], "needs_review": bool}."""
crt = (company or {}).get("crt")
pis = evaluate_pis_cofins(product, crt)
rtc_result = evaluate_rtc(product, rtc, today, crt)
blockers = [{"type": kind, "category": blocker_category(kind), "reason": reason} for kind, reason in pis["blockers"] + rtc_result["blockers"]]
warnings = [{"type": kind, "reason": reason} for kind, reason in pis["warnings"]]
mandatory = []
if pis["applicability"]["state"] == "REQUIRED":
mandatory += ["COD_SAIDA_PIS", "COD_SAIDA_COFINS"]
if rtc_result["applicability"]["state"] == "REQUIRED":
mandatory.append("CCLASSTRIB")
return {"blockers": blockers, "applicability": {"PIS_COFINS": pis["applicability"]["state"], "RTC": rtc_result["applicability"]["state"]},
"basis": {"PIS_COFINS": pis["applicability"]["basis"], "RTC": rtc_result["applicability"]["basis"]}, "mandatory_fields": mandatory, "warnings": warnings,
"needs_review": pis["applicability"]["state"] == "NEEDS_REVIEW"}
def legal_effective_context(crt) -> dict:
"""Summarize the broad IBS/CBS document timeline; cClassTrib requiredness has its own CRT/date gate above.
Source: Ato Conjunto RFB/CGIBS n 4/2026 (DOU 31/07/2026) art. 1: NF-e (55) and NFC-e (65) from 2026-08-03; par. 1: Simples optantes from 2027-01-01."""
simples = _text(crt) in SIMPLES_FAMILY
return {"crt": _text(crt) or None, "informational_only": True,
"ibs_cbs_in_documents": "MANDATORY_FROM_2027-01-01" if simples else ("MANDATORY_FROM_2026-08-03" if _text(crt) == "3" else "UNKNOWN_REGIME"),
"cclasstrib_orientation": "PENDING_OFFICIAL_NT" if simples else ("TABLE_AND_LC214_ANNEXES" if _text(crt) == "3" else "UNKNOWN_REGIME"),
"sources": ["Ato Conjunto RFB/CGIBS n 4/2026 (DOU 31/07/2026) art. 1 I, II and par. 1", "NT 2025.002-RTC v1.51 (Portal NF-e, 04/08/2026): CRT 1/2/4 orientations in a future NT", "LC 214/2025 art. 348"]}