Commit ·
d9d7b41
1
Parent(s): 24e2bbe
Building Speed LLM
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +112 -0
- __pycache__/main.cpython-312.pyc +0 -0
- pyproject.toml +43 -0
- requirements.txt +18 -0
- src/__init__.py +10 -0
- src/__pycache__/__init__.cpython-312.pyc +0 -0
- src/calculators/__init__.py +29 -0
- src/calculators/__pycache__/__init__.cpython-312.pyc +0 -0
- src/calculators/__pycache__/icms.cpython-312.pyc +0 -0
- src/calculators/__pycache__/ipi.cpython-312.pyc +0 -0
- src/calculators/__pycache__/irpj_csll.cpython-312.pyc +0 -0
- src/calculators/__pycache__/iss.cpython-312.pyc +0 -0
- src/calculators/__pycache__/pis_cofins.cpython-312.pyc +0 -0
- src/calculators/__pycache__/simples_nacional.cpython-312.pyc +0 -0
- src/calculators/icms.py +137 -0
- src/calculators/ipi.py +144 -0
- src/calculators/irpj_csll.py +209 -0
- src/calculators/iss.py +157 -0
- src/calculators/pis_cofins.py +180 -0
- src/calculators/simples_nacional.py +292 -0
- src/fiscal/__init__.py +0 -0
- src/fiscal/__pycache__/__init__.cpython-312.pyc +0 -0
- src/fiscal/__pycache__/constants.cpython-312.pyc +0 -0
- src/fiscal/__pycache__/entities.cpython-312.pyc +0 -0
- src/fiscal/constants.py +338 -0
- src/fiscal/entities.py +286 -0
- src/generators/__init__.py +0 -0
- src/generators/__pycache__/__init__.cpython-312.pyc +0 -0
- src/generators/__pycache__/cte.cpython-312.pyc +0 -0
- src/generators/__pycache__/dctf.cpython-312.pyc +0 -0
- src/generators/__pycache__/defis.cpython-312.pyc +0 -0
- src/generators/__pycache__/destda.cpython-312.pyc +0 -0
- src/generators/__pycache__/dirf.cpython-312.pyc +0 -0
- src/generators/__pycache__/ecd.cpython-312.pyc +0 -0
- src/generators/__pycache__/ecf.cpython-312.pyc +0 -0
- src/generators/__pycache__/efd_contribuicoes.cpython-312.pyc +0 -0
- src/generators/__pycache__/efd_icms_ipi.cpython-312.pyc +0 -0
- src/generators/__pycache__/efd_reinf.cpython-312.pyc +0 -0
- src/generators/__pycache__/esocial.cpython-312.pyc +0 -0
- src/generators/__pycache__/gia.cpython-312.pyc +0 -0
- src/generators/__pycache__/mdfe.cpython-312.pyc +0 -0
- src/generators/__pycache__/nfce_xml.cpython-312.pyc +0 -0
- src/generators/__pycache__/nfe_xml.cpython-312.pyc +0 -0
- src/generators/__pycache__/sped_writer.cpython-312.pyc +0 -0
- src/generators/cte.py +334 -0
- src/generators/dctf.py +271 -0
- src/generators/defis.py +200 -0
- src/generators/destda.py +186 -0
- src/generators/dirf.py +186 -0
- src/generators/ecd.py +294 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,115 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
# Byte-compiled / optimized / DLL files
|
| 37 |
+
__pycache__/
|
| 38 |
+
*.py[cod]
|
| 39 |
+
*$py.class
|
| 40 |
+
|
| 41 |
+
# Virtual environment
|
| 42 |
+
venv/
|
| 43 |
+
.env/
|
| 44 |
+
.venv/
|
| 45 |
+
env/
|
| 46 |
+
|
| 47 |
+
# Installer logs
|
| 48 |
+
pip-log.txt
|
| 49 |
+
pip-delete-this-directory.txt
|
| 50 |
+
|
| 51 |
+
# Unit test / coverage reports
|
| 52 |
+
htmlcov/
|
| 53 |
+
.tox/
|
| 54 |
+
.nox/
|
| 55 |
+
.coverage
|
| 56 |
+
*.cover
|
| 57 |
+
*.py,cover
|
| 58 |
+
.hypothesis/
|
| 59 |
+
.pytest_cache/
|
| 60 |
+
coverage.xml
|
| 61 |
+
|
| 62 |
+
# Jupyter Notebook
|
| 63 |
+
.ipynb_checkpoints
|
| 64 |
+
|
| 65 |
+
# Environments
|
| 66 |
+
.env
|
| 67 |
+
.venv
|
| 68 |
+
env/
|
| 69 |
+
|
| 70 |
+
# IDE specific files
|
| 71 |
+
.vscode/
|
| 72 |
+
.idea/
|
| 73 |
+
*.swp
|
| 74 |
+
*.swo
|
| 75 |
+
|
| 76 |
+
# Build artifacts
|
| 77 |
+
dist/
|
| 78 |
+
*.egg-info/
|
| 79 |
+
*.egg
|
| 80 |
+
*.manifest
|
| 81 |
+
*.spec
|
| 82 |
+
|
| 83 |
+
# Ignore only PyInstaller/setuptools build output inside build/, not our scripts
|
| 84 |
+
build/lib/
|
| 85 |
+
build/bdist*/
|
| 86 |
+
build/temp*/
|
| 87 |
+
|
| 88 |
+
# CLI binaries are tracked so Render can serve them for download
|
| 89 |
+
!app/cli/dist/
|
| 90 |
+
!app/cli/dist/agents-linux
|
| 91 |
+
!app/cli/dist/agents-windows.exe
|
| 92 |
+
|
| 93 |
+
# Installer logs
|
| 94 |
+
pip-log.txt
|
| 95 |
+
pip-delete-this-directory.txt
|
| 96 |
+
|
| 97 |
+
# pyenv
|
| 98 |
+
.python-version
|
| 99 |
+
|
| 100 |
+
# mypy
|
| 101 |
+
.mypy_cache/
|
| 102 |
+
dmypy.json
|
| 103 |
+
|
| 104 |
+
# PyCharm
|
| 105 |
+
.idea/
|
| 106 |
+
|
| 107 |
+
# VS Code
|
| 108 |
+
.vscode/
|
| 109 |
+
*.code-workspace
|
| 110 |
+
|
| 111 |
+
# macOS
|
| 112 |
+
.DS_Store
|
| 113 |
+
|
| 114 |
+
# Windows
|
| 115 |
+
Thumbs.db
|
| 116 |
+
|
| 117 |
+
# Database files
|
| 118 |
+
*.db
|
| 119 |
+
*.sqlite
|
| 120 |
+
|
| 121 |
+
# Log files
|
| 122 |
+
*.log
|
| 123 |
+
|
| 124 |
+
# Sphinx documentation
|
| 125 |
+
docs/_build/
|
| 126 |
+
|
| 127 |
+
# PyInstaller
|
| 128 |
+
*.spec
|
| 129 |
+
|
| 130 |
+
# IPython
|
| 131 |
+
profile_default/
|
| 132 |
+
ipython_config.py
|
| 133 |
+
|
| 134 |
+
# Poetry
|
| 135 |
+
poetry.lock
|
| 136 |
+
|
| 137 |
+
# PDM
|
| 138 |
+
__pypackages__/
|
| 139 |
+
.pdm.toml
|
| 140 |
+
|
| 141 |
+
# pytype
|
| 142 |
+
.pytype/
|
| 143 |
+
|
| 144 |
+
# Cython debug symbols
|
| 145 |
+
cython_debug/
|
| 146 |
+
# Generated at build time
|
| 147 |
+
app/cli/_build_config.py
|
__pycache__/main.cpython-312.pyc
ADDED
|
Binary file (29.8 kB). View file
|
|
|
pyproject.toml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=68", "wheel"]
|
| 3 |
+
build-backend = "setuptools.backends.legacy:build"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "llm-sped-fiscal"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "Sistema LLM para gestão de obrigações fiscais brasileiras (SPED)"
|
| 9 |
+
requires-python = ">=3.12"
|
| 10 |
+
license = {text = "MIT"}
|
| 11 |
+
dependencies = [
|
| 12 |
+
"torch>=2.3.0",
|
| 13 |
+
"numpy>=1.26.0",
|
| 14 |
+
"pydantic>=2.7.0",
|
| 15 |
+
"httpx>=0.27.0",
|
| 16 |
+
"aiofiles>=23.2.1",
|
| 17 |
+
"python-dateutil>=2.9.0",
|
| 18 |
+
"cryptography>=42.0.0",
|
| 19 |
+
"lxml>=5.2.0",
|
| 20 |
+
"python-dotenv>=1.0.0",
|
| 21 |
+
"requests>=2.31.0",
|
| 22 |
+
"zeep>=4.2.1",
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
[project.optional-dependencies]
|
| 26 |
+
dev = [
|
| 27 |
+
"pytest>=8.2.0",
|
| 28 |
+
"pytest-asyncio>=0.23.0",
|
| 29 |
+
"ruff>=0.4.0",
|
| 30 |
+
"mypy>=1.10.0",
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
[tool.setuptools.packages.find]
|
| 34 |
+
where = ["."]
|
| 35 |
+
include = ["src*"]
|
| 36 |
+
|
| 37 |
+
[tool.ruff]
|
| 38 |
+
line-length = 100
|
| 39 |
+
target-version = "py312"
|
| 40 |
+
|
| 41 |
+
[tool.mypy]
|
| 42 |
+
python_version = "3.12"
|
| 43 |
+
strict = true
|
requirements.txt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.3.0
|
| 2 |
+
numpy>=1.26.0
|
| 3 |
+
pydantic>=2.7.0
|
| 4 |
+
httpx>=0.27.0
|
| 5 |
+
aiofiles>=23.2.1
|
| 6 |
+
rich>=13.7.0
|
| 7 |
+
typer>=0.12.0
|
| 8 |
+
python-dateutil>=2.9.0
|
| 9 |
+
cryptography>=42.0.0
|
| 10 |
+
lxml>=5.2.0
|
| 11 |
+
python-dotenv>=1.0.0
|
| 12 |
+
sentencepiece>=0.2.0
|
| 13 |
+
requests>=2.31.0
|
| 14 |
+
zeep>=4.2.1
|
| 15 |
+
# testes
|
| 16 |
+
pytest>=8.0.0
|
| 17 |
+
pytest-asyncio>=0.23.0
|
| 18 |
+
pytest-cov>=5.0.0
|
src/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
LLM Fiscal Brasileiro — Sistema de Obrigações SPED com PyTorch.
|
| 3 |
+
|
| 4 |
+
Ponto de entrada:
|
| 5 |
+
from src import PipelineFiscal
|
| 6 |
+
pipeline = PipelineFiscal()
|
| 7 |
+
"""
|
| 8 |
+
from src.models.fiscal_llm import PipelineFiscal, TransformerFiscal, TokenizadorFiscal
|
| 9 |
+
|
| 10 |
+
__all__ = ["PipelineFiscal", "TransformerFiscal", "TokenizadorFiscal"]
|
src/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (491 Bytes). View file
|
|
|
src/calculators/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Calculadoras de tributos brasileiros."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from decimal import Decimal, ROUND_HALF_UP
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def arred(valor: Decimal, casas: int = 2) -> Decimal:
|
| 9 |
+
quantizador = Decimal(10) ** -casas
|
| 10 |
+
return valor.quantize(quantizador, rounding=ROUND_HALF_UP)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass
|
| 14 |
+
class ResultadoCalculo:
|
| 15 |
+
base_calculo: Decimal
|
| 16 |
+
aliquota: Decimal
|
| 17 |
+
valor_tributo: Decimal
|
| 18 |
+
deducoes: Decimal = Decimal("0")
|
| 19 |
+
creditos: Decimal = Decimal("0")
|
| 20 |
+
valor_a_recolher: Decimal = Decimal("0")
|
| 21 |
+
detalhes: dict = None
|
| 22 |
+
|
| 23 |
+
def __post_init__(self):
|
| 24 |
+
if self.detalhes is None:
|
| 25 |
+
self.detalhes = {}
|
| 26 |
+
if self.valor_a_recolher == Decimal("0"):
|
| 27 |
+
self.valor_a_recolher = max(
|
| 28 |
+
Decimal("0"), self.valor_tributo - self.creditos - self.deducoes
|
| 29 |
+
)
|
src/calculators/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.72 kB). View file
|
|
|
src/calculators/__pycache__/icms.cpython-312.pyc
ADDED
|
Binary file (6.79 kB). View file
|
|
|
src/calculators/__pycache__/ipi.cpython-312.pyc
ADDED
|
Binary file (5.74 kB). View file
|
|
|
src/calculators/__pycache__/irpj_csll.cpython-312.pyc
ADDED
|
Binary file (9.3 kB). View file
|
|
|
src/calculators/__pycache__/iss.cpython-312.pyc
ADDED
|
Binary file (6.16 kB). View file
|
|
|
src/calculators/__pycache__/pis_cofins.cpython-312.pyc
ADDED
|
Binary file (7.58 kB). View file
|
|
|
src/calculators/__pycache__/simples_nacional.cpython-312.pyc
ADDED
|
Binary file (15.1 kB). View file
|
|
|
src/calculators/icms.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Calculadora de ICMS com suporte a ST, DIFAL e FCP."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from decimal import Decimal
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
from src.calculators import ResultadoCalculo, arred
|
| 9 |
+
from src.fiscal.constants import ALIQ_ICMS_INTERNA, aliq_icms_interestadual
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class ParametrosICMS:
|
| 14 |
+
valor_mercadoria: Decimal
|
| 15 |
+
aliquota: Decimal
|
| 16 |
+
cst: str = "000"
|
| 17 |
+
uf_origem: str = "SP"
|
| 18 |
+
uf_destino: str = "SP"
|
| 19 |
+
reducao_base: Decimal = Decimal("0")
|
| 20 |
+
frete: Decimal = Decimal("0")
|
| 21 |
+
seguro: Decimal = Decimal("0")
|
| 22 |
+
outras_despesas: Decimal = Decimal("0")
|
| 23 |
+
desconto: Decimal = Decimal("0")
|
| 24 |
+
# Substituição Tributária
|
| 25 |
+
aliq_st: Optional[Decimal] = None
|
| 26 |
+
mva: Optional[Decimal] = None
|
| 27 |
+
base_st_retido: Decimal = Decimal("0")
|
| 28 |
+
# FCP (Fundo de Combate à Pobreza)
|
| 29 |
+
aliq_fcp: Decimal = Decimal("0")
|
| 30 |
+
aliq_fcp_st: Decimal = Decimal("0")
|
| 31 |
+
# DIFAL (Diferencial de Alíquota - EMENDA 87)
|
| 32 |
+
calcular_difal: bool = False
|
| 33 |
+
consumidor_final: bool = False
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@dataclass
|
| 37 |
+
class ResultadoICMS:
|
| 38 |
+
base_calculo: Decimal = Decimal("0")
|
| 39 |
+
aliquota: Decimal = Decimal("0")
|
| 40 |
+
valor_icms: Decimal = Decimal("0")
|
| 41 |
+
# ST
|
| 42 |
+
base_st: Decimal = Decimal("0")
|
| 43 |
+
aliq_st: Decimal = Decimal("0")
|
| 44 |
+
valor_st: Decimal = Decimal("0")
|
| 45 |
+
# FCP
|
| 46 |
+
base_fcp: Decimal = Decimal("0")
|
| 47 |
+
valor_fcp: Decimal = Decimal("0")
|
| 48 |
+
valor_fcp_st: Decimal = Decimal("0")
|
| 49 |
+
# DIFAL
|
| 50 |
+
valor_difal_origem: Decimal = Decimal("0")
|
| 51 |
+
valor_difal_destino: Decimal = Decimal("0")
|
| 52 |
+
# Total
|
| 53 |
+
valor_total_icms: Decimal = Decimal("0")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def calcular_icms(p: ParametrosICMS) -> ResultadoICMS:
|
| 57 |
+
r = ResultadoICMS()
|
| 58 |
+
|
| 59 |
+
if p.cst in {"040", "041", "050", "051", "060"}:
|
| 60 |
+
return r
|
| 61 |
+
|
| 62 |
+
base_bruta = (
|
| 63 |
+
p.valor_mercadoria + p.frete + p.seguro + p.outras_despesas - p.desconto
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
if p.cst in {"020", "070"}:
|
| 67 |
+
r.base_calculo = arred(base_bruta * (1 - p.reducao_base / 100))
|
| 68 |
+
else:
|
| 69 |
+
r.base_calculo = arred(base_bruta)
|
| 70 |
+
|
| 71 |
+
r.aliquota = p.aliquota
|
| 72 |
+
r.valor_icms = arred(r.base_calculo * p.aliquota / 100)
|
| 73 |
+
|
| 74 |
+
# FCP
|
| 75 |
+
if p.aliq_fcp > 0:
|
| 76 |
+
r.base_fcp = r.base_calculo
|
| 77 |
+
r.valor_fcp = arred(r.base_fcp * p.aliq_fcp / 100)
|
| 78 |
+
|
| 79 |
+
# Substituição Tributária
|
| 80 |
+
if p.cst in {"010", "030", "070"} and p.aliq_st is not None and p.mva is not None:
|
| 81 |
+
aliq_interna_dest = ALIQ_ICMS_INTERNA.get(p.uf_destino, Decimal("18"))
|
| 82 |
+
r.base_st = arred(r.base_calculo * (1 + p.mva / 100))
|
| 83 |
+
r.aliq_st = aliq_interna_dest
|
| 84 |
+
icms_proprio_dest = arred(r.base_st * aliq_interna_dest / 100)
|
| 85 |
+
r.valor_st = max(Decimal("0"), arred(icms_proprio_dest - r.valor_icms))
|
| 86 |
+
if p.aliq_fcp_st > 0:
|
| 87 |
+
r.valor_fcp_st = arred(r.base_st * p.aliq_fcp_st / 100)
|
| 88 |
+
|
| 89 |
+
# DIFAL (EC 87/2015) para operações interestaduais para consumidor final
|
| 90 |
+
if p.calcular_difal and p.consumidor_final and p.uf_origem != p.uf_destino:
|
| 91 |
+
aliq_interestadual = aliq_icms_interestadual(p.uf_origem, p.uf_destino)
|
| 92 |
+
aliq_interna_dest = ALIQ_ICMS_INTERNA.get(p.uf_destino, Decimal("18"))
|
| 93 |
+
difal = arred(r.base_calculo * (aliq_interna_dest - aliq_interestadual) / 100)
|
| 94 |
+
# Partilha DIFAL: 2019+ => 100% destino
|
| 95 |
+
r.valor_difal_destino = arred(difal * Decimal("0.60"))
|
| 96 |
+
r.valor_difal_origem = arred(difal * Decimal("0.40"))
|
| 97 |
+
|
| 98 |
+
r.valor_total_icms = arred(
|
| 99 |
+
r.valor_icms + r.valor_st + r.valor_fcp + r.valor_fcp_st
|
| 100 |
+
+ r.valor_difal_destino + r.valor_difal_origem
|
| 101 |
+
)
|
| 102 |
+
return r
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def calcular_icms_credito_entrada(
|
| 106 |
+
valor_nf: Decimal,
|
| 107 |
+
aliquota: Decimal,
|
| 108 |
+
percentual_aproveitamento: Decimal = Decimal("100"),
|
| 109 |
+
) -> Decimal:
|
| 110 |
+
credito_bruto = arred(valor_nf * aliquota / 100)
|
| 111 |
+
return arred(credito_bruto * percentual_aproveitamento / 100)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def calcular_icms_apuracao(
|
| 115 |
+
debitos: list[Decimal],
|
| 116 |
+
creditos: list[Decimal],
|
| 117 |
+
saldo_anterior: Decimal = Decimal("0"),
|
| 118 |
+
) -> ResultadoCalculo:
|
| 119 |
+
total_debitos = arred(sum(debitos))
|
| 120 |
+
total_creditos = arred(sum(creditos) + saldo_anterior)
|
| 121 |
+
saldo = total_creditos - total_debitos
|
| 122 |
+
valor_a_recolher = max(Decimal("0"), arred(total_debitos - total_creditos))
|
| 123 |
+
saldo_credor = max(Decimal("0"), arred(total_creditos - total_debitos))
|
| 124 |
+
return ResultadoCalculo(
|
| 125 |
+
base_calculo=total_debitos,
|
| 126 |
+
aliquota=Decimal("0"),
|
| 127 |
+
valor_tributo=total_debitos,
|
| 128 |
+
deducoes=Decimal("0"),
|
| 129 |
+
creditos=total_creditos,
|
| 130 |
+
valor_a_recolher=valor_a_recolher,
|
| 131 |
+
detalhes={
|
| 132 |
+
"total_debitos": total_debitos,
|
| 133 |
+
"total_creditos": total_creditos,
|
| 134 |
+
"saldo_devedor": valor_a_recolher,
|
| 135 |
+
"saldo_credor": saldo_credor,
|
| 136 |
+
},
|
| 137 |
+
)
|
src/calculators/ipi.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Calculadora de IPI (Imposto sobre Produtos Industrializados)."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from decimal import Decimal
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
from src.calculators import ResultadoCalculo, arred
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class ParametrosIPI:
|
| 13 |
+
valor_produtos: Decimal
|
| 14 |
+
aliquota: Decimal
|
| 15 |
+
cst: str = "50"
|
| 16 |
+
frete: Decimal = Decimal("0")
|
| 17 |
+
seguro: Decimal = Decimal("0")
|
| 18 |
+
outras_despesas: Decimal = Decimal("0")
|
| 19 |
+
desconto: Decimal = Decimal("0")
|
| 20 |
+
# Para operações por unidade (pauta)
|
| 21 |
+
quantidade: Decimal = Decimal("0")
|
| 22 |
+
valor_pauta_unidade: Optional[Decimal] = None
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def calcular_ipi(p: ParametrosIPI) -> ResultadoCalculo:
|
| 26 |
+
# CSTs isentos/não tributados
|
| 27 |
+
if p.cst in {"51", "52", "53", "54", "55", "02", "03", "04", "05"}:
|
| 28 |
+
return ResultadoCalculo(
|
| 29 |
+
base_calculo=Decimal("0"),
|
| 30 |
+
aliquota=p.aliquota,
|
| 31 |
+
valor_tributo=Decimal("0"),
|
| 32 |
+
valor_a_recolher=Decimal("0"),
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
if p.cst == "03" and p.valor_pauta_unidade and p.quantidade:
|
| 36 |
+
# Tributação por pauta
|
| 37 |
+
base = arred(p.quantidade * p.valor_pauta_unidade)
|
| 38 |
+
else:
|
| 39 |
+
base = arred(
|
| 40 |
+
p.valor_produtos + p.frete + p.seguro + p.outras_despesas - p.desconto
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
valor = arred(base * p.aliquota / 100)
|
| 44 |
+
|
| 45 |
+
return ResultadoCalculo(
|
| 46 |
+
base_calculo=base,
|
| 47 |
+
aliquota=p.aliquota,
|
| 48 |
+
valor_tributo=valor,
|
| 49 |
+
valor_a_recolher=valor,
|
| 50 |
+
detalhes={"cst": p.cst},
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def calcular_ipi_apuracao(
|
| 55 |
+
debitos: list[Decimal],
|
| 56 |
+
creditos: list[Decimal],
|
| 57 |
+
saldo_anterior: Decimal = Decimal("0"),
|
| 58 |
+
) -> ResultadoCalculo:
|
| 59 |
+
total_debitos = arred(sum(debitos))
|
| 60 |
+
total_creditos = arred(sum(creditos) + saldo_anterior)
|
| 61 |
+
valor_a_recolher = max(Decimal("0"), arred(total_debitos - total_creditos))
|
| 62 |
+
saldo_credor = max(Decimal("0"), arred(total_creditos - total_debitos))
|
| 63 |
+
return ResultadoCalculo(
|
| 64 |
+
base_calculo=total_debitos,
|
| 65 |
+
aliquota=Decimal("0"),
|
| 66 |
+
valor_tributo=total_debitos,
|
| 67 |
+
creditos=total_creditos,
|
| 68 |
+
valor_a_recolher=valor_a_recolher,
|
| 69 |
+
detalhes={
|
| 70 |
+
"total_debitos": total_debitos,
|
| 71 |
+
"total_creditos": total_creditos,
|
| 72 |
+
"saldo_credor": saldo_credor,
|
| 73 |
+
},
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
# Alíquotas IPI simplificadas por capítulo NCM (TIPI)
|
| 78 |
+
TIPI_ALIQUOTAS: dict[str, Decimal] = {
|
| 79 |
+
"01": Decimal("0"), # Animais vivos
|
| 80 |
+
"02": Decimal("0"), # Carnes e miudezas
|
| 81 |
+
"03": Decimal("0"), # Peixes e crustáceos
|
| 82 |
+
"04": Decimal("0"), # Leite, laticínios, ovos
|
| 83 |
+
"05": Decimal("0"), # Outros produtos de origem animal
|
| 84 |
+
"06": Decimal("0"), # Plantas vivas e produtos de floricultura
|
| 85 |
+
"07": Decimal("0"), # Produtos hortícolas
|
| 86 |
+
"08": Decimal("0"), # Frutas
|
| 87 |
+
"09": Decimal("0"), # Café, chá, mate
|
| 88 |
+
"10": Decimal("0"), # Cereais
|
| 89 |
+
"11": Decimal("0"), # Produtos da indústria de moagem
|
| 90 |
+
"12": Decimal("0"), # Sementes e frutos oleaginosos
|
| 91 |
+
"16": Decimal("0"), # Preparações de carne, peixe
|
| 92 |
+
"17": Decimal("5"), # Açúcares e produtos de confeitaria
|
| 93 |
+
"18": Decimal("5"), # Cacau e suas preparações
|
| 94 |
+
"19": Decimal("5"), # Preparações à base de cereais
|
| 95 |
+
"20": Decimal("5"), # Preparações de produtos hortícolas e frutas
|
| 96 |
+
"21": Decimal("5"), # Preparações alimentícias diversas
|
| 97 |
+
"22": Decimal("15"), # Bebidas, líquidos alcoólicos e vinagres
|
| 98 |
+
"24": Decimal("300"), # Tabaco e seus sucedâneos manufaturados
|
| 99 |
+
"25": Decimal("5"), # Sal; enxofre; terras e pedras
|
| 100 |
+
"27": Decimal("5"), # Combustíveis minerais, óleos minerais
|
| 101 |
+
"28": Decimal("5"), # Produtos químicos inorgânicos
|
| 102 |
+
"29": Decimal("5"), # Produtos químicos orgânicos
|
| 103 |
+
"30": Decimal("0"), # Produtos farmacêuticos
|
| 104 |
+
"31": Decimal("0"), # Adubos ou fertilizantes
|
| 105 |
+
"33": Decimal("7"), # Óleos essenciais e resinoides; cosméticos
|
| 106 |
+
"34": Decimal("5"), # Sabões, agentes orgânicos de superfície
|
| 107 |
+
"38": Decimal("5"), # Produtos diversos das indústrias químicas
|
| 108 |
+
"39": Decimal("15"), # Plásticos e suas obras
|
| 109 |
+
"40": Decimal("15"), # Borracha e suas obras
|
| 110 |
+
"41": Decimal("5"), # Peles, exceto peles com pelo
|
| 111 |
+
"42": Decimal("15"), # Obras de couro; artigos de seleiro
|
| 112 |
+
"44": Decimal("5"), # Madeira, carvão vegetal e obras de madeira
|
| 113 |
+
"48": Decimal("10"), # Papel e cartão
|
| 114 |
+
"49": Decimal("5"), # Livros, jornais, gravuras
|
| 115 |
+
"50": Decimal("10"), # Seda
|
| 116 |
+
"51": Decimal("10"), # Lã, pelos finos ou grosseiros
|
| 117 |
+
"52": Decimal("10"), # Algodão
|
| 118 |
+
"61": Decimal("20"), # Vestuário e seus acessórios (malha)
|
| 119 |
+
"62": Decimal("20"), # Vestuário e seus acessórios (exceto malha)
|
| 120 |
+
"64": Decimal("20"), # Calçados, polainas e artefatos semelhantes
|
| 121 |
+
"69": Decimal("15"), # Produtos cerâmicos
|
| 122 |
+
"70": Decimal("15"), # Vidro e suas obras
|
| 123 |
+
"72": Decimal("5"), # Ferro fundido, ferro e aço
|
| 124 |
+
"73": Decimal("10"), # Obras de ferro fundido, ferro ou aço
|
| 125 |
+
"74": Decimal("10"), # Cobre e suas obras
|
| 126 |
+
"76": Decimal("10"), # Alumínio e suas obras
|
| 127 |
+
"82": Decimal("10"), # Ferramentas, artefatos de cutelaria
|
| 128 |
+
"84": Decimal("10"), # Máquinas e aparelhos mecânicos
|
| 129 |
+
"85": Decimal("15"), # Máquinas e aparelhos elétricos
|
| 130 |
+
"86": Decimal("5"), # Veículos e material para vias férreas
|
| 131 |
+
"87": Decimal("25"), # Veículos automóveis, tratores
|
| 132 |
+
"88": Decimal("0"), # Aeronaves e aparelhos espaciais
|
| 133 |
+
"90": Decimal("15"), # Instrumentos e aparelhos de óptica
|
| 134 |
+
"91": Decimal("20"), # Instrumentos de relojoaria
|
| 135 |
+
"92": Decimal("25"), # Instrumentos musicais
|
| 136 |
+
"94": Decimal("15"), # Móveis; mobiliário médico-cirúrgico
|
| 137 |
+
"95": Decimal("25"), # Brinquedos, jogos, artigos para divertimento
|
| 138 |
+
"96": Decimal("10"), # Obras diversas
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def obter_aliquota_ipi_ncm(ncm: str) -> Decimal:
|
| 143 |
+
capitulo = ncm[:2]
|
| 144 |
+
return TIPI_ALIQUOTAS.get(capitulo, Decimal("5"))
|
src/calculators/irpj_csll.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Calculadora de IRPJ e CSLL (Lucro Real e Lucro Presumido)."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from decimal import Decimal
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
from src.calculators import ResultadoCalculo, arred
|
| 9 |
+
from src.fiscal.constants import (
|
| 10 |
+
ALIQ_CSLL_FINANCEIRAS,
|
| 11 |
+
ALIQ_CSLL_PADRAO,
|
| 12 |
+
ALIQ_IRPJ_ADICIONAL,
|
| 13 |
+
ALIQ_IRPJ_BASE,
|
| 14 |
+
LIMITE_ADICIONAL_IRPJ_TRIMESTRAL,
|
| 15 |
+
PERCENTUAL_PRESUMIDO_CSLL,
|
| 16 |
+
PERCENTUAL_PRESUMIDO_IRPJ,
|
| 17 |
+
)
|
| 18 |
+
from src.fiscal.entities import RegimeTributario
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class ResultadoIRPJ:
|
| 23 |
+
lucro_real_ajustado: Decimal = Decimal("0")
|
| 24 |
+
base_calculo: Decimal = Decimal("0")
|
| 25 |
+
aliquota_base: Decimal = Decimal("15")
|
| 26 |
+
valor_base: Decimal = Decimal("0")
|
| 27 |
+
base_adicional: Decimal = Decimal("0")
|
| 28 |
+
valor_adicional: Decimal = Decimal("0")
|
| 29 |
+
valor_irpj_total: Decimal = Decimal("0")
|
| 30 |
+
# Incentivos e deduções
|
| 31 |
+
incentivos_fiscais: Decimal = Decimal("0")
|
| 32 |
+
irpj_a_recolher: Decimal = Decimal("0")
|
| 33 |
+
compensacoes: Decimal = Decimal("0")
|
| 34 |
+
detalhes: dict = field(default_factory=dict)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclass
|
| 38 |
+
class ResultadoCSLL:
|
| 39 |
+
base_calculo: Decimal = Decimal("0")
|
| 40 |
+
aliquota: Decimal = Decimal("0")
|
| 41 |
+
valor_csll: Decimal = Decimal("0")
|
| 42 |
+
csll_a_recolher: Decimal = Decimal("0")
|
| 43 |
+
compensacoes: Decimal = Decimal("0")
|
| 44 |
+
detalhes: dict = field(default_factory=dict)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def calcular_irpj_lucro_real(
|
| 48 |
+
lucro_antes_ir: Decimal,
|
| 49 |
+
adicoes: Decimal = Decimal("0"),
|
| 50 |
+
exclusoes: Decimal = Decimal("0"),
|
| 51 |
+
compensacoes_prejuizo: Decimal = Decimal("0"),
|
| 52 |
+
incentivos_fiscais: Decimal = Decimal("0"),
|
| 53 |
+
irpj_estimativas: Decimal = Decimal("0"),
|
| 54 |
+
periodo: str = "trimestral",
|
| 55 |
+
) -> ResultadoIRPJ:
|
| 56 |
+
r = ResultadoIRPJ()
|
| 57 |
+
|
| 58 |
+
lucro_ajustado = arred(lucro_antes_ir + adicoes - exclusoes)
|
| 59 |
+
r.lucro_real_ajustado = lucro_ajustado
|
| 60 |
+
|
| 61 |
+
# Compensação de prejuízo (limite 30% do lucro ajustado)
|
| 62 |
+
limite_compensacao = arred(lucro_ajustado * Decimal("0.30"))
|
| 63 |
+
comp_efetiva = min(compensacoes_prejuizo, limite_compensacao)
|
| 64 |
+
r.base_calculo = max(Decimal("0"), arred(lucro_ajustado - comp_efetiva))
|
| 65 |
+
|
| 66 |
+
r.aliquota_base = ALIQ_IRPJ_BASE
|
| 67 |
+
r.valor_base = arred(r.base_calculo * ALIQ_IRPJ_BASE / 100)
|
| 68 |
+
|
| 69 |
+
limite_adicional = (
|
| 70 |
+
LIMITE_ADICIONAL_IRPJ_TRIMESTRAL
|
| 71 |
+
if periodo == "trimestral"
|
| 72 |
+
else arred(LIMITE_ADICIONAL_IRPJ_TRIMESTRAL * Decimal("4"))
|
| 73 |
+
)
|
| 74 |
+
r.base_adicional = max(Decimal("0"), arred(r.base_calculo - limite_adicional))
|
| 75 |
+
r.valor_adicional = arred(r.base_adicional * ALIQ_IRPJ_ADICIONAL / 100)
|
| 76 |
+
|
| 77 |
+
r.valor_irpj_total = arred(r.valor_base + r.valor_adicional)
|
| 78 |
+
r.incentivos_fiscais = arred(incentivos_fiscais)
|
| 79 |
+
|
| 80 |
+
r.irpj_a_recolher = max(
|
| 81 |
+
Decimal("0"),
|
| 82 |
+
arred(r.valor_irpj_total - r.incentivos_fiscais - irpj_estimativas),
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
r.detalhes = {
|
| 86 |
+
"adicoes": adicoes,
|
| 87 |
+
"exclusoes": exclusoes,
|
| 88 |
+
"compensacao_prejuizo_utilizada": comp_efetiva,
|
| 89 |
+
"compensacoes_disponiveis": compensacoes_prejuizo,
|
| 90 |
+
"limite_compensacao_30pct": limite_compensacao,
|
| 91 |
+
"estimativas_pagas": irpj_estimativas,
|
| 92 |
+
}
|
| 93 |
+
return r
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def calcular_irpj_lucro_presumido(
|
| 97 |
+
receita_bruta: Decimal,
|
| 98 |
+
outras_receitas: Decimal = Decimal("0"),
|
| 99 |
+
atividade: str = "venda_mercadorias",
|
| 100 |
+
incentivos_fiscais: Decimal = Decimal("0"),
|
| 101 |
+
irpj_anteriores: Decimal = Decimal("0"),
|
| 102 |
+
) -> ResultadoIRPJ:
|
| 103 |
+
r = ResultadoIRPJ()
|
| 104 |
+
|
| 105 |
+
percentual = PERCENTUAL_PRESUMIDO_IRPJ.get(atividade, Decimal("8.0"))
|
| 106 |
+
lucro_presumido = arred(receita_bruta * percentual / 100)
|
| 107 |
+
r.lucro_real_ajustado = arred(lucro_presumido + outras_receitas)
|
| 108 |
+
r.base_calculo = r.lucro_real_ajustado
|
| 109 |
+
|
| 110 |
+
r.aliquota_base = ALIQ_IRPJ_BASE
|
| 111 |
+
r.valor_base = arred(r.base_calculo * ALIQ_IRPJ_BASE / 100)
|
| 112 |
+
|
| 113 |
+
r.base_adicional = max(Decimal("0"), arred(r.base_calculo - LIMITE_ADICIONAL_IRPJ_TRIMESTRAL))
|
| 114 |
+
r.valor_adicional = arred(r.base_adicional * ALIQ_IRPJ_ADICIONAL / 100)
|
| 115 |
+
|
| 116 |
+
r.valor_irpj_total = arred(r.valor_base + r.valor_adicional)
|
| 117 |
+
r.incentivos_fiscais = incentivos_fiscais
|
| 118 |
+
r.irpj_a_recolher = max(
|
| 119 |
+
Decimal("0"),
|
| 120 |
+
arred(r.valor_irpj_total - incentivos_fiscais - irpj_anteriores),
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
r.detalhes = {
|
| 124 |
+
"receita_bruta": receita_bruta,
|
| 125 |
+
"percentual_presuncao": percentual,
|
| 126 |
+
"lucro_presumido": lucro_presumido,
|
| 127 |
+
"outras_receitas": outras_receitas,
|
| 128 |
+
"atividade": atividade,
|
| 129 |
+
}
|
| 130 |
+
return r
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def calcular_csll_lucro_real(
|
| 134 |
+
lucro_antes_csll: Decimal,
|
| 135 |
+
adicoes: Decimal = Decimal("0"),
|
| 136 |
+
exclusoes: Decimal = Decimal("0"),
|
| 137 |
+
compensacoes_base_negativa: Decimal = Decimal("0"),
|
| 138 |
+
tipo_empresa: str = "padrao",
|
| 139 |
+
csll_estimativas: Decimal = Decimal("0"),
|
| 140 |
+
) -> ResultadoCSLL:
|
| 141 |
+
r = ResultadoCSLL()
|
| 142 |
+
|
| 143 |
+
base_ajustada = arred(lucro_antes_csll + adicoes - exclusoes)
|
| 144 |
+
limite_comp = arred(base_ajustada * Decimal("0.30"))
|
| 145 |
+
comp_efetiva = min(compensacoes_base_negativa, limite_comp)
|
| 146 |
+
r.base_calculo = max(Decimal("0"), arred(base_ajustada - comp_efetiva))
|
| 147 |
+
|
| 148 |
+
aliq = ALIQ_CSLL_FINANCEIRAS if tipo_empresa == "financeira" else ALIQ_CSLL_PADRAO
|
| 149 |
+
r.aliquota = aliq
|
| 150 |
+
r.valor_csll = arred(r.base_calculo * aliq / 100)
|
| 151 |
+
r.csll_a_recolher = max(Decimal("0"), arred(r.valor_csll - csll_estimativas))
|
| 152 |
+
|
| 153 |
+
r.detalhes = {
|
| 154 |
+
"adicoes": adicoes,
|
| 155 |
+
"exclusoes": exclusoes,
|
| 156 |
+
"compensacao_utilizada": comp_efetiva,
|
| 157 |
+
"estimativas_pagas": csll_estimativas,
|
| 158 |
+
}
|
| 159 |
+
return r
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def calcular_csll_lucro_presumido(
|
| 163 |
+
receita_bruta: Decimal,
|
| 164 |
+
outras_receitas: Decimal = Decimal("0"),
|
| 165 |
+
atividade: str = "comercio_industria",
|
| 166 |
+
tipo_empresa: str = "padrao",
|
| 167 |
+
csll_anteriores: Decimal = Decimal("0"),
|
| 168 |
+
) -> ResultadoCSLL:
|
| 169 |
+
r = ResultadoCSLL()
|
| 170 |
+
|
| 171 |
+
percentual = PERCENTUAL_PRESUMIDO_CSLL.get(atividade, Decimal("12.0"))
|
| 172 |
+
base_presumida = arred(receita_bruta * percentual / 100 + outras_receitas)
|
| 173 |
+
r.base_calculo = base_presumida
|
| 174 |
+
|
| 175 |
+
aliq = ALIQ_CSLL_FINANCEIRAS if tipo_empresa == "financeira" else ALIQ_CSLL_PADRAO
|
| 176 |
+
r.aliquota = aliq
|
| 177 |
+
r.valor_csll = arred(r.base_calculo * aliq / 100)
|
| 178 |
+
r.csll_a_recolher = max(Decimal("0"), arred(r.valor_csll - csll_anteriores))
|
| 179 |
+
|
| 180 |
+
r.detalhes = {
|
| 181 |
+
"receita_bruta": receita_bruta,
|
| 182 |
+
"percentual_presuncao": percentual,
|
| 183 |
+
"base_presumida_bruta": base_presumida,
|
| 184 |
+
"outras_receitas": outras_receitas,
|
| 185 |
+
"atividade": atividade,
|
| 186 |
+
}
|
| 187 |
+
return r
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def calcular_irpj_estimativa_mensal(
|
| 191 |
+
receita_bruta_mes: Decimal,
|
| 192 |
+
atividade: str = "venda_mercadorias",
|
| 193 |
+
outras_receitas: Decimal = Decimal("0"),
|
| 194 |
+
) -> dict[str, Decimal]:
|
| 195 |
+
"""Cálculo de IRPJ por estimativa mensal (Lucro Real)."""
|
| 196 |
+
percentual = PERCENTUAL_PRESUMIDO_IRPJ.get(atividade, Decimal("8.0"))
|
| 197 |
+
base = arred(receita_bruta_mes * percentual / 100 + outras_receitas)
|
| 198 |
+
|
| 199 |
+
# Adicional: excedente ao limite mensal de R$20.000
|
| 200 |
+
limite_mensal = Decimal("20000")
|
| 201 |
+
irpj_base = arred(base * ALIQ_IRPJ_BASE / 100)
|
| 202 |
+
adicional = arred(max(Decimal("0"), base - limite_mensal) * ALIQ_IRPJ_ADICIONAL / 100)
|
| 203 |
+
|
| 204 |
+
return {
|
| 205 |
+
"base_estimada": base,
|
| 206 |
+
"irpj_aliquota_base": irpj_base,
|
| 207 |
+
"irpj_adicional": adicional,
|
| 208 |
+
"total_estimativa": arred(irpj_base + adicional),
|
| 209 |
+
}
|
src/calculators/iss.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Calculadora de ISS (Imposto sobre Serviços)."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from decimal import Decimal
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
from src.calculators import ResultadoCalculo, arred
|
| 9 |
+
from src.fiscal.constants import ALIQ_ISS_MAXIMA, ALIQ_ISS_MINIMA, ALIQ_ISS_SERVICOS
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class ParametrosISS:
|
| 14 |
+
valor_servico: Decimal
|
| 15 |
+
codigo_servico: str
|
| 16 |
+
aliquota_municipal: Optional[Decimal] = None
|
| 17 |
+
retencao_fonte: bool = False
|
| 18 |
+
municipio_prestacao: str = ""
|
| 19 |
+
municipio_tomador: str = ""
|
| 20 |
+
responsavel_retencao: str = "prestador"
|
| 21 |
+
|
| 22 |
+
def aliquota_efetiva(self) -> Decimal:
|
| 23 |
+
if self.aliquota_municipal is not None:
|
| 24 |
+
aliq = self.aliquota_municipal
|
| 25 |
+
aliq = max(aliq, ALIQ_ISS_MINIMA)
|
| 26 |
+
aliq = min(aliq, ALIQ_ISS_MAXIMA)
|
| 27 |
+
return aliq
|
| 28 |
+
return ALIQ_ISS_SERVICOS.get(
|
| 29 |
+
self.codigo_servico[:2], ALIQ_ISS_MAXIMA
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def calcular_iss(p: ParametrosISS) -> ResultadoCalculo:
|
| 34 |
+
aliq = p.aliquota_efetiva()
|
| 35 |
+
valor = arred(p.valor_servico * aliq / 100)
|
| 36 |
+
|
| 37 |
+
return ResultadoCalculo(
|
| 38 |
+
base_calculo=p.valor_servico,
|
| 39 |
+
aliquota=aliq,
|
| 40 |
+
valor_tributo=valor,
|
| 41 |
+
valor_a_recolher=valor,
|
| 42 |
+
detalhes={
|
| 43 |
+
"codigo_servico": p.codigo_servico,
|
| 44 |
+
"retencao_fonte": p.retencao_fonte,
|
| 45 |
+
"municipio_prestacao": p.municipio_prestacao,
|
| 46 |
+
"responsavel": "tomador" if p.retencao_fonte else "prestador",
|
| 47 |
+
},
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def calcular_iss_retido_tomador(
|
| 52 |
+
valor_servico: Decimal,
|
| 53 |
+
codigo_servico: str,
|
| 54 |
+
aliquota_municipal: Optional[Decimal] = None,
|
| 55 |
+
) -> Decimal:
|
| 56 |
+
p = ParametrosISS(
|
| 57 |
+
valor_servico=valor_servico,
|
| 58 |
+
codigo_servico=codigo_servico,
|
| 59 |
+
aliquota_municipal=aliquota_municipal,
|
| 60 |
+
retencao_fonte=True,
|
| 61 |
+
)
|
| 62 |
+
resultado = calcular_iss(p)
|
| 63 |
+
return resultado.valor_tributo
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# Serviços sujeitos a retenção obrigatória pelo tomador (LC 116/2003, Art. 6º)
|
| 67 |
+
SERVICOS_RETENCAO_OBRIGATORIA = {
|
| 68 |
+
"01", # Serviços de informática e congêneres
|
| 69 |
+
"02", # Pesquisa e desenvolvimento
|
| 70 |
+
"03", # Serviços prestados mediante locação de bens
|
| 71 |
+
"04", # Saúde, assistência médica
|
| 72 |
+
"07", # Engenharia, arquitetura, agronomia, agrimensura
|
| 73 |
+
"08", # Pesquisa, vigilância, rastreamento, monitoramento
|
| 74 |
+
"10", # Serviços de intermediação e congêneres
|
| 75 |
+
"11", # Guarda, estacionamento, armazenamento
|
| 76 |
+
"13", # Fonografia, fotografia, cinematografia, reprografia
|
| 77 |
+
"15", # Serviços bancários ou financeiros
|
| 78 |
+
"16", # Serviços de transporte de natureza municipal
|
| 79 |
+
"17", # Serviços de apoio técnico, administrativo, jurídico
|
| 80 |
+
"20", # Serviços portuários, aeroportuários
|
| 81 |
+
"21", # Serviços de registros públicos, cartorários
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def requer_retencao_iss(codigo_servico: str) -> bool:
|
| 86 |
+
return codigo_servico[:2] in SERVICOS_RETENCAO_OBRIGATORIA
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def calcular_iss_simples_nacional(
|
| 90 |
+
receita_bruta_acumulada_12m: Decimal,
|
| 91 |
+
receita_servicos_mes: Decimal,
|
| 92 |
+
anexo: str = "III",
|
| 93 |
+
) -> dict[str, Decimal]:
|
| 94 |
+
"""Cálculo do ISS dentro do Simples Nacional por anexo."""
|
| 95 |
+
# Tabelas simplificadas Simples Nacional (Lei Complementar 123/2006)
|
| 96 |
+
tabelas = {
|
| 97 |
+
"III": [ # Serviços em geral (Anexo III)
|
| 98 |
+
(Decimal("180000"), Decimal("6.0"), Decimal("0")),
|
| 99 |
+
(Decimal("360000"), Decimal("11.2"), Decimal("9360")),
|
| 100 |
+
(Decimal("720000"), Decimal("13.5"), Decimal("17640")),
|
| 101 |
+
(Decimal("1800000"), Decimal("16.0"), Decimal("35640")),
|
| 102 |
+
(Decimal("3600000"), Decimal("21.0"), Decimal("125640")),
|
| 103 |
+
(Decimal("4800000"), Decimal("33.0"), Decimal("648000")),
|
| 104 |
+
],
|
| 105 |
+
"IV": [ # Serviços especializados (Anexo IV) - sem CPP
|
| 106 |
+
(Decimal("180000"), Decimal("4.5"), Decimal("0")),
|
| 107 |
+
(Decimal("360000"), Decimal("9.0"), Decimal("8100")),
|
| 108 |
+
(Decimal("720000"), Decimal("10.2"), Decimal("12420")),
|
| 109 |
+
(Decimal("1800000"), Decimal("14.0"), Decimal("39780")),
|
| 110 |
+
(Decimal("3600000"), Decimal("22.0"), Decimal("183780")),
|
| 111 |
+
(Decimal("4800000"), Decimal("33.0"), Decimal("828000")),
|
| 112 |
+
],
|
| 113 |
+
"V": [ # Serviços com fator R (Anexo V)
|
| 114 |
+
(Decimal("180000"), Decimal("15.5"), Decimal("0")),
|
| 115 |
+
(Decimal("360000"), Decimal("18.0"), Decimal("4500")),
|
| 116 |
+
(Decimal("720000"), Decimal("19.5"), Decimal("9900")),
|
| 117 |
+
(Decimal("1800000"), Decimal("20.5"), Decimal("17100")),
|
| 118 |
+
(Decimal("3600000"), Decimal("23.0"), Decimal("62100")),
|
| 119 |
+
(Decimal("4800000"), Decimal("30.5"), Decimal("540000")),
|
| 120 |
+
],
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
tabela = tabelas.get(anexo, tabelas["III"])
|
| 124 |
+
aliq_nominal = Decimal("6.0")
|
| 125 |
+
deducao = Decimal("0")
|
| 126 |
+
|
| 127 |
+
for limite, aliq, ded in tabela:
|
| 128 |
+
if receita_bruta_acumulada_12m <= limite:
|
| 129 |
+
aliq_nominal = aliq
|
| 130 |
+
deducao = ded
|
| 131 |
+
break
|
| 132 |
+
|
| 133 |
+
aliq_efetiva = (
|
| 134 |
+
arred((receita_bruta_acumulada_12m * aliq_nominal / 100 - deducao) / receita_bruta_acumulada_12m * 100)
|
| 135 |
+
if receita_bruta_acumulada_12m > 0
|
| 136 |
+
else Decimal("0")
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# ISS representa ~7-10% do DAS no Anexo III/IV
|
| 140 |
+
percentual_iss_das = {
|
| 141 |
+
"III": Decimal("33.50"),
|
| 142 |
+
"IV": Decimal("0"), # ISS pago separado no Anexo IV
|
| 143 |
+
"V": Decimal("33.50"),
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
valor_das = arred(receita_servicos_mes * aliq_efetiva / 100)
|
| 147 |
+
valor_iss = arred(valor_das * percentual_iss_das.get(anexo, Decimal("33.5")) / 100)
|
| 148 |
+
|
| 149 |
+
return {
|
| 150 |
+
"receita_bruta_12m": receita_bruta_acumulada_12m,
|
| 151 |
+
"aliq_nominal": aliq_nominal,
|
| 152 |
+
"deducao": deducao,
|
| 153 |
+
"aliq_efetiva": aliq_efetiva,
|
| 154 |
+
"valor_das_total": valor_das,
|
| 155 |
+
"valor_iss_estimado": valor_iss,
|
| 156 |
+
"anexo": anexo,
|
| 157 |
+
}
|
src/calculators/pis_cofins.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Calculadora de PIS e COFINS (cumulativo e não-cumulativo)."""
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from decimal import Decimal
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
from src.calculators import ResultadoCalculo, arred
|
| 9 |
+
from src.fiscal.constants import (
|
| 10 |
+
ALIQ_COFINS_LUCRO_PRESUMIDO,
|
| 11 |
+
ALIQ_COFINS_LUCRO_REAL,
|
| 12 |
+
ALIQ_PIS_LUCRO_PRESUMIDO,
|
| 13 |
+
ALIQ_PIS_LUCRO_REAL,
|
| 14 |
+
)
|
| 15 |
+
from src.fiscal.entities import RegimeTributario
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class ReceitaBruta:
|
| 20 |
+
descricao: str
|
| 21 |
+
valor: Decimal
|
| 22 |
+
cst_pis: str = "01"
|
| 23 |
+
cst_cofins: str = "01"
|
| 24 |
+
aliq_pis_especifica: Optional[Decimal] = None
|
| 25 |
+
aliq_cofins_especifica: Optional[Decimal] = None
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@dataclass
|
| 29 |
+
class CreditoPisCofins:
|
| 30 |
+
descricao: str
|
| 31 |
+
base_calculo: Decimal
|
| 32 |
+
aliquota_pis: Decimal
|
| 33 |
+
aliquota_cofins: Decimal
|
| 34 |
+
cst_pis: str = "50"
|
| 35 |
+
cst_cofins: str = "50"
|
| 36 |
+
|
| 37 |
+
@property
|
| 38 |
+
def valor_pis(self) -> Decimal:
|
| 39 |
+
return arred(self.base_calculo * self.aliquota_pis / 100)
|
| 40 |
+
|
| 41 |
+
@property
|
| 42 |
+
def valor_cofins(self) -> Decimal:
|
| 43 |
+
return arred(self.base_calculo * self.aliquota_cofins / 100)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@dataclass
|
| 47 |
+
class ResultadoPisCofins:
|
| 48 |
+
# PIS
|
| 49 |
+
base_pis: Decimal = Decimal("0")
|
| 50 |
+
aliq_pis: Decimal = Decimal("0")
|
| 51 |
+
valor_pis_debito: Decimal = Decimal("0")
|
| 52 |
+
creditos_pis: Decimal = Decimal("0")
|
| 53 |
+
pis_a_recolher: Decimal = Decimal("0")
|
| 54 |
+
# COFINS
|
| 55 |
+
base_cofins: Decimal = Decimal("0")
|
| 56 |
+
aliq_cofins: Decimal = Decimal("0")
|
| 57 |
+
valor_cofins_debito: Decimal = Decimal("0")
|
| 58 |
+
creditos_cofins: Decimal = Decimal("0")
|
| 59 |
+
cofins_a_recolher: Decimal = Decimal("0")
|
| 60 |
+
# Regime
|
| 61 |
+
regime: str = ""
|
| 62 |
+
detalhes: dict = field(default_factory=dict)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def calcular_pis_cofins(
|
| 66 |
+
receitas: list[ReceitaBruta],
|
| 67 |
+
regime: RegimeTributario,
|
| 68 |
+
creditos: list[CreditoPisCofins] | None = None,
|
| 69 |
+
saldo_anterior_pis: Decimal = Decimal("0"),
|
| 70 |
+
saldo_anterior_cofins: Decimal = Decimal("0"),
|
| 71 |
+
) -> ResultadoPisCofins:
|
| 72 |
+
creditos = creditos or []
|
| 73 |
+
r = ResultadoPisCofins()
|
| 74 |
+
|
| 75 |
+
is_nao_cumulativo = regime == RegimeTributario.LUCRO_REAL
|
| 76 |
+
aliq_pis = ALIQ_PIS_LUCRO_REAL if is_nao_cumulativo else ALIQ_PIS_LUCRO_PRESUMIDO
|
| 77 |
+
aliq_cofins = ALIQ_COFINS_LUCRO_REAL if is_nao_cumulativo else ALIQ_COFINS_LUCRO_PRESUMIDO
|
| 78 |
+
|
| 79 |
+
r.regime = "Não-Cumulativo" if is_nao_cumulativo else "Cumulativo"
|
| 80 |
+
r.aliq_pis = aliq_pis
|
| 81 |
+
r.aliq_cofins = aliq_cofins
|
| 82 |
+
|
| 83 |
+
# Calcular débitos por receita
|
| 84 |
+
pis_debito_total = Decimal("0")
|
| 85 |
+
cofins_debito_total = Decimal("0")
|
| 86 |
+
base_tributavel_pis = Decimal("0")
|
| 87 |
+
base_tributavel_cofins = Decimal("0")
|
| 88 |
+
|
| 89 |
+
detalhes_receitas = []
|
| 90 |
+
for rec in receitas:
|
| 91 |
+
# CSTs que geram débito: 01, 02, 03, 04, 05
|
| 92 |
+
if rec.cst_pis in {"01", "02", "03", "04", "05"}:
|
| 93 |
+
ap = rec.aliq_pis_especifica if rec.aliq_pis_especifica is not None else aliq_pis
|
| 94 |
+
vp = arred(rec.valor * ap / 100)
|
| 95 |
+
pis_debito_total += vp
|
| 96 |
+
base_tributavel_pis += rec.valor
|
| 97 |
+
else:
|
| 98 |
+
vp = Decimal("0")
|
| 99 |
+
|
| 100 |
+
if rec.cst_cofins in {"01", "02", "03", "04", "05"}:
|
| 101 |
+
ac = rec.aliq_cofins_especifica if rec.aliq_cofins_especifica is not None else aliq_cofins
|
| 102 |
+
vc = arred(rec.valor * ac / 100)
|
| 103 |
+
cofins_debito_total += vc
|
| 104 |
+
base_tributavel_cofins += rec.valor
|
| 105 |
+
else:
|
| 106 |
+
vc = Decimal("0")
|
| 107 |
+
|
| 108 |
+
detalhes_receitas.append({
|
| 109 |
+
"descricao": rec.descricao,
|
| 110 |
+
"valor": rec.valor,
|
| 111 |
+
"cst_pis": rec.cst_pis,
|
| 112 |
+
"cst_cofins": rec.cst_cofins,
|
| 113 |
+
"valor_pis": vp,
|
| 114 |
+
"valor_cofins": vc,
|
| 115 |
+
})
|
| 116 |
+
|
| 117 |
+
r.base_pis = arred(base_tributavel_pis)
|
| 118 |
+
r.base_cofins = arred(base_tributavel_cofins)
|
| 119 |
+
r.valor_pis_debito = arred(pis_debito_total)
|
| 120 |
+
r.valor_cofins_debito = arred(cofins_debito_total)
|
| 121 |
+
|
| 122 |
+
# Créditos (apenas regime não-cumulativo)
|
| 123 |
+
creditos_pis = Decimal("0")
|
| 124 |
+
creditos_cofins = Decimal("0")
|
| 125 |
+
if is_nao_cumulativo:
|
| 126 |
+
for cred in creditos:
|
| 127 |
+
creditos_pis += cred.valor_pis
|
| 128 |
+
creditos_cofins += cred.valor_cofins
|
| 129 |
+
|
| 130 |
+
creditos_pis = arred(creditos_pis + saldo_anterior_pis)
|
| 131 |
+
creditos_cofins = arred(creditos_cofins + saldo_anterior_cofins)
|
| 132 |
+
|
| 133 |
+
r.creditos_pis = creditos_pis
|
| 134 |
+
r.creditos_cofins = creditos_cofins
|
| 135 |
+
r.pis_a_recolher = max(Decimal("0"), arred(r.valor_pis_debito - creditos_pis))
|
| 136 |
+
r.cofins_a_recolher = max(Decimal("0"), arred(r.valor_cofins_debito - creditos_cofins))
|
| 137 |
+
|
| 138 |
+
r.detalhes = {
|
| 139 |
+
"receitas": detalhes_receitas,
|
| 140 |
+
"saldo_credor_pis": max(Decimal("0"), arred(creditos_pis - r.valor_pis_debito)),
|
| 141 |
+
"saldo_credor_cofins": max(Decimal("0"), arred(creditos_cofins - r.valor_cofins_debito)),
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
return r
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def calcular_pis_cofins_retencao(
|
| 148 |
+
valor_servico: Decimal,
|
| 149 |
+
aliq_pis: Decimal = Decimal("0.65"),
|
| 150 |
+
aliq_cofins: Decimal = Decimal("3.00"),
|
| 151 |
+
aliq_csll: Decimal = Decimal("1.00"),
|
| 152 |
+
aliq_ir: Decimal = Decimal("1.50"),
|
| 153 |
+
) -> dict[str, Decimal]:
|
| 154 |
+
"""Retenção de PIS/COFINS/CSLL/IR na fonte para serviços (IN SRF 459/2004)."""
|
| 155 |
+
MIN_RETENCAO = Decimal("10.00")
|
| 156 |
+
|
| 157 |
+
valor_pis = arred(valor_servico * aliq_pis / 100)
|
| 158 |
+
valor_cofins = arred(valor_servico * aliq_cofins / 100)
|
| 159 |
+
valor_csll = arred(valor_servico * aliq_csll / 100)
|
| 160 |
+
valor_ir = arred(valor_servico * aliq_ir / 100)
|
| 161 |
+
total = arred(valor_pis + valor_cofins + valor_csll + valor_ir)
|
| 162 |
+
|
| 163 |
+
if total < MIN_RETENCAO:
|
| 164 |
+
return {
|
| 165 |
+
"valor_pis": Decimal("0"),
|
| 166 |
+
"valor_cofins": Decimal("0"),
|
| 167 |
+
"valor_csll": Decimal("0"),
|
| 168 |
+
"valor_ir": Decimal("0"),
|
| 169 |
+
"total_retido": Decimal("0"),
|
| 170 |
+
"observacao": f"Retenção dispensada: total R$ {total} < R$ {MIN_RETENCAO}",
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
return {
|
| 174 |
+
"valor_pis": valor_pis,
|
| 175 |
+
"valor_cofins": valor_cofins,
|
| 176 |
+
"valor_csll": valor_csll,
|
| 177 |
+
"valor_ir": valor_ir,
|
| 178 |
+
"total_retido": total,
|
| 179 |
+
"observacao": "",
|
| 180 |
+
}
|
src/calculators/simples_nacional.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from datetime import date
|
| 6 |
+
from decimal import Decimal
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
from src.calculators import ResultadoCalculo, arred
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# ---------------------------------------------------------------------------
|
| 13 |
+
# Tabelas do Simples Nacional (LC 123/2006 com alterações da LC 155/2016)
|
| 14 |
+
# Vigência: a partir de 01/01/2018
|
| 15 |
+
# Faixas: (limite_acumulado_12m, aliq_nominal, deducao)
|
| 16 |
+
# ---------------------------------------------------------------------------
|
| 17 |
+
|
| 18 |
+
TABELA_SIMPLES: dict[str, list[tuple[Decimal, Decimal, Decimal]]] = {
|
| 19 |
+
"I": [ # Comércio
|
| 20 |
+
(Decimal("180000.00"), Decimal("4.00"), Decimal("0.00")),
|
| 21 |
+
(Decimal("360000.00"), Decimal("7.30"), Decimal("5940.00")),
|
| 22 |
+
(Decimal("720000.00"), Decimal("9.50"), Decimal("13860.00")),
|
| 23 |
+
(Decimal("1800000.00"), Decimal("10.70"), Decimal("22500.00")),
|
| 24 |
+
(Decimal("3600000.00"), Decimal("14.30"), Decimal("87300.00")),
|
| 25 |
+
(Decimal("4800000.00"), Decimal("19.00"), Decimal("378000.00")),
|
| 26 |
+
],
|
| 27 |
+
"II": [ # Indústria
|
| 28 |
+
(Decimal("180000.00"), Decimal("4.50"), Decimal("0.00")),
|
| 29 |
+
(Decimal("360000.00"), Decimal("7.80"), Decimal("5940.00")),
|
| 30 |
+
(Decimal("720000.00"), Decimal("10.00"), Decimal("13860.00")),
|
| 31 |
+
(Decimal("1800000.00"), Decimal("11.20"), Decimal("22500.00")),
|
| 32 |
+
(Decimal("3600000.00"), Decimal("14.70"), Decimal("85500.00")),
|
| 33 |
+
(Decimal("4800000.00"), Decimal("30.00"), Decimal("720000.00")),
|
| 34 |
+
],
|
| 35 |
+
"III": [ # Serviços (com CPP)
|
| 36 |
+
(Decimal("180000.00"), Decimal("6.00"), Decimal("0.00")),
|
| 37 |
+
(Decimal("360000.00"), Decimal("11.20"), Decimal("9360.00")),
|
| 38 |
+
(Decimal("720000.00"), Decimal("13.50"), Decimal("17640.00")),
|
| 39 |
+
(Decimal("1800000.00"), Decimal("16.00"), Decimal("35640.00")),
|
| 40 |
+
(Decimal("3600000.00"), Decimal("21.00"), Decimal("125640.00")),
|
| 41 |
+
(Decimal("4800000.00"), Decimal("33.00"), Decimal("648000.00")),
|
| 42 |
+
],
|
| 43 |
+
"IV": [ # Serviços (sem CPP — INSS pago separado)
|
| 44 |
+
(Decimal("180000.00"), Decimal("4.50"), Decimal("0.00")),
|
| 45 |
+
(Decimal("360000.00"), Decimal("9.00"), Decimal("8100.00")),
|
| 46 |
+
(Decimal("720000.00"), Decimal("10.20"), Decimal("12420.00")),
|
| 47 |
+
(Decimal("1800000.00"), Decimal("14.00"), Decimal("39780.00")),
|
| 48 |
+
(Decimal("3600000.00"), Decimal("22.00"), Decimal("183780.00")),
|
| 49 |
+
(Decimal("4800000.00"), Decimal("33.00"), Decimal("828000.00")),
|
| 50 |
+
],
|
| 51 |
+
"V": [ # Serviços com fator R (profissões regulamentadas)
|
| 52 |
+
(Decimal("180000.00"), Decimal("15.50"), Decimal("0.00")),
|
| 53 |
+
(Decimal("360000.00"), Decimal("18.00"), Decimal("4500.00")),
|
| 54 |
+
(Decimal("720000.00"), Decimal("19.50"), Decimal("9900.00")),
|
| 55 |
+
(Decimal("1800000.00"), Decimal("20.50"), Decimal("17100.00")),
|
| 56 |
+
(Decimal("3600000.00"), Decimal("23.00"), Decimal("62100.00")),
|
| 57 |
+
(Decimal("4800000.00"), Decimal("30.50"), Decimal("540000.00")),
|
| 58 |
+
],
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
PARTILHA_DAS: dict[str, list[dict[str, Decimal]]] = {
|
| 63 |
+
"I": [
|
| 64 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("12.74"), "PIS": Decimal("2.76"), "CPP": Decimal("41.50"), "ICMS": Decimal("34.00")},
|
| 65 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("12.74"), "PIS": Decimal("2.76"), "CPP": Decimal("41.50"), "ICMS": Decimal("34.00")},
|
| 66 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("12.74"), "PIS": Decimal("2.76"), "CPP": Decimal("42.00"), "ICMS": Decimal("33.50")},
|
| 67 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("12.74"), "PIS": Decimal("2.76"), "CPP": Decimal("42.00"), "ICMS": Decimal("33.50")},
|
| 68 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("12.74"), "PIS": Decimal("2.76"), "CPP": Decimal("42.00"), "ICMS": Decimal("33.50")},
|
| 69 |
+
{"IRPJ": Decimal("13.5"), "CSLL": Decimal("10.0"), "COFINS": Decimal("28.27"), "PIS": Decimal("6.13"), "CPP": Decimal("42.10"), "ICMS": Decimal("0.00")},
|
| 70 |
+
],
|
| 71 |
+
"II": [
|
| 72 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("11.51"), "PIS": Decimal("2.49"), "CPP": Decimal("37.50"), "IPI": Decimal("7.50"), "ICMS": Decimal("32.00")},
|
| 73 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("11.51"), "PIS": Decimal("2.49"), "CPP": Decimal("37.50"), "IPI": Decimal("7.50"), "ICMS": Decimal("32.00")},
|
| 74 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("11.51"), "PIS": Decimal("2.49"), "CPP": Decimal("37.50"), "IPI": Decimal("7.50"), "ICMS": Decimal("32.00")},
|
| 75 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("11.51"), "PIS": Decimal("2.49"), "CPP": Decimal("37.50"), "IPI": Decimal("7.50"), "ICMS": Decimal("32.00")},
|
| 76 |
+
{"IRPJ": Decimal("5.5"), "CSLL": Decimal("3.5"), "COFINS": Decimal("11.51"), "PIS": Decimal("2.49"), "CPP": Decimal("37.50"), "IPI": Decimal("7.50"), "ICMS": Decimal("32.00")},
|
| 77 |
+
{"IRPJ": Decimal("8.0"), "CSLL": Decimal("7.5"), "COFINS": Decimal("20.96"), "PIS": Decimal("4.54"), "CPP": Decimal("23.50"), "IPI": Decimal("35.50"), "ICMS": Decimal("0.00")},
|
| 78 |
+
],
|
| 79 |
+
"III": [
|
| 80 |
+
{"IRPJ": Decimal("4.0"), "CSLL": Decimal("3.5"), "COFINS": Decimal("12.82"), "PIS": Decimal("2.78"), "CPP": Decimal("43.40"), "ISS": Decimal("33.50")},
|
| 81 |
+
{"IRPJ": Decimal("4.0"), "CSLL": Decimal("3.5"), "COFINS": Decimal("14.05"), "PIS": Decimal("3.05"), "CPP": Decimal("43.40"), "ISS": Decimal("32.00")},
|
| 82 |
+
{"IRPJ": Decimal("4.0"), "CSLL": Decimal("3.5"), "COFINS": Decimal("13.64"), "PIS": Decimal("2.96"), "CPP": Decimal("43.40"), "ISS": Decimal("32.50")},
|
| 83 |
+
{"IRPJ": Decimal("4.0"), "CSLL": Decimal("3.5"), "COFINS": Decimal("13.64"), "PIS": Decimal("2.96"), "CPP": Decimal("43.40"), "ISS": Decimal("32.50")},
|
| 84 |
+
{"IRPJ": Decimal("4.0"), "CSLL": Decimal("3.5"), "COFINS": Decimal("12.82"), "PIS": Decimal("2.78"), "CPP": Decimal("43.40"), "ISS": Decimal("33.50")},
|
| 85 |
+
{"IRPJ": Decimal("35.0"), "CSLL": Decimal("15.0"), "COFINS": Decimal("16.03"), "PIS": Decimal("3.47"), "CPP": Decimal("30.50"), "ISS": Decimal("0.00")},
|
| 86 |
+
],
|
| 87 |
+
"IV": [
|
| 88 |
+
{"IRPJ": Decimal("18.8"), "CSLL": Decimal("15.2"), "COFINS": Decimal("17.67"), "PIS": Decimal("3.83"), "ISS": Decimal("44.50")},
|
| 89 |
+
{"IRPJ": Decimal("19.8"), "CSLL": Decimal("15.2"), "COFINS": Decimal("20.55"), "PIS": Decimal("4.45"), "ISS": Decimal("40.00")},
|
| 90 |
+
{"IRPJ": Decimal("20.8"), "CSLL": Decimal("15.2"), "COFINS": Decimal("19.73"), "PIS": Decimal("4.27"), "ISS": Decimal("40.00")},
|
| 91 |
+
{"IRPJ": Decimal("17.8"), "CSLL": Decimal("19.2"), "COFINS": Decimal("18.90"), "PIS": Decimal("4.10"), "ISS": Decimal("40.00")},
|
| 92 |
+
{"IRPJ": Decimal("18.8"), "CSLL": Decimal("19.2"), "COFINS": Decimal("18.08"), "PIS": Decimal("3.92"), "ISS": Decimal("40.00")},
|
| 93 |
+
{"IRPJ": Decimal("53.5"), "CSLL": Decimal("21.5"), "COFINS": Decimal("20.55"), "PIS": Decimal("4.45"), "ISS": Decimal("0.00")},
|
| 94 |
+
],
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
LIMITE_SIMPLES = Decimal("4800000.00")
|
| 98 |
+
LIMITE_MEI = Decimal("81000.00")
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
@dataclass
|
| 102 |
+
class ResultadoSimples:
|
| 103 |
+
receita_bruta_mes: Decimal
|
| 104 |
+
receita_bruta_acumulada_12m: Decimal
|
| 105 |
+
anexo: str
|
| 106 |
+
faixa: int
|
| 107 |
+
aliq_nominal: Decimal
|
| 108 |
+
deducao: Decimal
|
| 109 |
+
aliq_efetiva: Decimal
|
| 110 |
+
valor_das: Decimal
|
| 111 |
+
partilha: dict[str, Decimal] = field(default_factory=dict)
|
| 112 |
+
periodo: str = ""
|
| 113 |
+
dentro_limite: bool = True
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def _calcular_aliq_efetiva(
|
| 117 |
+
rec_12m: Decimal,
|
| 118 |
+
aliq_nominal: Decimal,
|
| 119 |
+
deducao: Decimal,
|
| 120 |
+
) -> Decimal:
|
| 121 |
+
if rec_12m <= 0:
|
| 122 |
+
return Decimal("0")
|
| 123 |
+
return arred(((rec_12m * aliq_nominal / 100) - deducao) / rec_12m * 100, 6)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _obter_faixa(rec_12m: Decimal, tabela: list) -> tuple[int, Decimal, Decimal]:
|
| 127 |
+
"""Retorna (índice_faixa, aliq_nominal, deducao)."""
|
| 128 |
+
for i, (limite, aliq, ded) in enumerate(tabela):
|
| 129 |
+
if rec_12m <= limite:
|
| 130 |
+
return i, aliq, ded
|
| 131 |
+
|
| 132 |
+
return len(tabela) - 1, tabela[-1][1], tabela[-1][2]
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def calcular_das(
|
| 136 |
+
receita_bruta_mes: Decimal,
|
| 137 |
+
receita_bruta_acumulada_12m: Decimal,
|
| 138 |
+
anexo: str = "I",
|
| 139 |
+
periodo: str = "",
|
| 140 |
+
) -> ResultadoSimples:
|
| 141 |
+
"""
|
| 142 |
+
Calcula o DAS (Documento de Arrecadação do Simples Nacional) mensal.
|
| 143 |
+
|
| 144 |
+
Args:
|
| 145 |
+
receita_bruta_mes: Receita bruta do mês de referência
|
| 146 |
+
receita_bruta_acumulada_12m: Soma dos últimos 12 meses (ou desde o início)
|
| 147 |
+
anexo: "I", "II", "III", "IV" ou "V"
|
| 148 |
+
periodo: "YYYY-MM"
|
| 149 |
+
"""
|
| 150 |
+
dentro_limite = receita_bruta_acumulada_12m <= LIMITE_SIMPLES
|
| 151 |
+
tabela = TABELA_SIMPLES.get(anexo, TABELA_SIMPLES["I"])
|
| 152 |
+
|
| 153 |
+
faixa_idx, aliq_nominal, deducao = _obter_faixa(receita_bruta_acumulada_12m, tabela)
|
| 154 |
+
aliq_efetiva = _calcular_aliq_efetiva(receita_bruta_acumulada_12m, aliq_nominal, deducao)
|
| 155 |
+
valor_das = arred(receita_bruta_mes * aliq_efetiva / 100)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
partilha_tabela = PARTILHA_DAS.get(anexo, [])
|
| 159 |
+
partilha: dict[str, Decimal] = {}
|
| 160 |
+
if partilha_tabela and faixa_idx < len(partilha_tabela):
|
| 161 |
+
perc_faixa = partilha_tabela[faixa_idx]
|
| 162 |
+
for tributo, perc in perc_faixa.items():
|
| 163 |
+
partilha[tributo] = arred(valor_das * perc / 100)
|
| 164 |
+
|
| 165 |
+
return ResultadoSimples(
|
| 166 |
+
receita_bruta_mes=receita_bruta_mes,
|
| 167 |
+
receita_bruta_acumulada_12m=receita_bruta_acumulada_12m,
|
| 168 |
+
anexo=anexo,
|
| 169 |
+
faixa=faixa_idx + 1,
|
| 170 |
+
aliq_nominal=aliq_nominal,
|
| 171 |
+
deducao=deducao,
|
| 172 |
+
aliq_efetiva=aliq_efetiva,
|
| 173 |
+
valor_das=valor_das,
|
| 174 |
+
partilha=partilha,
|
| 175 |
+
periodo=periodo,
|
| 176 |
+
dentro_limite=dentro_limite,
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def calcular_fator_r(
|
| 181 |
+
folha_12m: Decimal,
|
| 182 |
+
rec_12m: Decimal,
|
| 183 |
+
) -> Decimal:
|
| 184 |
+
"""
|
| 185 |
+
Fator R: relação folha de pagamento / receita bruta nos últimos 12 meses.
|
| 186 |
+
Se Fator R >= 28%, o contribuinte pode optar pelo Anexo III ao invés do V.
|
| 187 |
+
"""
|
| 188 |
+
if rec_12m <= 0:
|
| 189 |
+
return Decimal("0")
|
| 190 |
+
return arred(folha_12m / rec_12m * 100, 4)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def determinar_anexo_servicos(
|
| 194 |
+
fator_r: Decimal,
|
| 195 |
+
lista_servico: str,
|
| 196 |
+
) -> str:
|
| 197 |
+
"""
|
| 198 |
+
Determina o anexo aplicável para empresas de serviços.
|
| 199 |
+
Serviços do Anexo V com Fator R >= 28% podem usar o Anexo III.
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
anexo_iv = {
|
| 203 |
+
"construcao_civil", "servicos_vigilancia", "limpeza",
|
| 204 |
+
"transporte_cargas", "cuidados_pessoais",
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
anexo_iii = {
|
| 208 |
+
"agencia_viagens", "academias", "agronegocio",
|
| 209 |
+
"arquitetura_engenharia_simples",
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
fator_r_atividades = {
|
| 213 |
+
"medicina", "odontologia", "psicologia", "fisioterapia",
|
| 214 |
+
"advocacia", "contabilidade", "consultoria", "ti_software",
|
| 215 |
+
"engenharia", "arquitetura",
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
if lista_servico in anexo_iv:
|
| 219 |
+
return "IV"
|
| 220 |
+
if lista_servico in anexo_iii:
|
| 221 |
+
return "III"
|
| 222 |
+
if lista_servico in fator_r_atividades:
|
| 223 |
+
return "III" if fator_r >= Decimal("28") else "V"
|
| 224 |
+
return "III"
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@dataclass
|
| 228 |
+
class ResultadoPGDAS:
|
| 229 |
+
"""Resultado do PGDAS-D mensal."""
|
| 230 |
+
cnpj: str
|
| 231 |
+
periodo: str
|
| 232 |
+
receita_bruta_total: Decimal
|
| 233 |
+
das_total: Decimal
|
| 234 |
+
das_por_estabelecimento: list[ResultadoSimples] = field(default_factory=list)
|
| 235 |
+
data_vencimento: Optional[date] = None
|
| 236 |
+
|
| 237 |
+
@property
|
| 238 |
+
def resumo_partilha(self) -> dict[str, Decimal]:
|
| 239 |
+
totais: dict[str, Decimal] = {}
|
| 240 |
+
for r in self.das_por_estabelecimento:
|
| 241 |
+
for tributo, valor in r.partilha.items():
|
| 242 |
+
totais[tributo] = totais.get(tributo, Decimal("0")) + valor
|
| 243 |
+
return totais
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def calcular_pgdas(
|
| 247 |
+
cnpj: str,
|
| 248 |
+
periodo: str,
|
| 249 |
+
receitas_por_atividade: list[dict],
|
| 250 |
+
receita_acumulada_12m: Decimal,
|
| 251 |
+
) -> ResultadoPGDAS:
|
| 252 |
+
"""
|
| 253 |
+
Calcula o PGDAS-D completo para um período.
|
| 254 |
+
|
| 255 |
+
Args:
|
| 256 |
+
cnpj: CNPJ da empresa
|
| 257 |
+
periodo: "YYYY-MM"
|
| 258 |
+
receitas_por_atividade: [{"valor": ..., "anexo": "I/II/III/IV/V"}]
|
| 259 |
+
receita_acumulada_12m: Receita dos últimos 12 meses
|
| 260 |
+
"""
|
| 261 |
+
das_total = Decimal("0")
|
| 262 |
+
resultados = []
|
| 263 |
+
|
| 264 |
+
for ativ in receitas_por_atividade:
|
| 265 |
+
valor = Decimal(str(ativ.get("valor", 0)))
|
| 266 |
+
anexo = ativ.get("anexo", "I")
|
| 267 |
+
resultado = calcular_das(valor, receita_acumulada_12m, anexo, periodo)
|
| 268 |
+
das_total += resultado.valor_das
|
| 269 |
+
resultados.append(resultado)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
ano, mes = map(int, periodo.split("-"))
|
| 273 |
+
mes_vcto = mes + 1 if mes < 12 else 1
|
| 274 |
+
ano_vcto = ano if mes < 12 else ano + 1
|
| 275 |
+
vcto = date(ano_vcto, mes_vcto, 20)
|
| 276 |
+
|
| 277 |
+
return ResultadoPGDAS(
|
| 278 |
+
cnpj=cnpj,
|
| 279 |
+
periodo=periodo,
|
| 280 |
+
receita_bruta_total=sum(Decimal(str(a["valor"])) for a in receitas_por_atividade),
|
| 281 |
+
das_total=arred(das_total),
|
| 282 |
+
das_por_estabelecimento=resultados,
|
| 283 |
+
data_vencimento=vcto,
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
MEI_VALORES_FIXOS = {
|
| 289 |
+
"comercio_industria": Decimal("70.60"), # R$ 70,60/mês (2024)
|
| 290 |
+
"servicos": Decimal("79.60"),
|
| 291 |
+
"comercio_servicos": Decimal("80.60"),
|
| 292 |
+
}
|
src/fiscal/__init__.py
ADDED
|
File without changes
|
src/fiscal/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (173 Bytes). View file
|
|
|
src/fiscal/__pycache__/constants.cpython-312.pyc
ADDED
|
Binary file (20.7 kB). View file
|
|
|
src/fiscal/__pycache__/entities.cpython-312.pyc
ADDED
|
Binary file (17.2 kB). View file
|
|
|
src/fiscal/constants.py
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from decimal import Decimal
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
ALIQ_ICMS_INTERNA: dict[str, Decimal] = {
|
| 6 |
+
"AC": Decimal("17"), "AL": Decimal("17"), "AP": Decimal("18"),
|
| 7 |
+
"AM": Decimal("20"), "BA": Decimal("19"), "CE": Decimal("20"),
|
| 8 |
+
"DF": Decimal("20"), "ES": Decimal("17"), "GO": Decimal("17"),
|
| 9 |
+
"MA": Decimal("22"), "MT": Decimal("17"), "MS": Decimal("17"),
|
| 10 |
+
"MG": Decimal("18"), "PA": Decimal("19"), "PB": Decimal("18"),
|
| 11 |
+
"PR": Decimal("19"), "PE": Decimal("20.5"), "PI": Decimal("21"),
|
| 12 |
+
"RJ": Decimal("20"), "RN": Decimal("18"), "RS": Decimal("17"),
|
| 13 |
+
"RO": Decimal("19.5"), "RR": Decimal("20"), "SC": Decimal("17"),
|
| 14 |
+
"SP": Decimal("18"), "SE": Decimal("19"), "TO": Decimal("20"),
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
ALIQ_ICMS_INTERESTADUAL: dict[str, Decimal] = {
|
| 19 |
+
"SUL_SUDESTE_para_OUTROS": Decimal("7"),
|
| 20 |
+
"OUTROS_para_SUL_SUDESTE": Decimal("12"),
|
| 21 |
+
"SUL_SUDESTE_para_SUL_SUDESTE": Decimal("12"),
|
| 22 |
+
"EXTERIOR": Decimal("4"),
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
UF_SUL_SUDESTE = {"SP", "RJ", "MG", "ES", "PR", "SC", "RS"}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def aliq_icms_interestadual(uf_origem: str, uf_destino: str) -> Decimal:
|
| 29 |
+
if uf_origem in UF_SUL_SUDESTE and uf_destino not in UF_SUL_SUDESTE:
|
| 30 |
+
return ALIQ_ICMS_INTERESTADUAL["SUL_SUDESTE_para_OUTROS"]
|
| 31 |
+
return ALIQ_ICMS_INTERESTADUAL["OUTROS_para_SUL_SUDESTE"]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
ALIQ_PIS_LUCRO_REAL = Decimal("1.65")
|
| 35 |
+
ALIQ_COFINS_LUCRO_REAL = Decimal("7.60")
|
| 36 |
+
ALIQ_PIS_LUCRO_PRESUMIDO = Decimal("0.65")
|
| 37 |
+
ALIQ_COFINS_LUCRO_PRESUMIDO = Decimal("3.00")
|
| 38 |
+
|
| 39 |
+
ALIQ_IRPJ_BASE = Decimal("15")
|
| 40 |
+
ALIQ_IRPJ_ADICIONAL = Decimal("10")
|
| 41 |
+
LIMITE_ADICIONAL_IRPJ_MENSAL = Decimal("20000")
|
| 42 |
+
LIMITE_ADICIONAL_IRPJ_TRIMESTRAL = Decimal("60000")
|
| 43 |
+
LIMITE_ADICIONAL_IRPJ_ANUAL = Decimal("240000")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
PERCENTUAL_PRESUMIDO_IRPJ: dict[str, Decimal] = {
|
| 47 |
+
"revenda_combustiveis": Decimal("1.6"),
|
| 48 |
+
"venda_mercadorias": Decimal("8.0"),
|
| 49 |
+
"transporte_cargas": Decimal("8.0"),
|
| 50 |
+
"atividades_rurais": Decimal("8.0"),
|
| 51 |
+
"outros_negocios": Decimal("8.0"),
|
| 52 |
+
"transporte_passageiros": Decimal("16.0"),
|
| 53 |
+
"servicos_em_geral": Decimal("32.0"),
|
| 54 |
+
"intermediacao_negocios": Decimal("32.0"),
|
| 55 |
+
"administracao_locacao": Decimal("32.0"),
|
| 56 |
+
"profissoes_regulamentadas": Decimal("32.0"),
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
PERCENTUAL_PRESUMIDO_CSLL: dict[str, Decimal] = {
|
| 60 |
+
"comercio_industria": Decimal("12.0"),
|
| 61 |
+
"servicos_em_geral": Decimal("32.0"),
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
ALIQ_CSLL_PADRAO = Decimal("9")
|
| 65 |
+
ALIQ_CSLL_FINANCEIRAS = Decimal("15")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
ALIQ_ISS_MINIMA = Decimal("2.0")
|
| 69 |
+
ALIQ_ISS_MAXIMA = Decimal("5.0")
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
ALIQ_ISS_SERVICOS: dict[str, Decimal] = {
|
| 73 |
+
"01": Decimal("5.0"), # Serviços de informática
|
| 74 |
+
"02": Decimal("5.0"), # Pesquisa e desenvolvimento
|
| 75 |
+
"03": Decimal("5.0"), # Serviços prestados mediante locação
|
| 76 |
+
"04": Decimal("5.0"), # Saúde, assistência médica
|
| 77 |
+
"05": Decimal("5.0"), # Medicina e assistência veterinária
|
| 78 |
+
"06": Decimal("5.0"), # Cuidados pessoais, estética, atividades físicas
|
| 79 |
+
"07": Decimal("5.0"), # Serviços relativos a engenharia
|
| 80 |
+
"08": Decimal("5.0"), # Pesquisa, vigilância, rastreamento
|
| 81 |
+
"09": Decimal("5.0"), # Serviços relativos a hospedagem, turismo, viagens
|
| 82 |
+
"10": Decimal("5.0"), # Serviços de intermediação
|
| 83 |
+
"11": Decimal("5.0"), # Serviços de guarda, estacionamento
|
| 84 |
+
"12": Decimal("5.0"), # Serviços de diversões, lazer, entretenimento
|
| 85 |
+
"13": Decimal("5.0"), # Serviços relativos à fonografia, fotografia, cinematografia
|
| 86 |
+
"14": Decimal("5.0"), # Serviços relativos a bens de terceiros
|
| 87 |
+
"15": Decimal("5.0"), # Serviços relacionados ao setor bancário ou financeiro
|
| 88 |
+
"16": Decimal("5.0"), # Serviços de transporte de natureza municipal
|
| 89 |
+
"17": Decimal("2.0"), # Serviços de apoio técnico, administrativo, jurídico
|
| 90 |
+
"18": Decimal("5.0"), # Serviços de regulação de sinistros
|
| 91 |
+
"19": Decimal("5.0"), # Serviços de distribuição e venda de bilhetes e demais produtos
|
| 92 |
+
"20": Decimal("5.0"), # Serviços portuários, aeroportuários
|
| 93 |
+
"21": Decimal("5.0"), # Serviços de registros públicos
|
| 94 |
+
"22": Decimal("5.0"), # Serviços de exploração de rodovias
|
| 95 |
+
"23": Decimal("5.0"), # Serviços de programação e comunicação visual
|
| 96 |
+
"24": Decimal("5.0"), # Serviços de chaveiros, confecção de carimbos
|
| 97 |
+
"25": Decimal("2.0"), # Serviços funerários
|
| 98 |
+
"26": Decimal("5.0"), # Serviços de coleta, remessa ou entrega de correspondências
|
| 99 |
+
"27": Decimal("5.0"), # Serviços de assistência social
|
| 100 |
+
"28": Decimal("5.0"), # Serviços de avaliação de bens e serviços
|
| 101 |
+
"29": Decimal("5.0"), # Serviços de biblioteconomia
|
| 102 |
+
"30": Decimal("5.0"), # Serviços de biologia, biotecnologia e química
|
| 103 |
+
"31": Decimal("5.0"), # Serviços técnicos em edificações
|
| 104 |
+
"32": Decimal("5.0"), # Serviços de desenho técnico
|
| 105 |
+
"33": Decimal("5.0"), # Serviços de desembaraço aduaneiro
|
| 106 |
+
"34": Decimal("5.0"), # Serviços de investigações particulares
|
| 107 |
+
"35": Decimal("5.0"), # Serviços de reportagem, assessoria de imprensa
|
| 108 |
+
"36": Decimal("5.0"), # Serviços de meteorologia
|
| 109 |
+
"37": Decimal("5.0"), # Serviços de artistas, atletas, modelos e manequins
|
| 110 |
+
"38": Decimal("5.0"), # Serviços de museologia
|
| 111 |
+
"39": Decimal("5.0"), # Serviços de ourivesaria e lapidação
|
| 112 |
+
"40": Decimal("5.0"), # Obras de arte sob encomenda
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
CST_ICMS: dict[str, str] = {
|
| 117 |
+
"000": "Tributada integralmente",
|
| 118 |
+
"010": "Tributada e com cobrança do ICMS por substituição tributária",
|
| 119 |
+
"020": "Com redução de base de cálculo",
|
| 120 |
+
"030": "Isenta ou não tributada e com cobrança do ICMS por substituição tributária",
|
| 121 |
+
"040": "Isenta",
|
| 122 |
+
"041": "Não tributada",
|
| 123 |
+
"050": "Suspensão",
|
| 124 |
+
"051": "Diferimento",
|
| 125 |
+
"060": "ICMS cobrado anteriormente por substituição tributária",
|
| 126 |
+
"070": "Com redução de base de cálculo e cobrança do ICMS por substituição tributária",
|
| 127 |
+
"090": "Outras",
|
| 128 |
+
# Simples Nacional
|
| 129 |
+
"101": "Tributada pelo Simples Nacional com permissão de crédito",
|
| 130 |
+
"102": "Tributada pelo Simples Nacional sem permissão de crédito",
|
| 131 |
+
"103": "Isenção do ICMS no Simples Nacional para faixa de receita bruta",
|
| 132 |
+
"201": "Tributada pelo Simples Nacional com permissão de crédito e com cobrança do ICMS por ST",
|
| 133 |
+
"202": "Tributada pelo Simples Nacional sem permissão de crédito e com cobrança do ICMS por ST",
|
| 134 |
+
"203": "Isenção do ICMS no Simples Nacional para faixa de receita bruta e com cobrança do ICMS por ST",
|
| 135 |
+
"300": "Imune",
|
| 136 |
+
"400": "Não tributada pelo Simples Nacional",
|
| 137 |
+
"500": "ICMS cobrado anteriormente por ST ou por antecipação",
|
| 138 |
+
"900": "Outros",
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
CST_IPI_ENTRADA: dict[str, str] = {
|
| 143 |
+
"00": "Entrada com recuperação de crédito",
|
| 144 |
+
"01": "Entrada tributada com alíquota zero",
|
| 145 |
+
"02": "Entrada isenta",
|
| 146 |
+
"03": "Entrada não-tributada",
|
| 147 |
+
"04": "Entrada imune",
|
| 148 |
+
"05": "Entrada com suspensão",
|
| 149 |
+
"49": "Outras entradas",
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
CST_IPI_SAIDA: dict[str, str] = {
|
| 153 |
+
"50": "Saída tributada",
|
| 154 |
+
"51": "Saída tributável com alíquota zero",
|
| 155 |
+
"52": "Saída isenta",
|
| 156 |
+
"53": "Saída não-tributada",
|
| 157 |
+
"54": "Saída imune",
|
| 158 |
+
"55": "Saída com suspensão",
|
| 159 |
+
"99": "Outras saídas",
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
CST_PIS_COFINS_SAIDA: dict[str, str] = {
|
| 164 |
+
"01": "Operação tributável (base de cálculo = valor da operação alíquota normal)",
|
| 165 |
+
"02": "Operação tributável (base de cálculo = valor da operação alíquota diferenciada)",
|
| 166 |
+
"03": "Operação tributável (base de cálculo = quantidade vendida x alíquota por unidade de produto)",
|
| 167 |
+
"04": "Operação tributável (tributação monofásica - revenda a alíquota zero)",
|
| 168 |
+
"05": "Operação tributável (ST)",
|
| 169 |
+
"06": "Operação tributável (alíquota zero)",
|
| 170 |
+
"07": "Operação isenta da contribuição",
|
| 171 |
+
"08": "Operação sem incidência da contribuição",
|
| 172 |
+
"09": "Operação com suspensão da contribuição",
|
| 173 |
+
"49": "Outras operações de saída",
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
CST_PIS_COFINS_ENTRADA: dict[str, str] = {
|
| 177 |
+
"50": "Operação com direito a crédito - vinculada exclusivamente a receita tributada no mercado interno",
|
| 178 |
+
"51": "Operação com direito a crédito - vinculada exclusivamente a receita não tributada no mercado interno",
|
| 179 |
+
"52": "Operação com direito a crédito - vinculada exclusivamente a receita de exportação",
|
| 180 |
+
"53": "Operação com direito a crédito - vinculada a receitas tributadas e não-tributadas no mercado interno",
|
| 181 |
+
"54": "Operação com direito a crédito - vinculada a receitas tributadas no mercado interno e de exportação",
|
| 182 |
+
"55": "Operação com direito a crédito - vinculada a receitas não-tributadas no mercado interno e de exportação",
|
| 183 |
+
"56": "Operação com direito a crédito - vinculada a receitas tributadas e não-tributadas no mercado interno e de exportação",
|
| 184 |
+
"60": "Crédito presumido - operação de aquisição vinculada exclusivamente a receita tributada no mercado interno",
|
| 185 |
+
"61": "Crédito presumido - operação de aquisição vinculada exclusivamente a receita não-tributada no mercado interno",
|
| 186 |
+
"62": "Crédito presumido - operação de aquisição vinculada exclusivamente a receita de exportação",
|
| 187 |
+
"63": "Crédito presumido - operação de aquisição vinculada a receitas tributadas e não-tributadas no mercado interno",
|
| 188 |
+
"64": "Crédito presumido - operação de aquisição vinculada a receitas tributadas no mercado interno e de exportação",
|
| 189 |
+
"65": "Crédito presumido - operação de aquisição vinculada a receitas não-tributadas no mercado interno e de exportação",
|
| 190 |
+
"66": "Crédito presumido - operação de aquisição vinculada a receitas tributadas e não-tributadas no mercado interno e de exportação",
|
| 191 |
+
"67": "Crédito presumido - outras operações",
|
| 192 |
+
"70": "Operação de aquisição sem direito a crédito",
|
| 193 |
+
"71": "Operação de aquisição com isenção",
|
| 194 |
+
"72": "Operação de aquisição com suspensão",
|
| 195 |
+
"73": "Operação de aquisição a alíquota zero",
|
| 196 |
+
"74": "Operação de aquisição (ST)",
|
| 197 |
+
"75": "Operação de aquisição sem incidência da contribuição",
|
| 198 |
+
"98": "Outras operações de entrada",
|
| 199 |
+
"99": "Outras operações",
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
CFOP: dict[str, str] = {
|
| 204 |
+
|
| 205 |
+
"1101": "Compra para industrialização ou produção rural",
|
| 206 |
+
"1102": "Compra para comercialização",
|
| 207 |
+
"1111": "Compra para industrialização de mercadoria recebida anteriormente em consignação industrial",
|
| 208 |
+
"1113": "Compra para comercialização, de mercadoria recebida anteriormente em consignação mercantil",
|
| 209 |
+
"1116": "Compra para industrialização originada de encomenda para recebimento futuro",
|
| 210 |
+
"1117": "Compra para comercialização originada de encomenda para recebimento futuro",
|
| 211 |
+
"1122": "Compra para comercialização de energia elétrica",
|
| 212 |
+
"1124": "Industrialização efetuada por outra empresa",
|
| 213 |
+
"1126": "Compra para utilização na prestação de serviço sujeita ao ISSQN",
|
| 214 |
+
"1151": "Transferência para industrialização ou produção rural",
|
| 215 |
+
"1152": "Transferência para comercialização",
|
| 216 |
+
"1201": "Devolução de venda de produção do estabelecimento",
|
| 217 |
+
"1202": "Devolução de venda de mercadoria adquirida ou recebida de terceiros",
|
| 218 |
+
"1251": "Compra de energia elétrica para distribuição ou comercialização",
|
| 219 |
+
"1301": "Aquisição de serviço de comunicação para execução de serviço da mesma natureza",
|
| 220 |
+
"1302": "Aquisição de serviço de comunicação por estabelecimento industrial",
|
| 221 |
+
"1303": "Aquisição de serviço de comunicação por estabelecimento comercial",
|
| 222 |
+
"1352": "Aquisição de serviço de transporte por estabelecimento comercial",
|
| 223 |
+
"1353": "Aquisição de serviço de transporte por estabelecimento de prestador de serviço de comunicação",
|
| 224 |
+
"1401": "Compra para industrialização em operação com mercadoria sujeita ao regime de ST",
|
| 225 |
+
"1403": "Compra para comercialização em operação com mercadoria sujeita ao regime de ST",
|
| 226 |
+
"1407": "Compra de mercadoria para uso ou consumo em operação com mercadoria sujeita ao regime de ST",
|
| 227 |
+
"1501": "Entrada de mercadoria recebida com fim específico de exportação",
|
| 228 |
+
"1503": "Entrada de mercadoria recebida com fim específico de exportação, de estabelecimento do mesmo titular",
|
| 229 |
+
"1556": "Compra de material para uso ou consumo",
|
| 230 |
+
"1601": "Recebimento, por transferência, de saldo credor de ICMS",
|
| 231 |
+
"1651": "Compra de combustível ou lubrificante para industrialização subsequente",
|
| 232 |
+
"1652": "Compra de combustível ou lubrificante para comercialização",
|
| 233 |
+
"1653": "Compra de combustível ou lubrificante por consumidor ou usuário final",
|
| 234 |
+
"1658": "Transferência de combustível ou lubrificante recebido para industrialização subsequente",
|
| 235 |
+
"1659": "Transferência de combustível ou lubrificante recebido para comercialização",
|
| 236 |
+
"1660": "Saída de combustível ou lubrificante, de produção do estabelecimento",
|
| 237 |
+
"1661": "Saída de combustível ou lubrificante adquirido ou recebido de terceiros",
|
| 238 |
+
|
| 239 |
+
"5101": "Venda de produção do estabelecimento",
|
| 240 |
+
"5102": "Venda de mercadoria adquirida ou recebida de terceiros",
|
| 241 |
+
"5103": "Venda de produção do estabelecimento, efetuada fora do estabelecimento",
|
| 242 |
+
"5104": "Venda de mercadoria adquirida ou recebida de terceiros, efetuada fora do estabelecimento",
|
| 243 |
+
"5105": "Venda de produção do estabelecimento que não deva por ele transitar",
|
| 244 |
+
"5106": "Venda de mercadoria adquirida ou recebida de terceiros, que não deva por ele transitar",
|
| 245 |
+
"5109": "Venda de produção do estabelecimento, destinada à Zona Franca de Manaus ou Áreas de Livre Comércio",
|
| 246 |
+
"5110": "Venda de mercadoria, adquirida ou recebida de terceiros, destinada à Zona Franca de Manaus ou Áreas de Livre Comércio",
|
| 247 |
+
"5111": "Venda de produção do estabelecimento remetida anteriormente em consignação industrial",
|
| 248 |
+
"5113": "Venda de mercadoria adquirida ou recebida de terceiros, remetida anteriormente em consignação mercantil",
|
| 249 |
+
"5116": "Venda de produção do estabelecimento originada de encomenda para entrega futura",
|
| 250 |
+
"5117": "Venda de mercadoria adquirida ou recebida de terceiros, originada de encomenda para entrega futura",
|
| 251 |
+
"5122": "Venda de energia elétrica no mercado interno",
|
| 252 |
+
"5124": "Industrialização efetuada para outra empresa",
|
| 253 |
+
"5125": "Industrialização efetuada para outra empresa quando a mercadoria recebida para utilização no processo de industrialização não transitar pelo estabelecimento adquirente da mercadoria",
|
| 254 |
+
"5151": "Transferência de produção do estabelecimento",
|
| 255 |
+
"5152": "Transferência de mercadoria adquirida ou recebida de terceiros",
|
| 256 |
+
"5201": "Devolução de compra para industrialização ou produção rural",
|
| 257 |
+
"5202": "Devolução de compra para comercialização",
|
| 258 |
+
"5401": "Venda de produção do estabelecimento em operação com produto sujeito ao regime de ST, como contribuinte substituto",
|
| 259 |
+
"5403": "Venda de mercadoria adquirida ou recebida de terceiros em operação com mercadoria sujeita ao regime de ST, como contribuinte substituto",
|
| 260 |
+
"5405": "Venda de mercadoria adquirida ou recebida de terceiros em operação com mercadoria sujeita ao regime de ST, como contribuinte substituído",
|
| 261 |
+
"5501": "Remessa de produção do estabelecimento, com fim específico de exportação",
|
| 262 |
+
"5503": "Remessa de mercadoria adquirida ou recebida de terceiros, com fim específico de exportação",
|
| 263 |
+
"5556": "Transferência de material para uso ou consumo",
|
| 264 |
+
"5601": "Transferência de saldo credor do ICMS para outro estabelecimento da mesma empresa, destinado à compensação de saldo devedor de ICMS",
|
| 265 |
+
"5602": "Transferência de saldo devedor de ICMS para outro estabelecimento da mesma empresa",
|
| 266 |
+
"5605": "Transferência de saldo credor de IPI para outro estabelecimento da mesma empresa, destinado à compensação de saldo devedor de IPI",
|
| 267 |
+
"5651": "Venda de combustível ou lubrificante de produção do estabelecimento destinados ao processo de industrialização",
|
| 268 |
+
"5652": "Venda de combustível ou lubrificante de produção do estabelecimento destinados à comercialização",
|
| 269 |
+
"5653": "Venda de combustível ou lubrificante de produção do estabelecimento destinados a consumidor ou usuário final",
|
| 270 |
+
# Saídas interestaduais
|
| 271 |
+
"6101": "Venda de produção do estabelecimento",
|
| 272 |
+
"6102": "Venda de mercadoria adquirida ou recebida de terceiros",
|
| 273 |
+
"6107": "Venda de produção do estabelecimento, destinada a não contribuinte",
|
| 274 |
+
"6108": "Venda de mercadoria adquirida ou recebida de terceiros, destinada a não contribuinte",
|
| 275 |
+
"6151": "Transferência de produção do estabelecimento",
|
| 276 |
+
"6152": "Transferência de mercadoria adquirida ou recebida de terceiros",
|
| 277 |
+
"6201": "Devolução de compra para industrialização ou produção rural",
|
| 278 |
+
"6202": "Devolução de compra para comercialização",
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
COD_PAISES: dict[str, str] = {
|
| 283 |
+
"1058": "BRASIL",
|
| 284 |
+
"0249": "ESTADOS UNIDOS DA AMERICA",
|
| 285 |
+
"0230": "ESPANHA",
|
| 286 |
+
"0232": "FRANCA",
|
| 287 |
+
"0276": "ALEMANHA",
|
| 288 |
+
"0040": "ARGENTINA",
|
| 289 |
+
"0119": "CHILE",
|
| 290 |
+
"0175": "COLOMBIA",
|
| 291 |
+
"0715": "REINO UNIDO",
|
| 292 |
+
"0538": "JAPAO",
|
| 293 |
+
"0628": "MEXICO",
|
| 294 |
+
"0765": "RUSSIA",
|
| 295 |
+
"0770": "SUECIA",
|
| 296 |
+
"0105": "CANADA",
|
| 297 |
+
"5760": "CHINA",
|
| 298 |
+
"0399": "ITALIA",
|
| 299 |
+
"0423": "HOLANDA",
|
| 300 |
+
"0773": "SUICA",
|
| 301 |
+
"0315": "INDIA",
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
CALENDARIO_OBRIGACOES: dict[str, list[str]] = {
|
| 306 |
+
"DCTF": ["Mensal - até o 15º dia útil do 2º mês subsequente"],
|
| 307 |
+
"EFD_ICMS_IPI": ["Mensal - até o 15º dia útil do mês subsequente"],
|
| 308 |
+
"EFD_CONTRIBUICOES": ["Mensal - até o 10º dia útil do 2º mês subsequente"],
|
| 309 |
+
"ECD": ["Anual - até o último dia útil de junho do ano seguinte"],
|
| 310 |
+
"ECF": ["Anual - até o último dia útil de julho do ano seguinte"],
|
| 311 |
+
"ESOCIAL": ["Mensal - folha até o dia 7 do mês seguinte"],
|
| 312 |
+
"EFD_REINF": ["Mensal - até o dia 15 do mês seguinte"],
|
| 313 |
+
"DEFIS": ["Anual - Simples Nacional - até 31 de março"],
|
| 314 |
+
"PGDAS": ["Mensal - Simples Nacional - até o dia 20"],
|
| 315 |
+
"DARF_PIS": ["Mensal - até o 25º dia do mês subsequente"],
|
| 316 |
+
"DARF_COFINS": ["Mensal - até o 25º dia do mês subsequente"],
|
| 317 |
+
"DARF_IRPJ_LP": ["Mensal - DARF estimativa até o último dia útil do mês subsequente"],
|
| 318 |
+
"DARF_CSLL_LP": ["Mensal - DARF estimativa até o último dia útil do mês subsequente"],
|
| 319 |
+
"DARF_IRPJ_LT": ["Trimestral - até o último dia útil do mês subsequente ao trimestre"],
|
| 320 |
+
"DARF_CSLL_LT": ["Trimestral - até o último dia útil do mês subsequente ao trimestre"],
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
COD_RECEITA_DARF: dict[str, str] = {
|
| 325 |
+
"6912": "IRPJ - Lucro Real - Estimativa Mensal",
|
| 326 |
+
"2362": "IRPJ - Lucro Real - Apuração Trimestral",
|
| 327 |
+
"2089": "IRPJ - Lucro Presumido - Apuração Trimestral",
|
| 328 |
+
"2372": "CSLL - Lucro Real - Estimativa Mensal",
|
| 329 |
+
"6773": "CSLL - Lucro Presumido - Apuração Trimestral",
|
| 330 |
+
"2484": "CSLL - Lucro Real - Apuração Trimestral",
|
| 331 |
+
"8109": "PIS/PASEP - Faturamento - Não-cumulativo",
|
| 332 |
+
"7987": "PIS/PASEP - Faturamento - Cumulativo",
|
| 333 |
+
"2172": "COFINS - Faturamento - Não-cumulativa",
|
| 334 |
+
"5856": "COFINS - Faturamento - Cumulativa",
|
| 335 |
+
"5253": "IPI - Geral",
|
| 336 |
+
"1038": "IRRF - Rendimentos do Trabalho",
|
| 337 |
+
"0561": "IRRF - Serviços Prestados por PJ",
|
| 338 |
+
}
|
src/fiscal/entities.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import re
|
| 5 |
+
from datetime import date
|
| 6 |
+
from decimal import Decimal
|
| 7 |
+
from enum import Enum
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
from pydantic import BaseModel, Field, field_validator
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class RegimeTributario(str, Enum):
|
| 14 |
+
LUCRO_REAL = "1"
|
| 15 |
+
LUCRO_PRESUMIDO = "2"
|
| 16 |
+
SIMPLES_NACIONAL = "3"
|
| 17 |
+
ARBITRADO = "4"
|
| 18 |
+
IMUNE = "5"
|
| 19 |
+
OPTANTE_SIMPLES = "6"
|
| 20 |
+
LUCRO_REAL_TRIMESTRAL = "7"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TipoEmpresa(str, Enum):
|
| 24 |
+
INDUSTRIA = "industria"
|
| 25 |
+
COMERCIO = "comercio"
|
| 26 |
+
SERVICOS = "servicos"
|
| 27 |
+
MISTO = "misto"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class PerfilSPED(str, Enum):
|
| 31 |
+
A = "A" # Escrituração completa
|
| 32 |
+
B = "B" # Escrituração com dados simplificados
|
| 33 |
+
C = "C" # Escrituração com dados simplificados (ME/EPP Simples)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class UF(str, Enum):
|
| 37 |
+
AC = "AC"; AL = "AL"; AP = "AP"; AM = "AM"
|
| 38 |
+
BA = "BA"; CE = "CE"; DF = "DF"; ES = "ES"
|
| 39 |
+
GO = "GO"; MA = "MA"; MT = "MT"; MS = "MS"
|
| 40 |
+
MG = "MG"; PA = "PA"; PB = "PB"; PR = "PR"
|
| 41 |
+
PE = "PE"; PI = "PI"; RJ = "RJ"; RN = "RN"
|
| 42 |
+
RS = "RS"; RO = "RO"; RR = "RR"; SC = "SC"
|
| 43 |
+
SP = "SP"; SE = "SE"; TO = "TO"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _limpar_cnpj(cnpj: str) -> str:
|
| 47 |
+
return re.sub(r"\D", "", cnpj)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _validar_cnpj(cnpj: str) -> bool:
|
| 51 |
+
d = _limpar_cnpj(cnpj)
|
| 52 |
+
if len(d) != 14 or d == d[0] * 14:
|
| 53 |
+
return False
|
| 54 |
+
for i, j in [(12, 13), (13, 14)]:
|
| 55 |
+
s = sum(int(d[k]) * ((i - k - 1) % 8 + 2) for k in range(i))
|
| 56 |
+
c = 0 if (r := s % 11) < 2 else 11 - r
|
| 57 |
+
if c != int(d[j - 1]):
|
| 58 |
+
return False
|
| 59 |
+
return True
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _validar_cpf(cpf: str) -> bool:
|
| 63 |
+
d = re.sub(r"\D", "", cpf)
|
| 64 |
+
if len(d) != 11 or d == d[0] * 11:
|
| 65 |
+
return False
|
| 66 |
+
for i in range(9, 11):
|
| 67 |
+
s = sum(int(d[k]) * (i + 1 - k) for k in range(i))
|
| 68 |
+
if (0 if (r := (s * 10 % 11)) >= 10 else r) != int(d[i]):
|
| 69 |
+
return False
|
| 70 |
+
return True
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class Endereco(BaseModel):
|
| 74 |
+
logradouro: str
|
| 75 |
+
numero: str
|
| 76 |
+
complemento: str = ""
|
| 77 |
+
bairro: str
|
| 78 |
+
municipio: str
|
| 79 |
+
uf: UF
|
| 80 |
+
cep: str
|
| 81 |
+
cod_municipio: str = ""
|
| 82 |
+
|
| 83 |
+
@field_validator("cep")
|
| 84 |
+
@classmethod
|
| 85 |
+
def normalizar_cep(cls, v: str) -> str:
|
| 86 |
+
return re.sub(r"\D", "", v).zfill(8)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class Contato(BaseModel):
|
| 90 |
+
nome: str = ""
|
| 91 |
+
telefone: str = ""
|
| 92 |
+
fax: str = ""
|
| 93 |
+
email: str = ""
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class Empresa(BaseModel):
|
| 97 |
+
cnpj: str
|
| 98 |
+
razao_social: str
|
| 99 |
+
nome_fantasia: str = ""
|
| 100 |
+
ie: str = ""
|
| 101 |
+
im: str = ""
|
| 102 |
+
suframa: str = ""
|
| 103 |
+
regime_tributario: RegimeTributario
|
| 104 |
+
tipo_empresa: TipoEmpresa
|
| 105 |
+
endereco: Endereco
|
| 106 |
+
contato: Contato = Field(default_factory=Contato)
|
| 107 |
+
perfil_sped: PerfilSPED = PerfilSPED.A
|
| 108 |
+
atividade_industrial: bool = False
|
| 109 |
+
data_inicio_atividade: Optional[date] = None
|
| 110 |
+
|
| 111 |
+
@field_validator("cnpj")
|
| 112 |
+
@classmethod
|
| 113 |
+
def validar_cnpj(cls, v: str) -> str:
|
| 114 |
+
d = _limpar_cnpj(v)
|
| 115 |
+
if not _validar_cnpj(d):
|
| 116 |
+
raise ValueError(f"CNPJ inválido: {v}")
|
| 117 |
+
return d
|
| 118 |
+
|
| 119 |
+
@property
|
| 120 |
+
def cnpj_formatado(self) -> str:
|
| 121 |
+
d = self.cnpj
|
| 122 |
+
return f"{d[:2]}.{d[2:5]}.{d[5:8]}/{d[8:12]}-{d[12:]}"
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class Contador(BaseModel):
|
| 126 |
+
nome: str
|
| 127 |
+
cpf: str
|
| 128 |
+
crc: str
|
| 129 |
+
cnpj_escritorio: str = ""
|
| 130 |
+
endereco: Optional[Endereco] = None
|
| 131 |
+
contato: Contato = Field(default_factory=Contato)
|
| 132 |
+
|
| 133 |
+
@field_validator("cpf")
|
| 134 |
+
@classmethod
|
| 135 |
+
def validar_cpf(cls, v: str) -> str:
|
| 136 |
+
d = re.sub(r"\D", "", v)
|
| 137 |
+
if not _validar_cpf(d):
|
| 138 |
+
raise ValueError(f"CPF inválido: {v}")
|
| 139 |
+
return d
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class Produto(BaseModel):
|
| 143 |
+
codigo: str
|
| 144 |
+
descricao: str
|
| 145 |
+
ncm: str
|
| 146 |
+
ex_ipi: str = ""
|
| 147 |
+
unidade: str
|
| 148 |
+
tipo_item: str = "00"
|
| 149 |
+
cod_gen: str = ""
|
| 150 |
+
aliq_icms: Decimal = Decimal("0")
|
| 151 |
+
aliq_ipi: Decimal = Decimal("0")
|
| 152 |
+
cst_icms: str = "000"
|
| 153 |
+
cst_ipi: str = "50"
|
| 154 |
+
cst_pis: str = "01"
|
| 155 |
+
cst_cofins: str = "01"
|
| 156 |
+
cest: str = ""
|
| 157 |
+
cod_barras: str = ""
|
| 158 |
+
peso_bruto: Decimal = Decimal("0")
|
| 159 |
+
peso_liquido: Decimal = Decimal("0")
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class ItemNotaFiscal(BaseModel):
|
| 163 |
+
numero_item: int
|
| 164 |
+
produto: Produto
|
| 165 |
+
cfop: str
|
| 166 |
+
quantidade: Decimal
|
| 167 |
+
valor_unitario: Decimal
|
| 168 |
+
desconto: Decimal = Decimal("0")
|
| 169 |
+
outras_despesas: Decimal = Decimal("0")
|
| 170 |
+
frete: Decimal = Decimal("0")
|
| 171 |
+
seguro: Decimal = Decimal("0")
|
| 172 |
+
|
| 173 |
+
@property
|
| 174 |
+
def valor_produtos(self) -> Decimal:
|
| 175 |
+
return (self.quantidade * self.valor_unitario) - self.desconto
|
| 176 |
+
|
| 177 |
+
@property
|
| 178 |
+
def base_icms(self) -> Decimal:
|
| 179 |
+
return self.valor_produtos + self.frete + self.seguro + self.outras_despesas
|
| 180 |
+
|
| 181 |
+
@property
|
| 182 |
+
def valor_icms(self) -> Decimal:
|
| 183 |
+
return (self.base_icms * self.produto.aliq_icms / 100).quantize(Decimal("0.01"))
|
| 184 |
+
|
| 185 |
+
@property
|
| 186 |
+
def base_ipi(self) -> Decimal:
|
| 187 |
+
return self.valor_produtos
|
| 188 |
+
|
| 189 |
+
@property
|
| 190 |
+
def valor_ipi(self) -> Decimal:
|
| 191 |
+
return (self.base_ipi * self.produto.aliq_ipi / 100).quantize(Decimal("0.01"))
|
| 192 |
+
|
| 193 |
+
@property
|
| 194 |
+
def valor_total(self) -> Decimal:
|
| 195 |
+
return self.valor_produtos + self.valor_ipi + self.frete + self.seguro + self.outras_despesas
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
class NotaFiscal(BaseModel):
|
| 199 |
+
numero: str
|
| 200 |
+
serie: str
|
| 201 |
+
modelo: str = "55"
|
| 202 |
+
data_emissao: date
|
| 203 |
+
data_saida_entrada: date
|
| 204 |
+
hora_saida_entrada: str = "00:00:00"
|
| 205 |
+
emitente: Empresa
|
| 206 |
+
destinatario: Empresa
|
| 207 |
+
natureza_operacao: str
|
| 208 |
+
tipo_operacao: str = "1"
|
| 209 |
+
ind_pagamento: str = "0"
|
| 210 |
+
chave_acesso: str = ""
|
| 211 |
+
protocolo: str = ""
|
| 212 |
+
itens: list[ItemNotaFiscal] = Field(default_factory=list)
|
| 213 |
+
info_complementar: str = ""
|
| 214 |
+
|
| 215 |
+
@property
|
| 216 |
+
def valor_produtos(self) -> Decimal:
|
| 217 |
+
return sum(i.valor_produtos for i in self.itens)
|
| 218 |
+
|
| 219 |
+
@property
|
| 220 |
+
def valor_frete(self) -> Decimal:
|
| 221 |
+
return sum(i.frete for i in self.itens)
|
| 222 |
+
|
| 223 |
+
@property
|
| 224 |
+
def valor_seguro(self) -> Decimal:
|
| 225 |
+
return sum(i.seguro for i in self.itens)
|
| 226 |
+
|
| 227 |
+
@property
|
| 228 |
+
def valor_desconto(self) -> Decimal:
|
| 229 |
+
return sum(i.desconto for i in self.itens)
|
| 230 |
+
|
| 231 |
+
@property
|
| 232 |
+
def valor_outras_despesas(self) -> Decimal:
|
| 233 |
+
return sum(i.outras_despesas for i in self.itens)
|
| 234 |
+
|
| 235 |
+
@property
|
| 236 |
+
def valor_icms(self) -> Decimal:
|
| 237 |
+
return sum(i.valor_icms for i in self.itens)
|
| 238 |
+
|
| 239 |
+
@property
|
| 240 |
+
def valor_ipi(self) -> Decimal:
|
| 241 |
+
return sum(i.valor_ipi for i in self.itens)
|
| 242 |
+
|
| 243 |
+
@property
|
| 244 |
+
def valor_total(self) -> Decimal:
|
| 245 |
+
return sum(i.valor_total for i in self.itens)
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class LancamentoContabil(BaseModel):
|
| 249 |
+
data: date
|
| 250 |
+
numero_lancamento: str
|
| 251 |
+
historico: str
|
| 252 |
+
debito_conta: str
|
| 253 |
+
credito_conta: str
|
| 254 |
+
valor: Decimal
|
| 255 |
+
complemento: str = ""
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
class MovimentacaoEstoque(BaseModel):
|
| 259 |
+
data: date
|
| 260 |
+
produto: Produto
|
| 261 |
+
tipo_movimento: str
|
| 262 |
+
quantidade: Decimal
|
| 263 |
+
valor_unitario: Decimal
|
| 264 |
+
documento: str = ""
|
| 265 |
+
|
| 266 |
+
@property
|
| 267 |
+
def valor_total(self) -> Decimal:
|
| 268 |
+
return self.quantidade * self.valor_unitario
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
class PeriodoApuracao(BaseModel):
|
| 272 |
+
data_inicio: date
|
| 273 |
+
data_fim: date
|
| 274 |
+
empresa: Empresa
|
| 275 |
+
notas_saida: list[NotaFiscal] = Field(default_factory=list)
|
| 276 |
+
notas_entrada: list[NotaFiscal] = Field(default_factory=list)
|
| 277 |
+
lancamentos: list[LancamentoContabil] = Field(default_factory=list)
|
| 278 |
+
movimentacoes: list[MovimentacaoEstoque] = Field(default_factory=list)
|
| 279 |
+
|
| 280 |
+
@property
|
| 281 |
+
def total_vendas(self) -> Decimal:
|
| 282 |
+
return sum(nf.valor_total for nf in self.notas_saida)
|
| 283 |
+
|
| 284 |
+
@property
|
| 285 |
+
def total_compras(self) -> Decimal:
|
| 286 |
+
return sum(nf.valor_total for nf in self.notas_entrada)
|
src/generators/__init__.py
ADDED
|
File without changes
|
src/generators/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (177 Bytes). View file
|
|
|
src/generators/__pycache__/cte.cpython-312.pyc
ADDED
|
Binary file (20.1 kB). View file
|
|
|
src/generators/__pycache__/dctf.cpython-312.pyc
ADDED
|
Binary file (16.6 kB). View file
|
|
|
src/generators/__pycache__/defis.cpython-312.pyc
ADDED
|
Binary file (12 kB). View file
|
|
|
src/generators/__pycache__/destda.cpython-312.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|
src/generators/__pycache__/dirf.cpython-312.pyc
ADDED
|
Binary file (9.74 kB). View file
|
|
|
src/generators/__pycache__/ecd.cpython-312.pyc
ADDED
|
Binary file (13.3 kB). View file
|
|
|
src/generators/__pycache__/ecf.cpython-312.pyc
ADDED
|
Binary file (19.7 kB). View file
|
|
|
src/generators/__pycache__/efd_contribuicoes.cpython-312.pyc
ADDED
|
Binary file (10.6 kB). View file
|
|
|
src/generators/__pycache__/efd_icms_ipi.cpython-312.pyc
ADDED
|
Binary file (21 kB). View file
|
|
|
src/generators/__pycache__/efd_reinf.cpython-312.pyc
ADDED
|
Binary file (22.2 kB). View file
|
|
|
src/generators/__pycache__/esocial.cpython-312.pyc
ADDED
|
Binary file (25.3 kB). View file
|
|
|
src/generators/__pycache__/gia.cpython-312.pyc
ADDED
|
Binary file (11.5 kB). View file
|
|
|
src/generators/__pycache__/mdfe.cpython-312.pyc
ADDED
|
Binary file (16.8 kB). View file
|
|
|
src/generators/__pycache__/nfce_xml.cpython-312.pyc
ADDED
|
Binary file (25.7 kB). View file
|
|
|
src/generators/__pycache__/nfe_xml.cpython-312.pyc
ADDED
|
Binary file (30.8 kB). View file
|
|
|
src/generators/__pycache__/sped_writer.cpython-312.pyc
ADDED
|
Binary file (6.3 kB). View file
|
|
|
src/generators/cte.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import hashlib
|
| 5 |
+
import re
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
from datetime import date, datetime
|
| 8 |
+
from decimal import Decimal
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from xml.etree import ElementTree as ET
|
| 11 |
+
|
| 12 |
+
from src.fiscal.entities import Empresa
|
| 13 |
+
|
| 14 |
+
NS_CTE = "http://www.portalfiscal.inf.br/cte"
|
| 15 |
+
VERSAO_CTE = "4.00"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _sub(pai: ET.Element, tag: str, texto: str = "") -> ET.Element:
|
| 19 |
+
el = ET.SubElement(pai, tag)
|
| 20 |
+
if texto:
|
| 21 |
+
el.text = str(texto)
|
| 22 |
+
return el
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _fmt_dec(v: Decimal, casas: int = 2) -> str:
|
| 26 |
+
return f"{v:.{casas}f}"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _limpar(s: str) -> str:
|
| 30 |
+
return re.sub(r"[^\w\s\-.,/@]", "", str(s))[:60]
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# ---------------------------------------------------------------------------
|
| 34 |
+
# Dataclasses auxiliares
|
| 35 |
+
# ---------------------------------------------------------------------------
|
| 36 |
+
|
| 37 |
+
@dataclass
|
| 38 |
+
class CargaCTe:
|
| 39 |
+
descricao: str
|
| 40 |
+
produto: str # código produto predominante
|
| 41 |
+
valor_carga: Decimal
|
| 42 |
+
peso_kg: Decimal = field(default_factory=lambda: Decimal("0"))
|
| 43 |
+
volume_m3: Decimal = field(default_factory=lambda: Decimal("0"))
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@dataclass
|
| 47 |
+
class DocumentoReferenciado:
|
| 48 |
+
chave_nfe: str # chave NF-e 44 dígitos
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dataclass
|
| 52 |
+
class DadosTransporte:
|
| 53 |
+
rntrc: str # Registro Nacional de Transportadores Rodoviários de Cargas
|
| 54 |
+
placa_veiculo: str
|
| 55 |
+
uf_veiculo: str
|
| 56 |
+
data_prevista_entrega: date
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@dataclass
|
| 60 |
+
class ParteCTe: # remetente, destinatário, etc.
|
| 61 |
+
cnpj: str
|
| 62 |
+
razao_social: str
|
| 63 |
+
endereco: str
|
| 64 |
+
municipio: str
|
| 65 |
+
uf: str
|
| 66 |
+
cep: str
|
| 67 |
+
cod_municipio: str
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# ---------------------------------------------------------------------------
|
| 71 |
+
# Gerador principal
|
| 72 |
+
# ---------------------------------------------------------------------------
|
| 73 |
+
|
| 74 |
+
class GeradorCTe:
|
| 75 |
+
"""
|
| 76 |
+
Gera o XML de CT-e conforme o leiaute 4.00 da SEFAZ.
|
| 77 |
+
|
| 78 |
+
O XML gerado deve ser assinado digitalmente com certificado ICP-Brasil
|
| 79 |
+
antes da transmissão.
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
VERSAO = "4.00"
|
| 83 |
+
MOD = "57"
|
| 84 |
+
|
| 85 |
+
_CUF_MAP = {
|
| 86 |
+
"AC": "12", "AL": "27", "AP": "16", "AM": "13", "BA": "29",
|
| 87 |
+
"CE": "23", "DF": "53", "ES": "32", "GO": "52", "MA": "21",
|
| 88 |
+
"MT": "51", "MS": "50", "MG": "31", "PA": "15", "PB": "25",
|
| 89 |
+
"PR": "41", "PE": "26", "PI": "22", "RJ": "33", "RN": "24",
|
| 90 |
+
"RS": "43", "RO": "11", "RR": "14", "SC": "42", "SP": "35",
|
| 91 |
+
"SE": "28", "TO": "17",
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
def __init__(
|
| 95 |
+
self,
|
| 96 |
+
emitente: Empresa,
|
| 97 |
+
remetente: ParteCTe,
|
| 98 |
+
destinatario: ParteCTe,
|
| 99 |
+
carga: CargaCTe,
|
| 100 |
+
documentos: list[DocumentoReferenciado],
|
| 101 |
+
transporte: DadosTransporte,
|
| 102 |
+
numero: str = "1",
|
| 103 |
+
serie: str = "001",
|
| 104 |
+
cfop: str = "6352",
|
| 105 |
+
natureza_operacao: str = "PRESTAÇÃO DE SERVIÇO DE TRANSPORTE",
|
| 106 |
+
aliq_icms: Decimal = Decimal("12"),
|
| 107 |
+
ambiente: str = "2",
|
| 108 |
+
):
|
| 109 |
+
self.emitente = emitente
|
| 110 |
+
self.remetente = remetente
|
| 111 |
+
self.destinatario = destinatario
|
| 112 |
+
self.carga = carga
|
| 113 |
+
self.documentos = documentos
|
| 114 |
+
self.transporte = transporte
|
| 115 |
+
self.numero = numero
|
| 116 |
+
self.serie = serie
|
| 117 |
+
self.cfop = cfop
|
| 118 |
+
self.natureza_operacao = natureza_operacao
|
| 119 |
+
self.aliq_icms = aliq_icms
|
| 120 |
+
self.ambiente = ambiente # 1=Produção, 2=Homologação
|
| 121 |
+
self._data_emissao: date = date.today()
|
| 122 |
+
|
| 123 |
+
# ------------------------------------------------------------------
|
| 124 |
+
# Utilitários internos
|
| 125 |
+
# ------------------------------------------------------------------
|
| 126 |
+
|
| 127 |
+
def _cuf(self, uf: str) -> str:
|
| 128 |
+
return self._CUF_MAP.get(uf.upper(), "35")
|
| 129 |
+
|
| 130 |
+
def _gerar_chave(self) -> str:
|
| 131 |
+
"""
|
| 132 |
+
Gera a chave de acesso de 44 dígitos do CT-e.
|
| 133 |
+
Estrutura: cUF(2) + AAMM(4) + CNPJ(14) + mod(2=57) + serie(3) +
|
| 134 |
+
nCT(9) + tpEmis(1) + cCT(8) + cDV(1)
|
| 135 |
+
"""
|
| 136 |
+
uf = self._cuf(self.emitente.endereco.uf.value)
|
| 137 |
+
aamm = self._data_emissao.strftime("%y%m")
|
| 138 |
+
cnpj = self.emitente.cnpj.zfill(14)
|
| 139 |
+
mod = self.MOD.zfill(2)
|
| 140 |
+
serie = self.serie.zfill(3)
|
| 141 |
+
numero = self.numero.zfill(9)
|
| 142 |
+
tp_emis = "1"
|
| 143 |
+
c_ct = hashlib.md5(f"{cnpj}{numero}".encode()).hexdigest()[:8].upper()
|
| 144 |
+
c_ct_digits = "".join(str(int(c, 16) % 10) for c in c_ct)[:8]
|
| 145 |
+
|
| 146 |
+
chave_sem_dv = f"{uf}{aamm}{cnpj}{mod}{serie}{numero}{tp_emis}{c_ct_digits}"
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
pesos = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4,
|
| 150 |
+
5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4]
|
| 151 |
+
soma = sum(int(chave_sem_dv[i]) * pesos[i] for i in range(43))
|
| 152 |
+
resto = soma % 11
|
| 153 |
+
dv = 0 if resto in (0, 1) else 11 - resto
|
| 154 |
+
|
| 155 |
+
return chave_sem_dv + str(dv)
|
| 156 |
+
|
| 157 |
+
def _fmt_dhemi(self) -> str:
|
| 158 |
+
return (
|
| 159 |
+
datetime.combine(self._data_emissao, datetime.min.time())
|
| 160 |
+
.strftime("%Y-%m-%dT%H:%M:%S") + "-03:00"
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
# ------------------------------------------------------------------
|
| 164 |
+
# Blocos XML
|
| 165 |
+
# ------------------------------------------------------------------
|
| 166 |
+
|
| 167 |
+
def _ide(self, inf_cte: ET.Element, chave: str) -> None:
|
| 168 |
+
emit_uf = self.emitente.endereco.uf.value
|
| 169 |
+
cuf = self._cuf(emit_uf)
|
| 170 |
+
ide = _sub(inf_cte, "ide")
|
| 171 |
+
_sub(ide, "cUF", cuf)
|
| 172 |
+
_sub(ide, "cCT", chave[35:43])
|
| 173 |
+
_sub(ide, "CFOP", self.cfop)
|
| 174 |
+
_sub(ide, "natOp", _limpar(self.natureza_operacao))
|
| 175 |
+
_sub(ide, "mod", self.MOD)
|
| 176 |
+
_sub(ide, "serie", self.serie.zfill(3))
|
| 177 |
+
_sub(ide, "nCT", self.numero.zfill(9))
|
| 178 |
+
_sub(ide, "dhEmi", self._fmt_dhemi())
|
| 179 |
+
_sub(ide, "tpImp", "1") # 1=DACTE normal retrato
|
| 180 |
+
_sub(ide, "tpEmis", "1") # 1=Emissão normal
|
| 181 |
+
_sub(ide, "cDV", chave[-1])
|
| 182 |
+
_sub(ide, "tpAmb", self.ambiente)
|
| 183 |
+
_sub(ide, "tpCT", "0") # 0=CT-e normal
|
| 184 |
+
_sub(ide, "tpServ", "0") # 0=Normal
|
| 185 |
+
cod_mun_emit = self.emitente.endereco.cod_municipio or "3550308"
|
| 186 |
+
_sub(ide, "cMunEnv", cod_mun_emit)
|
| 187 |
+
_sub(ide, "xMunEnv", _limpar(self.emitente.endereco.municipio))
|
| 188 |
+
_sub(ide, "UFEnv", emit_uf)
|
| 189 |
+
_sub(ide, "modal", "01") # 01=Rodoviário
|
| 190 |
+
_sub(ide, "dhSaidaOrig", self._fmt_dhemi())
|
| 191 |
+
_sub(ide, "tpMultimodal", "0")
|
| 192 |
+
_sub(ide, "UFIni", self.remetente.uf)
|
| 193 |
+
_sub(ide, "UFFim", self.destinatario.uf)
|
| 194 |
+
|
| 195 |
+
def _compl(self, inf_cte: ET.Element) -> None:
|
| 196 |
+
compl = _sub(inf_cte, "compl")
|
| 197 |
+
_sub(compl, "xObs", _limpar(self.carga.descricao)[:160] if self.carga.descricao else "")
|
| 198 |
+
|
| 199 |
+
def _emit(self, inf_cte: ET.Element) -> None:
|
| 200 |
+
emp = self.emitente
|
| 201 |
+
end = emp.endereco
|
| 202 |
+
emit = _sub(inf_cte, "emit")
|
| 203 |
+
_sub(emit, "CNPJ", emp.cnpj)
|
| 204 |
+
if emp.ie:
|
| 205 |
+
_sub(emit, "IE", re.sub(r"\D", "", emp.ie))
|
| 206 |
+
_sub(emit, "xNome", _limpar(emp.razao_social)[:60])
|
| 207 |
+
if emp.nome_fantasia:
|
| 208 |
+
_sub(emit, "xFant", _limpar(emp.nome_fantasia)[:60])
|
| 209 |
+
ender_emit = _sub(emit, "enderEmit")
|
| 210 |
+
_sub(ender_emit, "xLgr", _limpar(end.logradouro))
|
| 211 |
+
_sub(ender_emit, "nro", end.numero)
|
| 212 |
+
if end.complemento:
|
| 213 |
+
_sub(ender_emit, "xCpl", _limpar(end.complemento))
|
| 214 |
+
_sub(ender_emit, "xBairro", _limpar(end.bairro))
|
| 215 |
+
_sub(ender_emit, "cMun", end.cod_municipio or "3550308")
|
| 216 |
+
_sub(ender_emit, "xMun", _limpar(end.municipio))
|
| 217 |
+
_sub(ender_emit, "CEP", re.sub(r"\D", "", end.cep))
|
| 218 |
+
_sub(ender_emit, "UF", end.uf.value)
|
| 219 |
+
_sub(ender_emit, "cPais", "1058")
|
| 220 |
+
_sub(ender_emit, "xPais", "Brasil")
|
| 221 |
+
if emp.contato.telefone:
|
| 222 |
+
_sub(emit, "fone", re.sub(r"\D", "", emp.contato.telefone))
|
| 223 |
+
|
| 224 |
+
def _parte(self, inf_cte: ET.Element, tag: str, ender_tag: str, parte: ParteCTe) -> None:
|
| 225 |
+
el = _sub(inf_cte, tag)
|
| 226 |
+
_sub(el, "CNPJ", re.sub(r"\D", "", parte.cnpj).zfill(14))
|
| 227 |
+
_sub(el, "xNome", _limpar(parte.razao_social)[:60])
|
| 228 |
+
ender = _sub(el, ender_tag)
|
| 229 |
+
_sub(ender, "xLgr", _limpar(parte.endereco))
|
| 230 |
+
_sub(ender, "nro", "S/N")
|
| 231 |
+
_sub(ender, "xBairro", "CENTRO")
|
| 232 |
+
_sub(ender, "cMun", parte.cod_municipio)
|
| 233 |
+
_sub(ender, "xMun", _limpar(parte.municipio))
|
| 234 |
+
_sub(ender, "CEP", re.sub(r"\D", "", parte.cep).zfill(8))
|
| 235 |
+
_sub(ender, "UF", parte.uf)
|
| 236 |
+
_sub(ender, "cPais", "1058")
|
| 237 |
+
_sub(ender, "xPais", "Brasil")
|
| 238 |
+
|
| 239 |
+
def _v_prest(self, inf_cte: ET.Element) -> None:
|
| 240 |
+
v_prest = _sub(inf_cte, "vPrest")
|
| 241 |
+
_sub(v_prest, "vTPrest", _fmt_dec(self.carga.valor_carga))
|
| 242 |
+
_sub(v_prest, "vRec", _fmt_dec(self.carga.valor_carga))
|
| 243 |
+
|
| 244 |
+
def _imp(self, inf_cte: ET.Element) -> None:
|
| 245 |
+
imp = _sub(inf_cte, "imp")
|
| 246 |
+
icms = _sub(imp, "ICMS")
|
| 247 |
+
icms00 = _sub(icms, "ICMS00")
|
| 248 |
+
_sub(icms00, "CST", "00")
|
| 249 |
+
v_bc = self.carga.valor_carga
|
| 250 |
+
v_icms = (v_bc * self.aliq_icms / Decimal("100")).quantize(Decimal("0.01"))
|
| 251 |
+
_sub(icms00, "vBC", _fmt_dec(v_bc))
|
| 252 |
+
_sub(icms00, "pICMS", _fmt_dec(self.aliq_icms, 4))
|
| 253 |
+
_sub(icms00, "vICMS", _fmt_dec(v_icms))
|
| 254 |
+
|
| 255 |
+
def _inf_cte_norm(self, inf_cte: ET.Element) -> None:
|
| 256 |
+
inf_cte_norm = _sub(inf_cte, "infCTeNorm")
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
inf_carga = _sub(inf_cte_norm, "infCarga")
|
| 260 |
+
_sub(inf_carga, "vCarga", _fmt_dec(self.carga.valor_carga))
|
| 261 |
+
_sub(inf_carga, "proPred", _limpar(self.carga.produto)[:60])
|
| 262 |
+
inf_q = _sub(inf_carga, "infQ")
|
| 263 |
+
_sub(inf_q, "cUnid", "01") # 01=KG
|
| 264 |
+
_sub(inf_q, "tpMed", "PESO BRUTO")
|
| 265 |
+
_sub(inf_q, "qCarga", _fmt_dec(self.carga.peso_kg, 4))
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
if self.documentos:
|
| 269 |
+
inf_doc = _sub(inf_cte_norm, "infDoc")
|
| 270 |
+
for doc in self.documentos:
|
| 271 |
+
inf_nfe = _sub(inf_doc, "infNFe")
|
| 272 |
+
_sub(inf_nfe, "chave", doc.chave_nfe)
|
| 273 |
+
_sub(inf_nfe, "pin", "")
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
inf_modal = _sub(inf_cte_norm, "infModal")
|
| 277 |
+
inf_modal.set("versaoModal", "3.00")
|
| 278 |
+
rodo = _sub(inf_modal, "rodo")
|
| 279 |
+
_sub(rodo, "RNTRC", self.transporte.rntrc)
|
| 280 |
+
veic = _sub(rodo, "veicTracao")
|
| 281 |
+
_sub(veic, "placa", self.transporte.placa_veiculo.upper())
|
| 282 |
+
_sub(veic, "UF", self.transporte.uf_veiculo.upper())
|
| 283 |
+
_sub(veic, "prop") # tag vazia
|
| 284 |
+
occ = _sub(rodo, "occ")
|
| 285 |
+
_sub(occ, "serie", "001")
|
| 286 |
+
_sub(occ, "nOcc", "1")
|
| 287 |
+
_sub(occ, "dEmi", self._data_emissao.strftime("%Y-%m-%d"))
|
| 288 |
+
emi_occ = _sub(occ, "emiOcc")
|
| 289 |
+
_sub(emi_occ, "CNPJ", self.emitente.cnpj)
|
| 290 |
+
_sub(emi_occ, "xNome", _limpar(self.emitente.razao_social)[:60])
|
| 291 |
+
_sub(emi_occ, "cInt", "")
|
| 292 |
+
_sub(emi_occ, "IE", "")
|
| 293 |
+
|
| 294 |
+
# ------------------------------------------------------------------
|
| 295 |
+
# API pública
|
| 296 |
+
# ------------------------------------------------------------------
|
| 297 |
+
|
| 298 |
+
def gerar_xml(self) -> str:
|
| 299 |
+
"""Retorna o XML do CT-e não assinado como string."""
|
| 300 |
+
chave = self._gerar_chave()
|
| 301 |
+
|
| 302 |
+
cte_proc = ET.Element("cteProc")
|
| 303 |
+
cte_proc.set("xmlns", NS_CTE)
|
| 304 |
+
cte_proc.set("versao", self.VERSAO)
|
| 305 |
+
|
| 306 |
+
cte = _sub(cte_proc, "CTe")
|
| 307 |
+
cte.set("xmlns", NS_CTE)
|
| 308 |
+
|
| 309 |
+
inf_cte = _sub(cte, "infCte")
|
| 310 |
+
inf_cte.set("versao", self.VERSAO)
|
| 311 |
+
inf_cte.set("Id", f"CTe{chave}")
|
| 312 |
+
|
| 313 |
+
self._ide(inf_cte, chave)
|
| 314 |
+
self._compl(inf_cte)
|
| 315 |
+
self._emit(inf_cte)
|
| 316 |
+
self._parte(inf_cte, "rem", "enderReme", self.remetente)
|
| 317 |
+
self._parte(inf_cte, "dest", "enderDest", self.destinatario)
|
| 318 |
+
self._v_prest(inf_cte)
|
| 319 |
+
self._imp(inf_cte)
|
| 320 |
+
self._inf_cte_norm(inf_cte)
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
_sub(cte, "Signature")
|
| 324 |
+
|
| 325 |
+
ET.indent(cte_proc, space=" ")
|
| 326 |
+
return '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(cte_proc, encoding="unicode")
|
| 327 |
+
|
| 328 |
+
def salvar(self, diretorio: str | Path = ".") -> Path:
|
| 329 |
+
"""Salva o XML em diretório informado e retorna o caminho do arquivo."""
|
| 330 |
+
cnpj = self.emitente.cnpj
|
| 331 |
+
caminho = Path(diretorio) / f"CTe_{cnpj}_{self.numero.zfill(9)}.xml"
|
| 332 |
+
caminho.parent.mkdir(parents=True, exist_ok=True)
|
| 333 |
+
caminho.write_text(self.gerar_xml(), encoding="utf-8")
|
| 334 |
+
return caminho
|
src/generators/dctf.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from datetime import date
|
| 6 |
+
from decimal import Decimal
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Optional
|
| 9 |
+
from xml.etree import ElementTree as ET
|
| 10 |
+
|
| 11 |
+
from src.fiscal.constants import COD_RECEITA_DARF
|
| 12 |
+
from src.fiscal.entities import Empresa
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _sub(pai: ET.Element, tag: str, texto: str = "") -> ET.Element:
|
| 16 |
+
el = ET.SubElement(pai, tag)
|
| 17 |
+
if texto:
|
| 18 |
+
el.text = str(texto)
|
| 19 |
+
return el
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _fmt_dec(v: Decimal) -> str:
|
| 23 |
+
return f"{v:.2f}"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class DebitoDCTF:
|
| 28 |
+
"""Representa um débito tributário na DCTF."""
|
| 29 |
+
codigo_receita: str
|
| 30 |
+
periodo_apuracao: str # YYYY-MM ou YYYY-TT (trimestre)
|
| 31 |
+
valor_debito: Decimal
|
| 32 |
+
valor_suspenso: Decimal = Decimal("0")
|
| 33 |
+
numero_processo: str = ""
|
| 34 |
+
tipo_suspensao: str = "" # "D"=Decisão Judicial, "A"=Administrativo
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclass
|
| 38 |
+
class PagamentoDCTF:
|
| 39 |
+
"""Pagamento (DARF) vinculado a um débito."""
|
| 40 |
+
numero_darf: str
|
| 41 |
+
data_pagamento: date
|
| 42 |
+
valor_principal: Decimal
|
| 43 |
+
valor_multa: Decimal = Decimal("0")
|
| 44 |
+
valor_juros: Decimal = Decimal("0")
|
| 45 |
+
|
| 46 |
+
@property
|
| 47 |
+
def valor_total(self) -> Decimal:
|
| 48 |
+
return self.valor_principal + self.valor_multa + self.valor_juros
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dataclass
|
| 52 |
+
class CompensacaoDCTF:
|
| 53 |
+
"""Compensação de crédito vinculada a um débito."""
|
| 54 |
+
numero_per_comp: str # Número do PER/COMP
|
| 55 |
+
data_transmissao: date
|
| 56 |
+
valor_compensado: Decimal
|
| 57 |
+
codigo_receita_credito: str
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@dataclass
|
| 61 |
+
class ItemDCTF:
|
| 62 |
+
"""Item de débito com seus pagamentos e compensações."""
|
| 63 |
+
debito: DebitoDCTF
|
| 64 |
+
pagamentos: list[PagamentoDCTF] = field(default_factory=list)
|
| 65 |
+
compensacoes: list[CompensacaoDCTF] = field(default_factory=list)
|
| 66 |
+
|
| 67 |
+
@property
|
| 68 |
+
def total_pago(self) -> Decimal:
|
| 69 |
+
return sum(p.valor_total for p in self.pagamentos)
|
| 70 |
+
|
| 71 |
+
@property
|
| 72 |
+
def total_compensado(self) -> Decimal:
|
| 73 |
+
return sum(c.valor_compensado for c in self.compensacoes)
|
| 74 |
+
|
| 75 |
+
@property
|
| 76 |
+
def saldo_a_pagar(self) -> Decimal:
|
| 77 |
+
return max(
|
| 78 |
+
Decimal("0"),
|
| 79 |
+
self.debito.valor_debito - self.debito.valor_suspenso
|
| 80 |
+
- self.total_pago - self.total_compensado,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
@dataclass
|
| 85 |
+
class ConfigDCTF:
|
| 86 |
+
finalidade: str = "0" # 0=Original, 1=Retificadora
|
| 87 |
+
situacao_especial: str = "" # Em branco=sem situação especial
|
| 88 |
+
declarante_pj_inativa: bool = False
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class GeradorDCTF:
|
| 92 |
+
"""
|
| 93 |
+
Gera o arquivo XML da DCTF para importação no PGD DCTF Web.
|
| 94 |
+
|
| 95 |
+
Periodicidade: mensal.
|
| 96 |
+
Prazo: 15º dia útil do 2º mês subsequente ao período de apuração.
|
| 97 |
+
Obrigação: pessoas jurídicas com débitos de tributos federais.
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
VERSAO = "1.0"
|
| 101 |
+
|
| 102 |
+
def __init__(
|
| 103 |
+
self,
|
| 104 |
+
empresa: Empresa,
|
| 105 |
+
periodo: str, # "YYYY-MM"
|
| 106 |
+
itens: Optional[list[ItemDCTF]] = None,
|
| 107 |
+
cfg: Optional[ConfigDCTF] = None,
|
| 108 |
+
):
|
| 109 |
+
self.empresa = empresa
|
| 110 |
+
self.periodo = periodo
|
| 111 |
+
self.itens = itens or []
|
| 112 |
+
self.cfg = cfg or ConfigDCTF()
|
| 113 |
+
|
| 114 |
+
def _resumo_por_codigo(self) -> dict[str, dict]:
|
| 115 |
+
resumo: dict[str, dict] = {}
|
| 116 |
+
for item in self.itens:
|
| 117 |
+
cod = item.debito.codigo_receita
|
| 118 |
+
if cod not in resumo:
|
| 119 |
+
resumo[cod] = {
|
| 120 |
+
"debito": Decimal("0"),
|
| 121 |
+
"suspenso": Decimal("0"),
|
| 122 |
+
"pago": Decimal("0"),
|
| 123 |
+
"compensado": Decimal("0"),
|
| 124 |
+
"saldo": Decimal("0"),
|
| 125 |
+
"nome": COD_RECEITA_DARF.get(cod, cod),
|
| 126 |
+
}
|
| 127 |
+
resumo[cod]["debito"] += item.debito.valor_debito
|
| 128 |
+
resumo[cod]["suspenso"] += item.debito.valor_suspenso
|
| 129 |
+
resumo[cod]["pago"] += item.total_pago
|
| 130 |
+
resumo[cod]["compensado"] += item.total_compensado
|
| 131 |
+
resumo[cod]["saldo"] += item.saldo_a_pagar
|
| 132 |
+
return resumo
|
| 133 |
+
|
| 134 |
+
def gerar_xml(self) -> str:
|
| 135 |
+
"""Gera o XML da DCTF para importação no PGD DCTF Web."""
|
| 136 |
+
root = ET.Element("DCTF")
|
| 137 |
+
root.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
| 138 |
+
root.set("versao", self.VERSAO)
|
| 139 |
+
|
| 140 |
+
ide = _sub(root, "ideDeclarante")
|
| 141 |
+
_sub(ide, "CNPJ", self.empresa.cnpj)
|
| 142 |
+
_sub(ide, "nome", self.empresa.razao_social[:100])
|
| 143 |
+
_sub(ide, "perApuracao", self.periodo)
|
| 144 |
+
_sub(ide, "dtInicio", f"{self.periodo}-01")
|
| 145 |
+
_sub(ide, "dtFim", f"{self.periodo}-{self._ultimo_dia()}")
|
| 146 |
+
_sub(ide, "situacaoEspecial", self.cfg.situacao_especial)
|
| 147 |
+
_sub(ide, "indDeclaranteInativo", "S" if self.cfg.declarante_pj_inativa else "N")
|
| 148 |
+
_sub(ide, "finalidade", self.cfg.finalidade)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
for item in self.itens:
|
| 152 |
+
deb_el = _sub(root, "debito")
|
| 153 |
+
_sub(deb_el, "codReceita", item.debito.codigo_receita)
|
| 154 |
+
_sub(deb_el, "perApuracao", item.debito.periodo_apuracao)
|
| 155 |
+
_sub(deb_el, "vlrDebito", _fmt_dec(item.debito.valor_debito))
|
| 156 |
+
|
| 157 |
+
if item.debito.valor_suspenso > 0:
|
| 158 |
+
susp = _sub(deb_el, "suspensao")
|
| 159 |
+
_sub(susp, "tipoSuspensao", item.debito.tipo_suspensao or "D")
|
| 160 |
+
_sub(susp, "nrProcesso", item.debito.numero_processo)
|
| 161 |
+
_sub(susp, "vlrSuspenso", _fmt_dec(item.debito.valor_suspenso))
|
| 162 |
+
|
| 163 |
+
for pag in item.pagamentos:
|
| 164 |
+
pag_el = _sub(deb_el, "pagamento")
|
| 165 |
+
_sub(pag_el, "nrDarf", pag.numero_darf)
|
| 166 |
+
_sub(pag_el, "dtPagamento", pag.data_pagamento.strftime("%Y-%m-%d"))
|
| 167 |
+
_sub(pag_el, "vlrPrincipal", _fmt_dec(pag.valor_principal))
|
| 168 |
+
_sub(pag_el, "vlrMulta", _fmt_dec(pag.valor_multa))
|
| 169 |
+
_sub(pag_el, "vlrJuros", _fmt_dec(pag.valor_juros))
|
| 170 |
+
_sub(pag_el, "vlrTotal", _fmt_dec(pag.valor_total))
|
| 171 |
+
|
| 172 |
+
for comp in item.compensacoes:
|
| 173 |
+
comp_el = _sub(deb_el, "compensacao")
|
| 174 |
+
_sub(comp_el, "nrPERCOMP", comp.numero_per_comp)
|
| 175 |
+
_sub(comp_el, "dtTransmissao", comp.data_transmissao.strftime("%Y-%m-%d"))
|
| 176 |
+
_sub(comp_el, "vlrCompensado", _fmt_dec(comp.valor_compensado))
|
| 177 |
+
_sub(comp_el, "codReceitaCredito", comp.codigo_receita_credito)
|
| 178 |
+
|
| 179 |
+
_sub(deb_el, "vlrSaldoPagar", _fmt_dec(item.saldo_a_pagar))
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
resumo = self._resumo_por_codigo()
|
| 183 |
+
total_el = _sub(root, "totalDebitos")
|
| 184 |
+
_sub(total_el, "vlrTotalDebitos", _fmt_dec(sum(r["debito"] for r in resumo.values())))
|
| 185 |
+
_sub(total_el, "vlrTotalSuspenso", _fmt_dec(sum(r["suspenso"] for r in resumo.values())))
|
| 186 |
+
_sub(total_el, "vlrTotalPago", _fmt_dec(sum(r["pago"] for r in resumo.values())))
|
| 187 |
+
_sub(total_el, "vlrTotalCompensado", _fmt_dec(sum(r["compensado"] for r in resumo.values())))
|
| 188 |
+
_sub(total_el, "vlrTotalSaldo", _fmt_dec(sum(r["saldo"] for r in resumo.values())))
|
| 189 |
+
|
| 190 |
+
ET.indent(root, space=" ")
|
| 191 |
+
return '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(root, encoding="unicode")
|
| 192 |
+
|
| 193 |
+
def _ultimo_dia(self) -> str:
|
| 194 |
+
ano, mes = map(int, self.periodo.split("-"))
|
| 195 |
+
if mes == 12:
|
| 196 |
+
return "31"
|
| 197 |
+
ultimo = (date(ano, mes + 1, 1) - date(ano, mes, 1)).days
|
| 198 |
+
return str(ultimo).zfill(2)
|
| 199 |
+
|
| 200 |
+
def salvar(self, diretorio: str | Path = ".") -> Path:
|
| 201 |
+
caminho = Path(diretorio) / f"DCTF_{self.empresa.cnpj}_{self.periodo}.xml"
|
| 202 |
+
caminho.parent.mkdir(parents=True, exist_ok=True)
|
| 203 |
+
caminho.write_text(self.gerar_xml(), encoding="utf-8")
|
| 204 |
+
return caminho
|
| 205 |
+
|
| 206 |
+
def relatorio_resumo(self) -> str:
|
| 207 |
+
resumo = self._resumo_por_codigo()
|
| 208 |
+
linhas = [
|
| 209 |
+
f"DCTF — {self.empresa.razao_social} — Período: {self.periodo}",
|
| 210 |
+
"=" * 70,
|
| 211 |
+
]
|
| 212 |
+
total_geral = Decimal("0")
|
| 213 |
+
for cod, dados in resumo.items():
|
| 214 |
+
linhas.append(
|
| 215 |
+
f"{cod} — {dados['nome']}"
|
| 216 |
+
)
|
| 217 |
+
linhas.append(f" Débito: R$ {dados['debito']:>12,.2f}")
|
| 218 |
+
if dados["suspenso"] > 0:
|
| 219 |
+
linhas.append(f" Suspenso: R$ {dados['suspenso']:>12,.2f}")
|
| 220 |
+
if dados["pago"] > 0:
|
| 221 |
+
linhas.append(f" Pago (DARF): R$ {dados['pago']:>12,.2f}")
|
| 222 |
+
if dados["compensado"] > 0:
|
| 223 |
+
linhas.append(f" Compensado: R$ {dados['compensado']:>12,.2f}")
|
| 224 |
+
linhas.append(f" Saldo: R$ {dados['saldo']:>12,.2f}")
|
| 225 |
+
total_geral += dados["saldo"]
|
| 226 |
+
|
| 227 |
+
linhas += [
|
| 228 |
+
"=" * 70,
|
| 229 |
+
f"TOTAL A PAGAR: R$ {total_geral:>12,.2f}",
|
| 230 |
+
]
|
| 231 |
+
return "\n".join(linhas)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def montar_dctf_do_periodo(
|
| 235 |
+
empresa: Empresa,
|
| 236 |
+
periodo: str,
|
| 237 |
+
valor_irpj: Decimal = Decimal("0"),
|
| 238 |
+
valor_csll: Decimal = Decimal("0"),
|
| 239 |
+
valor_pis: Decimal = Decimal("0"),
|
| 240 |
+
valor_cofins: Decimal = Decimal("0"),
|
| 241 |
+
valor_cpp: Decimal = Decimal("0"),
|
| 242 |
+
valor_irrf: Decimal = Decimal("0"),
|
| 243 |
+
darfs_pagos: Optional[list[PagamentoDCTF]] = None,
|
| 244 |
+
) -> GeradorDCTF:
|
| 245 |
+
"""
|
| 246 |
+
Função auxiliar: monta a DCTF automaticamente a partir dos valores apurados.
|
| 247 |
+
"""
|
| 248 |
+
darfs_pagos = darfs_pagos or []
|
| 249 |
+
itens: list[ItemDCTF] = []
|
| 250 |
+
|
| 251 |
+
pares = [
|
| 252 |
+
("6912", valor_irpj),
|
| 253 |
+
("2484", valor_csll),
|
| 254 |
+
("8109", valor_pis),
|
| 255 |
+
("2172", valor_cofins),
|
| 256 |
+
("2100", valor_cpp),
|
| 257 |
+
("1038", valor_irrf),
|
| 258 |
+
]
|
| 259 |
+
|
| 260 |
+
for cod, valor in pares:
|
| 261 |
+
if valor > 0:
|
| 262 |
+
itens.append(ItemDCTF(
|
| 263 |
+
debito=DebitoDCTF(
|
| 264 |
+
codigo_receita=cod,
|
| 265 |
+
periodo_apuracao=periodo,
|
| 266 |
+
valor_debito=valor,
|
| 267 |
+
),
|
| 268 |
+
pagamentos=[p for p in darfs_pagos],
|
| 269 |
+
))
|
| 270 |
+
|
| 271 |
+
return GeradorDCTF(empresa=empresa, periodo=periodo, itens=itens)
|
src/generators/defis.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from datetime import date
|
| 6 |
+
from decimal import Decimal
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from xml.etree import ElementTree as ET
|
| 9 |
+
|
| 10 |
+
from src.fiscal.entities import Empresa
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _sub(pai: ET.Element, tag: str, texto: str = "") -> ET.Element:
|
| 14 |
+
el = ET.SubElement(pai, tag)
|
| 15 |
+
if texto:
|
| 16 |
+
el.text = str(texto)
|
| 17 |
+
return el
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _fmt_dec(v: Decimal) -> str:
|
| 21 |
+
return f"{v:.2f}"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass
|
| 25 |
+
class ReceitaMensalDEFIS:
|
| 26 |
+
mes: int # 1-12
|
| 27 |
+
receita_bruta_total: Decimal
|
| 28 |
+
receita_bruta_exportacao: Decimal = field(default_factory=lambda: Decimal("0"))
|
| 29 |
+
receita_bruta_isenta: Decimal = field(default_factory=lambda: Decimal("0"))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@dataclass
|
| 33 |
+
class SocioDEFIS:
|
| 34 |
+
cpf_cnpj: str
|
| 35 |
+
nome: str
|
| 36 |
+
percentual_capital: Decimal # ex: Decimal("50.00") = 50%
|
| 37 |
+
tipo: str = "PF" # "PF" ou "PJ"
|
| 38 |
+
pais_domicilio: str = "105" # 105=Brasil (código BACEN)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class EmpregadoDEFIS:
|
| 43 |
+
competencia: str # "YYYY-MM"
|
| 44 |
+
quantidade: int
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class GeradorDEFIS:
|
| 48 |
+
"""
|
| 49 |
+
Gera o arquivo DEFIS para declaração anual do Simples Nacional.
|
| 50 |
+
|
| 51 |
+
Prazo: 31 de março do ano seguinte ao período de apuração.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
VERSAO = "1.0"
|
| 55 |
+
|
| 56 |
+
def __init__(
|
| 57 |
+
self,
|
| 58 |
+
empresa: Empresa,
|
| 59 |
+
ano_calendario: int,
|
| 60 |
+
receitas_mensais: list[ReceitaMensalDEFIS],
|
| 61 |
+
socios: list[SocioDEFIS],
|
| 62 |
+
empregados: list[EmpregadoDEFIS] | None = None,
|
| 63 |
+
retificadora: bool = False,
|
| 64 |
+
numero_recibo_retificada: str = "",
|
| 65 |
+
):
|
| 66 |
+
self.empresa = empresa
|
| 67 |
+
self.ano_calendario = ano_calendario
|
| 68 |
+
self.receitas_mensais = receitas_mensais
|
| 69 |
+
self.socios = socios
|
| 70 |
+
self.empregados = empregados or []
|
| 71 |
+
self.retificadora = retificadora
|
| 72 |
+
self.numero_recibo_retificada = numero_recibo_retificada
|
| 73 |
+
|
| 74 |
+
# ------------------------------------------------------------------
|
| 75 |
+
# Totais auxiliares
|
| 76 |
+
# ------------------------------------------------------------------
|
| 77 |
+
|
| 78 |
+
@property
|
| 79 |
+
def _total_receita(self) -> Decimal:
|
| 80 |
+
return sum(r.receita_bruta_total for r in self.receitas_mensais)
|
| 81 |
+
|
| 82 |
+
@property
|
| 83 |
+
def _total_exportacao(self) -> Decimal:
|
| 84 |
+
return sum(r.receita_bruta_exportacao for r in self.receitas_mensais)
|
| 85 |
+
|
| 86 |
+
@property
|
| 87 |
+
def _total_isenta(self) -> Decimal:
|
| 88 |
+
return sum(r.receita_bruta_isenta for r in self.receitas_mensais)
|
| 89 |
+
|
| 90 |
+
# ------------------------------------------------------------------
|
| 91 |
+
# Geração do XML
|
| 92 |
+
# ------------------------------------------------------------------
|
| 93 |
+
|
| 94 |
+
def gerar_xml(self) -> str:
|
| 95 |
+
"""Gera o XML DEFIS para transmissão."""
|
| 96 |
+
root = ET.Element("DEFIS")
|
| 97 |
+
root.set("versao", self.VERSAO)
|
| 98 |
+
|
| 99 |
+
# Identificação do declarante
|
| 100 |
+
ide = _sub(root, "ideDeclarante")
|
| 101 |
+
_sub(ide, "CNPJ", self.empresa.cnpj)
|
| 102 |
+
_sub(ide, "anoCalendario", str(self.ano_calendario))
|
| 103 |
+
_sub(ide, "indRetificadora", "S" if self.retificadora else "N")
|
| 104 |
+
nrec = _sub(ide, "nrecRetificadora")
|
| 105 |
+
if self.retificadora and self.numero_recibo_retificada:
|
| 106 |
+
nrec.text = self.numero_recibo_retificada
|
| 107 |
+
_sub(ide, "dtInicio", f"{self.ano_calendario}-01-01")
|
| 108 |
+
_sub(ide, "dtFim", f"{self.ano_calendario}-12-31")
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
inf = _sub(root, "infEmpresa")
|
| 112 |
+
_sub(inf, "xNome", self.empresa.razao_social[:150])
|
| 113 |
+
_sub(inf, "xFantasia", self.empresa.nome_fantasia[:60] if self.empresa.nome_fantasia else "")
|
| 114 |
+
_sub(inf, "fone", self.empresa.contato.telefone)
|
| 115 |
+
_sub(inf, "email", self.empresa.contato.email)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
rec_bruta = _sub(root, "receitaBruta")
|
| 119 |
+
for rm in sorted(self.receitas_mensais, key=lambda r: r.mes):
|
| 120 |
+
mes_el = _sub(rec_bruta, "mes")
|
| 121 |
+
_sub(mes_el, "nMes", str(rm.mes))
|
| 122 |
+
_sub(mes_el, "vlrRecBruta", _fmt_dec(rm.receita_bruta_total))
|
| 123 |
+
_sub(mes_el, "vlrRecBrutaExportacao", _fmt_dec(rm.receita_bruta_exportacao))
|
| 124 |
+
_sub(mes_el, "vlrRecBrutaIsenta", _fmt_dec(rm.receita_bruta_isenta))
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
total = _sub(root, "totalAnual")
|
| 128 |
+
_sub(total, "vlrRecBrutaTotal", _fmt_dec(self._total_receita))
|
| 129 |
+
_sub(total, "vlrRecBrutaExportTotal", _fmt_dec(self._total_exportacao))
|
| 130 |
+
_sub(total, "vlrRecBrutaIsentaTotal", _fmt_dec(self._total_isenta))
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
qs = _sub(root, "quadroSocietario")
|
| 134 |
+
for socio in self.socios:
|
| 135 |
+
s_el = _sub(qs, "socio")
|
| 136 |
+
_sub(s_el, "CPF_CNPJ", socio.cpf_cnpj)
|
| 137 |
+
_sub(s_el, "xNome", socio.nome)
|
| 138 |
+
_sub(s_el, "pctCapital", _fmt_dec(socio.percentual_capital))
|
| 139 |
+
_sub(s_el, "tpSocio", socio.tipo)
|
| 140 |
+
_sub(s_el, "paisDomicilio", socio.pais_domicilio)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
emp_el = _sub(root, "empregados")
|
| 144 |
+
for emp in self.empregados:
|
| 145 |
+
comp_el = _sub(emp_el, "competencia")
|
| 146 |
+
_sub(comp_el, "perApur", emp.competencia)
|
| 147 |
+
_sub(comp_el, "qtdEmpregados", str(emp.quantidade))
|
| 148 |
+
|
| 149 |
+
ET.indent(root, space=" ")
|
| 150 |
+
return '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(root, encoding="unicode")
|
| 151 |
+
|
| 152 |
+
# ------------------------------------------------------------------
|
| 153 |
+
# Persistência
|
| 154 |
+
# ------------------------------------------------------------------
|
| 155 |
+
|
| 156 |
+
def salvar(self, diretorio: str | Path = ".") -> Path:
|
| 157 |
+
"""Salva como DEFIS_CNPJ_ANO.xml"""
|
| 158 |
+
caminho = Path(diretorio) / f"DEFIS_{self.empresa.cnpj}_{self.ano_calendario}.xml"
|
| 159 |
+
caminho.parent.mkdir(parents=True, exist_ok=True)
|
| 160 |
+
caminho.write_text(self.gerar_xml(), encoding="utf-8")
|
| 161 |
+
return caminho
|
| 162 |
+
|
| 163 |
+
# ------------------------------------------------------------------
|
| 164 |
+
# Relatório
|
| 165 |
+
# ------------------------------------------------------------------
|
| 166 |
+
|
| 167 |
+
def relatorio_resumo(self) -> str:
|
| 168 |
+
"""Resumo anual: receita total, maior mês, menores meses."""
|
| 169 |
+
linhas = [
|
| 170 |
+
f"DEFIS — {self.empresa.razao_social} — Ano: {self.ano_calendario}",
|
| 171 |
+
"=" * 70,
|
| 172 |
+
]
|
| 173 |
+
|
| 174 |
+
if not self.receitas_mensais:
|
| 175 |
+
linhas.append("Sem receitas informadas.")
|
| 176 |
+
return "\n".join(linhas)
|
| 177 |
+
|
| 178 |
+
nomes_mes = [
|
| 179 |
+
"", "Jan", "Fev", "Mar", "Abr", "Mai", "Jun",
|
| 180 |
+
"Jul", "Ago", "Set", "Out", "Nov", "Dez",
|
| 181 |
+
]
|
| 182 |
+
|
| 183 |
+
for rm in sorted(self.receitas_mensais, key=lambda r: r.mes):
|
| 184 |
+
nome = nomes_mes[rm.mes] if 1 <= rm.mes <= 12 else str(rm.mes)
|
| 185 |
+
linhas.append(f" {nome}: R$ {rm.receita_bruta_total:>12,.2f}")
|
| 186 |
+
|
| 187 |
+
maior = max(self.receitas_mensais, key=lambda r: r.receita_bruta_total)
|
| 188 |
+
menor = min(self.receitas_mensais, key=lambda r: r.receita_bruta_total)
|
| 189 |
+
|
| 190 |
+
linhas += [
|
| 191 |
+
"=" * 70,
|
| 192 |
+
f"Receita Total Anual: R$ {self._total_receita:>12,.2f}",
|
| 193 |
+
f"Receita Exportação: R$ {self._total_exportacao:>12,.2f}",
|
| 194 |
+
f"Receita Isenta: R$ {self._total_isenta:>12,.2f}",
|
| 195 |
+
f"Maior mês: {nomes_mes[maior.mes]} — R$ {maior.receita_bruta_total:,.2f}",
|
| 196 |
+
f"Menor mês: {nomes_mes[menor.mes]} — R$ {menor.receita_bruta_total:,.2f}",
|
| 197 |
+
f"Prazo de entrega: 31/03/{self.ano_calendario + 1}",
|
| 198 |
+
f"Retificadora: {'Sim' if self.retificadora else 'Não'}",
|
| 199 |
+
]
|
| 200 |
+
return "\n".join(linhas)
|
src/generators/destda.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass, field
|
| 5 |
+
from datetime import date
|
| 6 |
+
from decimal import Decimal
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from xml.etree import ElementTree as ET
|
| 9 |
+
|
| 10 |
+
from src.fiscal.entities import Empresa
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
COD_UF = {
|
| 14 |
+
"AC": "12", "AL": "27", "AM": "13", "AP": "16", "BA": "29",
|
| 15 |
+
"CE": "23", "DF": "53", "ES": "32", "GO": "52", "MA": "21",
|
| 16 |
+
"MG": "31", "MS": "50", "MT": "51", "PA": "15", "PB": "25",
|
| 17 |
+
"PE": "26", "PI": "22", "PR": "41", "RJ": "33", "RN": "24",
|
| 18 |
+
"RO": "11", "RR": "14", "RS": "43", "SC": "42", "SE": "28",
|
| 19 |
+
"SP": "35", "TO": "17",
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _sub(pai: ET.Element, tag: str, texto: str = "") -> ET.Element:
|
| 24 |
+
el = ET.SubElement(pai, tag)
|
| 25 |
+
if texto:
|
| 26 |
+
el.text = str(texto)
|
| 27 |
+
return el
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _fmt(v: Decimal) -> str:
|
| 31 |
+
return f"{v:.2f}"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclass
|
| 35 |
+
class OperacaoSTDeSTDA:
|
| 36 |
+
"""Operação de ICMS-ST, DIFAL ou antecipação do Simples Nacional."""
|
| 37 |
+
uf_origem: str
|
| 38 |
+
uf_destino: str
|
| 39 |
+
tipo: str # "ST" | "DIFAL" | "ANTEC"
|
| 40 |
+
base_calculo: Decimal
|
| 41 |
+
aliquota: Decimal # percentual, ex: Decimal("18")
|
| 42 |
+
valor_imposto: Decimal
|
| 43 |
+
valor_pago: Decimal = Decimal("0")
|
| 44 |
+
|
| 45 |
+
def __post_init__(self):
|
| 46 |
+
if self.valor_pago == Decimal("0"):
|
| 47 |
+
self.valor_pago = self.valor_imposto
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class GeradorDeSTDA:
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
VERSAO = "1.1"
|
| 54 |
+
|
| 55 |
+
def __init__(
|
| 56 |
+
self,
|
| 57 |
+
empresa: Empresa,
|
| 58 |
+
periodo: str, # "YYYY-MM"
|
| 59 |
+
operacoes: list[OperacaoSTDeSTDA],
|
| 60 |
+
uf_declarante: str = "SP",
|
| 61 |
+
retificadora: bool = False,
|
| 62 |
+
):
|
| 63 |
+
self.empresa = empresa
|
| 64 |
+
self.periodo = periodo
|
| 65 |
+
self.operacoes = operacoes
|
| 66 |
+
self.uf_declarante = uf_declarante.upper()
|
| 67 |
+
self.retificadora = retificadora
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def data_vencimento(self) -> date:
|
| 71 |
+
ano, mes = map(int, self.periodo.split("-"))
|
| 72 |
+
if mes == 12:
|
| 73 |
+
return date(ano + 1, 1, 20)
|
| 74 |
+
return date(ano, mes + 1, 20)
|
| 75 |
+
|
| 76 |
+
@property
|
| 77 |
+
def _ops_st(self) -> list[OperacaoSTDeSTDA]:
|
| 78 |
+
return [o for o in self.operacoes if o.tipo == "ST"]
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def _ops_difal(self) -> list[OperacaoSTDeSTDA]:
|
| 82 |
+
return [o for o in self.operacoes if o.tipo == "DIFAL"]
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def _ops_antec(self) -> list[OperacaoSTDeSTDA]:
|
| 86 |
+
return [o for o in self.operacoes if o.tipo == "ANTEC"]
|
| 87 |
+
|
| 88 |
+
def _total(self, ops: list[OperacaoSTDeSTDA], campo: str) -> Decimal:
|
| 89 |
+
return sum(getattr(o, campo) for o in ops)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def gerar_xml(self) -> str:
|
| 93 |
+
ano, mes = self.periodo.split("-")
|
| 94 |
+
ultimo_dia = self._ultimo_dia(int(ano), int(mes))
|
| 95 |
+
|
| 96 |
+
root = ET.Element("DeSTDA")
|
| 97 |
+
root.set("versao", self.VERSAO)
|
| 98 |
+
root.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
inf = _sub(root, "infDeSTDA")
|
| 102 |
+
_sub(inf, "CNPJ", self.empresa.cnpj)
|
| 103 |
+
_sub(inf, "cUF", COD_UF.get(self.uf_declarante, "35"))
|
| 104 |
+
_sub(inf, "dtInicio", f"{ano}-{mes}-01")
|
| 105 |
+
_sub(inf, "dtFim", f"{ano}-{mes}-{ultimo_dia}")
|
| 106 |
+
_sub(inf, "indRetificadora", "S" if self.retificadora else "N")
|
| 107 |
+
_sub(inf, "dtEnvio", date.today().strftime("%Y-%m-%d"))
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
self._bloco_st(root)
|
| 111 |
+
self._bloco_difal(root)
|
| 112 |
+
self._bloco_antec(root)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
tot = _sub(root, "totais")
|
| 116 |
+
vst = self._total(self._ops_st, "valor_imposto")
|
| 117 |
+
vdifal = self._total(self._ops_difal, "valor_imposto")
|
| 118 |
+
vantec = self._total(self._ops_antec, "valor_imposto")
|
| 119 |
+
vpago = self._total(self.operacoes, "valor_pago")
|
| 120 |
+
_sub(tot, "vST_Total", _fmt(vst))
|
| 121 |
+
_sub(tot, "vDIFAL_Total", _fmt(vdifal))
|
| 122 |
+
_sub(tot, "vANTEC_Total", _fmt(vantec))
|
| 123 |
+
_sub(tot, "vTotal", _fmt(vst + vdifal + vantec))
|
| 124 |
+
_sub(tot, "vPago_Total", _fmt(vpago))
|
| 125 |
+
|
| 126 |
+
ET.indent(root, space=" ")
|
| 127 |
+
return '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(root, encoding="unicode")
|
| 128 |
+
|
| 129 |
+
def _bloco_st(self, root: ET.Element) -> None:
|
| 130 |
+
for op in self._ops_st:
|
| 131 |
+
el = _sub(root, "ST")
|
| 132 |
+
_sub(el, "UF_ST", op.uf_destino)
|
| 133 |
+
_sub(el, "vBC_ST", _fmt(op.base_calculo))
|
| 134 |
+
_sub(el, "pICMS_ST", _fmt(op.aliquota))
|
| 135 |
+
_sub(el, "vICMS_ST", _fmt(op.valor_imposto))
|
| 136 |
+
_sub(el, "vICMS_ST_Pago", _fmt(op.valor_pago))
|
| 137 |
+
|
| 138 |
+
def _bloco_difal(self, root: ET.Element) -> None:
|
| 139 |
+
for op in self._ops_difal:
|
| 140 |
+
el = _sub(root, "DIFAL")
|
| 141 |
+
_sub(el, "UF_DIFAL", op.uf_destino)
|
| 142 |
+
_sub(el, "vBC_DIFAL", _fmt(op.base_calculo))
|
| 143 |
+
_sub(el, "pICMS_DIFAL", _fmt(op.aliquota))
|
| 144 |
+
_sub(el, "vICMS_DIFAL", _fmt(op.valor_imposto))
|
| 145 |
+
_sub(el, "vICMS_DIFAL_Pago", _fmt(op.valor_pago))
|
| 146 |
+
|
| 147 |
+
def _bloco_antec(self, root: ET.Element) -> None:
|
| 148 |
+
for op in self._ops_antec:
|
| 149 |
+
el = _sub(root, "ANTEC")
|
| 150 |
+
_sub(el, "UF_ANTEC", op.uf_origem)
|
| 151 |
+
_sub(el, "vBC_ANTEC", _fmt(op.base_calculo))
|
| 152 |
+
_sub(el, "pICMS_ANTEC", _fmt(op.aliquota))
|
| 153 |
+
_sub(el, "vICMS_ANTEC", _fmt(op.valor_imposto))
|
| 154 |
+
_sub(el, "vICMS_ANTEC_Pago", _fmt(op.valor_pago))
|
| 155 |
+
|
| 156 |
+
def salvar(self, diretorio: str | Path = ".") -> Path:
|
| 157 |
+
caminho = Path(diretorio) / f"DeSTDA_{self.empresa.cnpj}_{self.periodo}.xml"
|
| 158 |
+
caminho.parent.mkdir(parents=True, exist_ok=True)
|
| 159 |
+
caminho.write_text(self.gerar_xml(), encoding="utf-8")
|
| 160 |
+
return caminho
|
| 161 |
+
|
| 162 |
+
def relatorio_resumo(self) -> str:
|
| 163 |
+
vst = self._total(self._ops_st, "valor_imposto")
|
| 164 |
+
vdifal = self._total(self._ops_difal, "valor_imposto")
|
| 165 |
+
vantec = self._total(self._ops_antec, "valor_imposto")
|
| 166 |
+
total = vst + vdifal + vantec
|
| 167 |
+
venc = self.data_vencimento()
|
| 168 |
+
linhas = [
|
| 169 |
+
f"DeSTDA — {self.empresa.razao_social} — Período: {self.periodo}",
|
| 170 |
+
"=" * 60,
|
| 171 |
+
f"ICMS-ST: R$ {vst:>12,.2f} ({len(self._ops_st)} operação/ões)",
|
| 172 |
+
f"DIFAL: R$ {vdifal:>12,.2f} ({len(self._ops_difal)} operação/ões)",
|
| 173 |
+
f"Antecipação: R$ {vantec:>12,.2f} ({len(self._ops_antec)} operação/ões)",
|
| 174 |
+
"=" * 60,
|
| 175 |
+
f"TOTAL A PAGAR: R$ {total:>12,.2f}",
|
| 176 |
+
f"Vencimento: {venc.strftime('%d/%m/%Y')}",
|
| 177 |
+
]
|
| 178 |
+
return "\n".join(linhas)
|
| 179 |
+
|
| 180 |
+
@staticmethod
|
| 181 |
+
def _ultimo_dia(ano: int, mes: int) -> str:
|
| 182 |
+
if mes == 12:
|
| 183 |
+
return "31"
|
| 184 |
+
from datetime import date as d
|
| 185 |
+
dias = (d(ano, mes + 1, 1) - d(ano, mes, 1)).days
|
| 186 |
+
return str(dias).zfill(2)
|
src/generators/dirf.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import re
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
from datetime import date
|
| 7 |
+
from decimal import Decimal
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Optional
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# ---------------------------------------------------------------------------
|
| 13 |
+
# Dataclasses
|
| 14 |
+
# ---------------------------------------------------------------------------
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class BeneficiarioDIRF:
|
| 18 |
+
"""Beneficiário de rendimentos sujeitos à retenção na fonte."""
|
| 19 |
+
cpf_cnpj: str
|
| 20 |
+
nome: str
|
| 21 |
+
tipo: str # "PF" ou "PJ"
|
| 22 |
+
cod_receita: str # ex: "0561" (trabalho assalariado), "1708" (serviços PJ)
|
| 23 |
+
rendimentos_por_mes: dict[int, Decimal] = field(default_factory=dict) # {mes(1-12): valor}
|
| 24 |
+
ir_retido_por_mes: dict[int, Decimal] = field(default_factory=dict) # {mes(1-12): valor}
|
| 25 |
+
|
| 26 |
+
data_admissao: Optional[date] = None # para assalariados
|
| 27 |
+
data_demissao: Optional[date] = None
|
| 28 |
+
natureza: str = "A" # "A"=assalariado, "B"=autônomo, "C"=outros
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class ResponsavelDIRF:
|
| 33 |
+
cpf: str
|
| 34 |
+
nome: str
|
| 35 |
+
cargo: str
|
| 36 |
+
ddd: str
|
| 37 |
+
telefone: str
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# ---------------------------------------------------------------------------
|
| 41 |
+
# Gerador
|
| 42 |
+
# ---------------------------------------------------------------------------
|
| 43 |
+
|
| 44 |
+
class GeradorDIRF:
|
| 45 |
+
"""
|
| 46 |
+
Gera o arquivo DIRF no formato texto para importação no PGD DIRF.
|
| 47 |
+
|
| 48 |
+
Periodicidade: anual.
|
| 49 |
+
Prazo: último dia útil de fevereiro do ano seguinte.
|
| 50 |
+
Obrigação: toda PJ que pagou rendimentos sujeitos ao IRRF.
|
| 51 |
+
"""
|
| 52 |
+
|
| 53 |
+
def __init__(
|
| 54 |
+
self,
|
| 55 |
+
empresa, # Empresa (src.fiscal.entities.Empresa)
|
| 56 |
+
ano_calendario: int,
|
| 57 |
+
beneficiarios: list[BeneficiarioDIRF],
|
| 58 |
+
responsavel: ResponsavelDIRF,
|
| 59 |
+
retificadora: bool = False,
|
| 60 |
+
numero_recibo_retificada: str = "",
|
| 61 |
+
):
|
| 62 |
+
self.empresa = empresa
|
| 63 |
+
self.ano_calendario = ano_calendario
|
| 64 |
+
self.beneficiarios = beneficiarios
|
| 65 |
+
self.responsavel = responsavel
|
| 66 |
+
self.retificadora = retificadora
|
| 67 |
+
self.numero_recibo_retificada = numero_recibo_retificada
|
| 68 |
+
|
| 69 |
+
# ------------------------------------------------------------------
|
| 70 |
+
# Helpers
|
| 71 |
+
# ------------------------------------------------------------------
|
| 72 |
+
|
| 73 |
+
@staticmethod
|
| 74 |
+
def _apenas_digitos(s: str) -> str:
|
| 75 |
+
return re.sub(r"\D", "", s)
|
| 76 |
+
|
| 77 |
+
def _linha_cabecalho(self) -> str:
|
| 78 |
+
"""Linha DIRF — cabeçalho da declaração."""
|
| 79 |
+
ano_exercicio = self.ano_calendario + 1
|
| 80 |
+
ret = "S" if self.retificadora else "N"
|
| 81 |
+
partes = ["DIRF", str(ano_exercicio), ret]
|
| 82 |
+
if self.retificadora and self.numero_recibo_retificada:
|
| 83 |
+
partes.append(self.numero_recibo_retificada)
|
| 84 |
+
return " ".join(partes)
|
| 85 |
+
|
| 86 |
+
def _linha_respo(self) -> str:
|
| 87 |
+
"""Linha RESPO — identificação do responsável pela declaração."""
|
| 88 |
+
r = self.responsavel
|
| 89 |
+
cpf = self._apenas_digitos(r.cpf).zfill(11)
|
| 90 |
+
ddd = self._apenas_digitos(r.ddd)
|
| 91 |
+
tel = self._apenas_digitos(r.telefone)
|
| 92 |
+
return f"RESPO {cpf} {r.nome} {r.cargo} {ddd}{tel}"
|
| 93 |
+
|
| 94 |
+
def _linha_decpj(self) -> str:
|
| 95 |
+
"""Linha DECPJ — identificação da empresa declarante."""
|
| 96 |
+
emp = self.empresa
|
| 97 |
+
cnpj = self._apenas_digitos(emp.cnpj).zfill(14)
|
| 98 |
+
return f"DECPJ {cnpj} {emp.razao_social} 2062 N"
|
| 99 |
+
|
| 100 |
+
def _blocos_beneficiarios(self) -> list[str]:
|
| 101 |
+
"""Gera os blocos de beneficiários agrupados por código de receita."""
|
| 102 |
+
linhas: list[str] = []
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
por_receita: dict[str, list[BeneficiarioDIRF]] = {}
|
| 106 |
+
for ben in self.beneficiarios:
|
| 107 |
+
por_receita.setdefault(ben.cod_receita, []).append(ben)
|
| 108 |
+
|
| 109 |
+
for cod_receita, grupo in por_receita.items():
|
| 110 |
+
linhas.append(f"IDREC {cod_receita}")
|
| 111 |
+
for ben in grupo:
|
| 112 |
+
doc = self._apenas_digitos(ben.cpf_cnpj)
|
| 113 |
+
if ben.tipo == "PF":
|
| 114 |
+
doc = doc.zfill(11)
|
| 115 |
+
linhas.append(f"BPFDEC {doc} {ben.nome} {cod_receita}")
|
| 116 |
+
else:
|
| 117 |
+
doc = doc.zfill(14)
|
| 118 |
+
linhas.append(f"BPJDEC {doc} {ben.nome} {cod_receita}")
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
meses = sorted(
|
| 122 |
+
set(ben.rendimentos_por_mes.keys()) | set(ben.ir_retido_por_mes.keys())
|
| 123 |
+
)
|
| 124 |
+
for mes in meses:
|
| 125 |
+
rend = ben.rendimentos_por_mes.get(mes, Decimal("0"))
|
| 126 |
+
ir = ben.ir_retido_por_mes.get(mes, Decimal("0"))
|
| 127 |
+
linhas.append(
|
| 128 |
+
f"{self.ano_calendario} {mes:02d} {rend:.2f} {ir:.2f}"
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
return linhas
|
| 132 |
+
|
| 133 |
+
# ------------------------------------------------------------------
|
| 134 |
+
# Interface pública
|
| 135 |
+
# ------------------------------------------------------------------
|
| 136 |
+
|
| 137 |
+
def gerar_txt(self) -> str:
|
| 138 |
+
"""Gera o arquivo texto no layout PGD DIRF."""
|
| 139 |
+
linhas: list[str] = []
|
| 140 |
+
linhas.append(self._linha_cabecalho())
|
| 141 |
+
linhas.append(self._linha_respo())
|
| 142 |
+
linhas.append(self._linha_decpj())
|
| 143 |
+
linhas.extend(self._blocos_beneficiarios())
|
| 144 |
+
linhas.append("FIMDIRF")
|
| 145 |
+
return "\n".join(linhas) + "\n"
|
| 146 |
+
|
| 147 |
+
def salvar(self, diretorio: str | Path = ".") -> Path:
|
| 148 |
+
"""Salva o arquivo como DIRF_CNPJ_ANO.txt."""
|
| 149 |
+
cnpj = self._apenas_digitos(self.empresa.cnpj).zfill(14)
|
| 150 |
+
caminho = Path(diretorio) / f"DIRF_{cnpj}_{self.ano_calendario}.txt"
|
| 151 |
+
caminho.parent.mkdir(parents=True, exist_ok=True)
|
| 152 |
+
caminho.write_text(self.gerar_txt(), encoding="utf-8")
|
| 153 |
+
return caminho
|
| 154 |
+
|
| 155 |
+
def relatorio_resumo(self) -> str:
|
| 156 |
+
"""Retorna totais de rendimentos e IR retido agrupados por código de receita."""
|
| 157 |
+
totais: dict[str, dict[str, Decimal]] = {}
|
| 158 |
+
for ben in self.beneficiarios:
|
| 159 |
+
cod = ben.cod_receita
|
| 160 |
+
if cod not in totais:
|
| 161 |
+
totais[cod] = {"rendimentos": Decimal("0"), "ir_retido": Decimal("0"), "beneficiarios": 0}
|
| 162 |
+
totais[cod]["rendimentos"] += sum(ben.rendimentos_por_mes.values())
|
| 163 |
+
totais[cod]["ir_retido"] += sum(ben.ir_retido_por_mes.values())
|
| 164 |
+
totais[cod]["beneficiarios"] += 1 # type: ignore[operator]
|
| 165 |
+
|
| 166 |
+
emp = self.empresa
|
| 167 |
+
linhas = [
|
| 168 |
+
f"DIRF {self.ano_calendario} — {emp.razao_social} — CNPJ: {emp.cnpj}",
|
| 169 |
+
"=" * 70,
|
| 170 |
+
]
|
| 171 |
+
total_rend = Decimal("0")
|
| 172 |
+
total_ir = Decimal("0")
|
| 173 |
+
for cod, dados in sorted(totais.items()):
|
| 174 |
+
linhas.append(f"Cód. Receita: {cod}")
|
| 175 |
+
linhas.append(f" Beneficiários: {dados['beneficiarios']:>6}")
|
| 176 |
+
linhas.append(f" Rendimentos: R$ {dados['rendimentos']:>12,.2f}")
|
| 177 |
+
linhas.append(f" IR Retido: R$ {dados['ir_retido']:>12,.2f}")
|
| 178 |
+
total_rend += dados["rendimentos"]
|
| 179 |
+
total_ir += dados["ir_retido"]
|
| 180 |
+
|
| 181 |
+
linhas += [
|
| 182 |
+
"=" * 70,
|
| 183 |
+
f"TOTAL RENDIMENTOS: R$ {total_rend:>12,.2f}",
|
| 184 |
+
f"TOTAL IR RETIDO: R$ {total_ir:>12,.2f}",
|
| 185 |
+
]
|
| 186 |
+
return "\n".join(linhas)
|
src/generators/ecd.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from datetime import date
|
| 6 |
+
from decimal import Decimal
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
from src.fiscal.entities import Empresa, LancamentoContabil, PeriodoApuracao
|
| 11 |
+
from src.generators.sped_writer import ArquivoSPED, criar_registro
|
| 12 |
+
|
| 13 |
+
VERSAO_LEIAUTE = "010"
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class PlanoContas:
|
| 18 |
+
codigo: str
|
| 19 |
+
descricao: str
|
| 20 |
+
nivel: int
|
| 21 |
+
tipo: str # A=Analítica, S=Sintética
|
| 22 |
+
natureza: str # D=Devedora, C=Credora
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class ConfigECD:
|
| 27 |
+
versao: str = VERSAO_LEIAUTE
|
| 28 |
+
finalidade: str = "G" # G=Livro Diário, R=Razão Auxiliar
|
| 29 |
+
hash_anterior: str = ""
|
| 30 |
+
tipo_escrituracao: str = "G" # G=Geral
|
| 31 |
+
indicador_sit_especial: str = ""
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class GeradorECD:
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def __init__(
|
| 38 |
+
self,
|
| 39 |
+
periodo: PeriodoApuracao,
|
| 40 |
+
plano_contas: Optional[list[PlanoContas]] = None,
|
| 41 |
+
cfg: Optional[ConfigECD] = None,
|
| 42 |
+
contador: Optional[object] = None,
|
| 43 |
+
):
|
| 44 |
+
self.periodo = periodo
|
| 45 |
+
self.plano_contas = plano_contas or self._plano_contas_padrao()
|
| 46 |
+
self.cfg = cfg or ConfigECD()
|
| 47 |
+
self.contador = contador
|
| 48 |
+
self._arquivo = ArquivoSPED(
|
| 49 |
+
f"ECD_{periodo.empresa.cnpj}_{periodo.data_inicio.year}.txt"
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
def _plano_contas_padrao(self) -> list[PlanoContas]:
|
| 53 |
+
return [
|
| 54 |
+
PlanoContas("1", "ATIVO", 1, "S", "D"),
|
| 55 |
+
PlanoContas("1.1", "ATIVO CIRCULANTE", 2, "S", "D"),
|
| 56 |
+
PlanoContas("1.1.1", "CAIXA E EQUIVALENTES", 3, "S", "D"),
|
| 57 |
+
PlanoContas("1.1.1.01", "CAIXA", 4, "A", "D"),
|
| 58 |
+
PlanoContas("1.1.1.02", "BANCOS CONTA MOVIMENTO", 4, "A", "D"),
|
| 59 |
+
PlanoContas("1.1.2", "CONTAS A RECEBER", 3, "S", "D"),
|
| 60 |
+
PlanoContas("1.1.2.01", "CLIENTES", 4, "A", "D"),
|
| 61 |
+
PlanoContas("1.1.3", "ESTOQUES", 3, "S", "D"),
|
| 62 |
+
PlanoContas("1.1.3.01", "MERCADORIAS", 4, "A", "D"),
|
| 63 |
+
PlanoContas("1.2", "ATIVO NÃO CIRCULANTE", 2, "S", "D"),
|
| 64 |
+
PlanoContas("1.2.1", "IMOBILIZADO", 3, "S", "D"),
|
| 65 |
+
PlanoContas("1.2.1.01", "MÓVEIS E UTENSÍLIOS", 4, "A", "D"),
|
| 66 |
+
PlanoContas("1.2.1.02", "EQUIPAMENTOS", 4, "A", "D"),
|
| 67 |
+
PlanoContas("2", "PASSIVO", 1, "S", "C"),
|
| 68 |
+
PlanoContas("2.1", "PASSIVO CIRCULANTE", 2, "S", "C"),
|
| 69 |
+
PlanoContas("2.1.1", "FORNECEDORES", 3, "S", "C"),
|
| 70 |
+
PlanoContas("2.1.1.01", "FORNECEDORES NACIONAIS", 4, "A", "C"),
|
| 71 |
+
PlanoContas("2.1.2", "OBRIGAÇÕES FISCAIS", 3, "S", "C"),
|
| 72 |
+
PlanoContas("2.1.2.01", "ICMS A RECOLHER", 4, "A", "C"),
|
| 73 |
+
PlanoContas("2.1.2.02", "PIS A RECOLHER", 4, "A", "C"),
|
| 74 |
+
PlanoContas("2.1.2.03", "COFINS A RECOLHER", 4, "A", "C"),
|
| 75 |
+
PlanoContas("2.1.2.04", "IRPJ A RECOLHER", 4, "A", "C"),
|
| 76 |
+
PlanoContas("2.1.2.05", "CSLL A RECOLHER", 4, "A", "C"),
|
| 77 |
+
PlanoContas("2.1.3", "OBRIGAÇÕES TRABALHISTAS", 3, "S", "C"),
|
| 78 |
+
PlanoContas("2.1.3.01", "SALÁRIOS A PAGAR", 4, "A", "C"),
|
| 79 |
+
PlanoContas("2.2", "PASSIVO NÃO CIRCULANTE", 2, "S", "C"),
|
| 80 |
+
PlanoContas("3", "PATRIMÔNIO LÍQUIDO", 1, "S", "C"),
|
| 81 |
+
PlanoContas("3.1", "CAPITAL SOCIAL", 2, "S", "C"),
|
| 82 |
+
PlanoContas("3.1.01", "CAPITAL SUBSCRITO E INTEGRALIZADO", 3, "A", "C"),
|
| 83 |
+
PlanoContas("3.2", "RESERVAS E LUCROS", 2, "S", "C"),
|
| 84 |
+
PlanoContas("3.2.01", "LUCROS ACUMULADOS", 3, "A", "C"),
|
| 85 |
+
PlanoContas("4", "RECEITAS", 1, "S", "C"),
|
| 86 |
+
PlanoContas("4.1", "RECEITA OPERACIONAL BRUTA", 2, "S", "C"),
|
| 87 |
+
PlanoContas("4.1.01", "VENDAS DE MERCADORIAS", 3, "A", "C"),
|
| 88 |
+
PlanoContas("4.1.02", "PRESTAÇÃO DE SERVIÇOS", 3, "A", "C"),
|
| 89 |
+
PlanoContas("4.2", "DEDUÇÕES DA RECEITA", 2, "S", "D"),
|
| 90 |
+
PlanoContas("4.2.01", "DEVOLUÇÕES DE VENDAS", 3, "A", "D"),
|
| 91 |
+
PlanoContas("4.2.02", "IMPOSTOS SOBRE VENDAS", 3, "A", "D"),
|
| 92 |
+
PlanoContas("5", "CUSTOS", 1, "S", "D"),
|
| 93 |
+
PlanoContas("5.1", "CUSTO DE MERCADORIAS VENDIDAS", 2, "S", "D"),
|
| 94 |
+
PlanoContas("5.1.01", "CMV - MERCADORIAS", 3, "A", "D"),
|
| 95 |
+
PlanoContas("6", "DESPESAS OPERACIONAIS", 1, "S", "D"),
|
| 96 |
+
PlanoContas("6.1", "DESPESAS ADMINISTRATIVAS", 2, "S", "D"),
|
| 97 |
+
PlanoContas("6.1.01", "SALÁRIOS E ORDENADOS", 3, "A", "D"),
|
| 98 |
+
PlanoContas("6.1.02", "ENCARGOS SOCIAIS", 3, "A", "D"),
|
| 99 |
+
PlanoContas("6.1.03", "ALUGUÉIS", 3, "A", "D"),
|
| 100 |
+
PlanoContas("6.1.04", "SERVIÇOS DE TERCEIROS", 3, "A", "D"),
|
| 101 |
+
PlanoContas("6.2", "DESPESAS COMERCIAIS", 2, "S", "D"),
|
| 102 |
+
PlanoContas("6.2.01", "FRETES E CARRETOS", 3, "A", "D"),
|
| 103 |
+
PlanoContas("6.2.02", "COMISSÕES", 3, "A", "D"),
|
| 104 |
+
PlanoContas("6.3", "DESPESAS FINANCEIRAS", 2, "S", "D"),
|
| 105 |
+
PlanoContas("6.3.01", "JUROS E ENCARGOS", 3, "A", "D"),
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
def _bloco_0(self) -> None:
|
| 109 |
+
emp = self.periodo.empresa
|
| 110 |
+
end = emp.endereco
|
| 111 |
+
|
| 112 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 113 |
+
"0000",
|
| 114 |
+
self.cfg.versao,
|
| 115 |
+
self.cfg.tipo_escrituracao,
|
| 116 |
+
"G", # ind_sit_ini_pj (G=Geral)
|
| 117 |
+
emp.cnpj,
|
| 118 |
+
emp.regime_tributario.value,
|
| 119 |
+
emp.razao_social,
|
| 120 |
+
end.uf.value,
|
| 121 |
+
emp.ie,
|
| 122 |
+
end.cod_municipio,
|
| 123 |
+
self.cfg.indicador_sit_especial,
|
| 124 |
+
"", # nr_rec_anterior
|
| 125 |
+
self.periodo.data_inicio,
|
| 126 |
+
self.periodo.data_fim,
|
| 127 |
+
"N", # ind_grandes_contribuintes
|
| 128 |
+
self.cfg.finalidade,
|
| 129 |
+
"1", # ind_pub_soc_emp
|
| 130 |
+
"", # ind_ecd_complem
|
| 131 |
+
"", # hash_ecd_substituto
|
| 132 |
+
))
|
| 133 |
+
|
| 134 |
+
self._arquivo.adicionar_linha_raw(criar_registro("0001", "0"))
|
| 135 |
+
|
| 136 |
+
# 0007 - Identificação dos Signatários da ECD
|
| 137 |
+
if self.contador:
|
| 138 |
+
c = self.contador
|
| 139 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 140 |
+
"0007",
|
| 141 |
+
c.cpf,
|
| 142 |
+
"2", # qualif_assig (2=Contabilista)
|
| 143 |
+
))
|
| 144 |
+
|
| 145 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 146 |
+
"0990", self._arquivo.total_linhas() + 1
|
| 147 |
+
))
|
| 148 |
+
|
| 149 |
+
def _bloco_i(self) -> None:
|
| 150 |
+
|
| 151 |
+
self._arquivo.adicionar_linha_raw(criar_registro("I001", "0"))
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 155 |
+
"I010",
|
| 156 |
+
"G", # ind_lc (G=Livro Diário)
|
| 157 |
+
"", # nr_livro
|
| 158 |
+
"", # nr_pag_ini
|
| 159 |
+
"", # qt_lin
|
| 160 |
+
"", # nr_pag_fin
|
| 161 |
+
"", # nome_livro
|
| 162 |
+
self.periodo.data_inicio,
|
| 163 |
+
self.periodo.data_fim,
|
| 164 |
+
"", # hash_lc
|
| 165 |
+
"", # nr_pag_ini_cert
|
| 166 |
+
"", # nr_pag_fin_cert
|
| 167 |
+
"1", # dt_cert
|
| 168 |
+
"1", # nm_cart
|
| 169 |
+
"1", # nr_off_cart
|
| 170 |
+
"1", # tp_assinante
|
| 171 |
+
"1", # tratamento_diff
|
| 172 |
+
))
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 176 |
+
"I020",
|
| 177 |
+
"EMPRESA",
|
| 178 |
+
self.periodo.empresa.cnpj,
|
| 179 |
+
"1", # cod_ccp
|
| 180 |
+
"1", # cpf
|
| 181 |
+
))
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
for conta in self.plano_contas:
|
| 185 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 186 |
+
"I050",
|
| 187 |
+
conta.codigo,
|
| 188 |
+
conta.codigo, # cod_cta_ref
|
| 189 |
+
conta.nivel,
|
| 190 |
+
conta.tipo,
|
| 191 |
+
conta.natureza,
|
| 192 |
+
conta.descricao,
|
| 193 |
+
"", # cod_cta_cnt_sup
|
| 194 |
+
"", # cod_cta_cnt_cpl
|
| 195 |
+
"", # tipo_cta_ref
|
| 196 |
+
))
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
for conta in [c for c in self.plano_contas if c.tipo == "A"]:
|
| 200 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 201 |
+
"I100",
|
| 202 |
+
conta.codigo,
|
| 203 |
+
"0", # ind_dc_ini
|
| 204 |
+
Decimal("0"), # vl_saldo_ini
|
| 205 |
+
Decimal("0"), # vl_deb
|
| 206 |
+
Decimal("0"), # vl_cred
|
| 207 |
+
"0", # ind_dc_fin
|
| 208 |
+
Decimal("0"), # vl_saldo_fin
|
| 209 |
+
))
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
for i, lanc in enumerate(self.periodo.lancamentos, 1):
|
| 213 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 214 |
+
"I200",
|
| 215 |
+
str(i).zfill(6), # num_lanc
|
| 216 |
+
lanc.data,
|
| 217 |
+
"01", # cod_hist_padrao
|
| 218 |
+
lanc.historico,
|
| 219 |
+
Decimal("0"), # vl_lanc (total do lançamento)
|
| 220 |
+
))
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 224 |
+
"I250",
|
| 225 |
+
lanc.debito_conta,
|
| 226 |
+
"D",
|
| 227 |
+
lanc.valor,
|
| 228 |
+
"01", # cod_hist_padrao
|
| 229 |
+
lanc.historico,
|
| 230 |
+
"", # num_doc
|
| 231 |
+
"", # cod_part
|
| 232 |
+
))
|
| 233 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 234 |
+
"I250",
|
| 235 |
+
lanc.credito_conta,
|
| 236 |
+
"C",
|
| 237 |
+
lanc.valor,
|
| 238 |
+
"01",
|
| 239 |
+
lanc.historico,
|
| 240 |
+
"",
|
| 241 |
+
"",
|
| 242 |
+
))
|
| 243 |
+
|
| 244 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 245 |
+
"I990", self._arquivo.total_linhas()
|
| 246 |
+
))
|
| 247 |
+
|
| 248 |
+
def _bloco_j(self) -> None:
|
| 249 |
+
|
| 250 |
+
self._arquivo.adicionar_linha_raw(criar_registro("J001", "0"))
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 254 |
+
"J005",
|
| 255 |
+
self.periodo.data_fim,
|
| 256 |
+
"0", # cod_qualif_cp (0=não referenciado)
|
| 257 |
+
"0", # cod_pl_ref
|
| 258 |
+
"0", # cod_ver_pl_ref
|
| 259 |
+
))
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
for conta in self.plano_contas[:10]: # Primeiras contas como exemplo
|
| 263 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 264 |
+
"J100",
|
| 265 |
+
conta.codigo,
|
| 266 |
+
conta.codigo,
|
| 267 |
+
conta.descricao,
|
| 268 |
+
conta.nivel,
|
| 269 |
+
conta.tipo,
|
| 270 |
+
conta.natureza,
|
| 271 |
+
Decimal("0"), # vl_saldo_ini
|
| 272 |
+
"0", # ind_vl_cta
|
| 273 |
+
Decimal("0"), # vl_saldo_fin
|
| 274 |
+
"0",
|
| 275 |
+
"", # cod_cta_ref_conv
|
| 276 |
+
))
|
| 277 |
+
|
| 278 |
+
self._arquivo.adicionar_linha_raw(criar_registro(
|
| 279 |
+
"J990", self._arquivo.total_linhas()
|
| 280 |
+
))
|
| 281 |
+
|
| 282 |
+
def _bloco_9(self) -> None:
|
| 283 |
+
self._arquivo.adicionar_linha_raw(criar_registro("9001", "0"))
|
| 284 |
+
qt = self._arquivo.total_linhas() + 3
|
| 285 |
+
self._arquivo.adicionar_linha_raw(criar_registro("9900", "0000", "1"))
|
| 286 |
+
self._arquivo.adicionar_linha_raw(criar_registro("9990", qt))
|
| 287 |
+
self._arquivo.adicionar_linha_raw(criar_registro("9999", qt + 1))
|
| 288 |
+
|
| 289 |
+
def gerar(self, diretorio: str | Path = ".") -> Path:
|
| 290 |
+
self._bloco_0()
|
| 291 |
+
self._bloco_i()
|
| 292 |
+
self._bloco_j()
|
| 293 |
+
self._bloco_9()
|
| 294 |
+
return self._arquivo.salvar(diretorio)
|