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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 51 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,51 @@
# Venha para Recomb

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.
2) Apresentar o nome, identificador (CPF ou CNPJ), endereço dos clientes de um fornecedor.

**Escolha as tecnologias que você vai usar e tente montar uma solução completa para rodar a aplicação.**

Para enviar o resultado, basta realiazar um Fork deste repositório e abra um Pull Request, com seu nome.

É importante comentar que deve ser enviado apenas o código fonte. Não aceitaremos códigos compilados.

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

## Avaliação

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|

A pontuação do candidato será a soma dos valores obtidos nos critérios acima.

## Diferenciais

O candidato pode aumentar a sua pontuação na seleção implementando um ou mais dos itens abaixo:
|Item | Pontos Ganhos|
|-----|--------------|
|Criar um serviço com o problema |30|
|Utilizar banco de dados |30|
|Implementar Clean Code |20|
|Implementar o padrão de programação da tecnologia escolhida |20|
|Qualidade de Código com SonarQube| 15|
|Implementar testes unitários |15|
|Implementar testes comportamentais | 15|
|Implementar integração com Travis |10|
|Implementar integração com Travis + SonarQube |10|
|Implementar usando Docker |5|
|Total | 170|

A nota final do candidato será acrescido dos pontos referente ao item implementado corretamente.

## Penalizações

O candidato será desclassifiado nas seguintes situações:

1) Submeter um solução que não funcione;
2) Não cumprir os critérios presentes no seção Avaliação;
3) Plágio;




## Documentação da Solução

### Tecnologias Utilizadas
- Django: Framework web em Python.
- Sqlite3: Serviço de banco de dados local do Django.
- HTML, CSS e Bootstrap: para a interface do usuário.
- Docker.

### Funcionalidades Implementadas
1. **Parse de XML**: A função parse_xml recebe um arquivo XML de uma nota fiscal e extrai as informações relevantes, como fornecedor, clientes, endereços e boletos.
2. **Página Inicial**: A view index renderiza a página inicial do sistema, permitindo aos usuários enviar arquivos XML para processamento.
3. **Cadastro de Notas Fiscais**: Ao submeter um arquivo XML válido, as informações da nota fiscal são salvas no banco de dados, incluindo fornecedor, clientes e boletos associados.
4. **Listagem de Notas Fiscais**: A view list_nfs apresenta uma lista de todas as notas fiscais cadastradas no sistema.
5. **Detalhes da Nota Fiscal**: A view detail_nf permite visualizar os detalhes de uma nota fiscal específica, incluindo clientes e boletos associados.
6. **Exclusão de Notas Fiscais**: A view delete_nf permite excluir uma nota fiscal do banco de dados, mas não exclui dados de fornecedor e clientes.
7. **Listagem de Fornecedores**: A view list_fornecedores lista todos os fornecedores cadastrados no sistema.
8. **Exclusão de Fornecedores**: A view delete_fornecedor permite excluir um fornecedor do banco de dados. Ao excluir um fornecedor do banco de dados as notas fiscais associadas também são apagadas.
9. **Listagem de Clientes**: A view list_clientes lista todos os clientes cadastrados no sistema.
10. **Exclusão de Clientes**: A view delete_cliente permite excluir um cliente do banco de dados. Ao apagar um cliente, as notas fiscais relacionadas são apagadas.

### Como Executar a Aplicação
1. Clone o repositório do projeto:
```
git clone https://github.com/jcquadros/venhapararecomb-backend.git
```
2. Certifique-se de estar dentro do diretorio venhapararecomb/ Execute:
```
docker-compose build
docker-compose up
```
Acesse 'http://localhost:8000/' no navegador.

3. Testes:
Para executar os testes, certifique estar dentro do diretório venhapararecomb/ de instalar as bibliotecas e executar o teste:
```
pip install -r requirements.txt
python manage.py test
```
De forma semelhante, se optar por nao usar o Docker, a aplicação pode ser executada com os seguintes comandos:
```
pip install -r requirements.txt
python manage.py runserver
```
Após isso acesse 'http://127.0.0.1:8000/' no navegador.
## Diferenciais Implementados
- Interface de Usuário Responsiva: A interface do usuário foi desenvolvida de forma simples com Bootstrap, mas responsiva.
- Tratamento de Erros: O sistema trata erros comuns, como tentativa de envio de nota fiscal fornecedores e clientes duplicados.
- Padrão de programação Django MTV (Model, Template, View)
- Uso de banco de dados: A aplicação utiliza o Sqlite3 como serviço de banco de dados local.
- Implementação de Testes Unitários: Os testes cobrem as principais funções da aplicação, incluindo as views e os modelos do Django.
- Uso de docker.
20 changes: 20 additions & 0 deletions venhapararecomb/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Imagem base do Python
FROM python:3.10

# Configuração do diretório de trabalho dentro do contêiner
WORKDIR /app

# Copia o arquivo requirements.txt para o contêiner
COPY requirements.txt .

# Instala as dependências do projeto
RUN pip install -r requirements.txt

# Copia o restante do código-fonte para o contêiner
COPY . .

# Expõe a porta 8000 do contêiner
EXPOSE 8000

# Comando para executar o servidor Django
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
Binary file added venhapararecomb/db.sqlite3
Binary file not shown.
10 changes: 10 additions & 0 deletions venhapararecomb/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: '3'

services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
ports:
- "8000:8000"
22 changes: 22 additions & 0 deletions venhapararecomb/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'venhapararecomb.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == '__main__':
main()
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions venhapararecomb/notafiscal/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.contrib import admin
from .models import Fornecedor, Cliente, Boleto, NotaFiscal, Endereco

admin.site.register(Fornecedor)
admin.site.register(Cliente)
admin.site.register(Boleto)
admin.site.register(NotaFiscal)
admin.site.register(Endereco)
6 changes: 6 additions & 0 deletions venhapararecomb/notafiscal/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class NotafiscalConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'notafiscal'
65 changes: 65 additions & 0 deletions venhapararecomb/notafiscal/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Generated by Django 5.0.3 on 2024-03-10 21:02

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Endereco',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('logradouro', models.CharField(max_length=100)),
('numero', models.CharField(max_length=10)),
('bairro', models.CharField(max_length=50)),
('cidade', models.CharField(max_length=50)),
('estado', models.CharField(max_length=2)),
('cep', models.CharField(max_length=8)),
('pais', models.CharField(max_length=50)),
('telefone', models.CharField(max_length=15)),
],
),
migrations.CreateModel(
name='Fornecedor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nome', models.CharField(max_length=100)),
('cnpj', models.CharField(max_length=14, unique=True)),
],
),
migrations.CreateModel(
name='Cliente',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nome', models.CharField(max_length=100)),
('tipo_documento', models.CharField(choices=[('CPF', 'CPF'), ('CNPJ', 'CNPJ')], max_length=4)),
('documento', models.CharField(max_length=14, unique=True)),
('endereco', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='notafiscal.endereco')),
],
),
migrations.CreateModel(
name='NotaFiscal',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('identificador', models.CharField(max_length=50, unique=True)),
('clientes', models.ManyToManyField(related_name='notas_fiscais', to='notafiscal.cliente')),
('fornecedor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='notafiscal.fornecedor')),
],
),
migrations.CreateModel(
name='Boleto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('valor', models.DecimalField(decimal_places=2, max_digits=10)),
('data_vencimento', models.DateField()),
('nota_fiscal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='notafiscal.notafiscal')),
],
),
]
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
67 changes: 67 additions & 0 deletions venhapararecomb/notafiscal/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import Any
from django.db import models

class Fornecedor(models.Model):
"""
Modelo que representa um fornecedor de produtos. Cada fornecedor possui um nome e um CNPJ.
"""
nome = models.CharField(max_length=100)
cnpj = models.CharField(max_length=14, unique=True)

def __str__(self) -> str:
return self.nome

class NotaFiscal(models.Model):
"""
Modelo que representa uma nota fiscal. Cada nota fiscal possui um identificador único, um fornecedor e uma lista de clientes.
"""
identificador = models.CharField(max_length=50, unique=True)
fornecedor = models.ForeignKey(Fornecedor, on_delete=models.CASCADE)
clientes = models.ManyToManyField('Cliente', related_name='notas_fiscais')

def __str__(self) -> str:
return f'Nota Fiscal {self.id} - {self.fornecedor}'

class Boleto(models.Model):
"""
Modelo que representa um boleto. Cada boleto possui um valor, uma data de vencimento e uma nota fiscal.
"""
valor = models.DecimalField(max_digits=10, decimal_places=2)
data_vencimento = models.DateField()
nota_fiscal = models.ForeignKey(NotaFiscal, on_delete=models.CASCADE)

def __str__(self) -> str:
return f'Boleto {self.valor} - {self.data_vencimento}'

class Endereco(models.Model):
"""
Modelo que representa um endereço. Cada endereço possui um logradouro, um número, um bairro, uma cidade, um estado, um CEP, um país e um telefone.
"""
logradouro = models.CharField(max_length=100)
numero = models.CharField(max_length=10)
bairro = models.CharField(max_length=50)
cidade = models.CharField(max_length=50)
estado = models.CharField(max_length=2)
cep = models.CharField(max_length=8)
pais = models.CharField(max_length=50)
telefone = models.CharField(max_length=15)

def __str__(self) -> str:
return self.logradouro

class Cliente(models.Model):
"""
Modelo que representa um cliente. Cada cliente possui um nome, um tipo de documento (CPF ou CNPJ), um documento (CPF ou CNPJ) e um endereço.
"""
TIPO_DOCUMENTO = (
('CPF', 'CPF'),
('CNPJ', 'CNPJ'),
)

nome = models.CharField(max_length=100)
tipo_documento = models.CharField(max_length=4, choices=TIPO_DOCUMENTO)
documento = models.CharField(max_length=14, unique=True) # cpf ou cnpj
endereco = models.ForeignKey(Endereco, on_delete=models.CASCADE)

def __str__(self) -> str:
return self.nome
Loading