Este projeto foi desenvolvido para a disciplina de Banco de Dados e tem como objetivo implementar um sistema CRUD completo para o gerenciamento de uma marmitaria.
A aplicação simula o funcionamento de um sistema de vendas simples, permitindo o cadastro de clientes, marmitas e pedidos, além de consultas e geração de relatórios.
O projeto usa Dev Containers. Ao abrir no VS Code/Cursor, o ambiente é configurado automaticamente com Python 3.12, PostgreSQL, UV, lazygit e opencode.
Na paleta de comandos (Ctrl+Shift+P / Cmd+Shift+P), selecione:
Dev Containers: Reopen in Container
Ao abrir o container, o terminal exibirá um aviso caso o GitHub ainda não esteja configurado. Execute:
gh auth loginSiga as instruções e escolha:
- GitHub.com
- HTTPS (recomendado) ou SSH
- Login via browser (mais fácil)
Após autenticar, push e pull funcionarão normalmente.
As dependências Python são instaladas automaticamente via uv sync na criação do container. Para instalar manualmente:
uv syncAs dependências do frontend também são instaladas automaticamente no postCreateCommand, mas você pode reinstalar manualmente quando precisar:
cd frontend
npm installPara adicionar novos pacotes:
uv add nome-do-pacoteCom o Dev Container aberto, o banco PostgreSQL sobe pelo docker-compose da pasta .devcontainer e a variável DATABASE_URL já fica configurada no container.
Abra dois terminais.
Na raiz do projeto:
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadA API ficará disponível em:
http://127.0.0.1:8000http://127.0.0.1:8000/healthhttp://127.0.0.1:8000/docs
Em outro terminal:
cd frontend
npm run devO frontend ficará disponível em:
http://127.0.0.1:3000http://127.0.0.1:3000/loginhttp://127.0.0.1:3000/cadastro
- Abra o Dev Container
- Confirme as dependências com
uv syncecd frontend && npm install - Suba o backend
- Suba o frontend
- Acesse
http://127.0.0.1:3000
Se quiser validar que tudo subiu corretamente:
# backend
python - <<'PY'
import urllib.request
print(urllib.request.urlopen("http://127.0.0.1:8000/health").read().decode())
PY
# frontend
python - <<'PY'
import urllib.request
print(urllib.request.urlopen("http://127.0.0.1:3000/login").status)
PYO schema inicial cria automaticamente um usuário administrador:
- E-mail:
yao@lanches.com - Senha:
admin
# Todos os testes
uv run pytest tests/ -v
# Um arquivo específico
uv run pytest tests/test_cliente_repository.py -v
# Um teste específico
uv run pytest tests/test_pedido_repository.py::test_inserir_calcula_valor_total -verDiagram
clientes {
serial id PK
varchar nome
varchar numero
boolean ativo
}
pedidos {
serial id PK
int cliente_id FK
date data
estado_pedido estado
numeric valor
boolean pago
}
pedido_itens {
serial id PK
int pedido_id FK
int item_id FK
int quantidade
numeric valor_unitario
}
estoque {
serial id PK
varchar item
int quantidade_disponivel
numeric valor
boolean ativo
}
usuarios {
serial id PK
varchar nome
varchar email
varchar senha
varchar numero
varchar role
int cliente_id FK
boolean ativo
}
clientes ||--o{ pedidos : "realiza"
pedidos ||--|{ pedido_itens : "contém"
estoque ||--o{ pedido_itens : "referenciado em"
clientes ||--o| usuarios : "vincula"
| Coluna | Tipo | Restrições |
|---|---|---|
| id | SERIAL |
PRIMARY KEY |
| nome | VARCHAR(255) |
NOT NULL |
| numero | VARCHAR(20) |
NOT NULL |
| ativo | BOOLEAN |
NOT NULL, default true (remoção lógica) |
Índice: idx_clientes_nome em nome — para busca por nome.
Cardápio de itens disponíveis na loja (Yao).
| Coluna | Tipo | Restrições |
|---|---|---|
| id | SERIAL |
PRIMARY KEY |
| item | VARCHAR(255) |
NOT NULL |
| quantidade_disponivel | INT |
NOT NULL, >= 0 |
| valor | NUMERIC(10,2) |
NOT NULL, > 0 |
| ativo | BOOLEAN |
NOT NULL, default true (remoção lógica) |
| Coluna | Tipo | Restrições |
|---|---|---|
| id | SERIAL |
PRIMARY KEY |
| cliente_id | INT |
NOT NULL, FK → clientes(id) |
| data | DATE |
NOT NULL, default CURRENT_DATE |
| estado | estado_pedido |
NOT NULL, default 'EM_ANDAMENTO' |
| valor | NUMERIC(10,2) |
NOT NULL, default 0 |
| pago | BOOLEAN |
NOT NULL, default false |
Tipo ENUM estado_pedido: EM_ANDAMENTO → PRONTO → ENTREGUE → CANCELADO
Índices: idx_pedidos_cliente_id, idx_pedidos_estado
Tabela de junção entre pedidos e estoque (relação N:N).
Cada linha representa um item dentro de um pedido.
| Coluna | Tipo | Restrições |
|---|---|---|
| id | SERIAL |
PRIMARY KEY |
| pedido_id | INT |
NOT NULL, FK → pedidos(id) ON DELETE CASCADE |
| item_id | INT |
NOT NULL, FK → estoque(id) |
| quantidade | INT |
NOT NULL, > 0, default 1 |
| valor_unitario | NUMERIC(10,2) |
NOT NULL, > 0 |
Índices: idx_pedido_itens_pedido, idx_pedido_itens_item
Restrição adicional: UNIQUE (pedido_id, item_id) para impedir o mesmo item duplicado no mesmo pedido.
O
ON DELETE CASCADEgarante que ao remover um pedido, todos os seus itens são removidos automaticamente.
Tabela de autenticação e autorização da aplicação.
| Coluna | Tipo | Restrições |
|---|---|---|
| id | SERIAL |
PRIMARY KEY |
| nome | VARCHAR(255) |
NOT NULL |
VARCHAR(255) |
NOT NULL, UNIQUE |
|
| senha | VARCHAR(255) |
NOT NULL |
| numero | VARCHAR(20) |
NOT NULL |
| role | VARCHAR(20) |
NOT NULL, default 'user', CHECK (role IN ('admin', 'user')) |
| cliente_id | INT |
NULL, FK → clientes(id) |
| ativo | BOOLEAN |
NOT NULL, default true |
Índices: idx_usuarios_email, idx_usuarios_email_unique
Seed padrão: usuário admin yao@lanches.com
| Regra | Implementação |
|---|---|
| Estoque nunca negativo | CHECK (quantidade_disponivel >= 0) |
| Valor do item sempre positivo | CHECK (valor > 0) |
| Quantidade de item no pedido > 0 | CHECK (quantidade > 0) |
| Valor unitário do item no pedido > 0 | CHECK (valor_unitario > 0) |
| Estado do pedido restrito | ENUM com valores fixos |
| Papel do usuário restrito | CHECK (role IN ('admin', 'user')) |
| Pedido sempre vinculado a um cliente | NOT NULL REFERENCES clientes(id) |
| Itens orfãos removidos com o pedido | ON DELETE CASCADE em pedido_itens |
| E-mail de usuário único | UNIQUE em usuarios.email |
classDiagram
direction TB
class Cliente {
+int id
+str nome
+str numero
+bool ativo
+inserir()
+alterar()
+remover()
+buscar_por_nome(nome) List
+listar_todos() List
+exibir(id)
}
class Pedido {
+int id
+int cliente_id
+date data
+EstadoPedido estado
+Decimal valor
+bool pago
+inserir()
+alterar()
+remover()
+listar_todos() List
+exibir(id)
}
class PedidoItem {
+int id
+int pedido_id
+int item_id
+int quantidade
+Decimal valor_unitario
+listar_todos() List
+exibir(id)
+alterar()
+remover()
}
class Estoque {
+int id
+str item
+int quantidade_disponivel
+Decimal valor
+bool ativo
+inserir()
+alterar()
+remover()
+buscar_por_nome(nome) List
+listar_todos() List
+exibir(id)
}
class EstadoPedido {
<<enumeration>>
EM_ANDAMENTO
PRONTO
ENTREGUE
CANCELADO
}
class Usuario {
+int id
+str nome
+str email
+str senha
+str numero
+str role
+int cliente_id
+bool ativo
+cadastrar()
+autenticar(email, senha)
}
class Database {
-str url
+get_connection()
+close()
}
Cliente "1" --> "0..*" Pedido : realiza
Pedido "1" --> "1..*" PedidoItem : contém
PedidoItem "0..*" --> "1" Estoque : referencia
Pedido --> "1" EstadoPedido : estado
Usuario "0..*" --> "0..1" Cliente : vincula
Cliente ..> Database : usa
Pedido ..> Database : usa
Estoque ..> Database : usa
Usuario ..> Database : usa
Regra de negócio (remoção lógica de Cliente): clientes não são removidos fisicamente do banco. Quando o cliente não possui pedidos vinculados, o método remover() deve inativar o cliente (ex.: ativo = false) para preservar o histórico e permitir reativação futura. Quando o cliente possui pedidos vinculados, o método remover() deve lançar uma exceção e não permitir a remoção/inativação, garantindo a preservação do histórico de pedidos e relatórios. Métodos de listagem devem considerar apenas clientes ativos.
Regra de negócio (remoção lógica de Estoque): itens de estoque também não são removidos fisicamente. O método remover() de Estoque deve inativar o item (ex.: ativo = false) para preservar a integridade referencial com pedido_itens.item_id e o histórico de vendas. Métodos de listagem devem considerar apenas itens ativos.
| Entidade | Tabela | Descrição |
|---|---|---|
Cliente |
clientes |
Cadastro de clientes da marmitaria (remoção lógica via campo ativo) |
Pedido |
pedidos |
Pedidos realizados pelos clientes |
PedidoItem |
pedido_itens |
Itens de cada pedido (N:N entre pedidos e estoque), com quantidade e valor unitário congelado no momento da compra |
Estoque |
estoque |
Cardápio de itens disponíveis com preço e quantidade (Yao) |
Usuario |
usuarios |
Usuários de autenticação, com papel admin ou user e vínculo opcional com Cliente |
EstadoPedido |
— | Enum: EM_ANDAMENTO, PRONTO, ENTREGUE, CANCELADO |
Database |
— | Gerencia a conexão com o PostgreSQL |