From 381e51ae000ac0889fd067f9c48301e432a97dd3 Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 17:16:58 -0300 Subject: [PATCH 01/13] init --- .github/workflows/build.yml | 20 + .gitignore | 4 + Dockerfile | 20 + README.md | 23 + db/schema.sql | 33 + docs/src/database.html | 669 ++++++++++++++++++ docs/src/index.html | 75 ++ docs/src/models.html | 207 ++++++ docs/src/parsing.html | 285 ++++++++ docs/src/service.html | 253 +++++++ ...2718000117550010000217781877120005-nfe.xml | 0 NFe-002-3103.xml => in_xml/NFe-002-3103.xml | 0 requirements.txt | 6 + sonar-project.properties | 12 + src/database.py | 189 +++++ src/models.py | 39 + src/parsing.py | 79 +++ src/service.py | 59 ++ src/static/styles.scss | 135 ++++ src/templates/1.html | 34 + src/templates/2.html | 14 + src/templates/3.html | 44 ++ 22 files changed, 2200 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 db/schema.sql create mode 100644 docs/src/database.html create mode 100644 docs/src/index.html create mode 100644 docs/src/models.html create mode 100644 docs/src/parsing.html create mode 100644 docs/src/service.html rename 32211207872718000117550010000217781877120005-nfe.xml => in_xml/32211207872718000117550010000217781877120005-nfe.xml (100%) rename NFe-002-3103.xml => in_xml/NFe-002-3103.xml (100%) create mode 100644 requirements.txt create mode 100644 sonar-project.properties create mode 100644 src/database.py create mode 100644 src/models.py create mode 100644 src/parsing.py create mode 100644 src/service.py create mode 100644 src/static/styles.scss create mode 100644 src/templates/1.html create mode 100644 src/templates/2.html create mode 100644 src/templates/3.html diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c3c313d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: Build +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +jobs: + sonarcloud: + name: SonarCloud + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df6c0b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +env/ +db/database.db +__pycache__/ +src/__pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b3a7a7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# set base image (host OS) +FROM python:3.10 + +# set the working directory in the container +WORKDIR /recomb + +# copy the dependencies file to the working directory +COPY requirements.txt . + +# install dependencies +RUN pip install -r requirements.txt + +# copy the content of the local schema to the working directory +COPY db/schema.sql db/schema.sql + +# copy the content of the local src directory to the working directory +COPY src/ src/ + +# command to run on container start +CMD [ "python", "./src/service.py" ] diff --git a/README.md b/README.md index 817b8c7..12e0351 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,28 @@ # Venha para Recomb +## Documentação da solução + +### Execução + +```bash +docker build -t recomb . +docker run -it -p 5000:5000 recomb +``` + +## Lista dos diferenciais implementados + +|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 usando Docker |5| +|Total | 120| + +## Desafio + O desafio é desenvolver um programa que permita realizar as seguintes buscas: 1) Listar os valores e data de Vencimento dos boletos presentes em um nota fiscal conforme o CPF ou CNPJ de um fornecedor. diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..234c7d4 --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,33 @@ +DROP TABLE IF EXISTS fornecedores; +DROP TABLE IF EXISTS clientes; +DROP TABLE IF EXISTS boletos; +DROP TABLE IF EXISTS notas_fiscais; + +CREATE TABLE fornecedores ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + identificador TEXT NOT NULL UNIQUE +); + +CREATE TABLE clientes ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + identificador TEXT NOT NULL UNIQUE, + nome TEXT NOT NULL, + endereco TEXT NOT NULL +); + +CREATE TABLE notas_fiscais ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + fornecedor_id INTEGER, + cliente_id INTEGER, + FOREIGN KEY (fornecedor_id) REFERENCES fornecedores(_id), + FOREIGN KEY (cliente_id) REFERENCES clientes(_id) +); + +CREATE TABLE boletos ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + valor REAL NOT NULL, + vencimento TIMESTAMP NOT NULL, + nota_fiscal_id INTEGER, + FOREIGN KEY (nota_fiscal_id) REFERENCES notas_fiscais(_id) +); + diff --git a/docs/src/database.html b/docs/src/database.html new file mode 100644 index 0000000..9205089 --- /dev/null +++ b/docs/src/database.html @@ -0,0 +1,669 @@ + + + + + + +src.database API documentation + + + + + + + + + + + +
+
+
+

Module src.database

+
+
+
+ +Expand source code + +
import sqlite3
+from contextlib import closing
+from datetime import datetime
+from models import Fornecedor, Cliente, NotaFiscal
+
+def create_database(db_file_path, db_schema_path):
+    """
+    Creates a database file from a schema file.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        db_schema_path (str): The path to the schema file.
+    
+    Returns:
+        None
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with open(db_schema_path) as f:
+            connection.executescript(f.read())
+        connection.commit()
+
+def create_if_not_exist(connection, cursor, should_commit, table_name, columns, primary_key, primary_attribute, item_tuple):
+    """
+    Creates a new item in the database if it doesn't exist.
+    
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        table_name (str): The name of the table to insert the item into.
+        columns (list): The list of columns to insert the item into.
+        primary_key (str): The name of the primary key column.
+        primary_attribute (str): The value of the primary key attribute.
+        item_tuple (tuple): The tuple of values to insert into the table.
+        
+    Returns:
+        int: The id of the item. 
+    """
+    result = cursor.execute(f"SELECT * FROM {table_name} WHERE {primary_key} = ?", (primary_attribute,)).fetchone()
+    if result is None:
+        cursor.execute(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(['?']*len(columns))})",
+            item_tuple
+        )
+        _id = cursor.lastrowid
+        if should_commit:
+            connection.commit()
+    else:
+        _id = result[0]
+    return _id
+
+def create_many(connection, cursor, should_commit, table_name, columns, items_list):
+    """
+    Creates many items in the database.
+
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        table_name (str): The name of the table to insert the item into.
+        columns (list): The list of columns to insert the item into.
+        items_list (list): The list of items to insert into the table.
+
+    Returns:
+        None
+    """
+    cursor.executemany(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(['?']*len(columns))})",
+        items_list
+    )
+    if should_commit:
+        connection.commit()
+
+def create_fornecedor(connection, cursor, should_commit, fornecedor):
+    """
+    Creates a new fornecedor in the database.
+
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        fornecedor (Fornecedor): The fornecedor to insert into the database.
+
+    Returns:
+        int: The id of the fornecedor.
+    """
+    return create_if_not_exist(connection, cursor, should_commit, 
+        'fornecedores', ['identificador'], 'identificador', 
+        fornecedor.identificador, (fornecedor.identificador,))
+
+def create_cliente(connection, cursor, should_commit, cliente):
+    """
+    Creates a new cliente in the database.
+    
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        cliente (Cliente): The cliente to insert into the database.
+
+    Returns:
+        int: The id of the cliente.
+    """
+    return create_if_not_exist(connection, cursor, should_commit, 
+        'clientes', ['identificador', 'nome', 'endereco'], 'identificador', 
+        cliente.identificador, (cliente.identificador, cliente.nome, cliente.endereco))
+
+def create_boletos(connection, cursor, should_commit, boletos):
+    """
+    Creates many boletos in the database.
+
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        boletos (list): The list of boletos to insert into the database.
+    
+    Returns:
+        None
+    """
+    return create_many(connection, cursor, should_commit, 
+        'boletos', ['nota_fiscal_id', 'valor', 'vencimento'], 
+        boletos)
+
+def create_NF(db_file_path, nota_fiscal):
+    """
+    Creates a new nota fiscal in the database.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        nota_fiscal (NotaFiscal): The nota fiscal to insert into the database.
+
+    Returns:
+        int: The id of the nota fiscal.
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with closing(connection.cursor()) as cursor:
+            fornecedor_id = create_fornecedor(connection, cursor, False, nota_fiscal.fornecedor)
+            cliente_id = create_cliente(connection, cursor, False, nota_fiscal.cliente)
+            cursor.execute("INSERT INTO notas_fiscais (fornecedor_id, cliente_id) VALUES (?, ?)",
+                (fornecedor_id, cliente_id)
+            )
+            nota_fiscal_id = cursor.lastrowid
+            create_boletos(connection, cursor, False, [(nota_fiscal_id, boleto['valor'], boleto['vencimento']) for boleto in nota_fiscal.boletos])
+            connection.commit()
+
+def query1(db_file_path, fornecedor_identificador):
+    """
+    Queries the database for the list of all boletos issued to a fornecedor.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        fornecedor_identificador (str): The fornecedor's identificador.
+
+    Returns:
+        list: The list of boletos issued to the fornecedor.
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with closing(connection.cursor()) as cursor:
+            return [{'valor': float(val), 'vencimento': datetime.strptime(ven, '%Y-%m-%d %H:%M:%S')} for _, val, ven in
+                    cursor.execute("""
+                    SELECT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento
+                    FROM boletos
+                    INNER JOIN notas_fiscais ON boletos.nota_fiscal_id = notas_fiscais._id
+                    INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id
+                    WHERE fornecedores.identificador = ?;
+                """, (fornecedor_identificador, )).fetchall()
+            ]
+
+def query2(db_file_path, fornecedor_identificador):
+    """
+    Queries the database for the list of all clientes of a fornecedor.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        fornecedor_identificador (str): The fornecedor's identificador.
+
+    Returns:
+        list: The list of clientes of the fornecedor.
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with closing(connection.cursor()) as cursor:
+            return [Cliente(i, n, e) for i, n, e in 
+                cursor.execute("""
+                    SELECT clientes.identificador, clientes.nome, clientes.endereco
+                    FROM clientes
+                    INNER JOIN notas_fiscais ON clientes._id = notas_fiscais.cliente_id
+                    INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id
+                    WHERE fornecedores.identificador = ?;
+                """, (fornecedor_identificador, )).fetchall()
+            ]
+
+
+
+
+
+
+
+

Functions

+
+
+def create_NF(db_file_path, nota_fiscal) +
+
+

Creates a new nota fiscal in the database.

+

Args

+
+
db_file_path : str
+
The path to the database file.
+
nota_fiscal : NotaFiscal
+
The nota fiscal to insert into the database.
+
+

Returns

+
+
int
+
The id of the nota fiscal.
+
+
+ +Expand source code + +
def create_NF(db_file_path, nota_fiscal):
+    """
+    Creates a new nota fiscal in the database.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        nota_fiscal (NotaFiscal): The nota fiscal to insert into the database.
+
+    Returns:
+        int: The id of the nota fiscal.
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with closing(connection.cursor()) as cursor:
+            fornecedor_id = create_fornecedor(connection, cursor, False, nota_fiscal.fornecedor)
+            cliente_id = create_cliente(connection, cursor, False, nota_fiscal.cliente)
+            cursor.execute("INSERT INTO notas_fiscais (fornecedor_id, cliente_id) VALUES (?, ?)",
+                (fornecedor_id, cliente_id)
+            )
+            nota_fiscal_id = cursor.lastrowid
+            create_boletos(connection, cursor, False, [(nota_fiscal_id, boleto['valor'], boleto['vencimento']) for boleto in nota_fiscal.boletos])
+            connection.commit()
+
+
+
+def create_boletos(connection, cursor, should_commit, boletos) +
+
+

Creates many boletos in the database.

+

Args

+
+
connection : sqlite3.Connection
+
The connection to the database.
+
cursor : sqlite3.Cursor
+
The cursor to the database.
+
should_commit : bool
+
Whether or not to commit the changes to the database.
+
boletos : list
+
The list of boletos to insert into the database.
+
+

Returns

+

None

+
+ +Expand source code + +
def create_boletos(connection, cursor, should_commit, boletos):
+    """
+    Creates many boletos in the database.
+
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        boletos (list): The list of boletos to insert into the database.
+    
+    Returns:
+        None
+    """
+    return create_many(connection, cursor, should_commit, 
+        'boletos', ['nota_fiscal_id', 'valor', 'vencimento'], 
+        boletos)
+
+
+
+def create_cliente(connection, cursor, should_commit, cliente) +
+
+

Creates a new cliente in the database.

+

Args

+
+
connection : sqlite3.Connection
+
The connection to the database.
+
cursor : sqlite3.Cursor
+
The cursor to the database.
+
should_commit : bool
+
Whether or not to commit the changes to the database.
+
cliente : Cliente
+
The cliente to insert into the database.
+
+

Returns

+
+
int
+
The id of the cliente.
+
+
+ +Expand source code + +
def create_cliente(connection, cursor, should_commit, cliente):
+    """
+    Creates a new cliente in the database.
+    
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        cliente (Cliente): The cliente to insert into the database.
+
+    Returns:
+        int: The id of the cliente.
+    """
+    return create_if_not_exist(connection, cursor, should_commit, 
+        'clientes', ['identificador', 'nome', 'endereco'], 'identificador', 
+        cliente.identificador, (cliente.identificador, cliente.nome, cliente.endereco))
+
+
+
+def create_database(db_file_path, db_schema_path) +
+
+

Creates a database file from a schema file.

+

Args

+
+
db_file_path : str
+
The path to the database file.
+
db_schema_path : str
+
The path to the schema file.
+
+

Returns

+

None

+
+ +Expand source code + +
def create_database(db_file_path, db_schema_path):
+    """
+    Creates a database file from a schema file.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        db_schema_path (str): The path to the schema file.
+    
+    Returns:
+        None
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with open(db_schema_path) as f:
+            connection.executescript(f.read())
+        connection.commit()
+
+
+
+def create_fornecedor(connection, cursor, should_commit, fornecedor) +
+
+

Creates a new fornecedor in the database.

+

Args

+
+
connection : sqlite3.Connection
+
The connection to the database.
+
cursor : sqlite3.Cursor
+
The cursor to the database.
+
should_commit : bool
+
Whether or not to commit the changes to the database.
+
fornecedor : Fornecedor
+
The fornecedor to insert into the database.
+
+

Returns

+
+
int
+
The id of the fornecedor.
+
+
+ +Expand source code + +
def create_fornecedor(connection, cursor, should_commit, fornecedor):
+    """
+    Creates a new fornecedor in the database.
+
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        fornecedor (Fornecedor): The fornecedor to insert into the database.
+
+    Returns:
+        int: The id of the fornecedor.
+    """
+    return create_if_not_exist(connection, cursor, should_commit, 
+        'fornecedores', ['identificador'], 'identificador', 
+        fornecedor.identificador, (fornecedor.identificador,))
+
+
+
+def create_if_not_exist(connection, cursor, should_commit, table_name, columns, primary_key, primary_attribute, item_tuple) +
+
+

Creates a new item in the database if it doesn't exist.

+

Args

+
+
connection : sqlite3.Connection
+
The connection to the database.
+
cursor : sqlite3.Cursor
+
The cursor to the database.
+
should_commit : bool
+
Whether or not to commit the changes to the database.
+
table_name : str
+
The name of the table to insert the item into.
+
columns : list
+
The list of columns to insert the item into.
+
primary_key : str
+
The name of the primary key column.
+
primary_attribute : str
+
The value of the primary key attribute.
+
item_tuple : tuple
+
The tuple of values to insert into the table.
+
+

Returns

+
+
int
+
The id of the item.
+
+
+ +Expand source code + +
def create_if_not_exist(connection, cursor, should_commit, table_name, columns, primary_key, primary_attribute, item_tuple):
+    """
+    Creates a new item in the database if it doesn't exist.
+    
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        table_name (str): The name of the table to insert the item into.
+        columns (list): The list of columns to insert the item into.
+        primary_key (str): The name of the primary key column.
+        primary_attribute (str): The value of the primary key attribute.
+        item_tuple (tuple): The tuple of values to insert into the table.
+        
+    Returns:
+        int: The id of the item. 
+    """
+    result = cursor.execute(f"SELECT * FROM {table_name} WHERE {primary_key} = ?", (primary_attribute,)).fetchone()
+    if result is None:
+        cursor.execute(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(['?']*len(columns))})",
+            item_tuple
+        )
+        _id = cursor.lastrowid
+        if should_commit:
+            connection.commit()
+    else:
+        _id = result[0]
+    return _id
+
+
+
+def create_many(connection, cursor, should_commit, table_name, columns, items_list) +
+
+

Creates many items in the database.

+

Args

+
+
connection : sqlite3.Connection
+
The connection to the database.
+
cursor : sqlite3.Cursor
+
The cursor to the database.
+
should_commit : bool
+
Whether or not to commit the changes to the database.
+
table_name : str
+
The name of the table to insert the item into.
+
columns : list
+
The list of columns to insert the item into.
+
items_list : list
+
The list of items to insert into the table.
+
+

Returns

+

None

+
+ +Expand source code + +
def create_many(connection, cursor, should_commit, table_name, columns, items_list):
+    """
+    Creates many items in the database.
+
+    Args:
+        connection (sqlite3.Connection): The connection to the database.
+        cursor (sqlite3.Cursor): The cursor to the database.
+        should_commit (bool): Whether or not to commit the changes to the database.
+        table_name (str): The name of the table to insert the item into.
+        columns (list): The list of columns to insert the item into.
+        items_list (list): The list of items to insert into the table.
+
+    Returns:
+        None
+    """
+    cursor.executemany(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(['?']*len(columns))})",
+        items_list
+    )
+    if should_commit:
+        connection.commit()
+
+
+
+def query1(db_file_path, fornecedor_identificador) +
+
+

Queries the database for the list of all boletos issued to a fornecedor.

+

Args

+
+
db_file_path : str
+
The path to the database file.
+
fornecedor_identificador : str
+
The fornecedor's identificador.
+
+

Returns

+
+
list
+
The list of boletos issued to the fornecedor.
+
+
+ +Expand source code + +
def query1(db_file_path, fornecedor_identificador):
+    """
+    Queries the database for the list of all boletos issued to a fornecedor.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        fornecedor_identificador (str): The fornecedor's identificador.
+
+    Returns:
+        list: The list of boletos issued to the fornecedor.
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with closing(connection.cursor()) as cursor:
+            return [{'valor': float(val), 'vencimento': datetime.strptime(ven, '%Y-%m-%d %H:%M:%S')} for _, val, ven in
+                    cursor.execute("""
+                    SELECT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento
+                    FROM boletos
+                    INNER JOIN notas_fiscais ON boletos.nota_fiscal_id = notas_fiscais._id
+                    INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id
+                    WHERE fornecedores.identificador = ?;
+                """, (fornecedor_identificador, )).fetchall()
+            ]
+
+
+
+def query2(db_file_path, fornecedor_identificador) +
+
+

Queries the database for the list of all clientes of a fornecedor.

+

Args

+
+
db_file_path : str
+
The path to the database file.
+
fornecedor_identificador : str
+
The fornecedor's identificador.
+
+

Returns

+
+
list
+
The list of clientes of the fornecedor.
+
+
+ +Expand source code + +
def query2(db_file_path, fornecedor_identificador):
+    """
+    Queries the database for the list of all clientes of a fornecedor.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        fornecedor_identificador (str): The fornecedor's identificador.
+
+    Returns:
+        list: The list of clientes of the fornecedor.
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with closing(connection.cursor()) as cursor:
+            return [Cliente(i, n, e) for i, n, e in 
+                cursor.execute("""
+                    SELECT clientes.identificador, clientes.nome, clientes.endereco
+                    FROM clientes
+                    INNER JOIN notas_fiscais ON clientes._id = notas_fiscais.cliente_id
+                    INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id
+                    WHERE fornecedores.identificador = ?;
+                """, (fornecedor_identificador, )).fetchall()
+            ]
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/src/index.html b/docs/src/index.html new file mode 100644 index 0000000..4ccf7f1 --- /dev/null +++ b/docs/src/index.html @@ -0,0 +1,75 @@ + + + + + + +src API documentation + + + + + + + + + + + +
+ + +
+ + + \ No newline at end of file diff --git a/docs/src/models.html b/docs/src/models.html new file mode 100644 index 0000000..b3d4eef --- /dev/null +++ b/docs/src/models.html @@ -0,0 +1,207 @@ + + + + + + +src.models API documentation + + + + + + + + + + + +
+
+
+

Module src.models

+
+
+
+ +Expand source code + +
from datetime import datetime
+
+class Fornecedor():
+    """
+    Class that represents a fornecedor.
+
+    Attributes:
+        identificador (str): The fornecedor's identifier.
+    """
+    def __init__(self, identificador):
+        self.identificador = identificador
+
+class Cliente():
+    """
+    Class that represents a cliente.
+
+    Attributes:
+        identificador (str): The cliente's identifier.
+        nome (str): The cliente's name.
+        endereco (str): The cliente's address.
+    """
+    def __init__(self, identificador, nome, endereco):
+        self.identificador = identificador
+        self.nome = nome
+        self.endereco = endereco
+
+class NotaFiscal():
+    """
+    Class that represents a nota fiscal.
+
+    Attributes:
+        fornecedor (Fornecedor): The fornecedor of the nota fiscal.
+        cliente (Cliente): The cliente of the nota fiscal.
+        boletos (list): The list of boletos of the nota fiscal.
+    """
+    def __init__(self, fornecedor, cliente, boletos):
+        self.fornecedor = fornecedor
+        self.cliente = cliente
+        self.boletos = boletos
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class Cliente +(identificador, nome, endereco) +
+
+

Class that represents a cliente.

+

Attributes

+
+
identificador : str
+
The cliente's identifier.
+
nome : str
+
The cliente's name.
+
endereco : str
+
The cliente's address.
+
+
+ +Expand source code + +
class Cliente():
+    """
+    Class that represents a cliente.
+
+    Attributes:
+        identificador (str): The cliente's identifier.
+        nome (str): The cliente's name.
+        endereco (str): The cliente's address.
+    """
+    def __init__(self, identificador, nome, endereco):
+        self.identificador = identificador
+        self.nome = nome
+        self.endereco = endereco
+
+
+
+class Fornecedor +(identificador) +
+
+

Class that represents a fornecedor.

+

Attributes

+
+
identificador : str
+
The fornecedor's identifier.
+
+
+ +Expand source code + +
class Fornecedor():
+    """
+    Class that represents a fornecedor.
+
+    Attributes:
+        identificador (str): The fornecedor's identifier.
+    """
+    def __init__(self, identificador):
+        self.identificador = identificador
+
+
+
+class NotaFiscal +(fornecedor, cliente, boletos) +
+
+

Class that represents a nota fiscal.

+

Attributes

+
+
fornecedor : Fornecedor
+
The fornecedor of the nota fiscal.
+
cliente : Cliente
+
The cliente of the nota fiscal.
+
boletos : list
+
The list of boletos of the nota fiscal.
+
+
+ +Expand source code + +
class NotaFiscal():
+    """
+    Class that represents a nota fiscal.
+
+    Attributes:
+        fornecedor (Fornecedor): The fornecedor of the nota fiscal.
+        cliente (Cliente): The cliente of the nota fiscal.
+        boletos (list): The list of boletos of the nota fiscal.
+    """
+    def __init__(self, fornecedor, cliente, boletos):
+        self.fornecedor = fornecedor
+        self.cliente = cliente
+        self.boletos = boletos
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/src/parsing.html b/docs/src/parsing.html new file mode 100644 index 0000000..a6a7bea --- /dev/null +++ b/docs/src/parsing.html @@ -0,0 +1,285 @@ + + + + + + +src.parsing API documentation + + + + + + + + + + + +
+
+
+

Module src.parsing

+
+
+
+ +Expand source code + +
import xml.etree.ElementTree as ET
+import re
+from models import Fornecedor, Cliente, NotaFiscal
+from datetime import datetime
+
+def parse_fornecedor(fornecedor_XML, namespace=''):
+    """
+    Parses a fornecedor from an XML element.
+
+    Args:
+        fornecedor_XML (xml.etree.ElementTree.Element): The XML element to parse.
+        namespace (str): The namespace of the XML element.
+    
+    Returns:
+        Fornecedor: The parsed fornecedor.
+    """
+    if (cnpj := fornecedor_XML.find(f'{namespace}CNPJ')) is not None:
+        identificador = cnpj.text
+    else:
+        identificador = fornecedor_XML.find(f'{namespace}CPF').text
+
+    return Fornecedor(identificador)
+
+def parse_cliente(cliente_XML, namespace=''):
+    """"
+    Parses a cliente from an XML element.
+    
+    Args:
+        cliente_XML (xml.etree.ElementTree.Element): The XML element to parse.
+        namespace (str): The namespace of the XML element.
+
+    Returns:
+        Cliente: The parsed cliente.
+    """
+    if (cnpj := cliente_XML.find(f'{namespace}CNPJ')) is not None:
+        identificador = cnpj.text
+    else:
+        identificador = cliente_XML.find(f'{namespace}CPF').text
+    nome = cliente_XML.find(f'{namespace}xNome').text
+
+    endereco_XML = cliente_XML.find(f'{namespace}enderDest')
+    logradouro = endereco_XML.find(f'{namespace}xLgr').text
+    numero = endereco_XML.find(f'{namespace}nro').text
+    municipio = endereco_XML.find(f'{namespace}xMun').text
+    unidade_federativa = endereco_XML.find(f'{namespace}UF').text
+    endereco = f'{logradouro}, Nº {numero}, {municipio} - {unidade_federativa}'
+
+    return Cliente(identificador, nome, endereco)
+
+def parse_NF(file_content):
+    """
+    Parses a nota fiscal from an XML file.
+
+    Args:
+        file_content (str): The XML file content.
+
+    Returns:
+        NotaFiscal: The parsed nota fiscal.
+    """
+    root_XML = ET.fromstring(file_content)
+    NFe_XML = root_XML[0] # NFe
+    infNFe_XML = NFe_XML[0] # infNFe
+    namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_XML.tag)) else '' # xmlns
+
+    fornecedor = parse_fornecedor(infNFe_XML.find(f'{namespace}emit'), namespace)
+    cliente = parse_cliente(infNFe_XML.find(f'{namespace}dest'), namespace)
+
+    cobranca_XML = infNFe_XML.find(f'{namespace}cobr')
+    duplicata_XML = cobranca_XML.findall(f'{namespace}dup')
+    boletos = [
+        {
+            'valor': float(dup_XML.find(f'{namespace}vDup').text), 
+            'vencimento': datetime.strptime(dup_XML.find(f'{namespace}dVenc').text, '%Y-%m-%d')
+        } 
+        for dup_XML in duplicata_XML
+    ]
+
+    return NotaFiscal(fornecedor, cliente, boletos)
+
+
+
+
+
+
+
+

Functions

+
+
+def parse_NF(file_content) +
+
+

Parses a nota fiscal from an XML file.

+

Args

+
+
file_content : str
+
The XML file content.
+
+

Returns

+
+
NotaFiscal
+
The parsed nota fiscal.
+
+
+ +Expand source code + +
def parse_NF(file_content):
+    """
+    Parses a nota fiscal from an XML file.
+
+    Args:
+        file_content (str): The XML file content.
+
+    Returns:
+        NotaFiscal: The parsed nota fiscal.
+    """
+    root_XML = ET.fromstring(file_content)
+    NFe_XML = root_XML[0] # NFe
+    infNFe_XML = NFe_XML[0] # infNFe
+    namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_XML.tag)) else '' # xmlns
+
+    fornecedor = parse_fornecedor(infNFe_XML.find(f'{namespace}emit'), namespace)
+    cliente = parse_cliente(infNFe_XML.find(f'{namespace}dest'), namespace)
+
+    cobranca_XML = infNFe_XML.find(f'{namespace}cobr')
+    duplicata_XML = cobranca_XML.findall(f'{namespace}dup')
+    boletos = [
+        {
+            'valor': float(dup_XML.find(f'{namespace}vDup').text), 
+            'vencimento': datetime.strptime(dup_XML.find(f'{namespace}dVenc').text, '%Y-%m-%d')
+        } 
+        for dup_XML in duplicata_XML
+    ]
+
+    return NotaFiscal(fornecedor, cliente, boletos)
+
+
+
+def parse_cliente(cliente_XML, namespace='') +
+
+

" +Parses a cliente from an XML element.

+

Args

+
+
cliente_XML : xml.etree.ElementTree.Element
+
The XML element to parse.
+
namespace : str
+
The namespace of the XML element.
+
+

Returns

+
+
Cliente
+
The parsed cliente.
+
+
+ +Expand source code + +
def parse_cliente(cliente_XML, namespace=''):
+    """"
+    Parses a cliente from an XML element.
+    
+    Args:
+        cliente_XML (xml.etree.ElementTree.Element): The XML element to parse.
+        namespace (str): The namespace of the XML element.
+
+    Returns:
+        Cliente: The parsed cliente.
+    """
+    if (cnpj := cliente_XML.find(f'{namespace}CNPJ')) is not None:
+        identificador = cnpj.text
+    else:
+        identificador = cliente_XML.find(f'{namespace}CPF').text
+    nome = cliente_XML.find(f'{namespace}xNome').text
+
+    endereco_XML = cliente_XML.find(f'{namespace}enderDest')
+    logradouro = endereco_XML.find(f'{namespace}xLgr').text
+    numero = endereco_XML.find(f'{namespace}nro').text
+    municipio = endereco_XML.find(f'{namespace}xMun').text
+    unidade_federativa = endereco_XML.find(f'{namespace}UF').text
+    endereco = f'{logradouro}, Nº {numero}, {municipio} - {unidade_federativa}'
+
+    return Cliente(identificador, nome, endereco)
+
+
+
+def parse_fornecedor(fornecedor_XML, namespace='') +
+
+

Parses a fornecedor from an XML element.

+

Args

+
+
fornecedor_XML : xml.etree.ElementTree.Element
+
The XML element to parse.
+
namespace : str
+
The namespace of the XML element.
+
+

Returns

+
+
Fornecedor
+
The parsed fornecedor.
+
+
+ +Expand source code + +
def parse_fornecedor(fornecedor_XML, namespace=''):
+    """
+    Parses a fornecedor from an XML element.
+
+    Args:
+        fornecedor_XML (xml.etree.ElementTree.Element): The XML element to parse.
+        namespace (str): The namespace of the XML element.
+    
+    Returns:
+        Fornecedor: The parsed fornecedor.
+    """
+    if (cnpj := fornecedor_XML.find(f'{namespace}CNPJ')) is not None:
+        identificador = cnpj.text
+    else:
+        identificador = fornecedor_XML.find(f'{namespace}CPF').text
+
+    return Fornecedor(identificador)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/docs/src/service.html b/docs/src/service.html new file mode 100644 index 0000000..66681b4 --- /dev/null +++ b/docs/src/service.html @@ -0,0 +1,253 @@ + + + + + + +src.service API documentation + + + + + + + + + + + +
+
+
+

Module src.service

+
+
+
+ +Expand source code + +
from flask import Flask, redirect, render_template, request, url_for
+from werkzeug.utils import secure_filename
+from parsing import parse_NF
+from database import create_database as create_database_original, \
+    create_NF as create_NF_original, \
+    query1 as query1_original, \
+    query2 as query2_original
+
+# Setup functions for local database
+DB_FILE_PATH = 'db/database.db'
+DB_SCHEMA_PATH = 'db/schema.sql'
+create_database = lambda: create_database_original(DB_FILE_PATH, DB_SCHEMA_PATH)
+create_NF = lambda x: create_NF_original(DB_FILE_PATH, x)
+query1 = lambda x: query1_original(DB_FILE_PATH, x)
+query2 = lambda x: query2_original(DB_FILE_PATH, x)
+
+# Setup Flask app
+app = Flask(__name__)
+
+@app.route('/')
+def page_1():
+   return render_template('1.html')
+
+@app.route('/2', methods = ['GET', 'POST'])
+def page_2():
+    if request.method != 'POST':
+        # To avoid breaking the flow of application, we will redirect to the first page
+        return redirect(url_for('page_1'))
+
+    # Get the list of files from the request
+    files = request.files.getlist("file")
+    for file in files:
+        if file.filename == '':
+            continue
+        nf = parse_NF(file.read()) # Parse the file
+        create_NF(nf) # Create the nota fiscal in database
+
+    return render_template('2.html')
+
+@app.route('/3', methods = ['GET', 'POST'])
+def page_3():
+    if request.method != 'POST':
+        # To avoid breaking the flow of application, we will redirect to the first page
+        return redirect(url_for('page_1'))
+    identificador = request.form.get('identificador', '') # Get the identificador from the request
+    boletos = query1(identificador) # Get all boletos from a fornecedor
+    clientes = query2(identificador) # Get all clientes related to a fornecedor
+    print(clientes[0].__dict__)
+    return render_template('3.html', clientes=clientes, boletos=boletos)
+
+@app.template_filter()
+def format_datetime(value):
+    return value.strftime('%d/%m/%Y')
+
+if __name__ == '__main__':
+    create_database() # Create the database
+    app.run(debug = True) # Run the app
+
+
+
+
+
+
+
+

Functions

+
+
+def create_NF(x) +
+
+
+
+ +Expand source code + +
create_NF = lambda x: create_NF_original(DB_FILE_PATH, x)
+
+
+
+def create_database() +
+
+
+
+ +Expand source code + +
create_database = lambda: create_database_original(DB_FILE_PATH, DB_SCHEMA_PATH)
+
+
+
+def format_datetime(value) +
+
+
+
+ +Expand source code + +
@app.template_filter()
+def format_datetime(value):
+    return value.strftime('%d/%m/%Y')
+
+
+
+def page_1() +
+
+
+
+ +Expand source code + +
@app.route('/')
+def page_1():
+   return render_template('1.html')
+
+
+
+def page_2() +
+
+
+
+ +Expand source code + +
@app.route('/2', methods = ['GET', 'POST'])
+def page_2():
+    if request.method != 'POST':
+        # To avoid breaking the flow of application, we will redirect to the first page
+        return redirect(url_for('page_1'))
+
+    # Get the list of files from the request
+    files = request.files.getlist("file")
+    for file in files:
+        if file.filename == '':
+            continue
+        nf = parse_NF(file.read()) # Parse the file
+        create_NF(nf) # Create the nota fiscal in database
+
+    return render_template('2.html')
+
+
+
+def page_3() +
+
+
+
+ +Expand source code + +
@app.route('/3', methods = ['GET', 'POST'])
+def page_3():
+    if request.method != 'POST':
+        # To avoid breaking the flow of application, we will redirect to the first page
+        return redirect(url_for('page_1'))
+    identificador = request.form.get('identificador', '') # Get the identificador from the request
+    boletos = query1(identificador) # Get all boletos from a fornecedor
+    clientes = query2(identificador) # Get all clientes related to a fornecedor
+    print(clientes[0].__dict__)
+    return render_template('3.html', clientes=clientes, boletos=boletos)
+
+
+
+def query1(x) +
+
+
+
+ +Expand source code + +
query1 = lambda x: query1_original(DB_FILE_PATH, x)
+
+
+
+def query2(x) +
+
+
+
+ +Expand source code + +
query2 = lambda x: query2_original(DB_FILE_PATH, x)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/32211207872718000117550010000217781877120005-nfe.xml b/in_xml/32211207872718000117550010000217781877120005-nfe.xml similarity index 100% rename from 32211207872718000117550010000217781877120005-nfe.xml rename to in_xml/32211207872718000117550010000217781877120005-nfe.xml diff --git a/NFe-002-3103.xml b/in_xml/NFe-002-3103.xml similarity index 100% rename from NFe-002-3103.xml rename to in_xml/NFe-002-3103.xml diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dc434a0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +click==8.1.0 +Flask==2.1.0 +itsdangerous==2.1.2 +Jinja2==3.1.1 +MarkupSafe==2.1.1 +Werkzeug==2.1.0 diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..75b3a11 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=LKhoe_venhapararecomb +sonar.organization=lkhoe + +# This is the name and version displayed in the SonarCloud UI. +#sonar.projectName=venhapararecomb +#sonar.projectVersion=1.0 + +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +#sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8 \ No newline at end of file diff --git a/src/database.py b/src/database.py new file mode 100644 index 0000000..fe68aca --- /dev/null +++ b/src/database.py @@ -0,0 +1,189 @@ +import sqlite3 +from contextlib import closing +from datetime import datetime +from models import Fornecedor, Cliente, NotaFiscal + +def create_database(db_file_path, db_schema_path): + """ + Creates a database file from a schema file. + + Args: + db_file_path (str): The path to the database file. + db_schema_path (str): The path to the schema file. + + Returns: + None + """ + with closing(sqlite3.connect(db_file_path)) as connection: + with open(db_schema_path) as f: + connection.executescript(f.read()) + connection.commit() + +def create_if_not_exist(connection, cursor, should_commit, table_name, columns, primary_key, primary_attribute, item_tuple): + """ + Creates a new item in the database if it doesn't exist. + + Args: + connection (sqlite3.Connection): The connection to the database. + cursor (sqlite3.Cursor): The cursor to the database. + should_commit (bool): Whether or not to commit the changes to the database. + table_name (str): The name of the table to insert the item into. + columns (list): The list of columns to insert the item into. + primary_key (str): The name of the primary key column. + primary_attribute (str): The value of the primary key attribute. + item_tuple (tuple): The tuple of values to insert into the table. + + Returns: + int: The id of the item. + """ + result = cursor.execute(f"SELECT * FROM {table_name} WHERE {primary_key} = ?", (primary_attribute,)).fetchone() + if result is None: + cursor.execute(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(['?']*len(columns))})", + item_tuple + ) + _id = cursor.lastrowid + if should_commit: + connection.commit() + else: + _id = result[0] + return _id + +def create_many(connection, cursor, should_commit, table_name, columns, items_list): + """ + Creates many items in the database. + + Args: + connection (sqlite3.Connection): The connection to the database. + cursor (sqlite3.Cursor): The cursor to the database. + should_commit (bool): Whether or not to commit the changes to the database. + table_name (str): The name of the table to insert the item into. + columns (list): The list of columns to insert the item into. + items_list (list): The list of items to insert into the table. + + Returns: + None + """ + cursor.executemany(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(['?']*len(columns))})", + items_list + ) + if should_commit: + connection.commit() + +def create_fornecedor(connection, cursor, should_commit, fornecedor): + """ + Creates a new fornecedor in the database if it not exists. + + Args: + connection (sqlite3.Connection): The connection to the database. + cursor (sqlite3.Cursor): The cursor to the database. + should_commit (bool): Whether or not to commit the changes to the database. + fornecedor (Fornecedor): The fornecedor to insert into the database. + + Returns: + int: The id of the fornecedor. + """ + return create_if_not_exist(connection, cursor, should_commit, + 'fornecedores', ['identificador'], 'identificador', + fornecedor.identificador, (fornecedor.identificador,)) + +def create_cliente(connection, cursor, should_commit, cliente): + """ + Creates a new cliente in the database if it not exists. + + Args: + connection (sqlite3.Connection): The connection to the database. + cursor (sqlite3.Cursor): The cursor to the database. + should_commit (bool): Whether or not to commit the changes to the database. + cliente (Cliente): The cliente to insert into the database. + + Returns: + int: The id of the cliente. + """ + return create_if_not_exist(connection, cursor, should_commit, + 'clientes', ['identificador', 'nome', 'endereco'], 'identificador', + cliente.identificador, (cliente.identificador, cliente.nome, cliente.endereco)) + +def create_boletos(connection, cursor, should_commit, boletos): + """ + Creates many boletos in the database. + + Args: + connection (sqlite3.Connection): The connection to the database. + cursor (sqlite3.Cursor): The cursor to the database. + should_commit (bool): Whether or not to commit the changes to the database. + boletos (list): The list of boletos to insert into the database. + + Returns: + None + """ + return create_many(connection, cursor, should_commit, + 'boletos', ['nota_fiscal_id', 'valor', 'vencimento'], + boletos) + +def create_NF(db_file_path, nota_fiscal): + """ + Creates a new nota fiscal in the database. + + Args: + db_file_path (str): The path to the database file. + nota_fiscal (NotaFiscal): The nota fiscal to insert into the database. + + Returns: + int: The id of the nota fiscal. + """ + with closing(sqlite3.connect(db_file_path)) as connection: + with closing(connection.cursor()) as cursor: + fornecedor_id = create_fornecedor(connection, cursor, False, nota_fiscal.fornecedor) + cliente_id = create_cliente(connection, cursor, False, nota_fiscal.cliente) + cursor.execute("INSERT INTO notas_fiscais (fornecedor_id, cliente_id) VALUES (?, ?)", + (fornecedor_id, cliente_id) + ) + nota_fiscal_id = cursor.lastrowid + create_boletos(connection, cursor, False, [(nota_fiscal_id, boleto['valor'], boleto['vencimento']) for boleto in nota_fiscal.boletos]) + connection.commit() + +def query1(db_file_path, fornecedor_identificador): + """ + Queries the database for the list of all boletos issued to a fornecedor. + + Args: + db_file_path (str): The path to the database file. + fornecedor_identificador (str): The fornecedor's identificador. + + Returns: + list: The list of boletos issued to the fornecedor. + """ + with closing(sqlite3.connect(db_file_path)) as connection: + with closing(connection.cursor()) as cursor: + return [{'valor': float(val), 'vencimento': datetime.strptime(ven, '%Y-%m-%d %H:%M:%S')} for _, val, ven in + cursor.execute(""" + SELECT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento + FROM boletos + INNER JOIN notas_fiscais ON boletos.nota_fiscal_id = notas_fiscais._id + INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id + WHERE fornecedores.identificador = ?; + """, (fornecedor_identificador, )).fetchall() + ] + +def query2(db_file_path, fornecedor_identificador): + """ + Queries the database for the list of all clientes of a fornecedor. + + Args: + db_file_path (str): The path to the database file. + fornecedor_identificador (str): The fornecedor's identificador. + + Returns: + list: The list of clientes of the fornecedor. + """ + with closing(sqlite3.connect(db_file_path)) as connection: + with closing(connection.cursor()) as cursor: + return [Cliente(i, n, e) for i, n, e in + cursor.execute(""" + SELECT clientes.identificador, clientes.nome, clientes.endereco + FROM clientes + INNER JOIN notas_fiscais ON clientes._id = notas_fiscais.cliente_id + INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id + WHERE fornecedores.identificador = ?; + """, (fornecedor_identificador, )).fetchall() + ] diff --git a/src/models.py b/src/models.py new file mode 100644 index 0000000..a1639eb --- /dev/null +++ b/src/models.py @@ -0,0 +1,39 @@ +from datetime import datetime + +class Fornecedor(): + """ + Class that represents a fornecedor. + + Attributes: + identificador (str): The fornecedor's identifier. + """ + def __init__(self, identificador): + self.identificador = identificador + +class Cliente(): + """ + Class that represents a cliente. + + Attributes: + identificador (str): The cliente's identifier. + nome (str): The cliente's name. + endereco (str): The cliente's address. + """ + def __init__(self, identificador, nome, endereco): + self.identificador = identificador + self.nome = nome + self.endereco = endereco + +class NotaFiscal(): + """ + Class that represents a nota fiscal. + + Attributes: + fornecedor (Fornecedor): The fornecedor of the nota fiscal. + cliente (Cliente): The cliente of the nota fiscal. + boletos (list): The list of boletos of the nota fiscal. + """ + def __init__(self, fornecedor, cliente, boletos): + self.fornecedor = fornecedor + self.cliente = cliente + self.boletos = boletos diff --git a/src/parsing.py b/src/parsing.py new file mode 100644 index 0000000..75c4c50 --- /dev/null +++ b/src/parsing.py @@ -0,0 +1,79 @@ +import xml.etree.ElementTree as ET +import re +from models import Fornecedor, Cliente, NotaFiscal +from datetime import datetime + +def parse_fornecedor(fornecedor_XML, namespace=''): + """ + Parses a fornecedor from an XML element. + + Args: + fornecedor_XML (xml.etree.ElementTree.Element): The XML element to parse. + namespace (str): The namespace of the XML element. + + Returns: + Fornecedor: The parsed fornecedor. + """ + if (cnpj := fornecedor_XML.find(f'{namespace}CNPJ')) is not None: + identificador = cnpj.text + else: + identificador = fornecedor_XML.find(f'{namespace}CPF').text + + return Fornecedor(identificador) + +def parse_cliente(cliente_XML, namespace=''): + """" + Parses a cliente from an XML element. + + Args: + cliente_XML (xml.etree.ElementTree.Element): The XML element to parse. + namespace (str): The namespace of the XML element. + + Returns: + Cliente: The parsed cliente. + """ + if (cnpj := cliente_XML.find(f'{namespace}CNPJ')) is not None: + identificador = cnpj.text + else: + identificador = cliente_XML.find(f'{namespace}CPF').text + nome = cliente_XML.find(f'{namespace}xNome').text + + endereco_XML = cliente_XML.find(f'{namespace}enderDest') + logradouro = endereco_XML.find(f'{namespace}xLgr').text + numero = endereco_XML.find(f'{namespace}nro').text + municipio = endereco_XML.find(f'{namespace}xMun').text + unidade_federativa = endereco_XML.find(f'{namespace}UF').text + endereco = f'{logradouro}, Nº {numero}, {municipio} - {unidade_federativa}' + + return Cliente(identificador, nome, endereco) + +def parse_NF(file_content): + """ + Parses a nota fiscal from an XML file. + + Args: + file_content (str): The XML file content. + + Returns: + NotaFiscal: The parsed nota fiscal. + """ + root_XML = ET.fromstring(file_content) + NFe_XML = root_XML[0] # NFe + infNFe_XML = NFe_XML[0] # infNFe + namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_XML.tag)) else '' # xmlns + + fornecedor = parse_fornecedor(infNFe_XML.find(f'{namespace}emit'), namespace) + cliente = parse_cliente(infNFe_XML.find(f'{namespace}dest'), namespace) + + cobranca_XML = infNFe_XML.find(f'{namespace}cobr') + duplicata_XML = cobranca_XML.findall(f'{namespace}dup') + boletos = [ + { + 'valor': float(dup_XML.find(f'{namespace}vDup').text), + 'vencimento': datetime.strptime(dup_XML.find(f'{namespace}dVenc').text, '%Y-%m-%d') + } + for dup_XML in duplicata_XML + ] + + return NotaFiscal(fornecedor, cliente, boletos) + diff --git a/src/service.py b/src/service.py new file mode 100644 index 0000000..58e60b3 --- /dev/null +++ b/src/service.py @@ -0,0 +1,59 @@ +from flask import Flask, redirect, render_template, request, url_for +from werkzeug.utils import secure_filename +from parsing import parse_NF +from database import create_database as create_database_original, \ + create_NF as create_NF_original, \ + query1 as query1_original, \ + query2 as query2_original + +# Setup functions for local database +DB_FILE_PATH = 'db/database.db' +DB_SCHEMA_PATH = 'db/schema.sql' +create_database = lambda: create_database_original(DB_FILE_PATH, DB_SCHEMA_PATH) +create_NF = lambda x: create_NF_original(DB_FILE_PATH, x) +query1 = lambda x: query1_original(DB_FILE_PATH, x) +query2 = lambda x: query2_original(DB_FILE_PATH, x) + +# Setup Flask app +app = Flask(__name__) + +@app.route('/') +def page_1(): + return render_template('1.html') + +@app.route('/2', methods = ['GET', 'POST']) +def page_2(): + if request.method != 'POST': + # To avoid breaking the flow of application, we will redirect to the first page + return redirect(url_for('page_1')) + + # Get the list of files from the request + files = request.files.getlist("file") + for file in files: + if file.filename == '': + continue + nf = parse_NF(file.read()) # Parse the file + create_NF(nf) # Create the nota fiscal in database + + return render_template('2.html') + +@app.route('/3', methods = ['GET', 'POST']) +def page_3(): + if request.method != 'POST': + # To avoid breaking the flow of application, we will redirect to the first page + return redirect(url_for('page_1')) + identificador = request.form.get('identificador', '') # Get the identificador from the request + + # FILTER TO GET UNIQUES + boletos = query1(identificador) # Get all boletos from a fornecedor + clientes = query2(identificador) # Get all clientes related to a fornecedor + + return render_template('3.html', clientes=clientes, boletos=boletos) + +@app.template_filter() +def format_datetime(value): + return value.strftime('%d/%m/%Y') + +if __name__ == '__main__': + create_database() # Create the database + app.run("0.0.0.0", port=5000, debug = True) # Run the app diff --git a/src/static/styles.scss b/src/static/styles.scss new file mode 100644 index 0000000..416bb76 --- /dev/null +++ b/src/static/styles.scss @@ -0,0 +1,135 @@ +body { + background-color: #8EC5FC; + background-image: linear-gradient(50deg, #000 0%, #e2e2e2 100%); + height: 100vh; + font-family: 'Montserrat', sans-serif; +} + +.container { + position: absolute; + transform: translate(-50%,-50%); + top: 50%; + left: 50%; +} + +form { + background-color: $white; + padding: 3em; + min-height: 320px; + border-radius: 20px; + border-left: 1px solid $white; + border-top: 1px solid $white; + backdrop-filter: blur(10px); + box-shadow: 20px 20px 40px -6px rgba(0,0,0,0.2); + text-align: center; + position: relative; + transition: all 0.2s ease-in-out; +} + +p { + font-weight: 500; + color: #fff; + opacity: 0.7; + text-shadow: 2px 2px 4px rgba(0,0,0,0.2); +} + +.title { + font-size: 1.4rem; + margin-top: 0px; + margin-bottom: 50px; +} + +.sub-title { + font-size: 1.2rem; +} + +input, label { + background: transparent; + min-width: 200px; + padding: 1em; + margin-bottom: 2em; + border: none; + border-left: 1px solid $white; + border-top: 1px solid $white; + border-radius: 5000px; + backdrop-filter: blur(5px); + box-shadow: 4px 4px 60px rgba(0,0,0,0.2); + color: #fff; + font-family: Montserrat, sans-serif; + font-weight: 500; + transition: all 0.2s ease-in-out; + text-shadow: 2px 2px 4px rgba(0,0,0,0.2); + text-align:center; +} + +input:hover, label:hover { + background: rgba(255,255,255,0.1); + box-shadow: 4px 4px 60px 8px rgba(0,0,0,0.2); +} + +input:focus, label:focus { + background: rgba(255,255,255,0.1); + box-shadow: 4px 4px 60px 8px rgba(0,0,0,0.2); +} + +input::placeholder { + font-family: Montserrat, sans-serif; + font-weight: 400; + color: #fff; + text-shadow: 2px 2px 4px rgba(0,0,0,0.4); +} + +input[type="submit"] { + margin-top: 10px; + font-size: 1rem; + position: absolute; + transform: translate(-50%,0%); + bottom: 0%; + left: 50%; +} + +input[type="submit"]:hover { + cursor: pointer; +} + +input[type="submit"]:active { + background: rgba(255,255,255,0.2); +} + +input:focus, +button:focus { + outline: none; +} + +input[type="file"] { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} + +label { + cursor: pointer; +} + +ul { + padding-inline-start: 0px; +} + +li { + list-style-type: none; + max-width: 340px; + padding: 1em; + margin-bottom: 1em; + border-radius: 5px; + box-shadow: 2px 2px 15px rgb(0 0 0 / 20%); + color: #fff; + font-family: Montserrat, sans-serif; + font-weight: 500; + text-shadow: 2px 2px 4px rgb(0 0 0 / 20%); + text-align: center; + display: flex; + flex-direction: column; +} diff --git a/src/templates/1.html b/src/templates/1.html new file mode 100644 index 0000000..1c0c0bc --- /dev/null +++ b/src/templates/1.html @@ -0,0 +1,34 @@ + + + + + +
+
+

Upload do Arquivo

+ +
+

+

Caso nenhum arquivo seja selecionado,
os dados do banco serão utilizados

+
+
+
+ + + \ No newline at end of file diff --git a/src/templates/2.html b/src/templates/2.html new file mode 100644 index 0000000..acdd42e --- /dev/null +++ b/src/templates/2.html @@ -0,0 +1,14 @@ + + + + + +
+
+

Informações do Fornecedor

+
+
+
+
+ + \ No newline at end of file diff --git a/src/templates/3.html b/src/templates/3.html new file mode 100644 index 0000000..8233767 --- /dev/null +++ b/src/templates/3.html @@ -0,0 +1,44 @@ + + + + + +
+
+

Resultados

+

Clientes

+ {% if clientes|length > 0 %} +
    + {% for cliente in clientes %} +
  • + {{ cliente.nome }} + {{ cliente.identificador }} + {{ cliente.endereco }} +
  • + {% endfor %} +
+ {% else %} +

Nenhum cliente foi encontrado

+ {% endif %} + +
+ +

Boletos

+ {% if boletos|length > 0 %} +
    + {% for boleto in boletos %} +
  • + R$ {{ boleto['valor'] }} + Vence em {{ boleto['vencimento']|format_datetime }} +
  • + {% endfor %} +
+ {% else %} +

Nenhum boleto foi encontrado

+ {% endif %} +
+
+
+
+ + \ No newline at end of file From aeb1389d496a33c16306b4d806e7cffa43078a4e Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 17:19:43 -0300 Subject: [PATCH 02/13] branch name on actions --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3c313d..82bd213 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build on: push: branches: - - master + - main pull_request: types: [opened, synchronize, reopened] jobs: From 99000e6dfe8f0db3ed92323889f10b39dd68739a Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 17:24:00 -0300 Subject: [PATCH 03/13] sonnar test --- src/static/styles.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/static/styles.scss b/src/static/styles.scss index 416bb76..2bf208e 100644 --- a/src/static/styles.scss +++ b/src/static/styles.scss @@ -1,5 +1,4 @@ body { - background-color: #8EC5FC; background-image: linear-gradient(50deg, #000 0%, #e2e2e2 100%); height: 100vh; font-family: 'Montserrat', sans-serif; From 30a512ca8d6abdbc392b5414281e8798a857d268 Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 21:51:12 -0300 Subject: [PATCH 04/13] =?UTF-8?q?mudan=C3=A7as?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/documentation.yml | 22 ++ .github/workflows/{build.yml => quality.yml} | 4 +- db/schema.sql | 1 + docs/src/database.html | 128 ++++++------ docs/src/models.html | 8 +- docs/src/parsing.html | 207 ++++++++++--------- docs/src/service.html | 41 ++-- src/database.py | 19 +- src/models.py | 3 +- src/parsing.py | 68 +++--- src/service.py | 18 +- src/templates/1.html | 2 + src/templates/2.html | 2 + src/templates/3.html | 2 + 14 files changed, 290 insertions(+), 235 deletions(-) create mode 100644 .github/workflows/documentation.yml rename .github/workflows/{build.yml => quality.yml} (87%) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..fa51e03 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,22 @@ +name: Documentation +on: + push: + branches: + - main +jobs: + change-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Generate documentation + run: | + python3 -m venv env + source env/bin/activate + pip install -r requirements.txt + PYTHONPATH=src pdoc --html src/ --force --output-dir ./docs + - name: Commit and push changes + uses: devops-infra/action-commit-push@v0.9.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + commit_message: Documentation update diff --git a/.github/workflows/build.yml b/.github/workflows/quality.yml similarity index 87% rename from .github/workflows/build.yml rename to .github/workflows/quality.yml index 82bd213..9f87619 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/quality.yml @@ -1,10 +1,8 @@ -name: Build +name: Quality on: push: branches: - main - pull_request: - types: [opened, synchronize, reopened] jobs: sonarcloud: name: SonarCloud diff --git a/db/schema.sql b/db/schema.sql index 234c7d4..69ae57e 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -17,6 +17,7 @@ CREATE TABLE clientes ( CREATE TABLE notas_fiscais ( _id INTEGER PRIMARY KEY AUTOINCREMENT, + chave_acesso TEXT NOT NULL UNIQUE, fornecedor_id INTEGER, cliente_id INTEGER, FOREIGN KEY (fornecedor_id) REFERENCES fornecedores(_id), diff --git a/docs/src/database.html b/docs/src/database.html index 9205089..d404546 100644 --- a/docs/src/database.html +++ b/docs/src/database.html @@ -99,7 +99,7 @@

Module src.database

def create_fornecedor(connection, cursor, should_commit, fornecedor): """ - Creates a new fornecedor in the database. + Creates a new fornecedor in the database if it not exists. Args: connection (sqlite3.Connection): The connection to the database. @@ -116,7 +116,7 @@

Module src.database

def create_cliente(connection, cursor, should_commit, cliente): """ - Creates a new cliente in the database. + Creates a new cliente in the database if it not exists. Args: connection (sqlite3.Connection): The connection to the database. @@ -144,13 +144,17 @@

Module src.database

Returns: None """ + notas_fiscais_id = {str(boleto[0]) for boleto in boletos} + results = cursor.execute(f"SELECT * FROM boletos WHERE nota_fiscal_id IN ({','.join(notas_fiscais_id)})").fetchall() + results = map(lambda x: (x[3], x[1], datetime.strptime(x[2], '%Y-%m-%d %H:%M:%S')), results) + boletos = list(set(boletos) - set(results)) return create_many(connection, cursor, should_commit, 'boletos', ['nota_fiscal_id', 'valor', 'vencimento'], boletos) -def create_NF(db_file_path, nota_fiscal): +def create_nota_fiscal(db_file_path, nota_fiscal): """ - Creates a new nota fiscal in the database. + Creates a new nota fiscal in the database if it not exists. Args: db_file_path (str): The path to the database file. @@ -163,10 +167,9 @@

Module src.database

with closing(connection.cursor()) as cursor: fornecedor_id = create_fornecedor(connection, cursor, False, nota_fiscal.fornecedor) cliente_id = create_cliente(connection, cursor, False, nota_fiscal.cliente) - cursor.execute("INSERT INTO notas_fiscais (fornecedor_id, cliente_id) VALUES (?, ?)", - (fornecedor_id, cliente_id) - ) - nota_fiscal_id = cursor.lastrowid + nota_fiscal_id = create_if_not_exist(connection, cursor, False, + 'notas_fiscais', ['chave_acesso', 'fornecedor_id', 'cliente_id'], 'chave_acesso', + nota_fiscal.chave_acesso, (nota_fiscal.chave_acesso, fornecedor_id, cliente_id)) create_boletos(connection, cursor, False, [(nota_fiscal_id, boleto['valor'], boleto['vencimento']) for boleto in nota_fiscal.boletos]) connection.commit() @@ -185,7 +188,7 @@

Module src.database

with closing(connection.cursor()) as cursor: return [{'valor': float(val), 'vencimento': datetime.strptime(ven, '%Y-%m-%d %H:%M:%S')} for _, val, ven in cursor.execute(""" - SELECT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento + SELECT DISTINCT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento FROM boletos INNER JOIN notas_fiscais ON boletos.nota_fiscal_id = notas_fiscais._id INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id @@ -208,7 +211,7 @@

Module src.database

with closing(connection.cursor()) as cursor: return [Cliente(i, n, e) for i, n, e in cursor.execute(""" - SELECT clientes.identificador, clientes.nome, clientes.endereco + SELECT DISTINCT clientes.identificador, clientes.nome, clientes.endereco FROM clientes INNER JOIN notas_fiscais ON clientes._id = notas_fiscais.cliente_id INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id @@ -224,50 +227,6 @@

Module src.database

Functions

-
-def create_NF(db_file_path, nota_fiscal) -
-
-

Creates a new nota fiscal in the database.

-

Args

-
-
db_file_path : str
-
The path to the database file.
-
nota_fiscal : NotaFiscal
-
The nota fiscal to insert into the database.
-
-

Returns

-
-
int
-
The id of the nota fiscal.
-
-
- -Expand source code - -
def create_NF(db_file_path, nota_fiscal):
-    """
-    Creates a new nota fiscal in the database.
-
-    Args:
-        db_file_path (str): The path to the database file.
-        nota_fiscal (NotaFiscal): The nota fiscal to insert into the database.
-
-    Returns:
-        int: The id of the nota fiscal.
-    """
-    with closing(sqlite3.connect(db_file_path)) as connection:
-        with closing(connection.cursor()) as cursor:
-            fornecedor_id = create_fornecedor(connection, cursor, False, nota_fiscal.fornecedor)
-            cliente_id = create_cliente(connection, cursor, False, nota_fiscal.cliente)
-            cursor.execute("INSERT INTO notas_fiscais (fornecedor_id, cliente_id) VALUES (?, ?)",
-                (fornecedor_id, cliente_id)
-            )
-            nota_fiscal_id = cursor.lastrowid
-            create_boletos(connection, cursor, False, [(nota_fiscal_id, boleto['valor'], boleto['vencimento']) for boleto in nota_fiscal.boletos])
-            connection.commit()
-
-
def create_boletos(connection, cursor, should_commit, boletos)
@@ -303,6 +262,10 @@

Returns

Returns: None """ + notas_fiscais_id = {str(boleto[0]) for boleto in boletos} + results = cursor.execute(f"SELECT * FROM boletos WHERE nota_fiscal_id IN ({','.join(notas_fiscais_id)})").fetchall() + results = map(lambda x: (x[3], x[1], datetime.strptime(x[2], '%Y-%m-%d %H:%M:%S')), results) + boletos = list(set(boletos) - set(results)) return create_many(connection, cursor, should_commit, 'boletos', ['nota_fiscal_id', 'valor', 'vencimento'], boletos) @@ -312,7 +275,7 @@

Returns

def create_cliente(connection, cursor, should_commit, cliente)
-

Creates a new cliente in the database.

+

Creates a new cliente in the database if it not exists.

Args

connection : sqlite3.Connection
@@ -335,7 +298,7 @@

Returns

def create_cliente(connection, cursor, should_commit, cliente):
     """
-    Creates a new cliente in the database.
+    Creates a new cliente in the database if it not exists.
     
     Args:
         connection (sqlite3.Connection): The connection to the database.
@@ -390,7 +353,7 @@ 

Returns

def create_fornecedor(connection, cursor, should_commit, fornecedor)
-

Creates a new fornecedor in the database.

+

Creates a new fornecedor in the database if it not exists.

Args

connection : sqlite3.Connection
@@ -413,7 +376,7 @@

Returns

def create_fornecedor(connection, cursor, should_commit, fornecedor):
     """
-    Creates a new fornecedor in the database.
+    Creates a new fornecedor in the database if it not exists.
 
     Args:
         connection (sqlite3.Connection): The connection to the database.
@@ -540,6 +503,49 @@ 

Returns

connection.commit()
+
+def create_nota_fiscal(db_file_path, nota_fiscal) +
+
+

Creates a new nota fiscal in the database if it not exists.

+

Args

+
+
db_file_path : str
+
The path to the database file.
+
nota_fiscal : NotaFiscal
+
The nota fiscal to insert into the database.
+
+

Returns

+
+
int
+
The id of the nota fiscal.
+
+
+ +Expand source code + +
def create_nota_fiscal(db_file_path, nota_fiscal):
+    """
+    Creates a new nota fiscal in the database if it not exists.
+
+    Args:
+        db_file_path (str): The path to the database file.
+        nota_fiscal (NotaFiscal): The nota fiscal to insert into the database.
+
+    Returns:
+        int: The id of the nota fiscal.
+    """
+    with closing(sqlite3.connect(db_file_path)) as connection:
+        with closing(connection.cursor()) as cursor:
+            fornecedor_id = create_fornecedor(connection, cursor, False, nota_fiscal.fornecedor)
+            cliente_id = create_cliente(connection, cursor, False, nota_fiscal.cliente)
+            nota_fiscal_id = create_if_not_exist(connection, cursor, False, 
+                'notas_fiscais', ['chave_acesso', 'fornecedor_id', 'cliente_id'], 'chave_acesso',
+                nota_fiscal.chave_acesso, (nota_fiscal.chave_acesso, fornecedor_id, cliente_id))
+            create_boletos(connection, cursor, False, [(nota_fiscal_id, boleto['valor'], boleto['vencimento']) for boleto in nota_fiscal.boletos])
+            connection.commit()
+
+
def query1(db_file_path, fornecedor_identificador)
@@ -576,7 +582,7 @@

Returns

with closing(connection.cursor()) as cursor: return [{'valor': float(val), 'vencimento': datetime.strptime(ven, '%Y-%m-%d %H:%M:%S')} for _, val, ven in cursor.execute(""" - SELECT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento + SELECT DISTINCT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento FROM boletos INNER JOIN notas_fiscais ON boletos.nota_fiscal_id = notas_fiscais._id INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id @@ -621,7 +627,7 @@

Returns

with closing(connection.cursor()) as cursor: return [Cliente(i, n, e) for i, n, e in cursor.execute(""" - SELECT clientes.identificador, clientes.nome, clientes.endereco + SELECT DISTINCT clientes.identificador, clientes.nome, clientes.endereco FROM clientes INNER JOIN notas_fiscais ON clientes._id = notas_fiscais.cliente_id INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id @@ -648,13 +654,13 @@

Index

  • Functions

    diff --git a/docs/src/models.html b/docs/src/models.html index b3d4eef..d365d82 100644 --- a/docs/src/models.html +++ b/docs/src/models.html @@ -61,7 +61,8 @@

    Module src.models

    cliente (Cliente): The cliente of the nota fiscal. boletos (list): The list of boletos of the nota fiscal. """ - def __init__(self, fornecedor, cliente, boletos): + def __init__(self, chave_acesso, fornecedor, cliente, boletos): + self.chave_acesso = chave_acesso self.fornecedor = fornecedor self.cliente = cliente self.boletos = boletos
  • @@ -138,7 +139,7 @@

    Attributes

    class NotaFiscal -(fornecedor, cliente, boletos) +(chave_acesso, fornecedor, cliente, boletos)

    Class that represents a nota fiscal.

    @@ -164,7 +165,8 @@

    Attributes

    cliente (Cliente): The cliente of the nota fiscal. boletos (list): The list of boletos of the nota fiscal. """ - def __init__(self, fornecedor, cliente, boletos): + def __init__(self, chave_acesso, fornecedor, cliente, boletos): + self.chave_acesso = chave_acesso self.fornecedor = fornecedor self.cliente = cliente self.boletos = boletos diff --git a/docs/src/parsing.html b/docs/src/parsing.html index a6a7bea..84f0e3a 100644 --- a/docs/src/parsing.html +++ b/docs/src/parsing.html @@ -31,51 +31,51 @@

    Module src.parsing

    from models import Fornecedor, Cliente, NotaFiscal from datetime import datetime -def parse_fornecedor(fornecedor_XML, namespace=''): +def parse_fornecedor(fornecedor_xml, namespace=''): """ Parses a fornecedor from an XML element. Args: - fornecedor_XML (xml.etree.ElementTree.Element): The XML element to parse. + fornecedor_xml (xml.etree.ElementTree.Element): The XML element to parse. namespace (str): The namespace of the XML element. Returns: Fornecedor: The parsed fornecedor. """ - if (cnpj := fornecedor_XML.find(f'{namespace}CNPJ')) is not None: + if (cnpj := fornecedor_xml.find(f'{namespace}CNPJ')) is not None: identificador = cnpj.text else: - identificador = fornecedor_XML.find(f'{namespace}CPF').text + identificador = fornecedor_xml.find(f'{namespace}CPF').text return Fornecedor(identificador) -def parse_cliente(cliente_XML, namespace=''): +def parse_cliente(cliente_xml, namespace=''): """" Parses a cliente from an XML element. Args: - cliente_XML (xml.etree.ElementTree.Element): The XML element to parse. + cliente_xml (xml.etree.ElementTree.Element): The XML element to parse. namespace (str): The namespace of the XML element. Returns: Cliente: The parsed cliente. """ - if (cnpj := cliente_XML.find(f'{namespace}CNPJ')) is not None: + if (cnpj := cliente_xml.find(f'{namespace}CNPJ')) is not None: identificador = cnpj.text else: - identificador = cliente_XML.find(f'{namespace}CPF').text - nome = cliente_XML.find(f'{namespace}xNome').text + identificador = cliente_xml.find(f'{namespace}CPF').text + nome = cliente_xml.find(f'{namespace}xNome').text - endereco_XML = cliente_XML.find(f'{namespace}enderDest') - logradouro = endereco_XML.find(f'{namespace}xLgr').text - numero = endereco_XML.find(f'{namespace}nro').text - municipio = endereco_XML.find(f'{namespace}xMun').text - unidade_federativa = endereco_XML.find(f'{namespace}UF').text + endereco_xml = cliente_xml.find(f'{namespace}enderDest') + logradouro = endereco_xml.find(f'{namespace}xLgr').text + numero = endereco_xml.find(f'{namespace}nro').text + municipio = endereco_xml.find(f'{namespace}xMun').text + unidade_federativa = endereco_xml.find(f'{namespace}UF').text endereco = f'{logradouro}, Nº {numero}, {municipio} - {unidade_federativa}' return Cliente(identificador, nome, endereco) -def parse_NF(file_content): +def parse_nota_fiscal(file_content): """ Parses a nota fiscal from an XML file. @@ -83,27 +83,31 @@

    Module src.parsing

    file_content (str): The XML file content. Returns: - NotaFiscal: The parsed nota fiscal. + NotaFiscal/None: The parsed nota fiscal or None if the file is not a nota fiscal. """ - root_XML = ET.fromstring(file_content) - NFe_XML = root_XML[0] # NFe - infNFe_XML = NFe_XML[0] # infNFe - namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_XML.tag)) else '' # xmlns + try: + root_xml = ET.fromstring(file_content) + NFe_xml = root_xml[0] # NFe + infNFe_xml = NFe_xml[0] # infNFe + namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_xml.tag)) else '' # xmlns - fornecedor = parse_fornecedor(infNFe_XML.find(f'{namespace}emit'), namespace) - cliente = parse_cliente(infNFe_XML.find(f'{namespace}dest'), namespace) + fornecedor = parse_fornecedor(infNFe_xml.find(f'{namespace}emit'), namespace) + cliente = parse_cliente(infNFe_xml.find(f'{namespace}dest'), namespace) - cobranca_XML = infNFe_XML.find(f'{namespace}cobr') - duplicata_XML = cobranca_XML.findall(f'{namespace}dup') - boletos = [ - { - 'valor': float(dup_XML.find(f'{namespace}vDup').text), - 'vencimento': datetime.strptime(dup_XML.find(f'{namespace}dVenc').text, '%Y-%m-%d') - } - for dup_XML in duplicata_XML - ] + cobranca_xml = infNFe_xml.find(f'{namespace}cobr') + duplicata_xml = cobranca_xml.findall(f'{namespace}dup') + boletos = [ + { + 'valor': float(dup_xml.find(f'{namespace}vDup').text), + 'vencimento': datetime.strptime(dup_xml.find(f'{namespace}dVenc').text, '%Y-%m-%d') + } + for dup_xml in duplicata_xml + ] - return NotaFiscal(fornecedor, cliente, boletos) + return NotaFiscal(infNFe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) + + except Exception: + return None
    @@ -113,65 +117,15 @@

    Module src.parsing

    Functions

    -
    -def parse_NF(file_content) -
    -
    -

    Parses a nota fiscal from an XML file.

    -

    Args

    -
    -
    file_content : str
    -
    The XML file content.
    -
    -

    Returns

    -
    -
    NotaFiscal
    -
    The parsed nota fiscal.
    -
    -
    - -Expand source code - -
    def parse_NF(file_content):
    -    """
    -    Parses a nota fiscal from an XML file.
    -
    -    Args:
    -        file_content (str): The XML file content.
    -
    -    Returns:
    -        NotaFiscal: The parsed nota fiscal.
    -    """
    -    root_XML = ET.fromstring(file_content)
    -    NFe_XML = root_XML[0] # NFe
    -    infNFe_XML = NFe_XML[0] # infNFe
    -    namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_XML.tag)) else '' # xmlns
    -
    -    fornecedor = parse_fornecedor(infNFe_XML.find(f'{namespace}emit'), namespace)
    -    cliente = parse_cliente(infNFe_XML.find(f'{namespace}dest'), namespace)
    -
    -    cobranca_XML = infNFe_XML.find(f'{namespace}cobr')
    -    duplicata_XML = cobranca_XML.findall(f'{namespace}dup')
    -    boletos = [
    -        {
    -            'valor': float(dup_XML.find(f'{namespace}vDup').text), 
    -            'vencimento': datetime.strptime(dup_XML.find(f'{namespace}dVenc').text, '%Y-%m-%d')
    -        } 
    -        for dup_XML in duplicata_XML
    -    ]
    -
    -    return NotaFiscal(fornecedor, cliente, boletos)
    -
    -
    -def parse_cliente(cliente_XML, namespace='') +def parse_cliente(cliente_xml, namespace='')

    " Parses a cliente from an XML element.

    Args

    -
    cliente_XML : xml.etree.ElementTree.Element
    +
    cliente_xml : xml.etree.ElementTree.Element
    The XML element to parse.
    namespace : str
    The namespace of the XML element.
    @@ -185,41 +139,41 @@

    Returns

    Expand source code -
    def parse_cliente(cliente_XML, namespace=''):
    +
    def parse_cliente(cliente_xml, namespace=''):
         """"
         Parses a cliente from an XML element.
         
         Args:
    -        cliente_XML (xml.etree.ElementTree.Element): The XML element to parse.
    +        cliente_xml (xml.etree.ElementTree.Element): The XML element to parse.
             namespace (str): The namespace of the XML element.
     
         Returns:
             Cliente: The parsed cliente.
         """
    -    if (cnpj := cliente_XML.find(f'{namespace}CNPJ')) is not None:
    +    if (cnpj := cliente_xml.find(f'{namespace}CNPJ')) is not None:
             identificador = cnpj.text
         else:
    -        identificador = cliente_XML.find(f'{namespace}CPF').text
    -    nome = cliente_XML.find(f'{namespace}xNome').text
    +        identificador = cliente_xml.find(f'{namespace}CPF').text
    +    nome = cliente_xml.find(f'{namespace}xNome').text
     
    -    endereco_XML = cliente_XML.find(f'{namespace}enderDest')
    -    logradouro = endereco_XML.find(f'{namespace}xLgr').text
    -    numero = endereco_XML.find(f'{namespace}nro').text
    -    municipio = endereco_XML.find(f'{namespace}xMun').text
    -    unidade_federativa = endereco_XML.find(f'{namespace}UF').text
    +    endereco_xml = cliente_xml.find(f'{namespace}enderDest')
    +    logradouro = endereco_xml.find(f'{namespace}xLgr').text
    +    numero = endereco_xml.find(f'{namespace}nro').text
    +    municipio = endereco_xml.find(f'{namespace}xMun').text
    +    unidade_federativa = endereco_xml.find(f'{namespace}UF').text
         endereco = f'{logradouro}, Nº {numero}, {municipio} - {unidade_federativa}'
     
         return Cliente(identificador, nome, endereco)
    -def parse_fornecedor(fornecedor_XML, namespace='') +def parse_fornecedor(fornecedor_xml, namespace='')

    Parses a fornecedor from an XML element.

    Args

    -
    fornecedor_XML : xml.etree.ElementTree.Element
    +
    fornecedor_xml : xml.etree.ElementTree.Element
    The XML element to parse.
    namespace : str
    The namespace of the XML element.
    @@ -233,25 +187,76 @@

    Returns

    Expand source code -
    def parse_fornecedor(fornecedor_XML, namespace=''):
    +
    def parse_fornecedor(fornecedor_xml, namespace=''):
         """
         Parses a fornecedor from an XML element.
     
         Args:
    -        fornecedor_XML (xml.etree.ElementTree.Element): The XML element to parse.
    +        fornecedor_xml (xml.etree.ElementTree.Element): The XML element to parse.
             namespace (str): The namespace of the XML element.
         
         Returns:
             Fornecedor: The parsed fornecedor.
         """
    -    if (cnpj := fornecedor_XML.find(f'{namespace}CNPJ')) is not None:
    +    if (cnpj := fornecedor_xml.find(f'{namespace}CNPJ')) is not None:
             identificador = cnpj.text
         else:
    -        identificador = fornecedor_XML.find(f'{namespace}CPF').text
    +        identificador = fornecedor_xml.find(f'{namespace}CPF').text
     
         return Fornecedor(identificador)
    +
    +def parse_nota_fiscal(file_content) +
    +
    +

    Parses a nota fiscal from an XML file.

    +

    Args

    +
    +
    file_content : str
    +
    The XML file content.
    +
    +

    Returns

    +

    NotaFiscal/None: The parsed nota fiscal or None if the file is not a nota fiscal.

    +
    + +Expand source code + +
    def parse_nota_fiscal(file_content):
    +    """
    +    Parses a nota fiscal from an XML file.
    +
    +    Args:
    +        file_content (str): The XML file content.
    +
    +    Returns:
    +        NotaFiscal/None: The parsed nota fiscal or None if the file is not a nota fiscal.
    +    """
    +    try:
    +        root_xml = ET.fromstring(file_content)
    +        NFe_xml = root_xml[0] # NFe
    +        infNFe_xml = NFe_xml[0] # infNFe
    +        namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_xml.tag)) else '' # xmlns
    +
    +        fornecedor = parse_fornecedor(infNFe_xml.find(f'{namespace}emit'), namespace)
    +        cliente = parse_cliente(infNFe_xml.find(f'{namespace}dest'), namespace)
    +
    +        cobranca_xml = infNFe_xml.find(f'{namespace}cobr')
    +        duplicata_xml = cobranca_xml.findall(f'{namespace}dup')
    +        boletos = [
    +            {
    +                'valor': float(dup_xml.find(f'{namespace}vDup').text), 
    +                'vencimento': datetime.strptime(dup_xml.find(f'{namespace}dVenc').text, '%Y-%m-%d')
    +            } 
    +            for dup_xml in duplicata_xml
    +        ]
    +
    +        return NotaFiscal(infNFe_xml.attrib['Id'][3:], fornecedor, cliente, boletos)
    +
    +    except Exception:
    +        return None
    +
    +
    @@ -270,9 +275,9 @@

    Index

  • Functions

  • diff --git a/docs/src/service.html b/docs/src/service.html index 66681b4..8fba781 100644 --- a/docs/src/service.html +++ b/docs/src/service.html @@ -28,9 +28,9 @@

    Module src.service

    from flask import Flask, redirect, render_template, request, url_for
     from werkzeug.utils import secure_filename
    -from parsing import parse_NF
    +from parsing import parse_nota_fiscal
     from database import create_database as create_database_original, \
    -    create_NF as create_NF_original, \
    +    create_nota_fiscal as create_nota_fiscal_original, \
         query1 as query1_original, \
         query2 as query2_original
     
    @@ -38,7 +38,7 @@ 

    Module src.service

    DB_FILE_PATH = 'db/database.db' DB_SCHEMA_PATH = 'db/schema.sql' create_database = lambda: create_database_original(DB_FILE_PATH, DB_SCHEMA_PATH) -create_NF = lambda x: create_NF_original(DB_FILE_PATH, x) +create_nota_fiscal = lambda x: create_nota_fiscal_original(DB_FILE_PATH, x) query1 = lambda x: query1_original(DB_FILE_PATH, x) query2 = lambda x: query2_original(DB_FILE_PATH, x) @@ -60,8 +60,9 @@

    Module src.service

    for file in files: if file.filename == '': continue - nf = parse_NF(file.read()) # Parse the file - create_NF(nf) # Create the nota fiscal in database + nf = parse_nota_fiscal(file.read()) # Parse the file + if nf is not None: + create_nota_fiscal(nf) # Create the nota fiscal in database return render_template('2.html') @@ -73,7 +74,7 @@

    Module src.service

    identificador = request.form.get('identificador', '') # Get the identificador from the request boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor - print(clientes[0].__dict__) + return render_template('3.html', clientes=clientes, boletos=boletos) @app.template_filter() @@ -81,8 +82,11 @@

    Module src.service

    return value.strftime('%d/%m/%Y') if __name__ == '__main__': - create_database() # Create the database - app.run(debug = True) # Run the app
    + try: + create_database() # Create the database + except Exception: + print("Não foi possível criar o banco de dados") + app.run("0.0.0.0", port=5000, debug = True) # Run the app
    @@ -92,8 +96,8 @@

    Module src.service

    Functions

    -
    -def create_NF(x) +
    +def create_database()
    @@ -101,11 +105,11 @@

    Functions

    Expand source code -
    create_NF = lambda x: create_NF_original(DB_FILE_PATH, x)
    +
    create_database = lambda: create_database_original(DB_FILE_PATH, DB_SCHEMA_PATH)
    -
    -def create_database() +
    +def create_nota_fiscal(x)
    @@ -113,7 +117,7 @@

    Functions

    Expand source code -
    create_database = lambda: create_database_original(DB_FILE_PATH, DB_SCHEMA_PATH)
    +
    create_nota_fiscal = lambda x: create_nota_fiscal_original(DB_FILE_PATH, x)
    @@ -164,8 +168,9 @@

    Functions

    for file in files: if file.filename == '': continue - nf = parse_NF(file.read()) # Parse the file - create_NF(nf) # Create the nota fiscal in database + nf = parse_nota_fiscal(file.read()) # Parse the file + if nf is not None: + create_nota_fiscal(nf) # Create the nota fiscal in database return render_template('2.html')
    @@ -187,7 +192,7 @@

    Functions

    identificador = request.form.get('identificador', '') # Get the identificador from the request boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor - print(clientes[0].__dict__) + return render_template('3.html', clientes=clientes, boletos=boletos)
    @@ -233,8 +238,8 @@

    Index

  • Functions

      -
    • create_NF
    • create_database
    • +
    • create_nota_fiscal
    • format_datetime
    • page_1
    • page_2
    • diff --git a/src/database.py b/src/database.py index fe68aca..b011422 100644 --- a/src/database.py +++ b/src/database.py @@ -116,13 +116,17 @@ def create_boletos(connection, cursor, should_commit, boletos): Returns: None """ + notas_fiscais_id = {str(boleto[0]) for boleto in boletos} + results = cursor.execute(f"SELECT * FROM boletos WHERE nota_fiscal_id IN ({','.join(notas_fiscais_id)})").fetchall() + results = map(lambda x: (x[3], x[1], datetime.strptime(x[2], '%Y-%m-%d %H:%M:%S')), results) + boletos = list(set(boletos) - set(results)) return create_many(connection, cursor, should_commit, 'boletos', ['nota_fiscal_id', 'valor', 'vencimento'], boletos) -def create_NF(db_file_path, nota_fiscal): +def create_nota_fiscal(db_file_path, nota_fiscal): """ - Creates a new nota fiscal in the database. + Creates a new nota fiscal in the database if it not exists. Args: db_file_path (str): The path to the database file. @@ -135,10 +139,9 @@ def create_NF(db_file_path, nota_fiscal): with closing(connection.cursor()) as cursor: fornecedor_id = create_fornecedor(connection, cursor, False, nota_fiscal.fornecedor) cliente_id = create_cliente(connection, cursor, False, nota_fiscal.cliente) - cursor.execute("INSERT INTO notas_fiscais (fornecedor_id, cliente_id) VALUES (?, ?)", - (fornecedor_id, cliente_id) - ) - nota_fiscal_id = cursor.lastrowid + nota_fiscal_id = create_if_not_exist(connection, cursor, False, + 'notas_fiscais', ['chave_acesso', 'fornecedor_id', 'cliente_id'], 'chave_acesso', + nota_fiscal.chave_acesso, (nota_fiscal.chave_acesso, fornecedor_id, cliente_id)) create_boletos(connection, cursor, False, [(nota_fiscal_id, boleto['valor'], boleto['vencimento']) for boleto in nota_fiscal.boletos]) connection.commit() @@ -157,7 +160,7 @@ def query1(db_file_path, fornecedor_identificador): with closing(connection.cursor()) as cursor: return [{'valor': float(val), 'vencimento': datetime.strptime(ven, '%Y-%m-%d %H:%M:%S')} for _, val, ven in cursor.execute(""" - SELECT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento + SELECT DISTINCT boletos.nota_fiscal_id, boletos.valor, boletos.vencimento FROM boletos INNER JOIN notas_fiscais ON boletos.nota_fiscal_id = notas_fiscais._id INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id @@ -180,7 +183,7 @@ def query2(db_file_path, fornecedor_identificador): with closing(connection.cursor()) as cursor: return [Cliente(i, n, e) for i, n, e in cursor.execute(""" - SELECT clientes.identificador, clientes.nome, clientes.endereco + SELECT DISTINCT clientes.identificador, clientes.nome, clientes.endereco FROM clientes INNER JOIN notas_fiscais ON clientes._id = notas_fiscais.cliente_id INNER JOIN fornecedores ON notas_fiscais.fornecedor_id = fornecedores._id diff --git a/src/models.py b/src/models.py index a1639eb..5b654e6 100644 --- a/src/models.py +++ b/src/models.py @@ -33,7 +33,8 @@ class NotaFiscal(): cliente (Cliente): The cliente of the nota fiscal. boletos (list): The list of boletos of the nota fiscal. """ - def __init__(self, fornecedor, cliente, boletos): + def __init__(self, chave_acesso, fornecedor, cliente, boletos): + self.chave_acesso = chave_acesso self.fornecedor = fornecedor self.cliente = cliente self.boletos = boletos diff --git a/src/parsing.py b/src/parsing.py index 75c4c50..41c7e80 100644 --- a/src/parsing.py +++ b/src/parsing.py @@ -3,51 +3,51 @@ from models import Fornecedor, Cliente, NotaFiscal from datetime import datetime -def parse_fornecedor(fornecedor_XML, namespace=''): +def parse_fornecedor(fornecedor_xml, namespace=''): """ Parses a fornecedor from an XML element. Args: - fornecedor_XML (xml.etree.ElementTree.Element): The XML element to parse. + fornecedor_xml (xml.etree.ElementTree.Element): The XML element to parse. namespace (str): The namespace of the XML element. Returns: Fornecedor: The parsed fornecedor. """ - if (cnpj := fornecedor_XML.find(f'{namespace}CNPJ')) is not None: + if (cnpj := fornecedor_xml.find(f'{namespace}CNPJ')) is not None: identificador = cnpj.text else: - identificador = fornecedor_XML.find(f'{namespace}CPF').text + identificador = fornecedor_xml.find(f'{namespace}CPF').text return Fornecedor(identificador) -def parse_cliente(cliente_XML, namespace=''): +def parse_cliente(cliente_xml, namespace=''): """" Parses a cliente from an XML element. Args: - cliente_XML (xml.etree.ElementTree.Element): The XML element to parse. + cliente_xml (xml.etree.ElementTree.Element): The XML element to parse. namespace (str): The namespace of the XML element. Returns: Cliente: The parsed cliente. """ - if (cnpj := cliente_XML.find(f'{namespace}CNPJ')) is not None: + if (cnpj := cliente_xml.find(f'{namespace}CNPJ')) is not None: identificador = cnpj.text else: - identificador = cliente_XML.find(f'{namespace}CPF').text - nome = cliente_XML.find(f'{namespace}xNome').text + identificador = cliente_xml.find(f'{namespace}CPF').text + nome = cliente_xml.find(f'{namespace}xNome').text - endereco_XML = cliente_XML.find(f'{namespace}enderDest') - logradouro = endereco_XML.find(f'{namespace}xLgr').text - numero = endereco_XML.find(f'{namespace}nro').text - municipio = endereco_XML.find(f'{namespace}xMun').text - unidade_federativa = endereco_XML.find(f'{namespace}UF').text + endereco_xml = cliente_xml.find(f'{namespace}enderDest') + logradouro = endereco_xml.find(f'{namespace}xLgr').text + numero = endereco_xml.find(f'{namespace}nro').text + municipio = endereco_xml.find(f'{namespace}xMun').text + unidade_federativa = endereco_xml.find(f'{namespace}UF').text endereco = f'{logradouro}, Nº {numero}, {municipio} - {unidade_federativa}' return Cliente(identificador, nome, endereco) -def parse_NF(file_content): +def parse_nota_fiscal(file_content): """ Parses a nota fiscal from an XML file. @@ -55,25 +55,29 @@ def parse_NF(file_content): file_content (str): The XML file content. Returns: - NotaFiscal: The parsed nota fiscal. + NotaFiscal/None: The parsed nota fiscal or None if the file is not a nota fiscal. """ - root_XML = ET.fromstring(file_content) - NFe_XML = root_XML[0] # NFe - infNFe_XML = NFe_XML[0] # infNFe - namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_XML.tag)) else '' # xmlns + try: + root_xml = ET.fromstring(file_content) + NFe_xml = root_xml[0] # NFe + infNFe_xml = NFe_xml[0] # infNFe + namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_xml.tag)) else '' # xmlns - fornecedor = parse_fornecedor(infNFe_XML.find(f'{namespace}emit'), namespace) - cliente = parse_cliente(infNFe_XML.find(f'{namespace}dest'), namespace) + fornecedor = parse_fornecedor(infNFe_xml.find(f'{namespace}emit'), namespace) + cliente = parse_cliente(infNFe_xml.find(f'{namespace}dest'), namespace) - cobranca_XML = infNFe_XML.find(f'{namespace}cobr') - duplicata_XML = cobranca_XML.findall(f'{namespace}dup') - boletos = [ - { - 'valor': float(dup_XML.find(f'{namespace}vDup').text), - 'vencimento': datetime.strptime(dup_XML.find(f'{namespace}dVenc').text, '%Y-%m-%d') - } - for dup_XML in duplicata_XML - ] + cobranca_xml = infNFe_xml.find(f'{namespace}cobr') + duplicata_xml = cobranca_xml.findall(f'{namespace}dup') + boletos = [ + { + 'valor': float(dup_xml.find(f'{namespace}vDup').text), + 'vencimento': datetime.strptime(dup_xml.find(f'{namespace}dVenc').text, '%Y-%m-%d') + } + for dup_xml in duplicata_xml + ] - return NotaFiscal(fornecedor, cliente, boletos) + return NotaFiscal(infNFe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) + + except Exception: + return None diff --git a/src/service.py b/src/service.py index 58e60b3..30e65b2 100644 --- a/src/service.py +++ b/src/service.py @@ -1,8 +1,8 @@ from flask import Flask, redirect, render_template, request, url_for from werkzeug.utils import secure_filename -from parsing import parse_NF +from parsing import parse_nota_fiscal from database import create_database as create_database_original, \ - create_NF as create_NF_original, \ + create_nota_fiscal as create_nota_fiscal_original, \ query1 as query1_original, \ query2 as query2_original @@ -10,7 +10,7 @@ DB_FILE_PATH = 'db/database.db' DB_SCHEMA_PATH = 'db/schema.sql' create_database = lambda: create_database_original(DB_FILE_PATH, DB_SCHEMA_PATH) -create_NF = lambda x: create_NF_original(DB_FILE_PATH, x) +create_nota_fiscal = lambda x: create_nota_fiscal_original(DB_FILE_PATH, x) query1 = lambda x: query1_original(DB_FILE_PATH, x) query2 = lambda x: query2_original(DB_FILE_PATH, x) @@ -32,8 +32,9 @@ def page_2(): for file in files: if file.filename == '': continue - nf = parse_NF(file.read()) # Parse the file - create_NF(nf) # Create the nota fiscal in database + nf = parse_nota_fiscal(file.read()) # Parse the file + if nf is not None: + create_nota_fiscal(nf) # Create the nota fiscal in database return render_template('2.html') @@ -43,8 +44,6 @@ def page_3(): # To avoid breaking the flow of application, we will redirect to the first page return redirect(url_for('page_1')) identificador = request.form.get('identificador', '') # Get the identificador from the request - - # FILTER TO GET UNIQUES boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor @@ -55,5 +54,8 @@ def format_datetime(value): return value.strftime('%d/%m/%Y') if __name__ == '__main__': - create_database() # Create the database + try: + create_database() # Create the database + except Exception: + print("Não foi possível criar o banco de dados") app.run("0.0.0.0", port=5000, debug = True) # Run the app diff --git a/src/templates/1.html b/src/templates/1.html index 1c0c0bc..010b424 100644 --- a/src/templates/1.html +++ b/src/templates/1.html @@ -1,5 +1,7 @@ + + Venha para Recomb diff --git a/src/templates/2.html b/src/templates/2.html index acdd42e..496a853 100644 --- a/src/templates/2.html +++ b/src/templates/2.html @@ -1,5 +1,7 @@ + + Venha para Recomb diff --git a/src/templates/3.html b/src/templates/3.html index 8233767..8fa86e4 100644 --- a/src/templates/3.html +++ b/src/templates/3.html @@ -1,5 +1,7 @@ + + Venha para Recomb From b8380a2e5ada58b811cb3deef40121d91f7f81fa Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 22:23:04 -0300 Subject: [PATCH 05/13] ajustes --- .github/workflows/documentation.yml | 2 -- requirements.txt | 3 +++ src/parsing.py | 12 ++++++------ src/static/{styles.scss => styles.css} | 11 +++++------ src/templates/1.html | 7 ++++--- src/templates/2.html | 5 +++-- src/templates/3.html | 5 +++-- 7 files changed, 24 insertions(+), 21 deletions(-) rename src/static/{styles.scss => styles.css} (90%) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index fa51e03..9127bea 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -11,8 +11,6 @@ jobs: uses: actions/checkout@v2 - name: Generate documentation run: | - python3 -m venv env - source env/bin/activate pip install -r requirements.txt PYTHONPATH=src pdoc --html src/ --force --output-dir ./docs - name: Commit and push changes diff --git a/requirements.txt b/requirements.txt index dc434a0..9e1538e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,8 @@ click==8.1.0 Flask==2.1.0 itsdangerous==2.1.2 Jinja2==3.1.1 +Mako==1.2.0 +Markdown==3.3.6 MarkupSafe==2.1.1 +pdoc3==0.10.0 Werkzeug==2.1.0 diff --git a/src/parsing.py b/src/parsing.py index 41c7e80..2736e34 100644 --- a/src/parsing.py +++ b/src/parsing.py @@ -59,14 +59,14 @@ def parse_nota_fiscal(file_content): """ try: root_xml = ET.fromstring(file_content) - NFe_xml = root_xml[0] # NFe - infNFe_xml = NFe_xml[0] # infNFe + nfe_xml = root_xml[0] # NFe + inf_nfe_xml = nfe_xml[0] # infNFe namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_xml.tag)) else '' # xmlns - fornecedor = parse_fornecedor(infNFe_xml.find(f'{namespace}emit'), namespace) - cliente = parse_cliente(infNFe_xml.find(f'{namespace}dest'), namespace) + fornecedor = parse_fornecedor(inf_nfe_xml.find(f'{namespace}emit'), namespace) + cliente = parse_cliente(inf_nfe_xml.find(f'{namespace}dest'), namespace) - cobranca_xml = infNFe_xml.find(f'{namespace}cobr') + cobranca_xml = inf_nfe_xml.find(f'{namespace}cobr') duplicata_xml = cobranca_xml.findall(f'{namespace}dup') boletos = [ { @@ -76,7 +76,7 @@ def parse_nota_fiscal(file_content): for dup_xml in duplicata_xml ] - return NotaFiscal(infNFe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) + return NotaFiscal(inf_nfe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) except Exception: return None diff --git a/src/static/styles.scss b/src/static/styles.css similarity index 90% rename from src/static/styles.scss rename to src/static/styles.css index 2bf208e..ebe82ce 100644 --- a/src/static/styles.scss +++ b/src/static/styles.css @@ -12,12 +12,12 @@ body { } form { - background-color: $white; + background-color: rgba(255,255,255,0.1); padding: 3em; min-height: 320px; border-radius: 20px; - border-left: 1px solid $white; - border-top: 1px solid $white; + border-left: 1px solid rgba(255,255,255,0.1); + border-top: 1px solid rgba(255,255,255,0.1); backdrop-filter: blur(10px); box-shadow: 20px 20px 40px -6px rgba(0,0,0,0.2); text-align: center; @@ -48,10 +48,9 @@ input, label { padding: 1em; margin-bottom: 2em; border: none; - border-left: 1px solid $white; - border-top: 1px solid $white; + border-left: 1px solid rgba(255,255,255,0.1); + border-top: 1px solid rgba(255,255,255,0.1); border-radius: 5000px; - backdrop-filter: blur(5px); box-shadow: 4px 4px 60px rgba(0,0,0,0.2); color: #fff; font-family: Montserrat, sans-serif; diff --git a/src/templates/1.html b/src/templates/1.html index 010b424..d2ed3e8 100644 --- a/src/templates/1.html +++ b/src/templates/1.html @@ -1,8 +1,9 @@ - + - + Venha para Recomb - + +
      diff --git a/src/templates/2.html b/src/templates/2.html index 496a853..f1e2329 100644 --- a/src/templates/2.html +++ b/src/templates/2.html @@ -1,8 +1,9 @@ - + Venha para Recomb - + +
      diff --git a/src/templates/3.html b/src/templates/3.html index 8fa86e4..8b6105e 100644 --- a/src/templates/3.html +++ b/src/templates/3.html @@ -1,8 +1,9 @@ - + Venha para Recomb - + +
      From eb36382f16677b62a6e8f1c0e58677b51fc6450f Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 22:32:10 -0300 Subject: [PATCH 06/13] fix --- .github/workflows/documentation.yml | 2 +- src/templates/1.html | 7 ++++--- src/templates/2.html | 5 +++-- src/templates/3.html | 5 +++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 9127bea..4dc3d3f 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -11,7 +11,7 @@ jobs: uses: actions/checkout@v2 - name: Generate documentation run: | - pip install -r requirements.txt + pip install pdoc PYTHONPATH=src pdoc --html src/ --force --output-dir ./docs - name: Commit and push changes uses: devops-infra/action-commit-push@v0.9.0 diff --git a/src/templates/1.html b/src/templates/1.html index d2ed3e8..f36864c 100644 --- a/src/templates/1.html +++ b/src/templates/1.html @@ -1,9 +1,10 @@ - - - Venha para Recomb + + + + Venha para Recomb
      diff --git a/src/templates/2.html b/src/templates/2.html index f1e2329..bcb1bba 100644 --- a/src/templates/2.html +++ b/src/templates/2.html @@ -1,9 +1,10 @@ - + - Venha para Recomb + + Venha para Recomb
      diff --git a/src/templates/3.html b/src/templates/3.html index 8b6105e..1288946 100644 --- a/src/templates/3.html +++ b/src/templates/3.html @@ -1,9 +1,10 @@ - + - Venha para Recomb + + Venha para Recomb
      From 15f482bd1f366bdf12488955c8701352d52eb1ff Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 22:34:44 -0300 Subject: [PATCH 07/13] pdoc3 --- .github/workflows/documentation.yml | 2 +- README.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 4dc3d3f..3065a5c 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -11,7 +11,7 @@ jobs: uses: actions/checkout@v2 - name: Generate documentation run: | - pip install pdoc + pip install pdoc3 PYTHONPATH=src pdoc --html src/ --force --output-dir ./docs - name: Commit and push changes uses: devops-infra/action-commit-push@v0.9.0 diff --git a/README.md b/README.md index 12e0351..1151afb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Venha para Recomb +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=LKhoe_venhapararecomb&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=LKhoe_venhapararecomb) + ## Documentação da solução ### Execução From 9991a2070ff5aba94a86c30e780c4547192da517 Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 22:35:59 -0300 Subject: [PATCH 08/13] doc --- .github/workflows/documentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3065a5c..e5e2cd6 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -12,6 +12,7 @@ jobs: - name: Generate documentation run: | pip install pdoc3 + pip install flask PYTHONPATH=src pdoc --html src/ --force --output-dir ./docs - name: Commit and push changes uses: devops-infra/action-commit-push@v0.9.0 From c7dadac7e8a10b352c8e9355a11a18ababa5266f Mon Sep 17 00:00:00 2001 From: LKhoe Date: Thu, 31 Mar 2022 01:36:26 +0000 Subject: [PATCH 09/13] Documentation update Files changed:\nM docs/src/parsing.html --- docs/src/parsing.html | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/src/parsing.html b/docs/src/parsing.html index 84f0e3a..dd976c8 100644 --- a/docs/src/parsing.html +++ b/docs/src/parsing.html @@ -87,14 +87,14 @@

      Module src.parsing

      """ try: root_xml = ET.fromstring(file_content) - NFe_xml = root_xml[0] # NFe - infNFe_xml = NFe_xml[0] # infNFe + nfe_xml = root_xml[0] # NFe + inf_nfe_xml = nfe_xml[0] # infNFe namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_xml.tag)) else '' # xmlns - fornecedor = parse_fornecedor(infNFe_xml.find(f'{namespace}emit'), namespace) - cliente = parse_cliente(infNFe_xml.find(f'{namespace}dest'), namespace) + fornecedor = parse_fornecedor(inf_nfe_xml.find(f'{namespace}emit'), namespace) + cliente = parse_cliente(inf_nfe_xml.find(f'{namespace}dest'), namespace) - cobranca_xml = infNFe_xml.find(f'{namespace}cobr') + cobranca_xml = inf_nfe_xml.find(f'{namespace}cobr') duplicata_xml = cobranca_xml.findall(f'{namespace}dup') boletos = [ { @@ -104,7 +104,7 @@

      Module src.parsing

      for dup_xml in duplicata_xml ] - return NotaFiscal(infNFe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) + return NotaFiscal(inf_nfe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) except Exception: return None @@ -234,14 +234,14 @@

      Returns

      """ try: root_xml = ET.fromstring(file_content) - NFe_xml = root_xml[0] # NFe - infNFe_xml = NFe_xml[0] # infNFe + nfe_xml = root_xml[0] # NFe + inf_nfe_xml = nfe_xml[0] # infNFe namespace = regex.group(0) if (regex := re.match(r'\{.*\}', root_xml.tag)) else '' # xmlns - fornecedor = parse_fornecedor(infNFe_xml.find(f'{namespace}emit'), namespace) - cliente = parse_cliente(infNFe_xml.find(f'{namespace}dest'), namespace) + fornecedor = parse_fornecedor(inf_nfe_xml.find(f'{namespace}emit'), namespace) + cliente = parse_cliente(inf_nfe_xml.find(f'{namespace}dest'), namespace) - cobranca_xml = infNFe_xml.find(f'{namespace}cobr') + cobranca_xml = inf_nfe_xml.find(f'{namespace}cobr') duplicata_xml = cobranca_xml.findall(f'{namespace}dup') boletos = [ { @@ -251,7 +251,7 @@

      Returns

      for dup_xml in duplicata_xml ] - return NotaFiscal(infNFe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) + return NotaFiscal(inf_nfe_xml.attrib['Id'][3:], fornecedor, cliente, boletos) except Exception: return None
      From 5e1c8b39c34399d2c1c330d9af4249da6e6ff9cf Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 23:12:18 -0300 Subject: [PATCH 10/13] readme --- .DS_Store | Bin 0 -> 6148 bytes README.md | 86 ++++++++++++++++--------------------------------- src/service.py | 4 ++- 3 files changed, 30 insertions(+), 60 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ca2b7874ee44cef1857d5487e8c8184223131c55 GIT binary patch literal 6148 zcmeH~F$w}f3`G;&La^D=avBfd4F=H@>;(h`8(BfodXDZ-CJ2t!BJu;tpJXO1`-+{7 zi0JxuSc&u^GJ~7S(n4d3ypw~RWiQwJa2ZeM@rat$Cvn!+@Lrnz*rt#G36KB@kN^q% z5COZlVY7KvMiL+a5_l4@??Zx{=Fn2rKOG1@0zf;I-LUpq0-CG<&7q|#Dlm=dL8DcD z46(YmLsOi~p`~hV7meXV4M3`}dgXhH(h?7~0-B+w9;*1Wg-e+&OK|2Hj6Nq_|Y zjDU8VVY9|d#ohY$dRE^>)z$?L_2URHKLJSWDqg_du%B!J&7q|#Dlq;CI0gn1_$q-1 D?w=C8 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 1151afb..1084932 100644 --- a/README.md +++ b/README.md @@ -4,60 +4,42 @@ ## Documentação da solução -### Execução - -```bash -docker build -t recomb . -docker run -it -p 5000:5000 recomb -``` - -## Lista dos diferenciais implementados - -|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 usando Docker |5| -|Total | 120| +### Solução -## Desafio +O Código desenvolvido cria um serviço Flask que permite o upload de arquivos de NFe em formato `.xml` e realiza o parsing em cada um deles. -O desafio é desenvolver um programa que permita realizar as seguintes buscas: +A partir do parsing as informações de Cliente, Fornecedor e Boletos de uma NFe são extraídos e armazenados em um banco de dados. -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. +Também no serviço Flask é possível consultar informações de um Fornecedor a partir de seu identificador (CPF ou CNPJ). -**Escolha as tecnologias que você vai usar e tente montar uma solução completa para rodar a aplicação.** +As informações informadas são aquelas especificadas no desafio: +1. Nome, CPF ou CNPJ e Endereço dos Clientes que compram desse Fornecedor; +2. Boletos emitidos por esse Fornecedor; -Para enviar o resultado, basta realiazar um Fork deste repositório e abra um Pull Request, com seu nome. +### Código -É importante comentar que deve ser enviado apenas o código fonte. Não aceitaremos códigos compilados. +O código foi desenvolvido em módulos, onde cada módulo realiza operações em uma área específica. +- [Database](./src/database.py): Responsável pela conexão de manipulação do banco de dados. +- [Parsing](./src/parsing.py): Responsável por reconhecer dos arquivos de entrada e extrair as informações dele. +- [Models](./src/models.py): Define as classes que representam os objetos que serão manipulados. -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 +Além dos módulos foi desenvolvido um serviço em Flask para facilitar o uso. +- [Service](./src/service.py): Serviço em Flask responsável pela aplicação web. -## Avaliação +A documentação de cada uma das funções dos módulos foi gerada pelo [pdoc3](https://pypi.org/project/pdoc3/), através dos comentários feitos no código. +Para consultar a documentação em um formato amigável: +- Clone o repositório ```git clone https://github.com/LKhoe/venhapararecomb.git``` +- Na pasta `docs/` abra o arquivo `index.html` com algum navegador. -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 -A pontuação do candidato será a soma dos valores obtidos nos critérios acima. +```bash +docker build -t recomb . +docker run -it -p 5000:5000 recomb +``` -## Diferenciais +## 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| @@ -65,23 +47,9 @@ O candidato pode aumentar a sua pontuação na seleção implementando um ou mai |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; - - +|Total | 120| +## Desafio +Descrito no [repositório original](https://github.com/recombX/venhapararecomb#readme) do desafio. \ No newline at end of file diff --git a/src/service.py b/src/service.py index 30e65b2..0656af5 100644 --- a/src/service.py +++ b/src/service.py @@ -1,5 +1,6 @@ from flask import Flask, redirect, render_template, request, url_for from werkzeug.utils import secure_filename +import re from parsing import parse_nota_fiscal from database import create_database as create_database_original, \ create_nota_fiscal as create_nota_fiscal_original, \ @@ -43,7 +44,8 @@ def page_3(): if request.method != 'POST': # To avoid breaking the flow of application, we will redirect to the first page return redirect(url_for('page_1')) - identificador = request.form.get('identificador', '') # Get the identificador from the request + # Get the identificador from the request without the following characters . - / + identificador = re.sub(r'\.|\-|\/', '', request.form.get('identificador', '')) boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor From 960d562c3de5c415dc893024c7896a99d8683047 Mon Sep 17 00:00:00 2001 From: LKhoe Date: Thu, 31 Mar 2022 02:13:01 +0000 Subject: [PATCH 11/13] Documentation update Files changed:\nM docs/src/service.html --- docs/src/service.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/src/service.html b/docs/src/service.html index 8fba781..80aa0cc 100644 --- a/docs/src/service.html +++ b/docs/src/service.html @@ -28,6 +28,7 @@

      Module src.service

      from flask import Flask, redirect, render_template, request, url_for
       from werkzeug.utils import secure_filename
      +import re
       from parsing import parse_nota_fiscal
       from database import create_database as create_database_original, \
           create_nota_fiscal as create_nota_fiscal_original, \
      @@ -71,7 +72,8 @@ 

      Module src.service

      if request.method != 'POST': # To avoid breaking the flow of application, we will redirect to the first page return redirect(url_for('page_1')) - identificador = request.form.get('identificador', '') # Get the identificador from the request + # Get the identificador from the request without the following characters . - / + identificador = re.sub(r'\.|\-|\/', '', request.form.get('identificador', '')) boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor @@ -189,7 +191,8 @@

      Functions

      if request.method != 'POST': # To avoid breaking the flow of application, we will redirect to the first page return redirect(url_for('page_1')) - identificador = request.form.get('identificador', '') # Get the identificador from the request + # Get the identificador from the request without the following characters . - / + identificador = re.sub(r'\.|\-|\/', '', request.form.get('identificador', '')) boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor From 76206dcf3f7650b247123bc3c5e6fd4f55e10c5a Mon Sep 17 00:00:00 2001 From: Leonardo Khoury Picoli Date: Wed, 30 Mar 2022 23:45:52 -0300 Subject: [PATCH 12/13] finalizado --- src/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service.py b/src/service.py index 0656af5..71331b1 100644 --- a/src/service.py +++ b/src/service.py @@ -45,7 +45,7 @@ def page_3(): # To avoid breaking the flow of application, we will redirect to the first page return redirect(url_for('page_1')) # Get the identificador from the request without the following characters . - / - identificador = re.sub(r'\.|\-|\/', '', request.form.get('identificador', '')) + identificador = re.sub(r'[\.\-\/]', '', request.form.get('identificador', '')) boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor From 83fef54ceb9c7d310ce14654fe6296bf0956c456 Mon Sep 17 00:00:00 2001 From: LKhoe Date: Thu, 31 Mar 2022 02:46:13 +0000 Subject: [PATCH 13/13] Documentation update Files changed:\nM docs/src/service.html --- docs/src/service.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/service.html b/docs/src/service.html index 80aa0cc..f0d9be5 100644 --- a/docs/src/service.html +++ b/docs/src/service.html @@ -73,7 +73,7 @@

      Module src.service

      # To avoid breaking the flow of application, we will redirect to the first page return redirect(url_for('page_1')) # Get the identificador from the request without the following characters . - / - identificador = re.sub(r'\.|\-|\/', '', request.form.get('identificador', '')) + identificador = re.sub(r'[\.\-\/]', '', request.form.get('identificador', '')) boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor @@ -192,7 +192,7 @@

      Functions

      # To avoid breaking the flow of application, we will redirect to the first page return redirect(url_for('page_1')) # Get the identificador from the request without the following characters . - / - identificador = re.sub(r'\.|\-|\/', '', request.form.get('identificador', '')) + identificador = re.sub(r'[\.\-\/]', '', request.form.get('identificador', '')) boletos = query1(identificador) # Get all boletos from a fornecedor clientes = query2(identificador) # Get all clientes related to a fornecedor