Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Biblioteca para geração dos arquivos do Sistema Público de Escrituração Dig

* python
* six
* coveralls
* pytest
* cchardet
* xlsxwriter

## Como instalar

Expand Down
2 changes: 2 additions & 0 deletions requeriments.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
coveralls==0.5
six==1.9.0
pytest==2.6.4
cchardet==2.1.5
xlsxwriter=1.2.7
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def run_tests(self):
'Programming Language :: Python :: 3.6',
],
keywords='sped fiscal contábil contabilidade receita federal',
install_requires=['six'],
install_requires=['six','cchardet','xlsxwriter'],
tests_require=['pytest'],
extras_require={
'dev': ['pylint>=1.9.1'],
Expand Down
2 changes: 1 addition & 1 deletion sped/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
from .escrituracao import Escrituracao


__version__ = '1.0.2'
__version__ = '1.0.3'
51 changes: 38 additions & 13 deletions sped/arquivos.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-


import re
from collections import OrderedDict
from io import StringIO

from .registros import RegistroIndefinido


class ArquivoDigital(object):
registros = None
blocos = None
Expand All @@ -18,30 +17,56 @@ def __init__(self):
self._registro_abertura = self.registro_abertura()
self._registro_encerramento = self.registro_encerramento()
self._blocos = OrderedDict()

def readfile(self, filename):
with open(filename) as spedfile:
for line in [line.rstrip('\r\n') for line in spedfile]:
self.read_registro(line.decode('utf8'))
self.leitura_completa = False

def readfile(self, filename, codificacao=None, verbose=None):
if codificacao is None: # 'utf-8', 'latin-1', ...
codificacao = 'utf-8'
with open(filename, 'r', encoding=codificacao, errors='ignore') as spedfile:
for line in [line.strip() for line in spedfile]:
# A simple way to remove multiple spaces in a string
line = re.sub(r'\s{2,}', ' ', line)
# Em algumas EFDs foram encontrados registros digitados incorretamente em minúsculo.
# Por exemplo, o registro 'c491' deve ser corrigido para 'C491'.
line = line[:6].upper() + line[6:] # line = '|c491|...' --> '|C491|...'
self.read_registro(line)
# Verificar se o arquivo SPED foi lido até a última linha válida que contém o registro '9999'.
if self.leitura_completa:
break
if not self.leitura_completa:
raise RuntimeError(u"\nOcorreu uma falha ao ler o arquivo: '%s'.\n" % filename)
elif verbose:
print(u"O arquivo SPED '%s' foi lido com sucesso.\n" % filename)

def read_registro(self, line):
reg_id = line.split('|')[1]

try:
registro_class = getattr(self.__class__.registros,
'Registro' + reg_id)
# https://stackoverflow.com/questions/25577578/access-class-variable-from-instance
# Devo substituir 'self.__class__.registros' por 'type(self).registros' ?
registro_class = getattr(self.__class__.registros, 'Registro' + reg_id)
except AttributeError:
raise RuntimeError(u"Arquivo inválido para EFD - PIS/COFINS")
raise RuntimeError(u"Arquivo inválido para EFD - PIS/COFINS. Registro: %s" % reg_id)

registro = registro_class(line)
bloco_id = reg_id[0]
bloco = self._blocos[bloco_id]

if registro.__class__ == self.__class__.registro_abertura:
# Atualizar o registro de abertura 0000 do SPED
self._registro_abertura = registro
elif registro.__class__ == self.__class__.registro_encerramento:
# Atualizar o registro de encerramento 9999 do SPED
self._registro_encerramento = registro
self.leitura_completa = True
elif registro.__class__ == bloco.registro_abertura.__class__:
# Atualizar os registros de abertura dos blocos: 0001, A001, C001, ...
bloco.registro_abertura = registro
elif registro.__class__ == bloco.registro_encerramento.__class__:
# Atualizar os registros de encerramento dos blocos: 0990, A990, C990, ...
bloco.registro_encerramento = registro
else:
bloco_id = reg_id[0]
bloco = self._blocos[bloco_id]
# Adicionar informações dos registros a cada linha obtida de filename
bloco.add(registro)

def write_to(self, buff):
Expand Down
8 changes: 1 addition & 7 deletions sped/blocos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from .registros import Registro


class Bloco(object):
def __init__(self, nome=''):
self._nome = nome
Expand All @@ -14,20 +13,15 @@ def __repr__(self):

@property
def abertura(self):
# Define o indicador de movimento ou dados
return self.registro_abertura

@property
def encerramento(self):
# Define a quantidade de registros
return self.registro_encerramento

@property
def registros(self):
return [self.abertura] + self._registros + [self.encerramento]

def add(self, registro):
# Não adiciona o registro de abertura e fechamento
if not registro.__class__ == self.registro_abertura.__class__ and \
not registro.__class__ == self.registro_encerramento.__class__:
self._registros.append(registro)
self._registros.append(registro)
93 changes: 92 additions & 1 deletion sped/campos.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-


import re

from datetime import date
Expand Down Expand Up @@ -188,13 +187,23 @@ def get(self, registro):
return datetime.strptime(valor, '%d%m%Y').date()

def set(self, registro, valor):
# https://stackoverflow.com/questions/19887353/attributeerror-str-object-has-no-attribute-strftime
valor = datetime.strptime(valor, '%d%m%Y')
if isinstance(valor, date):
super().set(registro, valor.strftime('%d%m%Y'))
elif not valor:
super().set(registro, None)
else:
raise FormatoInvalidoError(registro, self.nome)

@staticmethod
def formatar(data_in):
dt = datetime.strptime(data_in, "%d%m%Y") # ddmmaaaa
#data_out = dt.isoformat('T')
#data_out = dt.strftime('%x %X') # excel date format
data_out = dt.strftime("%d/%m/%Y")
return data_out


class CampoRegex(Campo):
def __init__(self, indice, nome, obrigatorio=False, regex=None):
Expand Down Expand Up @@ -239,6 +248,16 @@ def validar(valor):

return True

@staticmethod
def formatar(cnpj):
mensagem_de_validacao = ''
if len(cnpj) >= 1:
if not CampoCNPJ.validar(cnpj):
mensagem_de_validacao = ' : dígito verificador do cnpj inválido!'
if len(cnpj) == 14:
cnpj = "%s.%s.%s/%s-%s" % (cnpj[0:2],cnpj[2:5],cnpj[5:8],cnpj[8:12],cnpj[12:14])
return cnpj + mensagem_de_validacao


class CampoCPF(Campo):
@staticmethod
Expand All @@ -265,6 +284,16 @@ def validar(valor):
return False

return True

@staticmethod
def formatar(cpf):
mensagem_de_validacao = ''
if len(cpf) >= 1:
if not CampoCPF.validar(cpf):
mensagem_de_validacao = ' : dígito verificador do cpf inválido!'
if len(cpf) == 11:
cpf = "%s.%s.%s-%s" % (cpf[0:3],cpf[3:6],cpf[6:9],cpf[9:11])
return cpf + mensagem_de_validacao


class CampoCPFouCNPJ(Campo):
Expand All @@ -275,3 +304,65 @@ def validar(valor):
if len(valor) == 11:
return CampoCPF.validar(valor)
return False

@staticmethod
def formatar(digt):
mensagem_de_validacao = ''
if len(digt) >= 1:
if len(digt) == 11 and not CampoCPF.validar(digt):
mensagem_de_validacao = ' : dígito verificador do cpf inválido!'
elif len(digt) == 14 and not CampoCNPJ.validar(digt):
mensagem_de_validacao = ' : dígito verificador do cnpj inválido!'

if len(digt) == 11:
digt = "CPF %s.%s.%s-%s" % (digt[0:3],digt[3:6],digt[6:9],digt[9:11])
elif len(digt) == 14:
digt = "CNPJ %s.%s.%s/%s-%s" % (digt[0:2],digt[2:5],digt[5:8],digt[8:12],digt[12:14])
return digt + mensagem_de_validacao


# Fonte: 'NFe Manual_de_Orientacao_Contribuinte_v_6.00.pdf', pg 144.
# 5.4 Cálculo do Dígito Verificador da Chave de Acesso da NF-e
class CampoChaveEletronica(Campo):
@staticmethod
def validar(valor):
if not re.search(r'^\d{44}$', str(valor)):
return False

chave = [int(digito) for digito in valor]
multiplicadores = [4, 3, 2] + [9, 8, 7, 6, 5, 4, 3, 2] * 5 + [0]

soma = sum([chave[i] * multiplicadores[i] for i in range(44)])

resto_da_divisao = soma % 11
digito_verificador = 11 - resto_da_divisao

if digito_verificador >= 10:
digito_verificador = 0

if chave[-1] != digito_verificador:
return False

# dentro da chave eletrônica há o CNPJ do emitente
# que também será verificado
cnpj = str(valor)[6:20]

return CampoCNPJ.validar(cnpj)

@staticmethod
def formatar(chave):
mensagem_de_validacao = ''
if len(chave) >= 1:
if not CampoChaveEletronica.validar(chave):
mensagem_de_validacao = ' : dígito verificador da chave inválido!'
if len(chave) == 44:
chave = "%s.%s.%s.%s.%s.%s.%s.%s-%s" % (chave[0:2],chave[2:6],chave[6:20],chave[20:22],chave[22:25],chave[25:34],chave[34:35],chave[35:43],chave[43:44])
return chave + mensagem_de_validacao


class CampoNCM(Campo):
@staticmethod
def formatar(ncm):
if len(ncm) == 8:
ncm = "%s.%s.%s" % (ncm[0:4],ncm[4:6],ncm[6:8])
return ncm
Loading