Skip to content

Commit

Permalink
Grupo de pagamentos (#332)
Browse files Browse the repository at this point in the history
* Grupo de pagamentos

* Avoid 0 that is not None but False

* tipo_pagamento as default None

* Use the new method on test_nfce_serializacao
  • Loading branch information
felps-dev authored Apr 27, 2024
1 parent 52470db commit 7e1e6d5
Show file tree
Hide file tree
Showing 33 changed files with 460 additions and 50 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,5 @@ target/
db.*

TODO*

venv
7 changes: 7 additions & 0 deletions pynfe/entidades/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class CampoDeprecated(object):
anterior: str
novo: str
motivo: str
apenas_warning: bool = False


class Entidade(object):
Expand Down Expand Up @@ -53,6 +54,12 @@ def __setattr__(self, name, value):
)
setattr(self, campo_deprecado.novo, value)
return
if campo_deprecado.apenas_warning:
warnings.warn(
f"O campo {campo_deprecado.anterior} foi deprecado e será removido em versões futuras. "
f"Motivo: {campo_deprecado.motivo}",
DeprecationWarning,
)
else:
raise AttributeError(
f"O campo {campo_deprecado.anterior} foi deprecado e removido."
Expand Down
54 changes: 47 additions & 7 deletions pynfe/entidades/notafiscal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@


class NotaFiscal(Entidade):
# campos deprecados
campos_deprecados = [
CampoDeprecated(
"tipo_pagamento",
novo=None,
motivo="Por favor utilize os grupos de pagamento pela função adicionar_pagamento.",
apenas_warning=True,
),
]

status = NF_STATUS[0]

# Código numérico aleatório que compõe a chave de acesso
Expand Down Expand Up @@ -73,7 +83,7 @@ class NotaFiscal(Entidade):
90= Sem pagamento
99=Outros
"""
tipo_pagamento = int()
tipo_pagamento = None

# - Forma de emissao (obrigatorio - seleciona de lista) - NF_FORMAS_EMISSAO
forma_emissao = str()
Expand Down Expand Up @@ -381,6 +391,11 @@ class NotaFiscal(Entidade):
# - Processo Referenciado (lista 1 para * / ManyToManyField)
processos_referenciados = None

# - pagamentos
pagamentos = list()
# valor do troco
valor_troco = Decimal()

def __init__(self, *args, **kwargs):
self.autorizados_baixar_xml = []
self.notas_fiscais_referenciadas = []
Expand All @@ -390,12 +405,19 @@ def __init__(self, *args, **kwargs):
self.observacoes_contribuinte = []
self.processos_referenciados = []
self.responsavel_tecnico = []
self.pagamentos = []

super(NotaFiscal, self).__init__(*args, **kwargs)

def __str__(self):
return " ".join([str(self.modelo), self.serie, self.numero_nf])

def adicionar_pagamento(self, **kwargs):
"""Adiciona uma instancia de Responsavel Tecnico"""
obj = NotaFiscalPagamentos(**kwargs)
self.pagamentos.append(obj)
return obj

def adicionar_autorizados_baixar_xml(self, **kwargs):
obj = AutorizadosBaixarXML(**kwargs)
self.autorizados_baixar_xml.append(obj)
Expand Down Expand Up @@ -551,12 +573,6 @@ def identificador_unico(self):


class NotaFiscalReferenciada(Entidade):
# Campos depreciados
campos_deprecados = [
CampoDeprecated("fcp_percentual", "fcp_aliquota", "Consistencia de nomes"),
CampoDeprecated("fcp_st_percentual", "fcp_st_aliquota", "Consistencia de nomes"),
]

# - Tipo (seleciona de lista) - NF_REFERENCIADA_TIPOS
tipo = str()

Expand Down Expand Up @@ -588,6 +604,11 @@ class NotaFiscalReferenciada(Entidade):


class NotaFiscalProduto(Entidade):
# Campos depreciados
campos_deprecados = [
CampoDeprecated("fcp_percentual", "fcp_aliquota", "Consistencia de nomes"),
CampoDeprecated("fcp_st_percentual", "fcp_st_aliquota", "Consistencia de nomes"),
]
# - Dados
# - Codigo (obrigatorio)
codigo = str()
Expand Down Expand Up @@ -1210,3 +1231,22 @@ class NotaFiscalResponsavelTecnico(Entidade):

class AutorizadosBaixarXML(Entidade):
CPFCNPJ = str()


class NotaFiscalPagamentos(Entidade):
# forma de pagamento flag: FORMAS_PAGAMENTO
t_pag = str()
# descrição da forma de pagametno
x_pag = str()
# valor
v_pag = Decimal()
# tipo de integracao: '', '1' integrado, '2' - não integrado
tp_integra = str()
# CNPJ da Credenciadora de cartão de crédito e/ou débito
cnpj = str()
# Bandeira da operadora de cartão de crédito e/ou débito flag: BANDEIRA_CARTAO
t_band = int()
# Número de autorização da operação cartão de crédito e/ou débito
c_aut = str()
# Indicador da Forma de Pagamento: 0=à Vista, 1=à Prazo
ind_pag = int()
119 changes: 88 additions & 31 deletions pynfe/processamento/serializacao.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
import base64
import hashlib
import re
import warnings

from datetime import datetime

import pynfe.utils.xml_writer as xmlw
from pynfe.entidades import Manifesto, NotaFiscal
from pynfe.utils import (
etree,
Expand Down Expand Up @@ -1341,6 +1344,71 @@ def _serializar_responsavel_tecnico(
else:
return raiz

def _serializar_pagamentos_antigo_deprecado(self, tipo_pagamento, finalidade_emissao, totais_icms_total_nota):
pag = etree.Element('pag')
detpag = etree.SubElement(pag, "detPag")
if (
str(finalidade_emissao) == "3"
or str(finalidade_emissao) == "4"
):
etree.SubElement(detpag, "tPag").text = "90"
etree.SubElement(detpag, "vPag").text = "{:.2f}".format(0)
else:
etree.SubElement(detpag, "tPag").text = str(
tipo_pagamento
).zfill(2)
etree.SubElement(detpag, "vPag").text = "{:.2f}".format(
totais_icms_total_nota
)
if tipo_pagamento == 3 or tipo_pagamento == 4:
cartao = etree.SubElement(detpag, "card")
""" Tipo de Integração do processo de pagamento com
o sistema de automação da empresa:
1=Pagamento integrado com o sistema de automação da empresa
2= Pagamento não integrado com o sistema de automação da empresa
"""
etree.SubElement(cartao, "tpIntegra").text = "2"
# etree.SubElement(cartao, 'CNPJ').text = ''
# # Informar o CNPJ da Credenciadora de cartão de crédito / débito
# etree.SubElement(cartao, 'tBand').text = ''
# # 01=Visa 02=Mastercard 03=American Express 04=Sorocred
# 05=Diners Club 06=Elo 07=Hipercard 08=Aura 09=Caba 99=Outros
# etree.SubElement(cartao, 'cAut').text = ''
# # Identifica o número da autorização da transação da operação
# com cartão de crédito e/ou débito
# troco
# etree.SubElement(pag, 'vTroco').text = str('')
return pag

def _serializar_pagamentos(self, pagamentos: list(), finalidade_emissao='', valor_troco = 0.00, retorna_string=True):
pag = etree.Element('pag')
if (finalidade_emissao in [3, 4]):
detpag = etree.SubElement(pag, "detPag")
etree.SubElement(detpag, "tPag").text = "90"
etree.SubElement(detpag, "vPag").text = "{:.2f}".format(0)
else:
for item in pagamentos:
det = etree.Element("detPag")
xmlw.write_txt(det, "indPag", item.ind_pag, False)
xmlw.write_txt(det, "tPag", item.t_pag, True)
xmlw.write_txt(det, 'xPag', item.x_pag, False)
xmlw.write_float(det, 'vPag', item.v_pag, True, 2, 2)
if item.tp_integra:
card = etree.SubElement(det, "card")
xmlw.write_txt(card, "tpIntegra", item.tp_integra, True)
xmlw.write_txt(card, "CNPJ", item.cnpj, False)
xmlw.write_txt(card, "tBand", item.t_band, False)
xmlw.write_txt(card, "cAut", item.c_aut, False)
pag.append(det)

# troco
xmlw.write_float(pag, 'vTroco', valor_troco, False, 2, 2)

if retorna_string:
return etree.tostring(pag, encoding="unicode", pretty_print=False)
else:
return pag

def _serializar_nota_fiscal(
self, nota_fiscal, tag_raiz="infNFe", retorna_string=True
):
Expand Down Expand Up @@ -1710,39 +1778,28 @@ def _serializar_nota_fiscal(
""" Obrigatório o preenchimento do Grupo Informações de Pagamento para NF-e e NFC-e.
Para as notas com finalidade de Ajuste ou Devolução
o campo Forma de Pagamento deve ser preenchido com 90=Sem Pagamento. """
pag = etree.SubElement(raiz, "pag")
detpag = etree.SubElement(pag, "detPag")
if (
str(nota_fiscal.finalidade_emissao) == "3"
or str(nota_fiscal.finalidade_emissao) == "4"
):
etree.SubElement(detpag, "tPag").text = "90"
etree.SubElement(detpag, "vPag").text = "{:.2f}".format(0)
if (nota_fiscal.tipo_pagamento is not None):
warnings.warn(
"O campo 'tipo_pagamento' está obsoleto e será removido em versões futuras. "
"Utilize o campo 'pagamentos' em seu lugar.",
DeprecationWarning,
)
raiz.append(
self._serializar_pagamentos_antigo_deprecado(
tipo_pagamento=nota_fiscal.tipo_pagamento,
finalidade_emissao=nota_fiscal.finalidade_emissao,
totais_icms_total_nota=nota_fiscal.totais_icms_total_nota,
)
)
else:
etree.SubElement(detpag, "tPag").text = str(
nota_fiscal.tipo_pagamento
).zfill(2)
etree.SubElement(detpag, "vPag").text = "{:.2f}".format(
nota_fiscal.totais_icms_total_nota
raiz.append(
self._serializar_pagamentos(
pagamentos=nota_fiscal.pagamentos,
finalidade_emissao=nota_fiscal.finalidade_emissao,
valor_troco=nota_fiscal.valor_troco,
retorna_string=False
)
)
if nota_fiscal.tipo_pagamento == 3 or nota_fiscal.tipo_pagamento == 4:
cartao = etree.SubElement(detpag, "card")
""" Tipo de Integração do processo de pagamento com
o sistema de automação da empresa:
1=Pagamento integrado com o sistema de automação da empresa
2= Pagamento não integrado com o sistema de automação da empresa
"""
etree.SubElement(cartao, "tpIntegra").text = "2"
# etree.SubElement(cartao, 'CNPJ').text = ''
# # Informar o CNPJ da Credenciadora de cartão de crédito / débito
# etree.SubElement(cartao, 'tBand').text = ''
# # 01=Visa 02=Mastercard 03=American Express 04=Sorocred
# 05=Diners Club 06=Elo 07=Hipercard 08=Aura 09=Caba 99=Outros
# etree.SubElement(cartao, 'cAut').text = ''
# # Identifica o número da autorização da transação da operação
# com cartão de crédito e/ou débito
# troco
# etree.SubElement(pag, 'vTroco').text = str('')

# Informações adicionais
if (
Expand Down
66 changes: 66 additions & 0 deletions pynfe/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import os
from unicodedata import normalize
from signxml import XMLSigner
from typing import Literal
from decimal import Decimal

try:
from lxml import etree # noqa: F401
Expand Down Expand Up @@ -178,3 +180,67 @@ def __init__(self, method, signature_algorithm, digest_algorithm, c14n_algorithm

def check_deprecated_methods(self):
pass

def is_empty(value):
"""
Verifica se um valor está vazio.
Parameters:
- value: O valor a ser verificado.
Returns:
- True se o valor estiver vazio, False caso contrário.
"""
if value is None:
return True
elif isinstance(value, (int, float, Decimal)) and value == Decimal(0):
# Verifica se o valor numérico é igual a zero.
return True
elif isinstance(value, str) and not value.strip():
# Verifica se a string está vazia ou contém apenas espaços em branco.
return True
elif isinstance(value, (list, tuple, dict)) and not value:
# Verifica se a lista, tupla ou dicionário está vazio.
return True
else:
return False


def truncar_valor(float_number: float, decimal_places: int, suprimir_zeros: bool = False):
multiplier = 10**decimal_places
result = str(int(float_number * multiplier) / multiplier)
if suprimir_zeros:
result = result.rstrip("0").rstrip(".")
return result


def arredondar_valor(value: float, decimal_places: int, suprimir_zeros: bool = False):
f = f"%.{decimal_places}f"
result = f % value
if suprimir_zeros:
result = result.rstrip("0").rstrip(".")
return result


def ajustar_valor(
value: float, decimal_places: int = 2, min_decimal_places: int = 2, tipo: Literal["ROUND", "TRUNC"] = "ROUND", decimal_separator: str = "."
):
value = 0 if value is None else value

formated_value: str = "0"
supress_zeros = min_decimal_places < decimal_places

if tipo == "ROUND":
formated_value = arredondar_valor(value, decimal_places, supress_zeros)
else:
formated_value = truncar_valor(value, decimal_places, supress_zeros)

pi, sep, dec = list(formated_value.partition("."))

# preenche com zeros a direita até a quantidade minima
if min_decimal_places:
dec = dec.ljust(min_decimal_places, "0")
# se não tem decimais não haverá separator
sep = decimal_separator if dec else ""

return f"{pi}{sep}{dec}".replace(".", decimal_separator)
Loading

0 comments on commit 7e1e6d5

Please sign in to comment.