Skip to content

Commit 9133d24

Browse files
authored
feat(reforma): suporte a gIBSCBSMono para CST 620 monofasica [DEV-1929] (#2)
* feat(reforma): suporte a gIBSCBSMono para CST 620 monofasica [DEV-1929] Adiciona suporte a tributacao monofasica IBS/CBS conforme NT 2025.002-RTC. Produtos com CST 620 (combustiveis e demais itens monofasicos) agora emitem <gIBSCBSMono> com qBCMono, adRemIBS, vIBSMono, adRemCBS e vCBSMono no lugar do grupo padrao <gIBSCBS> (pIBSUF/pIBSMun/pCBS). Contexto: SEFAZ rejeita (cStat 1026 - "Aliquota do IBS da UF invalida") qualquer NF-e com CST 620 emitida com gIBSCBS padrao, pois a spec exige o grupo gIBSCBSMono para regime monofasico. Mudancas: - NotaFiscalProduto ganha 5 novos atributos (ibscbs_q_bc_mono, ibscbs_ad_rem_ibs, ibscbs_v_ibs_mono, ibscbs_ad_rem_cbs, ibscbs_v_cbs_mono). - _serializar_ibscbs roteia para _serializar_gibscbs_mono quando CST in _IBSCBS_CST_MONOFASICO (por ora so "620"; 630/640 virao no futuro). - CST 620 removido de _IBSCBS_CST_TRIBUTADOS (agora pertence ao conjunto monofasico) para evitar emissao duplicada. - Docs atualizadas: gIBSCBSMono removido da lista "Nao inclui". - 3 novos testes (CST 620 com valores zero, CST 620 com ad rem, regressao CST 000). * style(serializacao): aplica ruff format em gIBSCBSMono [DEV-1929] --------- Co-authored-by: felps-dev <felps-dev@users.noreply.github.com>
1 parent e1e5e7e commit 9133d24

4 files changed

Lines changed: 258 additions & 31 deletions

File tree

docs/reforma_tributaria.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,25 @@ A implementacao cobre:
3737
- `vNF` **NAO inclui** IBS/CBS (proibido em 2025-2026)
3838
- `finNFe=5` (Nota de Debito) e `finNFe=6` (Nota de Credito)
3939
- Campos de entidade para IS (Imposto Seletivo) — **armazenados mas nao serializados** ate o schema suportar (2027)
40+
- Tributacao monofasica (`gIBSCBSMono`) para CST 620 — combustiveis e demais produtos sujeitos ao regime monofasico de IBS/CBS
4041

41-
**Nao inclui** (ainda): Split Payment, cashback, eventos de apuracao assistida, Grupo VB (total do item), Grupo VC (referenciamento de DF-e), Grupo BB (antecipacao de pagamento), tributacao monofasica (`gIBSCBSMono`), diferimento per-item (`gDif`), devolucao de tributos per-item (`gDevTrib`), reducao de aliquota per-item (`gRed`), estorno de credito (`gEstornoCred`), credito presumido per-item (`gCredPresOper`, `gCredPresIBSZFM`).
42+
**Nao inclui** (ainda): Split Payment, cashback, eventos de apuracao assistida, Grupo VB (total do item), Grupo VC (referenciamento de DF-e), Grupo BB (antecipacao de pagamento), diferimento per-item (`gDif`), devolucao de tributos per-item (`gDevTrib`), reducao de aliquota per-item (`gRed`), estorno de credito (`gEstornoCred`), credito presumido per-item (`gCredPresOper`, `gCredPresIBSZFM`).
43+
44+
### Tributacao monofasica — `gIBSCBSMono`
45+
46+
Para produtos com CST 620 (combustiveis, etc.) o grupo emitido dentro de `<IBSCBS>` e `<gIBSCBSMono>` ao inves de `<gIBSCBS>`. Campos obrigatorios:
47+
48+
| Campo | Tipo | Descricao |
49+
|-------|------|-----------|
50+
| `qBCMono` | TDec_1104v | Quantidade tributada na base monofasica |
51+
| `adRemIBS` | TDec_0302a10 | Aliquota ad rem IBS (valor em BRL por unidade) |
52+
| `vIBSMono` | TDec_1302 | Valor IBS monofasico |
53+
| `adRemCBS` | TDec_0302a10 | Aliquota ad rem CBS (valor em BRL por unidade) |
54+
| `vCBSMono` | TDec_1302 | Valor CBS monofasico |
55+
56+
Atributos na entidade `NotaFiscalProduto`: `ibscbs_q_bc_mono`, `ibscbs_ad_rem_ibs`, `ibscbs_v_ibs_mono`, `ibscbs_ad_rem_cbs`, `ibscbs_v_cbs_mono`.
57+
58+
Durante o Teste de Carga 2026 os ad rem ainda nao foram publicados pela SEFAZ, entao os valores podem ser zerados — o grupo `gIBSCBSMono` ainda sera emitido corretamente.
4259

4360
## CSTs disponiveis
4461

pynfe/entidades/notafiscal.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,17 @@ class NotaFiscalProduto(Entidade):
10461046
ibscbs_p_cbs = Decimal() # pCBS
10471047
ibscbs_v_cbs = Decimal() # vCBS
10481048

1049+
# gIBSCBSMono - Tributacao monofasica (CST 620)
1050+
# Emitted as <gIBSCBSMono> instead of <gIBSCBS> for CST 620 items.
1051+
# qBCMono = quantity in monophasic base unit (TDec_1104v)
1052+
# adRemIBS / adRemCBS = ad rem rate in BRL per unit (TDec_0302a10)
1053+
# vIBSMono / vCBSMono = final value in BRL
1054+
ibscbs_q_bc_mono = Decimal() # qBCMono
1055+
ibscbs_ad_rem_ibs = Decimal() # adRemIBS
1056+
ibscbs_v_ibs_mono = Decimal() # vIBSMono
1057+
ibscbs_ad_rem_cbs = Decimal() # adRemCBS
1058+
ibscbs_v_cbs_mono = Decimal() # vCBSMono
1059+
10491060
# IS (Imposto Seletivo) - Group UB-IS
10501061
is_cst_selec = str() # CSTSelec (2-digit)
10511062
is_c_class_trib = str() # cClassTribIS 6-digit

pynfe/processamento/serializacao.py

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,8 +1316,13 @@ def _serializar_imposto_importacao(
13161316
# Reforma Tributaria - IVA Dual (NT 2025.002-RTC)
13171317
# =============================================
13181318

1319-
# CSTs that have taxable values (vBC, rates, amounts)
1320-
_IBSCBS_CST_TRIBUTADOS = ("000", "010", "200", "400", "510", "600", "620", "800", "810", "900")
1319+
# CSTs that have taxable values (vBC, rates, amounts) and use <gIBSCBS>
1320+
_IBSCBS_CST_TRIBUTADOS = ("000", "010", "200", "400", "510", "600", "800", "810", "900")
1321+
1322+
# CSTs that use the monophasic tax regime and emit <gIBSCBSMono> instead of <gIBSCBS>
1323+
# (qBCMono, adRemIBS/adRemCBS, vIBSMono/vCBSMono). Start with 620 (combustiveis);
1324+
# 630/640 will be added when SEFAZ publishes the corresponding cClassTrib codes.
1325+
_IBSCBS_CST_MONOFASICO = ("620",)
13211326

13221327
def _serializar_imposto_ibscbs(
13231328
self, produto_servico, modelo, tag_raiz="imposto", retorna_string=True
@@ -1340,45 +1345,83 @@ def _serializar_imposto_ibscbs(
13401345
# self._serializar_is(produto_servico, tag_raiz)
13411346

13421347
def _serializar_ibscbs(self, produto_servico, tag_raiz):
1343-
"""Serializa <IBSCBS> com gIBSCBS contendo gIBSUF, gIBSMun e gCBS."""
1348+
"""Serializa <IBSCBS>.
1349+
1350+
Para CSTs monofasicas (ex: 620) emite <gIBSCBSMono> com qBCMono, adRemIBS,
1351+
vIBSMono, adRemCBS, vCBSMono. Para demais CSTs tributados emite <gIBSCBS>
1352+
com vBC + gIBSUF + gIBSMun + gCBS.
1353+
"""
13441354
ibscbs = etree.SubElement(tag_raiz, "IBSCBS")
13451355
etree.SubElement(ibscbs, "CST").text = produto_servico.ibscbs_cst
13461356

13471357
if produto_servico.ibscbs_c_class_trib:
13481358
etree.SubElement(ibscbs, "cClassTrib").text = produto_servico.ibscbs_c_class_trib
13491359

1350-
if produto_servico.ibscbs_cst in self._IBSCBS_CST_TRIBUTADOS:
1351-
gibscbs = etree.SubElement(ibscbs, "gIBSCBS")
1360+
if produto_servico.ibscbs_cst in self._IBSCBS_CST_MONOFASICO:
1361+
self._serializar_gibscbs_mono(produto_servico, ibscbs)
1362+
elif produto_servico.ibscbs_cst in self._IBSCBS_CST_TRIBUTADOS:
1363+
self._serializar_gibscbs(produto_servico, ibscbs)
13521364

1353-
etree.SubElement(gibscbs, "vBC").text = "{:.2f}".format(produto_servico.ibscbs_vbc or 0)
1365+
def _serializar_gibscbs(self, produto_servico, ibscbs):
1366+
"""Serializa <gIBSCBS> padrao com vBC, gIBSUF, gIBSMun, vIBS e gCBS."""
1367+
gibscbs = etree.SubElement(ibscbs, "gIBSCBS")
13541368

1355-
# gIBSUF
1356-
gibsuf = etree.SubElement(gibscbs, "gIBSUF")
1357-
etree.SubElement(gibsuf, "pIBSUF").text = "{:.4f}".format(
1358-
produto_servico.ibscbs_p_ibs_uf or 0
1359-
)
1360-
etree.SubElement(gibsuf, "vIBSUF").text = "{:.2f}".format(
1361-
produto_servico.ibscbs_v_ibs_uf or 0
1362-
)
1369+
etree.SubElement(gibscbs, "vBC").text = "{:.2f}".format(produto_servico.ibscbs_vbc or 0)
13631370

1364-
# gIBSMun
1365-
gibsmun = etree.SubElement(gibscbs, "gIBSMun")
1366-
etree.SubElement(gibsmun, "pIBSMun").text = "{:.4f}".format(
1367-
produto_servico.ibscbs_p_ibs_mun or 0
1368-
)
1369-
etree.SubElement(gibsmun, "vIBSMun").text = "{:.2f}".format(
1370-
produto_servico.ibscbs_v_ibs_mun or 0
1371-
)
1371+
# gIBSUF
1372+
gibsuf = etree.SubElement(gibscbs, "gIBSUF")
1373+
etree.SubElement(gibsuf, "pIBSUF").text = "{:.4f}".format(
1374+
produto_servico.ibscbs_p_ibs_uf or 0
1375+
)
1376+
etree.SubElement(gibsuf, "vIBSUF").text = "{:.2f}".format(
1377+
produto_servico.ibscbs_v_ibs_uf or 0
1378+
)
13721379

1373-
# vIBS total
1374-
etree.SubElement(gibscbs, "vIBS").text = "{:.2f}".format(
1375-
produto_servico.ibscbs_v_ibs or 0
1376-
)
1380+
# gIBSMun
1381+
gibsmun = etree.SubElement(gibscbs, "gIBSMun")
1382+
etree.SubElement(gibsmun, "pIBSMun").text = "{:.4f}".format(
1383+
produto_servico.ibscbs_p_ibs_mun or 0
1384+
)
1385+
etree.SubElement(gibsmun, "vIBSMun").text = "{:.2f}".format(
1386+
produto_servico.ibscbs_v_ibs_mun or 0
1387+
)
13771388

1378-
# gCBS
1379-
gcbs = etree.SubElement(gibscbs, "gCBS")
1380-
etree.SubElement(gcbs, "pCBS").text = "{:.4f}".format(produto_servico.ibscbs_p_cbs or 0)
1381-
etree.SubElement(gcbs, "vCBS").text = "{:.2f}".format(produto_servico.ibscbs_v_cbs or 0)
1389+
# vIBS total
1390+
etree.SubElement(gibscbs, "vIBS").text = "{:.2f}".format(produto_servico.ibscbs_v_ibs or 0)
1391+
1392+
# gCBS
1393+
gcbs = etree.SubElement(gibscbs, "gCBS")
1394+
etree.SubElement(gcbs, "pCBS").text = "{:.4f}".format(produto_servico.ibscbs_p_cbs or 0)
1395+
etree.SubElement(gcbs, "vCBS").text = "{:.2f}".format(produto_servico.ibscbs_v_cbs or 0)
1396+
1397+
def _serializar_gibscbs_mono(self, produto_servico, ibscbs):
1398+
"""Serializa <gIBSCBSMono> para CSTs monofasicas (620).
1399+
1400+
Estrutura obrigatoria por NT 2025.002-RTC:
1401+
<gIBSCBSMono>
1402+
<qBCMono>TDec_1104v (4 casas)</qBCMono>
1403+
<adRemIBS>TDec_0302a10 (4 casas)</adRemIBS>
1404+
<vIBSMono>TDec_1302 (2 casas)</vIBSMono>
1405+
<adRemCBS>TDec_0302a10 (4 casas)</adRemCBS>
1406+
<vCBSMono>TDec_1302 (2 casas)</vCBSMono>
1407+
</gIBSCBSMono>
1408+
"""
1409+
gibscbs_mono = etree.SubElement(ibscbs, "gIBSCBSMono")
1410+
etree.SubElement(gibscbs_mono, "qBCMono").text = "{:.4f}".format(
1411+
produto_servico.ibscbs_q_bc_mono or 0
1412+
)
1413+
etree.SubElement(gibscbs_mono, "adRemIBS").text = "{:.4f}".format(
1414+
produto_servico.ibscbs_ad_rem_ibs or 0
1415+
)
1416+
etree.SubElement(gibscbs_mono, "vIBSMono").text = "{:.2f}".format(
1417+
produto_servico.ibscbs_v_ibs_mono or 0
1418+
)
1419+
etree.SubElement(gibscbs_mono, "adRemCBS").text = "{:.4f}".format(
1420+
produto_servico.ibscbs_ad_rem_cbs or 0
1421+
)
1422+
etree.SubElement(gibscbs_mono, "vCBSMono").text = "{:.2f}".format(
1423+
produto_servico.ibscbs_v_cbs_mono or 0
1424+
)
13821425

13831426
def _serializar_is(self, produto_servico, tag_raiz):
13841427
"""Serializa <IS> (Imposto Seletivo) como filho direto de <imposto>.

tests/test_nfe_serializacao_reforma_tributaria.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,162 @@ def test_cmunfgibs_emitido_no_ide(self):
667667
cmunfgibs_idx = tags.index("cMunFGIBS")
668668
self.assertGreater(cmunfgibs_idx, cmunfg_idx)
669669

670+
# ------------------------------------------------------------------
671+
# Test gIBSCBSMono: CST 620 emits monophasic group
672+
# ------------------------------------------------------------------
673+
def test_cst620_monofasica_emite_gibscbsmono(self):
674+
"""CST 620 (tributacao monofasica) must emit <gIBSCBSMono> with
675+
qBCMono, adRemIBS, vIBSMono, adRemCBS, vCBSMono — NOT <gIBSCBS>."""
676+
emitente = self._emitente()
677+
cliente = self._cliente()
678+
nf = self._nota_fiscal(emitente, cliente)
679+
680+
kwargs = self._base_product_kwargs()
681+
kwargs.update(
682+
codigo="010",
683+
descricao="GLP em Botijao 13KG (CST 620 monofasica)",
684+
ncm="27111910",
685+
quantidade_comercial=Decimal("18"),
686+
valor_unitario_comercial=Decimal("74.04"),
687+
valor_total_bruto=Decimal("1332.72"),
688+
quantidade_tributavel=Decimal("18"),
689+
valor_unitario_tributavel=Decimal("74.04"),
690+
ibscbs_cst="620",
691+
ibscbs_c_class_trib="620006",
692+
# Monophasic fields
693+
ibscbs_q_bc_mono=Decimal("18.0000"),
694+
ibscbs_ad_rem_ibs=Decimal("0.0000"),
695+
ibscbs_v_ibs_mono=Decimal("0.00"),
696+
ibscbs_ad_rem_cbs=Decimal("0.0000"),
697+
ibscbs_v_cbs_mono=Decimal("0.00"),
698+
)
699+
nf.adicionar_produto_servico(**kwargs)
700+
nf.adicionar_pagamento(t_pag="01", x_pag="Dinheiro", v_pag=1332.72, ind_pag=0)
701+
702+
xml = self._serializar_e_assinar()
703+
704+
# <IBSCBS> is emitted
705+
ibscbs = xml.xpath("//ns:det/ns:imposto/ns:IBSCBS", namespaces=self.ns)
706+
self.assertEqual(len(ibscbs), 1)
707+
708+
# CST is 620
709+
cst = xml.xpath("//ns:IBSCBS/ns:CST", namespaces=self.ns)[0].text
710+
self.assertEqual(cst, "620")
711+
712+
# cClassTrib is 620006
713+
cclass = xml.xpath("//ns:IBSCBS/ns:cClassTrib", namespaces=self.ns)[0].text
714+
self.assertEqual(cclass, "620006")
715+
716+
# <gIBSCBSMono> is emitted
717+
gibscbs_mono = xml.xpath("//ns:IBSCBS/ns:gIBSCBSMono", namespaces=self.ns)
718+
self.assertEqual(len(gibscbs_mono), 1)
719+
720+
# <gIBSCBS> is NOT emitted (we use monophasic instead)
721+
gibscbs = xml.xpath("//ns:IBSCBS/ns:gIBSCBS", namespaces=self.ns)
722+
self.assertEqual(len(gibscbs), 0)
723+
724+
# Verify the 5 required monophasic fields in correct order
725+
q_bc_mono = xml.xpath("//ns:gIBSCBSMono/ns:qBCMono", namespaces=self.ns)[0].text
726+
self.assertEqual(q_bc_mono, "18.0000")
727+
728+
ad_rem_ibs = xml.xpath("//ns:gIBSCBSMono/ns:adRemIBS", namespaces=self.ns)[0].text
729+
self.assertEqual(ad_rem_ibs, "0.0000")
730+
731+
v_ibs_mono = xml.xpath("//ns:gIBSCBSMono/ns:vIBSMono", namespaces=self.ns)[0].text
732+
self.assertEqual(v_ibs_mono, "0.00")
733+
734+
ad_rem_cbs = xml.xpath("//ns:gIBSCBSMono/ns:adRemCBS", namespaces=self.ns)[0].text
735+
self.assertEqual(ad_rem_cbs, "0.0000")
736+
737+
v_cbs_mono = xml.xpath("//ns:gIBSCBSMono/ns:vCBSMono", namespaces=self.ns)[0].text
738+
self.assertEqual(v_cbs_mono, "0.00")
739+
740+
# Field order: qBCMono, adRemIBS, vIBSMono, adRemCBS, vCBSMono
741+
mono_elem = gibscbs_mono[0]
742+
field_names = [child.tag.split("}")[-1] for child in mono_elem]
743+
self.assertEqual(
744+
field_names,
745+
["qBCMono", "adRemIBS", "vIBSMono", "adRemCBS", "vCBSMono"],
746+
)
747+
748+
def test_cst620_monofasica_com_valores_calculados(self):
749+
"""CST 620 with non-zero ad rem rates produces non-zero monophasic values."""
750+
emitente = self._emitente()
751+
cliente = self._cliente()
752+
nf = self._nota_fiscal(emitente, cliente)
753+
754+
kwargs = self._base_product_kwargs()
755+
kwargs.update(
756+
codigo="011",
757+
descricao="Combustivel monofasico com ad rem",
758+
ibscbs_cst="620",
759+
ibscbs_c_class_trib="620001",
760+
ibscbs_q_bc_mono=Decimal("100.0000"),
761+
ibscbs_ad_rem_ibs=Decimal("0.1500"),
762+
ibscbs_v_ibs_mono=Decimal("15.00"),
763+
ibscbs_ad_rem_cbs=Decimal("0.8500"),
764+
ibscbs_v_cbs_mono=Decimal("85.00"),
765+
)
766+
nf.adicionar_produto_servico(**kwargs)
767+
nf.adicionar_pagamento(t_pag="01", x_pag="Dinheiro", v_pag=1000.00, ind_pag=0)
768+
769+
xml = self._serializar_e_assinar()
770+
771+
self.assertEqual(
772+
xml.xpath("//ns:gIBSCBSMono/ns:qBCMono", namespaces=self.ns)[0].text, "100.0000"
773+
)
774+
self.assertEqual(
775+
xml.xpath("//ns:gIBSCBSMono/ns:adRemIBS", namespaces=self.ns)[0].text, "0.1500"
776+
)
777+
self.assertEqual(
778+
xml.xpath("//ns:gIBSCBSMono/ns:vIBSMono", namespaces=self.ns)[0].text, "15.00"
779+
)
780+
self.assertEqual(
781+
xml.xpath("//ns:gIBSCBSMono/ns:adRemCBS", namespaces=self.ns)[0].text, "0.8500"
782+
)
783+
self.assertEqual(
784+
xml.xpath("//ns:gIBSCBSMono/ns:vCBSMono", namespaces=self.ns)[0].text, "85.00"
785+
)
786+
787+
def test_cst000_nao_emite_gibscbsmono_regressao(self):
788+
"""Regression test: CST 000 (regular taxation) must still emit <gIBSCBS>
789+
and must NOT emit <gIBSCBSMono>."""
790+
emitente = self._emitente()
791+
cliente = self._cliente()
792+
nf = self._nota_fiscal(emitente, cliente)
793+
794+
kwargs = self._base_product_kwargs()
795+
kwargs.update(
796+
ibscbs_cst="000",
797+
ibscbs_c_class_trib="000001",
798+
ibscbs_vbc=Decimal("1000.00"),
799+
ibscbs_p_ibs_uf=Decimal("0.1000"),
800+
ibscbs_v_ibs_uf=Decimal("1.00"),
801+
ibscbs_p_ibs_mun=Decimal("0.0000"),
802+
ibscbs_v_ibs_mun=Decimal("0.00"),
803+
ibscbs_v_ibs=Decimal("1.00"),
804+
ibscbs_p_cbs=Decimal("0.9000"),
805+
ibscbs_v_cbs=Decimal("9.00"),
806+
)
807+
nf.adicionar_produto_servico(**kwargs)
808+
nf.adicionar_pagamento(t_pag="01", x_pag="Dinheiro", v_pag=1000.00, ind_pag=0)
809+
810+
xml = self._serializar_e_assinar()
811+
812+
# <gIBSCBS> is emitted (regular taxation path unchanged)
813+
gibscbs = xml.xpath("//ns:IBSCBS/ns:gIBSCBS", namespaces=self.ns)
814+
self.assertEqual(len(gibscbs), 1)
815+
816+
# <gIBSCBSMono> must NOT be emitted
817+
gibscbs_mono = xml.xpath("//ns:IBSCBS/ns:gIBSCBSMono", namespaces=self.ns)
818+
self.assertEqual(len(gibscbs_mono), 0)
819+
820+
# Verify <gIBSCBS> still has pIBSUF/pIBSMun/pCBS (regression)
821+
p_ibs_uf = xml.xpath("//ns:gIBSCBS/ns:gIBSUF/ns:pIBSUF", namespaces=self.ns)[0].text
822+
self.assertEqual(p_ibs_uf, "0.1000")
823+
p_cbs = xml.xpath("//ns:gIBSCBS/ns:gCBS/ns:pCBS", namespaces=self.ns)[0].text
824+
self.assertEqual(p_cbs, "0.9000")
825+
670826
# ------------------------------------------------------------------
671827
# Test 10: cMunFGIBS NOT emitted when not set
672828
# ------------------------------------------------------------------

0 commit comments

Comments
 (0)