diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f20b403
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+bin
+env
+*png
+__pycache__
diff --git a/32211207872718000117550010000217781877120005-nfe.xml b/32211207872718000117550010000217781877120005-nfe.xml
deleted file mode 100644
index 0b01f56..0000000
--- a/32211207872718000117550010000217781877120005-nfe.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-3287712000VENDA MERCAD.ADQ.TERCEIROS551217782021-12-01T19:03:21-03:002021-12-01T19:03:21-03:0011320130811511010MeLinux 6.307872718000117COMERCIAL S.R.DE ALIMENTOS LTDA-MESR ALIMENTOSR.ODONIA DA COSTA MACHADO TOLEDO56QUADRA 1 LOTE 5TIRADENTES3201308CARIACICAES291435291058BRASIL2733363026082384975317197072000173JEFERSON SIMAO DE OLIVEIRA - MERUA SAO JOAO49ITACIBA3201308CARIACICAES291502301058BRASIL279965021211082917825001378SEM GTINFILE MERLUZA ARGENTINA INTERF.CX 18 c/ 18 KG030474005102CX1503.82503.82SEM GTINCX1827.991220358.82207.4617.0035.279995301503.820.653.2701503.823.0015.110001957896481907388CUPIM MAGRO FRISA CX -+ 18KG0201300017084005405KG21.7129.12989406632.417896481907388KG21.7129.1298940610609995301632.410.654.1101632.413.0018.97207.4635.270.000.000.000.000.000.001136.230.000.000.000.000.000.007.3834.080.001136.23907872718000117COMERCIAL S.R.DE ALIMENTOS LTDA-ME082384975RUA ODONIA DA COSTA MACHADO TOLEDO, 41 QD 1 LOTE 5 TIRADENTECARIACICAES23DIVERSASDIVERSAS40.1060217781136.230.001136.230012022-12-15568.110022022-12-22568.12151136.23Cod.Dest: 7452 Fantasia: REST.ESPA.GRILL Faturista: IGOR REIS # Forma Pag: BOLETO # Vendedor: TACIANI (27)99735-5228 Num.Ped: 102667 # Doc.emitido por ME/EPP optante do SIMPLES NACIONAL; nao gera credito fiscal de IPI e ISS # RUA DA FEIRA # sem email cadastrado para envio do XML # # Confira no ato da entrega, evite reclamacoes posteriores19495981000113OTMA SOLUCOES LTDAcontato@otmasolucoes.com.br2721417943yU29u4Sva8be/dmxDzD7eyMIOaY=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
-1SVRS202111190951322112078727180001175500100002177818771200052021-12-01T19:03:22-03:00332210083442441yU29u4Sva8be/dmxDzD7eyMIOaY=100Autorizado o uso da NF-e
\ No newline at end of file
diff --git a/Database.py b/Database.py
new file mode 100644
index 0000000..4cc4c1d
--- /dev/null
+++ b/Database.py
@@ -0,0 +1,218 @@
+from multiprocessing.connection import Connection
+import sqlite3
+from sqlite3 import Error
+from NotaFiscal import NotaFiscal
+
+'''
+Utilização do banco de dados:
+ Justificativa: Uma vez que um arquivo é lido ele é salvo no banco permitindo
+ acesso as informações relevante a aplicação em executações futuras do mesmo
+ 1. O programa deve listar valores e data de vencimento de boletos segundo um dado
+ indentificador (CPF OU CNPJ) de um fornecedor (emitente).
+ 2. o program deve listar nome, identificador (CPF ou CNPJ) e endereço dos
+ clientes de um dado fornecedor(emitente)
+
+'''
+
+class Database():
+ def __init__(self, input_path: str, schema_path: str):
+ self.db_path : str = input_path
+ self.schema_path : str = schema_path
+ self.conn : Connection = self.create_connection()
+ self.cursor = self.conn.cursor()
+
+ # Cria conexão com um banco de dados já existente ou cria
+ # um novo banco de dados atraves do esquema passado
+ def create_connection(self) -> Connection:
+ conn : Connection = None
+ try:
+ conn = sqlite3.connect(self.db_path)
+ with open(self.schema_path) as f:
+ conn.executescript(f.read())
+ return conn
+ except Error as e:
+ print(e)
+
+ return conn
+
+ # Cria tabela no banco de dados ou caso sejam passos argumentos
+ # os mesmos são usados para prencher uma tabela existente.
+ def create_table(self, create_table_sql : str, args : str = None) -> None:
+ try:
+ if args == None:
+ self.cursor.execute(create_table_sql)
+ else:
+ self.cursor.execute(create_table_sql, args)
+ self.conn.commit()
+ except Error as e:
+ print(e)
+
+ def create_emitente(self, emitente) -> None:
+
+ sql = ''' INSERT INTO emitente(emitente_id,
+ nome)
+ VALUES(?,?) '''
+ self.create_table(sql, (emitente['emitente_id'],
+ emitente['nome']))
+
+ def create_destinador(self, destinador) -> None:
+ sql = ''' INSERT INTO destinador(destinador_id,
+ nome)
+ VALUES(?,?) '''
+ self.create_table(sql, (destinador['destinador_id'],
+ destinador['nome']))
+
+ def create_endereco_destinador(self, endereco) -> None:
+ sql = ''' INSERT INTO endereco_destinador(destinador_endereco_id,
+ destinador_id,
+ logradouro,
+ numero,
+ bairro,
+ municipio,
+ estado,
+ cep,
+ telefone)
+ VALUES(?,?,?,?,?,?,?,?,?) '''
+ self.create_table(sql, (endereco['destinador_endereco_id'],
+ endereco['destinador_id'],
+ endereco['xLgr'],
+ endereco['nro'],
+ endereco['xBairro'],
+ endereco['xMun'],
+ endereco['UF'],
+ endereco['CEP'],
+ endereco['fone']
+ ))
+
+ def create_nota_fiscal(self, nota_fiscal):
+ sql = ''' INSERT INTO nota_fiscal(nota_fiscal_id,
+ emitente_id,
+ destinador_id,
+ valor_total)
+ VALUES(?,?,?,?) '''
+
+ self.create_table(sql, (nota_fiscal['nota_fiscal_id'],
+ nota_fiscal['emitente_id'],
+ nota_fiscal['destinador_id'],
+ nota_fiscal['valor_total']))
+
+ def create_duplicata(self, duplicata):
+ sql = ''' INSERT INTO duplicata(duplicata_id,
+ nota_fiscal_id,
+ data_vencimento,
+ valor_pago)
+ VALUES(?,?,?,?) '''
+ self.create_table(sql, (duplicata['duplicata_id'],
+ duplicata['nota_fiscal_id'],
+ duplicata['data_vencimento'],
+ duplicata['valor_pago']))
+
+ # Checa se um dado identificador existe em uma dada tabela
+ def check_if_exists_in_bd(self, table : str, identifier : str) -> bool:
+ sql = "SELECT * FROM "+ table + " WHERE "+ table +"_id = ?"
+ self.cursor.execute(sql, (identifier,))
+
+ data = self.cursor.fetchone()
+ if data is None:
+ return True
+ else:
+ print('Emitente com identificador %s já existe no sistema'%(identifier))
+ return False
+
+ # Consulta nota fiscal de duplicatas de um dado emitente.
+ def consulta_nota_fiscal_e_duplicatas_de_um_emitente(self, identificador_emitente : str) -> dict:
+
+ self.cursor.execute("SELECT * FROM nota_fiscal WHERE emitente_id = ?",
+ (identificador_emitente,))
+ notas_fiscais=self.cursor.fetchall()
+ if notas_fiscais == None:
+ return None
+
+ notas_fiscais_dict = []
+ for nota_fiscal in notas_fiscais:
+ nota_fiscal_dict = {}
+
+ nota_fiscal_dict['nota_fical_id'] = nota_fiscal[0]
+ nota_fiscal_dict['valor_total'] = nota_fiscal[3]
+
+ self.cursor.execute("SELECT * FROM duplicata WHERE nota_fiscal_id = ?",
+ (nota_fiscal[0],))
+ duplicatas=self.cursor.fetchall()
+ duplicatas_dict = []
+
+ for dup in duplicatas:
+ duplicata_dict = {}
+ duplicata_dict['duplicata_id'] = dup[0].split('NFe')[0]
+ duplicata_dict['data_vencimento'] = dup[2]
+ duplicata_dict['valor_pago'] = dup[3]
+ duplicatas_dict.append(duplicata_dict)
+ nota_fiscal_dict['duplicatas'] = duplicatas_dict
+ notas_fiscais_dict.append(nota_fiscal_dict)
+
+ return notas_fiscais_dict
+
+
+ def consulta_clientes_de_um_emitente(self, identificador_emitente : str) -> dict:
+
+ self.cursor.execute("SELECT * FROM nota_fiscal WHERE emitente_id = ?",
+ (identificador_emitente,))
+ notas_fiscais=self.cursor.fetchall()
+
+ if notas_fiscais == None:
+ return None
+
+ destinadores_dict = []
+
+ for nota_fiscal in notas_fiscais:
+ self.cursor.execute("SELECT * FROM destinador WHERE destinador_id = ?",
+ (nota_fiscal[2],))
+ destinadores=self.cursor.fetchall()
+
+ for dest in destinadores:
+
+ destinador_dict = {'destinador_id' : dest[0], 'nome':dest[1]}
+ destinadores_dict.append(destinador_dict)
+
+ return destinadores_dict
+
+ # Salva nota fiscal no banco de dados
+ def save_nota_fiscal_on_db(self, nota_fiscal : NotaFiscal):
+
+ # Emitente
+ # Salva novo emitente (fornecedor) no banco de dados caso o mesmo
+ # não exista.
+ emitente_dict = {'emitente_id' : nota_fiscal.get_emit_identifier(),
+ 'nome' : nota_fiscal.get_emit_name()}
+ if self.check_if_exists_in_bd('emitente',emitente_dict['emitente_id']):
+ self.create_emitente(emitente_dict)
+
+ # Destinador
+ # Salva novo destinador (cliente) no banco de dados caso o mesmo
+ # não exista
+ destinador_dict = {'destinador_id' : nota_fiscal.get_dest_identifier(),
+ 'nome' : nota_fiscal.get_dest_name()}
+
+ if self.check_if_exists_in_bd("destinador",destinador_dict['destinador_id']):
+ self.create_destinador(destinador_dict)
+
+ # Endereço do destinador
+ # Salva endereço relacionado a um novo destinador.
+ endereco_destinador = nota_fiscal.get_dest_enderDest()
+ self.create_endereco_destinador(endereco_destinador)
+
+ # Nota fiscal
+ # Salva uma nova nota fiscal no banco caso a mesma não exista
+ nota_fiscal_dict = {'nota_fiscal_id' : nota_fiscal.get_nota_fiscal_id(),
+ 'emitente_id' : nota_fiscal.get_emit_identifier(),
+ 'destinador_id' : nota_fiscal.get_dest_identifier(),
+ 'valor_total' : nota_fiscal.get_nota_fiscal_valor_total()}
+
+ if self.check_if_exists_in_bd("nota_fiscal",nota_fiscal_dict['nota_fiscal_id']):
+ self.create_nota_fiscal(nota_fiscal_dict)
+
+ # Salva as duplicatas relacionadas a uma nota fiscal.
+ # Duplicata é parte da fatura pois a mesma pode ter mais de uma
+ # parcela. Duplicata também é chamada de boleto no contexto em
+ # que esse programa se aplica
+ for duplicata in nota_fiscal.get_faturas():
+ self.create_duplicata(duplicata)
\ No newline at end of file
diff --git a/NFe-002-3103.xml b/NFe-002-3103.xml
deleted file mode 100644
index 831d193..0000000
--- a/NFe-002-3103.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-3100464032Vendas a prazo55231032019-04-10T17:24:03-02:002019-04-11T17:17:30-02:001131702061171101000106273476000182MECA Office Mobil. Eireli-MEMECA Office Mobil. Eireli-MEAV. MARCOS DE FREITAS COSTA1055DANIEL FOSECA3170206UberlandiaMG384003281058BRASIL34323855857022916720058125587387000155HLTS ENGENHARIA E CONSTRUCOES LTDARUA MACHADO DE ASSIS1324LIDICE3170206UberlandiaMG384000811058BRASIL34322359661702177134005400331SEM GTINCADEIRA GIRATORIA S/ BRACO ALMOFADADA PRETO940190905102UN2165.00000330.00SEM GTINUN2165.00000177.6801020808IMOBILIZADO IMOB - 2317 CADEIRA GIRATORIA S/ BRACO ALMOFADADA PRETO - IMOBILIZADO EQUIP. MOVEIS P/ CANTEIRO MATERIAIS INSTALACAO DE CANTEIRO.01228SEM GTINCADEIRA GIRATORIA C/ BRACO ALMOFADADA PRETO940190905102UN1215.00000215.00SEM GTINUN1215.00000150.6101020808IMOBILIZADO EQUIP/MOVEIS P/ CANTEIRO MATERIAIS INSTALACAO DE CANTEIRO01324SEM GTINBANQUETA ALTA 70CM PARA BALCAO ASSENTO 30 OU940190905102PC190.0000090.00SEM GTINPC190.00000121.190102080840CM NA COR PRETA - IMOBILIZADO EQUIP./MOVEIS P/ CANTEIRO MATERIAIS INSTALACAO DE CANTEIRO01644SEM GTINESTANTE ACO C/ 06 PRATELEIRAS 0,93X040X1,98M940690205102PC2234.00000468.00SEM GTINPC2234.00000145.8701020808(REFORCADA) AMAPA - IMOBILIZADO.0.000.000.000.000.000.000.000.001103.000.000.000.000.000.000.000.000.000.001103.00195.3506VOLUMEVARIAS0.0000.0000000031031103.000.001103.000012019-05-111103.00141103.000.00ORDEM DE FORNECIMENTO 36994 - 28DD - INFORMACOES COMPLEMENTARES a seguinte informacao. EMPRESA ENQUADRADA NO SIMPLES NACIONAL. NAO GERA CREDITO DE IPI/ISS. GERA CREDITO DE ICMS. Trib aprox R$: 54,84 Federal 140,51 Estadual Fonte: IBPT empresometro.com.br S3A6R4ngqVwH6QNCAHyRuI529RIAr7Nyk=IAxnZ+del9SR4hBrWJOxR6R+9+4wX7K4QIFevGOhjzE36Fe77GbFB3SigoqsZ+ypUDyCz/6dm7ejsDjC6s3ROafT8NBrMFL0bE14WhNK0D0GdrLWCUZdi+IGT/B4rw8unpwq+2JVPe7vLdxpRZPPYaoZCt52yLBiZTxnGEoHRIgUbvByiYDTxvXStpRXXUKCrd2/2G13W+HoEVWOtg97taSgQfbiOT5kTGCC9DQ/EthiOj71TFaWIQV18pfwjAeP0cNFMAp5ILEmXfKZ/Jm6LKRoiVfUZRafK+QU7MatTGHxWKyZSvW/82Ob38kT6jZChea+7vh9N9hDQiTcWmcGUw==MIIH/DCCBeSgAwIBAgIIeSrFaXUFq/8wDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCQlIxEzARBgNVBAoTCklDUC1CcmFzaWwxNjA0BgNVBAsTLVNlY3JldGFyaWEgZGEgUmVjZWl0YSBGZWRlcmFsIGRvIEJyYXNpbCAtIFJGQjEUMBIGA1UEAxMLQUMgTElOSyBSRkIwHhcNMTcwNjA1MTMwMDI3WhcNMjAwNjA1MTMwMDI3WjCB4DELMAkGA1UEBhMCQlIxCzAJBgNVBAgTAk1HMRMwEQYDVQQHEwpVQkVSTEFORElBMRMwEQYDVQQKEwpJQ1AtQnJhc2lsMTYwNAYDVQQLEy1TZWNyZXRhcmlhIGRhIFJlY2VpdGEgRmVkZXJhbCBkbyBCcmFzaWwgLSBSRkIxFjAUBgNVBAsTDVJGQiBlLUNOUEogQTMxEDAOBgNVBAsTB0FSIExJTksxODA2BgNVBAMTL01FQ0EgT0ZGSUNFIE1PQklMSUFSSU8gRUlSRUxJIE1FOjA2MjczNDc2MDAwMTgyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlwkJ5RGg+4dBViFqKPh0Em6TN3WrdhpPemslBkLtjYftEy42lELdOkOj+wBVliwAx0Vb1bUBhSAcFDqA4wO1JJLWtglgPvmfZe7dKJeHngFE3BO8aNtaPAr31gZjRLVSr7yIbAiDrXeRh3E+iZKmPPjAtPs8Ulr0rF2nZEV2v/Yer4aeTbGPH//nxaBgrz04O9Iqy/x3Xr7MhgDaywjLvH9bkO3154yYIQIdsEWwRy17S95lhk+Y78EnLmi0IY+8MBBVJGdbvewHN45c3hmZ8zuZMg3oJboZlYegcJBW4W2MDG3Y4NURWES0T4fFhYiJ7r1La5hzPOj/9YUZx9OvMQIDAQABo4IDJzCCAyMwHwYDVR0jBBgwFoAUWY0sJWzh8x5duiYhXoEJKGWF1agwDgYDVR0PAQH/BAQDAgXgMG4GA1UdIARnMGUwYwYGYEwBAgM4MFkwVwYIKwYBBQUHAgEWS2h0dHA6Ly9yZXBvc2l0b3Jpby5saW5rY2VydGlmaWNhY2FvLmNvbS5ici9hYy1saW5rcmZiL2FjLWxpbmstcmZiLXBjLWEzLnBkZjCB+QYDVR0fBIHxMIHuMFCgTqBMhkpodHRwOi8vcmVwb3NpdG9yaW8ubGlua2NlcnRpZmljYWNhby5jb20uYnIvYWMtbGlua3JmYi9sY3ItYWMtbGlua3JmYnYyLmNybDBRoE+gTYZLaHR0cDovL3JlcG9zaXRvcmlvMi5saW5rY2VydGlmaWNhY2FvLmNvbS5ici9hYy1saW5rcmZiL2xjci1hYy1saW5rcmZidjIuY3JsMEegRaBDhkFodHRwOi8vcmVwb3NpdG9yaW8uaWNwYnJhc2lsLmdvdi5ici9sY3IvbGluay9sY3ItYWMtbGlua3JmYnYyLmNybDCBlQYIKwYBBQUHAQEEgYgwgYUwUgYIKwYBBQUHMAKGRmh0dHA6Ly9yZXBvc2l0b3Jpby5saW5rY2VydGlmaWNhY2FvLmNvbS5ici9hYy1saW5rcmZiL2FjLWxpbmtyZmJ2Mi5wN2IwLwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmxpbmtjZXJ0aWZpY2FjYW8uY29tLmJyMIHBBgNVHREEgbkwgbaBGFZFTkRBU0BNRUNBT0ZGSUNFLkNPTS5CUqAsBgVgTAEDAqAjEyFDUklTVElOQSBHT01FUyBEQSBTSUxWQSBHT05DQUxWRVOgGQYFYEwBAwOgEBMOMDYyNzM0NzYwMDAxODKgOAYFYEwBAwSgLxMtMDkwODE5Njk2NTI0MDUwMjY2ODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwoBcGBWBMAQMHoA4TDDAwMDAwMDAwMDAwMDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAgEARglY6zgtmvQ1Tu0B/UegLOfwBT9vvTfJ+owFDczzqxoflLu2zOWj05/YOAup6TNDy/ODjfPz2kwYxAA3jezRk1pLSWyJiJ3iTdMiokSrDBYWdDecvzP/QyzbATiwPFfm4Z1olnrh6btlwlwO1dV4mfUd6s77P+v9+RAK+MK7z3+i/6ye+9AJwCRmHAc7Sw01QIGrYmUUQrP6eSTTrYRhzHE4gjKqJxRBnXJSx+PwVy426nuBJPz5CTavy3xPNqaTaO4YUu1xCl2isGvYOuyXCm6up1RStK3aF0MrTHrGELfh2TVxglkf26YoN9LNWyX9Eqe2sU03P4H67S0hbR4NdhvNI7Kh4j1/JkNyQI2VFewuMnRAz0Ysa6chq/UadsTWWgrCUjGqpJXFm2oF5EUFBhSZUFE8s9PCoAQ4CoweaDRbrOwwEvwTUe2f5dpbai1hJ7cqLAg9PCcVUXMr5x15BEMQ6aXN9mvnqjGLSFsVXuqfpEzfGJAz89OyWHhGwMMIBUhNjDfNYySdyUKCLZviX52DHLUb3qDG/i1jCapeEB+Op/kxsExp4UWVUjpA3qPfo25Iv+dXsrWVfU7gva8jhoEwZd9f0il8v/+sMunH2eivETmucHCBQ+fc/ypwrSd+WGHwnthMxjdSnGL79bdhzt/T4eAJlg/x3O4d3i7a6Ms=
- 1
- 14.2.26
- 31190406273476000182550020000031031004640327
- 2019-04-10T17:23:27-03:00
- 131193257591884
- ngqVwH6QNCAHyRuI529RIAr7Nyk=
- 100
- Autorizado o uso da NF-e
-
-
diff --git a/NotaFiscal.py b/NotaFiscal.py
new file mode 100644
index 0000000..6c10edc
--- /dev/null
+++ b/NotaFiscal.py
@@ -0,0 +1,179 @@
+# Biblioteca para trabalhar com XML
+import xml.etree.ElementTree as ET
+
+class NotaFiscal:
+ def __init__(self, input_path: str):
+ self.xml_path : str = input_path
+ self.namespace: str = None
+ self.root : ET.Element = self.xml_to_ElementTree(input_path)
+
+ def __str__(self) -> str:
+ str = ET.tostring(self.root, encoding='utf8').decode('utf8')
+ return str
+
+ # Extrai ElementTree da XML
+ # ElementTree é uma árvore de elementos que representa do XML
+ # 'elemet' ou 'elemento' da árvore será utilizado para referênciar
+ # os galhoes ou folhas da árvore principal guardada em self.root
+ def xml_to_ElementTree(self,input_path: str) -> ET.Element:
+ try:
+ root_with_namespace : ET.Element = ET.iterparse(input_path)
+ except:
+ print('Erro no XML ', input_path )
+ exit(1)
+ root : ET.Element = self.remove_namespace_from_nota_fiscal(root_with_namespace)
+ return root
+
+ # Retorna ElementTree sem nameSpace
+ def remove_namespace_from_nota_fiscal(self, input: ET.Element) -> ET.Element:
+
+ try:
+ for _, el in input:
+ prefix, has_namespace, postfix = el.tag.partition('}')
+ if has_namespace:
+ if postfix == 'NFe':
+ self.set_namespace_nota_fiscal(has_namespace)
+ el.tag = postfix # strip all namespaces
+ except:
+ print('Erro no XML ', self.xml_path )
+ exit(1)
+
+ root : ET.Element = input.root
+ return root
+
+ # Guarda Namespace da nota fiscal
+ def set_namespace_nota_fiscal(self, namespace: str) -> None:
+ self.namespace = namespace
+
+ # --------- Emitente ---------
+
+ # Retorna nome do destinatário
+ def get_emit_name(self) -> str:
+ name = self.get_text_from_xml_tree_element('./NFe/infNFe/emit/xNome')
+ return name
+
+ # Retorna cnpj do emissor da nota fiscal caso o
+ # mesmo seja uma pessoa jurídica
+ def get_emit_cnpj(self) -> str:
+ cnpj = self.get_text_from_xml_tree_element('./NFe/infNFe/emit/CNPJ')
+ return cnpj
+
+ # Retorna cpf do emissor da nota fiscal caso o
+ # mesmo seja uma pessoa física
+ def get_emit_cpf(self) -> str:
+ cpf = self.get_text_from_xml_tree_element('./NFe/infNFe/emit/CPF')
+ return cpf
+
+ # Retorna identificador do emissor
+ def get_emit_identifier(self) -> str:
+ cnpj = self.get_emit_cnpj()
+ if(cnpj):
+ return cnpj
+ else:
+ return self.get_emit_cpf()
+
+ # Retorna endereço do emissor
+ def get_emit_enderEmit(self) -> dict:
+ enderEmit = self.get_dict_from_xml_tree_element('./NFe/infNFe/emit/enderEmit')
+ return enderEmit
+
+ # --------- Destinador ---------
+
+ # Retorna cnpj do destinatário da nota fiscal caso o
+ # mesmo seja uma pessoa jurídica
+ def get_dest_cnpj(self) -> str:
+ cnpj = self.get_text_from_xml_tree_element('./NFe/infNFe/dest/CNPJ')
+ return cnpj
+
+ # Retorna cpf do destinatário da nota fiscal caso o
+ # mesmo seja uma pessoa física
+ def get_dest_cpf(self) -> str:
+ cnpj = self.get_text_from_xml_tree_element('./NFe/infNFe/dest/CPF')
+ return cnpj
+
+ # Retorna identificador do destinatário
+ def get_dest_identifier(self) -> str:
+ cnpj = self.get_dest_cnpj()
+ if(cnpj):
+ return cnpj
+ else:
+ return self.get_dest_cpf()
+
+ # Retorna nome do destinatário
+ def get_dest_name(self) -> str:
+ name = self.get_text_from_xml_tree_element('./NFe/infNFe/dest/xNome')
+ return name
+
+ # --------- Endereço ---------
+
+ # Retorna endereço do destinatário
+ def get_dest_enderDest(self) -> dict:
+ enderDest = self.get_dict_from_xml_tree_element('./NFe/infNFe/dest/enderDest')
+ enderDest['destinador_endereco_id'] = self.get_dest_cnpj()
+ enderDest['destinador_id'] = self.get_dest_cnpj()
+
+ return enderDest
+
+ # --------- Dados para identificação da nota fiscal ---------
+ def get_nota_fiscal_id(self) -> str:
+ xml_tree_element = self.root.find('./NFe/infNFe')
+ return xml_tree_element.attrib['Id']
+
+ def get_nota_fiscal_valor_total(self) -> str:
+ name = self.get_text_from_xml_tree_element('./NFe/infNFe/pag/detPag/vPag')
+ return name
+
+ # --------- Fatura ---------
+
+ # Retorna lista de faturas contidas em uma
+ # nota fiscal
+ def get_faturas(self) -> dict:
+ faturas = []
+ for tree_element_fatura in self.root.findall('./NFe/infNFe/cobr/dup'):
+ fatura = {}
+ fatura['duplicata_id'] = tree_element_fatura.find('nDup').text + self.get_nota_fiscal_id()
+ fatura['nota_fiscal_id'] = self.get_nota_fiscal_id()
+ fatura['data_vencimento'] = tree_element_fatura.find('dVenc').text
+ fatura['valor_pago'] = tree_element_fatura.find('vDup').text
+ faturas.append(fatura)
+ return faturas
+
+ # --------- Specific methods ---------
+
+ # Retorna data de vencimento da nota fiscal
+ def get_data_vencimento(self) -> str:
+ data_vencimento = self.get_text_from_xml_tree_element('./NFe/infNFe/cobr/dup/dVenc')
+ return data_vencimento
+
+ # Retorna valor total da nota fiscal
+ # (impostos + valor unitário*unidades de cada produto)
+ def get_valor_total(self) -> str:
+ valor_total_nota = self.get_text_from_xml_tree_element('./NFe/infNFe/total/ICMSTot/vNF')
+ return valor_total_nota
+
+ # Exibe faturas da nota fiscal
+ # Listar os valores e data de Vencimento dos boletos
+ # presentes em um nota fiscal
+ def print_fatura(self) -> None:
+ faturas = self.get_faturas()
+ for fatura in faturas:
+ print("Valor: R$", fatura['vDup'])
+ print("Data de cencimento: ", fatura['dVenc'])
+
+ # --------- UTILS ---------
+
+ # Retorna o texto de um determinado elemento
+ def get_text_from_xml_tree_element(self, tree_path: str) -> str:
+ xml_tree_element = self.root.find(tree_path)
+ if xml_tree_element != None:
+ return xml_tree_element.text
+ return None
+
+ # Retorna retorna dicionário equivalente
+ # a um elemento
+ def get_dict_from_xml_tree_element(self, tree_path: str) -> dict:
+ result_dict = {}
+ xml_tree_element = self.root.find(tree_path)
+ for chield in xml_tree_element:
+ result_dict[chield.tag] = chield.text
+ return result_dict
diff --git a/README.md b/README.md
index 817b8c7..9f217a7 100644
--- a/README.md
+++ b/README.md
@@ -1,62 +1,53 @@
# Venha para Recomb
+[](https://sonarcloud.io/summary/new_code?id=00KL_venhapararecomb)
-O desafio é desenvolver um programa que permita realizar as seguintes buscas:
+## Documentação da solução
-1) Listar os valores e data de Vencimento dos boletos presentes em um nota fiscal conforme o CPF ou CNPJ de um fornecedor.
-2) Apresentar o nome, identificador (CPF ou CNPJ), endereço dos clientes de um fornecedor.
+### Solução
-**Escolha as tecnologias que você vai usar e tente montar uma solução completa para rodar a aplicação.**
+A solução implementada recebe um ou mais arquivos .xml de notas fiscais em um página web.
-Para enviar o resultado, basta realiazar um Fork deste repositório e abra um Pull Request, com seu nome.
+Através de um serviço Flask, esses arquivos são enviado da página web para ter suas informações extraidas e salvas em um banco de dados.
-É importante comentar que deve ser enviado apenas o código fonte. Não aceitaremos códigos compilados.
+Por fim, é possível realizar consultas aos clientes e boletos de um dado fornecedor.
+### Módulos
-Por fim, o candidato deve atualizar o Readme.md com as seguintes informações:
-
- 1) Documentação da solução;
- 2) Lista dos diferenciais implementados
+A implentação da solução foi dividida em módulos:
+- [Database](./Database.py): Classe que realiza comunicação com o banco.
+- [NotaFiscal](./NotaFiscal.py): Classe que extrai e armazena em memória as informações de um arquivo XML.
+- [App](./app.py): Arquivo com funções que criam interfaces entre as classes chave do sistema e os componentes HTML que exibem os resultados.
+- [Schema](./schema.sql): Arquivo com schema do banco de dados.
-## Avaliação
+### Pastas
+- [Static](./static): Pasta que contem os estilos CSS das páginas do sistema.
+- [Templates](./templates/): Pasta que contem os arquivos HTML da página web.
+- [Uploads](./uploads/): Pasta que armazena os quivos XML enviados pelo usuário do sistema.
-O programa será avaliado levando em conta os seguintes critérios:
-|Critério| Valor|
-|-------|--------|
-|Legibilidade do Código |10|
-|Organização do Código|10|
-|Documentação do código |10|
-|Documentação da solução |10|
-|Tratamento de Erros |10|
-|Total| 50|
+### Execução
+Para preparar o ambiente execute o comando:
+```
+make init_env
+```
-A pontuação do candidato será a soma dos valores obtidos nos critérios acima.
+Para executar o código após a preparação do ambiente execute:
+```
+make all
+```
-## Diferenciais
+Durante a execução do algorítimo os dados utilizados serão guardados assim, caso o usuário deseje apagar todos os dados salvos basta rodar o comando:
+```
+make clean_db_uploads
+```
+
+## Lista dos diferenciais implementados
-O candidato pode aumentar a sua pontuação na seleção implementando um ou mais dos itens abaixo:
|Item | Pontos Ganhos|
|-----|--------------|
|Criar um serviço com o problema |30|
|Utilizar banco de dados |30|
-|Implementar Clean Code |20|
-|Implementar o padrão de programação da tecnologia escolhida |20|
-|Qualidade de Código com SonarQube| 15|
-|Implementar testes unitários |15|
-|Implementar testes comportamentais | 15|
-|Implementar integração com Travis |10|
-|Implementar integração com Travis + SonarQube |10|
-|Implementar usando Docker |5|
-|Total | 170|
-
-A nota final do candidato será acrescido dos pontos referente ao item implementado corretamente.
-
-## Penalizações
-
-O candidato será desclassifiado nas seguintes situações:
-
-1) Submeter um solução que não funcione;
-2) Não cumprir os critérios presentes no seção Avaliação;
-3) Plágio;
-
-
+|Qualidade de Código com SonarQube |15|
+|Total | 75|
+## Desafio
+Descrito no [repositório original](https://github.com/recombX/venhapararecomb#readme) do desafio.
diff --git a/__pycache__/NotaFiscal.cpython-38.pyc b/__pycache__/NotaFiscal.cpython-38.pyc
new file mode 100644
index 0000000..256f5ae
Binary files /dev/null and b/__pycache__/NotaFiscal.cpython-38.pyc differ
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..62a1f4c
--- /dev/null
+++ b/app.py
@@ -0,0 +1,101 @@
+from flask import Flask, render_template, request, url_for, flash, redirect, jsonify
+from flask_dropzone import Dropzone
+from werkzeug.exceptions import abort
+from typing import List
+from NotaFiscal import NotaFiscal
+from Database import Database
+import os
+import sqlite3
+
+test_db : Database = None
+
+def get_db_connection():
+ conn = sqlite3.connect('database.db')
+ conn.row_factory = sqlite3.Row
+ return conn
+
+def get_post(post_id):
+ conn = get_db_connection()
+ post = conn.execute('SELECT * FROM posts WHERE id = ?',
+ (post_id,)).fetchone()
+ conn.close()
+ if post is None:
+ abort(404)
+ return post
+
+
+app = Flask(__name__)
+app.config['SECRET_KEY'] = 'your secret key'
+basedir = os.path.abspath(os.path.dirname(__file__))
+app.config.update(
+ UPLOADED_PATH=os.path.join(basedir, 'uploads'),
+ DROPZONE_ALLOWED_FILE_CUSTOM = True,
+ DROPZONE_ALLOWED_FILE_TYPE='.xml',
+ DROPZONE_MAX_FILE_SIZE=3,
+ DROPZONE_MAX_FILES=30,
+)
+
+app.run(debug=True)
+dropzone = Dropzone(app)
+
+@app.route('/')
+def index():
+
+ return render_template('index.html')
+
+# Busca no banco de d
+@app.route('/search', methods=['GET', 'POST'])
+def search():
+
+ # Recebe da página web o valor de um indentificador (CPF/CNPJ)
+ idetifier = request.args.get('identificador')
+ # Caso não receba nada não haverá busca alguma
+ if(idetifier == ''):
+ return render_template('index.html')
+
+ # os.remove('database.db')
+ # Cria conexão com banco existente ou cria um novo banco
+ # Baseado no schema contido no aquivo schema.sql
+ test_db : Database = Database('database.db', 'schema.sql')
+
+ # Consulta as notas fiscais e boletos de um dado emitente (fornecedor)
+ notas_fiscais_de_um_emitente = test_db.consulta_nota_fiscal_e_duplicatas_de_um_emitente(idetifier)
+ # Caso não encontre nenhuma nota fiscal não há o que procurar
+ if notas_fiscais_de_um_emitente == None:
+ return render_template('index.html')
+
+ # Consulta todos os destinadores (clientes) de um fornecedor
+ clientes_de_um_emitente = test_db.consulta_clientes_de_um_emitente(idetifier)
+
+ return render_template('index.html', notas_fiscais_de_um_emitente=notas_fiscais_de_um_emitente, clientes_de_um_emitente = clientes_de_um_emitente)
+
+# Permite que os arquivos colocados na área de upload sejam salvos na pasta uploads
+@app.route('/', methods=['POST', 'GET'])
+def upload_xml_files():
+ if request.method == 'POST':
+ f = request.files.get('file')
+ file_path = os.path.join(app.config['UPLOADED_PATH'], f.filename)
+ f.save(file_path)
+
+ # os.remove('database.db')
+ test_db : Database = Database('database.db', 'schema.sql')
+
+ # vetor das notas fiscais lidas na entrada
+ vet_nota_fiscal : List[NotaFiscal] = []
+
+ # Lê arquivos xml e guarda eles em objetos
+ # do tipo NotaFiscal
+ for file in os.listdir("uploads"):
+ if file.endswith(".xml"):
+ nota_fiscal = NotaFiscal("uploads/" + file)
+ vet_nota_fiscal.append(nota_fiscal)
+
+ # Percorre as notas fiscais guardadas
+ for nota_fiscal in vet_nota_fiscal:
+ # Salva uma nota fiscal no banco de dados
+ test_db.save_nota_fiscal_on_db(nota_fiscal)
+
+ return render_template('index.html')
+
+if __name__ == '__main__':
+ app.run(debug=True)
\ No newline at end of file
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..173b016
--- /dev/null
+++ b/makefile
@@ -0,0 +1,19 @@
+export FLASK_ENV := development
+
+all:
+ echo FLASK_ENV: $$FLASK_ENV
+ flask run
+
+clean_db_uploads:
+ rm -rf database.db uploads/*
+
+init_env:
+ python3 -m venv env
+ . env/bin/activate
+ pip install -r requirements.txt
+
+
+
+
+
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..287aaf7
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,9 @@
+click==8.1.2
+Flask==2.1.1
+Flask-Dropzone==1.6.0
+importlib-metadata==4.11.3
+itsdangerous==2.1.2
+Jinja2==3.1.1
+MarkupSafe==2.1.1
+Werkzeug==2.1.0
+zipp==3.7.0
\ No newline at end of file
diff --git a/schema.sql b/schema.sql
new file mode 100644
index 0000000..bfd6fec
--- /dev/null
+++ b/schema.sql
@@ -0,0 +1,49 @@
+DROP TABLE IF EXISTS posts;
+
+CREATE TABLE posts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ title TEXT NOT NULL,
+ content TEXT NOT NULL
+);
+
+
+CREATE TABLE IF NOT EXISTS emitente (
+ emitente_id text PRIMARY KEY,
+ nome text NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS destinador (
+ destinador_id text PRIMARY KEY,
+ nome text NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS endereco_destinador (
+ destinador_endereco_id text PRIMARY KEY,
+ destinador_id text,
+ logradouro text NOT NULL,
+ numero text NOT NULL,
+ bairro text NOT NULL,
+ municipio text NOT NULL,
+ estado text NOT NULL,
+ cep text NOT NULL,
+ telefone text NOT NULL,
+ FOREIGN KEY (destinador_id) REFERENCES destinador (destinador_id)
+);
+
+CREATE TABLE IF NOT EXISTS nota_fiscal (
+ nota_fiscal_id text PRIMARY KEY,
+ emitente_id text NOT NULL,
+ destinador_id text NOT NULL,
+ valor_total text NOT NULL,
+ FOREIGN KEY (emitente_id) REFERENCES emitente (emitente_id),
+ FOREIGN KEY (destinador_id) REFERENCES destinador (destinador_id)
+);
+
+CREATE TABLE IF NOT EXISTS duplicata (
+ duplicata_id text PRIMARY KEY,
+ nota_fiscal_id text NOT NULL,
+ data_vencimento text NOT NULL,
+ valor_pago text NOT NULL,
+ FOREIGN KEY (nota_fiscal_id) REFERENCES nota_fiscal (nota_fiscal_id)
+);
\ No newline at end of file
diff --git a/static/css/style.css b/static/css/style.css
new file mode 100644
index 0000000..110b3ef
--- /dev/null
+++ b/static/css/style.css
@@ -0,0 +1,6 @@
+h1 {
+ border: 2px #eee solid;
+ color: brown;
+ text-align: center;
+ padding: 10px;
+}
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 0000000..abbb1ee
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..42e4b0c
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,82 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
{% block title %} Bem vindo ao leitor de notas fiscais {% endblock %}
+
+
+ Este site busca boletos e clientes baseado no identificador (CPF ou CNPJ) de um fornecedor.
+
+
+
+
+
+
Clientes:
+
+
+
Tabela: Clientes
+
+
+
#
+
destinador_id
+
nome
+
+
+
+ {% for cliente in clientes_de_um_emitente %}
+
+
1
+
{{ cliente.destinador_id }}
+
{{ cliente.nome }}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
Notas fiscais:
+ {% for nota_fiscal in notas_fiscais_de_um_emitente %}
+
+
{{'infNFe: ' + nota_fiscal.nota_fical_id + ' Valor total a pagar: ' + nota_fiscal.valor_total}}
+ {% if nota_fiscal.duplicatas == []%}
+
Esta nota fiscal não possui boletos.
+ {% else %}
+
+
Tabela: Notas fiscais
+
+
+
Boleto n°
+
data_vencimento
+
valor_pago
+
+
+
+ {% for duplicata in nota_fiscal.duplicatas %}
+
+
1
+
{{ duplicata.data_vencimento }}
+
{{ duplicata.valor_pago }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
+ {{ dropzone.create(action='upload_xml_files') }}
+ {{ dropzone.load_js() }}
+ {{ dropzone.config(default_message='Arreste os arquivos .xml das notas fiscais aqui.') }}
+ {# You can get the success response from server like this: #}
+ {#{ dropzone.config(custom_options="success: function(file, response){console.log(response);}") }#}
+
+
+{% endblock %}