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
1 change: 1 addition & 0 deletions requeriments.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
coveralls==0.5
six==1.9.0
pytest==2.6.4
cchardet==2.1.5
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'],
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'
47 changes: 35 additions & 12 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 @@ -19,31 +18,55 @@ def __init__(self):
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'))
def readfile(self, filename, codificacao='utf-8', verbose=None):
sucesso = False
with open(filename, 'r', encoding=codificacao) as spedfile: # encoding='utf-8', 'latin-1'
for line in [line.strip() for line in spedfile]:
# a simple way to remove multiple spaces in a string
line = re.sub('\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|...'
regt = self.read_registro(line)
# Verificar se foi lido o arquivo SPED até a última linha válida que contém o registro '9999'.
if regt.__class__ == self.__class__.registro_encerramento:
sucesso = True
break
if not sucesso:
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)
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
self._registro_abertura = registro
elif registro.__class__ == self.__class__.registro_encerramento:
# Atualizar o registro de encerramento 9999
self._registro_encerramento = registro
elif registro.__class__ == bloco.registro_abertura.__class__:
# Atualizar os registros de abertura: 0001, A001, C001, ...
bloco.registro_abertura = registro
elif registro.__class__ == bloco.registro_encerramento.__class__:
# Atualizar os registros de encerramento: 0990, A990, C990, ...
bloco.registro_encerramento = registro
else:
bloco_id = reg_id[0]
bloco = self._blocos[bloco_id]
# Adicionar novos registros a cada linha obtida de filename
bloco.add(registro)

return registro

def write_to(self, buff):
buff.write(self._registro_abertura.as_line() + u'\r\n')
reg_count = 2
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)
33 changes: 32 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,6 +187,8 @@ 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:
Expand Down Expand Up @@ -275,3 +276,33 @@ def validar(valor):
if len(valor) == 11:
return CampoCPF.validar(valor)
return False


# 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('^\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)

Loading