-
Notifications
You must be signed in to change notification settings - Fork 220
feat: suporte a gIBSCBSMono para CST 620 (Tributacao Monofasica) — NT 2025.002-RTC #456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
9133d24
135ec04
fa099e8
354cb8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1046,6 +1046,17 @@ class NotaFiscalProduto(Entidade): | |
| ibscbs_p_cbs = Decimal() # pCBS | ||
| ibscbs_v_cbs = Decimal() # vCBS | ||
|
|
||
| # gIBSCBSMono - Tributacao monofasica (CST 620) | ||
| # Emitted as <gIBSCBSMono> instead of <gIBSCBS> for CST 620 items. | ||
| # qBCMono = quantity in monophasic base unit (TDec_1104v) | ||
| # adRemIBS / adRemCBS = ad rem rate in BRL per unit (TDec_0302a10) | ||
| # vIBSMono / vCBSMono = final value in BRL | ||
| ibscbs_q_bc_mono = Decimal() # qBCMono | ||
| ibscbs_ad_rem_ibs = Decimal() # adRemIBS | ||
| ibscbs_v_ibs_mono = Decimal() # vIBSMono | ||
| ibscbs_ad_rem_cbs = Decimal() # adRemCBS | ||
| ibscbs_v_cbs_mono = Decimal() # vCBSMono | ||
|
Comment on lines
+1097
to
+1101
|
||
|
|
||
| # IS (Imposto Seletivo) - Group UB-IS | ||
| is_cst_selec = str() # CSTSelec (2-digit) | ||
| is_c_class_trib = str() # cClassTribIS 6-digit | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1316,8 +1316,13 @@ def _serializar_imposto_importacao( | |
| # Reforma Tributaria - IVA Dual (NT 2025.002-RTC) | ||
| # ============================================= | ||
|
|
||
| # CSTs that have taxable values (vBC, rates, amounts) | ||
| _IBSCBS_CST_TRIBUTADOS = ("000", "010", "200", "400", "510", "600", "620", "800", "810", "900") | ||
| # CSTs that have taxable values (vBC, rates, amounts) and use <gIBSCBS> | ||
| _IBSCBS_CST_TRIBUTADOS = ("000", "010", "200", "400", "510", "600", "800", "810", "900") | ||
|
|
||
| # CSTs that use the monophasic tax regime and emit <gIBSCBSMono> instead of <gIBSCBS> | ||
| # (qBCMono, adRemIBS/adRemCBS, vIBSMono/vCBSMono). Start with 620 (combustiveis); | ||
| # 630/640 will be added when SEFAZ publishes the corresponding cClassTrib codes. | ||
| _IBSCBS_CST_MONOFASICO = ("620",) | ||
|
|
||
| def _serializar_imposto_ibscbs( | ||
| self, produto_servico, modelo, tag_raiz="imposto", retorna_string=True | ||
|
|
@@ -1340,45 +1345,83 @@ def _serializar_imposto_ibscbs( | |
| # self._serializar_is(produto_servico, tag_raiz) | ||
|
|
||
| def _serializar_ibscbs(self, produto_servico, tag_raiz): | ||
| """Serializa <IBSCBS> com gIBSCBS contendo gIBSUF, gIBSMun e gCBS.""" | ||
| """Serializa <IBSCBS>. | ||
|
|
||
| Para CSTs monofasicas (ex: 620) emite <gIBSCBSMono> com qBCMono, adRemIBS, | ||
| vIBSMono, adRemCBS, vCBSMono. Para demais CSTs tributados emite <gIBSCBS> | ||
| com vBC + gIBSUF + gIBSMun + gCBS. | ||
| """ | ||
| ibscbs = etree.SubElement(tag_raiz, "IBSCBS") | ||
| etree.SubElement(ibscbs, "CST").text = produto_servico.ibscbs_cst | ||
|
|
||
| if produto_servico.ibscbs_c_class_trib: | ||
| etree.SubElement(ibscbs, "cClassTrib").text = produto_servico.ibscbs_c_class_trib | ||
|
|
||
| if produto_servico.ibscbs_cst in self._IBSCBS_CST_TRIBUTADOS: | ||
| gibscbs = etree.SubElement(ibscbs, "gIBSCBS") | ||
| if produto_servico.ibscbs_cst in self._IBSCBS_CST_MONOFASICO: | ||
| self._serializar_gibscbs_mono(produto_servico, ibscbs) | ||
| elif produto_servico.ibscbs_cst in self._IBSCBS_CST_TRIBUTADOS: | ||
| self._serializar_gibscbs(produto_servico, ibscbs) | ||
|
Comment on lines
+1360
to
+1363
|
||
|
|
||
| etree.SubElement(gibscbs, "vBC").text = "{:.2f}".format(produto_servico.ibscbs_vbc or 0) | ||
| def _serializar_gibscbs(self, produto_servico, ibscbs): | ||
| """Serializa <gIBSCBS> padrao com vBC, gIBSUF, gIBSMun, vIBS e gCBS.""" | ||
| gibscbs = etree.SubElement(ibscbs, "gIBSCBS") | ||
|
|
||
| # gIBSUF | ||
| gibsuf = etree.SubElement(gibscbs, "gIBSUF") | ||
| etree.SubElement(gibsuf, "pIBSUF").text = "{:.4f}".format( | ||
| produto_servico.ibscbs_p_ibs_uf or 0 | ||
| ) | ||
| etree.SubElement(gibsuf, "vIBSUF").text = "{:.2f}".format( | ||
| produto_servico.ibscbs_v_ibs_uf or 0 | ||
| ) | ||
| etree.SubElement(gibscbs, "vBC").text = "{:.2f}".format(produto_servico.ibscbs_vbc or 0) | ||
|
|
||
| # gIBSMun | ||
| gibsmun = etree.SubElement(gibscbs, "gIBSMun") | ||
| etree.SubElement(gibsmun, "pIBSMun").text = "{:.4f}".format( | ||
| produto_servico.ibscbs_p_ibs_mun or 0 | ||
| ) | ||
| etree.SubElement(gibsmun, "vIBSMun").text = "{:.2f}".format( | ||
| produto_servico.ibscbs_v_ibs_mun or 0 | ||
| ) | ||
| # gIBSUF | ||
| gibsuf = etree.SubElement(gibscbs, "gIBSUF") | ||
| etree.SubElement(gibsuf, "pIBSUF").text = "{:.4f}".format( | ||
| produto_servico.ibscbs_p_ibs_uf or 0 | ||
| ) | ||
| etree.SubElement(gibsuf, "vIBSUF").text = "{:.2f}".format( | ||
| produto_servico.ibscbs_v_ibs_uf or 0 | ||
| ) | ||
|
|
||
| # vIBS total | ||
| etree.SubElement(gibscbs, "vIBS").text = "{:.2f}".format( | ||
| produto_servico.ibscbs_v_ibs or 0 | ||
| ) | ||
| # gIBSMun | ||
| gibsmun = etree.SubElement(gibscbs, "gIBSMun") | ||
| etree.SubElement(gibsmun, "pIBSMun").text = "{:.4f}".format( | ||
| produto_servico.ibscbs_p_ibs_mun or 0 | ||
| ) | ||
| etree.SubElement(gibsmun, "vIBSMun").text = "{:.2f}".format( | ||
| produto_servico.ibscbs_v_ibs_mun or 0 | ||
| ) | ||
|
|
||
| # gCBS | ||
| gcbs = etree.SubElement(gibscbs, "gCBS") | ||
| etree.SubElement(gcbs, "pCBS").text = "{:.4f}".format(produto_servico.ibscbs_p_cbs or 0) | ||
| etree.SubElement(gcbs, "vCBS").text = "{:.2f}".format(produto_servico.ibscbs_v_cbs or 0) | ||
| # vIBS total | ||
| etree.SubElement(gibscbs, "vIBS").text = "{:.2f}".format(produto_servico.ibscbs_v_ibs or 0) | ||
|
|
||
| # gCBS | ||
| gcbs = etree.SubElement(gibscbs, "gCBS") | ||
| etree.SubElement(gcbs, "pCBS").text = "{:.4f}".format(produto_servico.ibscbs_p_cbs or 0) | ||
| etree.SubElement(gcbs, "vCBS").text = "{:.2f}".format(produto_servico.ibscbs_v_cbs or 0) | ||
|
|
||
| def _serializar_gibscbs_mono(self, produto_servico, ibscbs): | ||
| """Serializa <gIBSCBSMono> para CSTs monofasicas (620). | ||
|
|
||
| Estrutura obrigatoria por NT 2025.002-RTC: | ||
| <gIBSCBSMono> | ||
| <qBCMono>TDec_1104v (4 casas)</qBCMono> | ||
| <adRemIBS>TDec_0302a10 (4 casas)</adRemIBS> | ||
| <vIBSMono>TDec_1302 (2 casas)</vIBSMono> | ||
| <adRemCBS>TDec_0302a10 (4 casas)</adRemCBS> | ||
| <vCBSMono>TDec_1302 (2 casas)</vCBSMono> | ||
| </gIBSCBSMono> | ||
| """ | ||
| gibscbs_mono = etree.SubElement(ibscbs, "gIBSCBSMono") | ||
| etree.SubElement(gibscbs_mono, "qBCMono").text = "{:.4f}".format( | ||
| produto_servico.ibscbs_q_bc_mono or 0 | ||
| ) | ||
| etree.SubElement(gibscbs_mono, "adRemIBS").text = "{:.4f}".format( | ||
| produto_servico.ibscbs_ad_rem_ibs or 0 | ||
| ) | ||
| etree.SubElement(gibscbs_mono, "vIBSMono").text = "{:.2f}".format( | ||
| produto_servico.ibscbs_v_ibs_mono or 0 | ||
| ) | ||
| etree.SubElement(gibscbs_mono, "adRemCBS").text = "{:.4f}".format( | ||
| produto_servico.ibscbs_ad_rem_cbs or 0 | ||
| ) | ||
| etree.SubElement(gibscbs_mono, "vCBSMono").text = "{:.2f}".format( | ||
| produto_servico.ibscbs_v_cbs_mono or 0 | ||
| ) | ||
|
|
||
| def _serializar_is(self, produto_servico, tag_raiz): | ||
| """Serializa <IS> (Imposto Seletivo) como filho direto de <imposto>. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -667,6 +667,162 @@ def test_cmunfgibs_emitido_no_ide(self): | |||||||||||||||
| cmunfgibs_idx = tags.index("cMunFGIBS") | ||||||||||||||||
| self.assertGreater(cmunfgibs_idx, cmunfg_idx) | ||||||||||||||||
|
|
||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||
| # Test gIBSCBSMono: CST 620 emits monophasic group | ||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||
|
Comment on lines
+670
to
+672
|
||||||||||||||||
| def test_cst620_monofasica_emite_gibscbsmono(self): | ||||||||||||||||
| """CST 620 (tributacao monofasica) must emit <gIBSCBSMono> with | ||||||||||||||||
| qBCMono, adRemIBS, vIBSMono, adRemCBS, vCBSMono — NOT <gIBSCBS>.""" | ||||||||||||||||
| emitente = self._emitente() | ||||||||||||||||
| cliente = self._cliente() | ||||||||||||||||
| nf = self._nota_fiscal(emitente, cliente) | ||||||||||||||||
|
|
||||||||||||||||
| kwargs = self._base_product_kwargs() | ||||||||||||||||
| kwargs.update( | ||||||||||||||||
| codigo="010", | ||||||||||||||||
| descricao="GLP em Botijao 13KG (CST 620 monofasica)", | ||||||||||||||||
| ncm="27111910", | ||||||||||||||||
| quantidade_comercial=Decimal("18"), | ||||||||||||||||
| valor_unitario_comercial=Decimal("74.04"), | ||||||||||||||||
| valor_total_bruto=Decimal("1332.72"), | ||||||||||||||||
| quantidade_tributavel=Decimal("18"), | ||||||||||||||||
| valor_unitario_tributavel=Decimal("74.04"), | ||||||||||||||||
| ibscbs_cst="620", | ||||||||||||||||
| ibscbs_c_class_trib="620006", | ||||||||||||||||
| # Monophasic fields | ||||||||||||||||
| ibscbs_q_bc_mono=Decimal("18.0000"), | ||||||||||||||||
| ibscbs_ad_rem_ibs=Decimal("0.0000"), | ||||||||||||||||
| ibscbs_v_ibs_mono=Decimal("0.00"), | ||||||||||||||||
| ibscbs_ad_rem_cbs=Decimal("0.0000"), | ||||||||||||||||
| ibscbs_v_cbs_mono=Decimal("0.00"), | ||||||||||||||||
| ) | ||||||||||||||||
| nf.adicionar_produto_servico(**kwargs) | ||||||||||||||||
| nf.adicionar_pagamento(t_pag="01", x_pag="Dinheiro", v_pag=1332.72, ind_pag=0) | ||||||||||||||||
|
|
||||||||||||||||
| xml = self._serializar_e_assinar() | ||||||||||||||||
|
|
||||||||||||||||
| # <IBSCBS> is emitted | ||||||||||||||||
| ibscbs = xml.xpath("//ns:det/ns:imposto/ns:IBSCBS", namespaces=self.ns) | ||||||||||||||||
| self.assertEqual(len(ibscbs), 1) | ||||||||||||||||
|
|
||||||||||||||||
| # CST is 620 | ||||||||||||||||
| cst = xml.xpath("//ns:IBSCBS/ns:CST", namespaces=self.ns)[0].text | ||||||||||||||||
| self.assertEqual(cst, "620") | ||||||||||||||||
|
|
||||||||||||||||
| # cClassTrib is 620006 | ||||||||||||||||
| cclass = xml.xpath("//ns:IBSCBS/ns:cClassTrib", namespaces=self.ns)[0].text | ||||||||||||||||
| self.assertEqual(cclass, "620006") | ||||||||||||||||
|
|
||||||||||||||||
| # <gIBSCBSMono> is emitted | ||||||||||||||||
| gibscbs_mono = xml.xpath("//ns:IBSCBS/ns:gIBSCBSMono", namespaces=self.ns) | ||||||||||||||||
| self.assertEqual(len(gibscbs_mono), 1) | ||||||||||||||||
|
|
||||||||||||||||
| # <gIBSCBS> is NOT emitted (we use monophasic instead) | ||||||||||||||||
| gibscbs = xml.xpath("//ns:IBSCBS/ns:gIBSCBS", namespaces=self.ns) | ||||||||||||||||
| self.assertEqual(len(gibscbs), 0) | ||||||||||||||||
|
|
||||||||||||||||
| # Verify the 5 required monophasic fields in correct order | ||||||||||||||||
| q_bc_mono = xml.xpath("//ns:gIBSCBSMono/ns:qBCMono", namespaces=self.ns)[0].text | ||||||||||||||||
| self.assertEqual(q_bc_mono, "18.0000") | ||||||||||||||||
|
|
||||||||||||||||
| ad_rem_ibs = xml.xpath("//ns:gIBSCBSMono/ns:adRemIBS", namespaces=self.ns)[0].text | ||||||||||||||||
| self.assertEqual(ad_rem_ibs, "0.0000") | ||||||||||||||||
|
|
||||||||||||||||
| v_ibs_mono = xml.xpath("//ns:gIBSCBSMono/ns:vIBSMono", namespaces=self.ns)[0].text | ||||||||||||||||
| self.assertEqual(v_ibs_mono, "0.00") | ||||||||||||||||
|
|
||||||||||||||||
| ad_rem_cbs = xml.xpath("//ns:gIBSCBSMono/ns:adRemCBS", namespaces=self.ns)[0].text | ||||||||||||||||
| self.assertEqual(ad_rem_cbs, "0.0000") | ||||||||||||||||
|
|
||||||||||||||||
| v_cbs_mono = xml.xpath("//ns:gIBSCBSMono/ns:vCBSMono", namespaces=self.ns)[0].text | ||||||||||||||||
| self.assertEqual(v_cbs_mono, "0.00") | ||||||||||||||||
|
|
||||||||||||||||
| # Field order: qBCMono, adRemIBS, vIBSMono, adRemCBS, vCBSMono | ||||||||||||||||
| mono_elem = gibscbs_mono[0] | ||||||||||||||||
| field_names = [child.tag.split("}")[-1] for child in mono_elem] | ||||||||||||||||
| self.assertEqual( | ||||||||||||||||
| field_names, | ||||||||||||||||
| ["qBCMono", "adRemIBS", "vIBSMono", "adRemCBS", "vCBSMono"], | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| def test_cst620_monofasica_com_valores_calculados(self): | ||||||||||||||||
| """CST 620 with non-zero ad rem rates produces non-zero monophasic values.""" | ||||||||||||||||
| emitente = self._emitente() | ||||||||||||||||
| cliente = self._cliente() | ||||||||||||||||
| nf = self._nota_fiscal(emitente, cliente) | ||||||||||||||||
|
|
||||||||||||||||
| kwargs = self._base_product_kwargs() | ||||||||||||||||
| kwargs.update( | ||||||||||||||||
| codigo="011", | ||||||||||||||||
| descricao="Combustivel monofasico com ad rem", | ||||||||||||||||
| ibscbs_cst="620", | ||||||||||||||||
| ibscbs_c_class_trib="620001", | ||||||||||||||||
| ibscbs_q_bc_mono=Decimal("100.0000"), | ||||||||||||||||
| ibscbs_ad_rem_ibs=Decimal("0.1500"), | ||||||||||||||||
| ibscbs_v_ibs_mono=Decimal("15.00"), | ||||||||||||||||
| ibscbs_ad_rem_cbs=Decimal("0.8500"), | ||||||||||||||||
| ibscbs_v_cbs_mono=Decimal("85.00"), | ||||||||||||||||
| ) | ||||||||||||||||
| nf.adicionar_produto_servico(**kwargs) | ||||||||||||||||
| nf.adicionar_pagamento(t_pag="01", x_pag="Dinheiro", v_pag=1000.00, ind_pag=0) | ||||||||||||||||
|
|
||||||||||||||||
| xml = self._serializar_e_assinar() | ||||||||||||||||
|
|
||||||||||||||||
| self.assertEqual( | ||||||||||||||||
| xml.xpath("//ns:gIBSCBSMono/ns:qBCMono", namespaces=self.ns)[0].text, "100.0000" | ||||||||||||||||
| ) | ||||||||||||||||
| self.assertEqual( | ||||||||||||||||
| xml.xpath("//ns:gIBSCBSMono/ns:adRemIBS", namespaces=self.ns)[0].text, "0.1500" | ||||||||||||||||
| ) | ||||||||||||||||
| self.assertEqual( | ||||||||||||||||
| xml.xpath("//ns:gIBSCBSMono/ns:vIBSMono", namespaces=self.ns)[0].text, "15.00" | ||||||||||||||||
| ) | ||||||||||||||||
| self.assertEqual( | ||||||||||||||||
| xml.xpath("//ns:gIBSCBSMono/ns:adRemCBS", namespaces=self.ns)[0].text, "0.8500" | ||||||||||||||||
| ) | ||||||||||||||||
| self.assertEqual( | ||||||||||||||||
| xml.xpath("//ns:gIBSCBSMono/ns:vCBSMono", namespaces=self.ns)[0].text, "85.00" | ||||||||||||||||
| ) | ||||||||||||||||
|
||||||||||||||||
| ) | |
| ) | |
| self.assertEqual( | |
| xml.xpath("//ns:IBSCBSTot", namespaces=self.ns), | |
| [], | |
| "Enquanto os totais monofasicos nao forem acumulados, <IBSCBSTot> nao deve ser emitido.", | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A frase "o grupo emitido dentro de e " fica ambigua/gramaticalmente incorreta (parece listar dois grupos). Sugestao: reescrever para deixar explicito que o grupo emitido dentro de e (em vez de ).