mirror of
https://github.com/welton89/RRBEC.git
synced 2026-04-05 13:35:42 +00:00
feat: add user management viewset and sync module to API routes
This commit is contained in:
111
DOCUMENTACAO_PROJETO.md
Normal file
111
DOCUMENTACAO_PROJETO.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Documentação do Projeto RRBEC (Gestão Raul)
|
||||||
|
|
||||||
|
Esta documentação fornece uma visão geral técnica e funcional do sistema **Gestão Raul**, abrangendo sua estrutura, regras de negócio, modelagem de banco de dados, APIs e mecanismos de autenticação.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Estrutura do Projeto
|
||||||
|
|
||||||
|
O projeto é baseado no framework **Django** (Python) e segue o padrão de arquitetura de aplicações modulares.
|
||||||
|
|
||||||
|
### Diretórios Principais
|
||||||
|
- `gestaoRaul/`: Configurações centrais do Django (settings, urls, wsgi).
|
||||||
|
- `products/`: Gestão de produtos, categorias e unidades de medida.
|
||||||
|
- `clients/`: Cadastro de clientes e controle de débitos ("Fiados").
|
||||||
|
- `mesas/`: Gestão das mesas físicas do estabelecimento.
|
||||||
|
- `comandas/`: Núcleo do sistema. Gerencia o consumo, estoque e fluxo de vendas.
|
||||||
|
- `orders/`: Controle da fila de produção (cozinha/bar).
|
||||||
|
- `payments/`: Registros de pagamentos e cálculos de taxas.
|
||||||
|
- `typePay/`: Tipos de pagamentos aceitos (Crédito, Débito, Pix, Fiado, etc.).
|
||||||
|
- `login/`: Lógica de autenticação e customização de tokens JWT.
|
||||||
|
- `templates/`: Arquivos HTML para a interface web (utiliza Vanilla CSS e JS).
|
||||||
|
- `media/`: Armazenamento de imagens de produtos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Regras de Negócio
|
||||||
|
|
||||||
|
O sistema foi projetado para gerenciar o fluxo operacional de um restaurante/bar de ponta a ponta.
|
||||||
|
|
||||||
|
### Fluxo de Comanda e Vendas
|
||||||
|
1. **Abertura**: Uma comanda é vinculada a uma mesa e, opcionalmente, a um cliente. O status inicial é `OPEN`.
|
||||||
|
2. **Lançamento de Itens**: Ao adicionar um produto a uma comanda, o sistema automaticamente:
|
||||||
|
- Deduz a quantidade do produto do estoque.
|
||||||
|
- Se o produto for composto (combo/receita), deduz os componentes individuais.
|
||||||
|
- Se o produto tiver a flag `cuisine=True`, gera um pedido (`Order`) na fila da cozinha.
|
||||||
|
3. **Gestão de Estoque**: Todas as entradas e saídas são registradas na tabela de `StockMovement` para auditoria.
|
||||||
|
4. **Fechamento**: Ao realizar o pagamento (total ou parcial), a comanda pode ser encerrada (`CLOSED`).
|
||||||
|
5. **Taxa de Serviço**: O sistema calcula automaticamente uma taxa de 10% sobre o consumo total.
|
||||||
|
|
||||||
|
### Fila de Produção (Cozinha)
|
||||||
|
Os pedidos passam por estados cronometrados:
|
||||||
|
- `Queue` (Em espera) -> `Preparing` (Preparando) -> `Finished` (Pronto) -> `Delivered` (Entregue).
|
||||||
|
- Também é possível cancelar um pedido, o que gera um estorno automático para o estoque.
|
||||||
|
|
||||||
|
### Sistema de "Fiado" (Débitos de Clientes)
|
||||||
|
- Clientes podem ter comandas marcadas com o tipo de pagamento "FIADO".
|
||||||
|
- O sistema mantém um registro do débito acumulado do cliente.
|
||||||
|
- Existem endpoints específicos para listar e quitar múltiplos débitos de uma vez.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Modelagem do Banco de Dados
|
||||||
|
|
||||||
|
O banco de dados utiliza a seguinte estrutura relacional (simplificada):
|
||||||
|
|
||||||
|
### Principais Entidades
|
||||||
|
- **Mesa**: `id`, `name`, `active`.
|
||||||
|
- **Client**: `id`, `name`, `debt`, `contact`, `active`.
|
||||||
|
- **Product**: `id`, `name`, `price`, `quantity`, `category_id`, `cuisine` (boolean), `active`.
|
||||||
|
- **ProductComponent**: (Tabela intermediária de composição) Liga um `Product` a outros `Products` com uma `quantity_required`.
|
||||||
|
- **Comanda**: `id`, `mesa_id`, `client_id`, `user_id`, `status` (OPEN/CLOSED/FIADO), `dt_open`, `dt_close`.
|
||||||
|
- **ProductComanda**: `id`, `comanda_id`, `product_id`, `data_time`, `applicant`.
|
||||||
|
- **Order (Pedido)**: `id`, `productComanda_id`, `obs`, `queue`, `preparing`, `finished`, `delivered`, `canceled`.
|
||||||
|
- **Payments**: `id`, `comanda_id`, `type_pay_id`, `value`, `client_id`, `datetime`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. API e Autenticação
|
||||||
|
|
||||||
|
A API é construída com **Django REST Framework (DRF)** e segue os princípios REST.
|
||||||
|
|
||||||
|
### Autenticação
|
||||||
|
O sistema utiliza **JWT (JSON Web Token)** para proteger os endpoints.
|
||||||
|
|
||||||
|
1. **Obter Token**: `POST /api/v1/token/`
|
||||||
|
- Body: `{"username": "...", "password": "..."}`
|
||||||
|
- Retorno: `access` e `refresh` tokens, além de dados do usuário (ID, nome, grupos).
|
||||||
|
2. **Atualizar Token**: `POST /api/v1/token/refresh/`
|
||||||
|
- Body: `{"refresh": "..."}`
|
||||||
|
3. **Uso**: Deve-se enviar o token no cabeçalho: `Authorization: Bearer <seu_token>`.
|
||||||
|
|
||||||
|
### Endpoints Principais (`/api/v1/`)
|
||||||
|
|
||||||
|
| Endpoint | Método | Descrição |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `comandas/` | GET/POST | Lista ou cria novas comandas. |
|
||||||
|
| `comandas/{id}/pagar/` | POST | Registra pagamento e fecha a comanda. |
|
||||||
|
| `comandas/{id}/apagar/` | POST | Limpa todos os itens e fecha a comanda (estorna estoque). |
|
||||||
|
| `items-comanda/` | POST/DELETE | Adiciona ou remove itens de uma comanda individualmente. |
|
||||||
|
| `orders/` | GET/PATCH | Lista pedidos da cozinha ou edita observações. |
|
||||||
|
| `orders/{id}/preparing/` | POST | Inicia o preparo do pedido. |
|
||||||
|
| `orders/{id}/finish/` | POST | Marca como pronto para entrega. |
|
||||||
|
| `clients/{id}/fiados/` | GET | Lista todas as comandas pendentes (fiado) de um cliente. |
|
||||||
|
| `clients/pagar_fiados/`| POST | Quita uma lista de IDs de comandas fiadas. |
|
||||||
|
| `products/` | GET | Lista produtos ativos e estoque. |
|
||||||
|
|
||||||
|
### Lógica de Negócio Integrada na API
|
||||||
|
- **POST `items-comanda/`**: Ao postar um item, a API automaticamente executa `StockMovement.subTransactionStock` e cria um `Order` se necessário.
|
||||||
|
- **DELETE `items-comanda/{id}/`**: Ao deletar, a API executa `StockMovement.sumTransactionStock` para devolver o item ao estoque.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Tecnologias Utilizadas
|
||||||
|
- **Backend**: Django 5.1, Django REST Framework, SimpleJWT.
|
||||||
|
- **Frontend**: HTML5, Vanilla CSS, JavaScript.
|
||||||
|
- **Service Worker**: Configurado para suporte a PWA (Progressive Web App).
|
||||||
|
- **Banco de Dados**: SQLite (Desenvolvimento) / PostgreSQL ou MySQL (Produção).
|
||||||
|
- **Middleware**: WhiteNoise (arquivos estáticos), CORS Headers.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Atualizado em: 24 de Março de 2026*
|
||||||
54
SYNC_GO_MIDDLEWARE.md
Normal file
54
SYNC_GO_MIDDLEWARE.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Guia de Sincronização: Django <-> Middleware Go
|
||||||
|
|
||||||
|
Este documento descreve o funcionamento do sistema de **ChangeLog** implementado no Django para permitir que o middleware em Go mantenha uma cópia local (local-first) dos dados de forma eficiente.
|
||||||
|
|
||||||
|
## 1. Funcionamento Técnico
|
||||||
|
|
||||||
|
### Rastreamento de Mudanças (Django Signals)
|
||||||
|
Foi criada uma aplicação chamada `sync` que utiliza **Django Signals**. Sempre que um dos modelos abaixo é criado, editado ou excluído, uma entrada é gerada automaticamente na tabela `ChangeLog`:
|
||||||
|
|
||||||
|
- `Product`
|
||||||
|
- `Comanda`
|
||||||
|
- `ProductComanda`
|
||||||
|
- `Order`
|
||||||
|
- `Client`
|
||||||
|
- `Categories`
|
||||||
|
- `Mesa`
|
||||||
|
- `Payments`
|
||||||
|
|
||||||
|
### Tabela de Log (`ChangeLog`)
|
||||||
|
Cada registro no log contém:
|
||||||
|
- `id`: Identificador sequencial da mudança.
|
||||||
|
- `model_name`: Nome do modelo (ex: "Product").
|
||||||
|
- `object_id`: ID do objeto que mudou.
|
||||||
|
- `action`: "SAVE" (para criação/edição) ou "DELETE".
|
||||||
|
- `timestamp`: Quando a mudança ocorreu.
|
||||||
|
|
||||||
|
## 2. API de Sincronização
|
||||||
|
|
||||||
|
O middleware Go deve consumir o seguinte endpoint:
|
||||||
|
|
||||||
|
**Endpoint:** `GET /api/v1/sync/`
|
||||||
|
|
||||||
|
### Parâmetros:
|
||||||
|
- `since_id` (opcional): Retorna apenas mudanças com ID maior que este valor.
|
||||||
|
|
||||||
|
### Exemplo de Fluxo no Go:
|
||||||
|
|
||||||
|
1. **Estado Inicial**: O Go armazena o `last_sync_id` (começando em 0).
|
||||||
|
2. **Polling**: De tempos em tempos (ex: a cada 5 segundos), o Go chama:
|
||||||
|
`GET http://seu-servidor/api/v1/sync/?since_id=100` (supondo que o último ID processado foi 100).
|
||||||
|
3. **Processamento**:
|
||||||
|
- O Django retorna uma lista de mudanças (ex: IDs 101, 102, 103).
|
||||||
|
- Para cada mudança `SAVE` no log, o Go deve fazer um `GET` no endpoint específico do modelo para buscar os dados atualizados:
|
||||||
|
- Se `model_name == "Product"`, buscar em `GET /api/v1/products/{object_id}/`.
|
||||||
|
- Para cada mudança `DELETE`, o Go deve remover o item correspondente do seu banco de dados local.
|
||||||
|
4. **Atualização**: O Go atualiza seu `last_sync_id` para o maior ID recebido (neste caso, 103).
|
||||||
|
|
||||||
|
## 3. Vantagens
|
||||||
|
- **Performance**: O Go não precisa baixar todos os produtos/pedidos toda vez. Ele só baixa o que realmente mudou.
|
||||||
|
- **Detecção de Deleção**: O sistema informa explicitamente o que foi apagado no Django.
|
||||||
|
- **Resiliência**: Se a conexão cair, ao reconectar, o Go apenas retoma a partir do último ID que ele conhece, garantindo que nenhuma mudança seja perdida.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Configurado em: 28 de Março de 2026*
|
||||||
@@ -11,7 +11,8 @@ from payments.api_views import PaymentsViewSet
|
|||||||
from rest_framework_simplejwt.views import (
|
from rest_framework_simplejwt.views import (
|
||||||
TokenRefreshView,
|
TokenRefreshView,
|
||||||
)
|
)
|
||||||
from login.api_views import MyTokenObtainPairView
|
from login.api_views import MyTokenObtainPairView, UserViewSet
|
||||||
|
from sync.api_views import ChangeLogViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'orders', OrderViewSet, basename='order')
|
router.register(r'orders', OrderViewSet, basename='order')
|
||||||
@@ -23,6 +24,8 @@ router.register(r'items-comanda', ProductComandaViewSet, basename='items-comanda
|
|||||||
router.register(r'categories', CategoriesViewSet, basename='category')
|
router.register(r'categories', CategoriesViewSet, basename='category')
|
||||||
router.register(r'payment-types', TypePayViewSet, basename='payment-type')
|
router.register(r'payment-types', TypePayViewSet, basename='payment-type')
|
||||||
router.register(r'payments', PaymentsViewSet, basename='payment')
|
router.register(r'payments', PaymentsViewSet, basename='payment')
|
||||||
|
router.register(r'users', UserViewSet, basename='user')
|
||||||
|
router.register(r'sync', ChangeLogViewSet, basename='sync')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ INSTALLED_APPS = [
|
|||||||
'balcao',
|
'balcao',
|
||||||
'orders',
|
'orders',
|
||||||
'login',
|
'login',
|
||||||
|
'sync',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'pwa',
|
'pwa',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||||
from .serializers import MyTokenObtainPairSerializer
|
from rest_framework import viewsets
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from .serializers import MyTokenObtainPairSerializer, UserSerializer
|
||||||
|
|
||||||
class MyTokenObtainPairView(TokenObtainPairView):
|
class MyTokenObtainPairView(TokenObtainPairView):
|
||||||
serializer_class = MyTokenObtainPairSerializer
|
serializer_class = MyTokenObtainPairSerializer
|
||||||
|
|
||||||
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = User.objects.all()
|
||||||
|
serializer_class = UserSerializer
|
||||||
|
|||||||
@@ -1,4 +1,31 @@
|
|||||||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
# Removido write_only=True para que o hash da senha seja enviado no GET
|
||||||
|
password = serializers.CharField(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_staff', 'is_active', 'groups', 'password']
|
||||||
|
read_only_fields = ['id', 'is_staff']
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
password = validated_data.pop('password', None)
|
||||||
|
user = super().create(validated_data)
|
||||||
|
if password:
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
password = validated_data.pop('password', None)
|
||||||
|
user = super().update(instance, validated_data)
|
||||||
|
if password:
|
||||||
|
user.set_password(password)
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
|
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
14
sync/api_views.py
Normal file
14
sync/api_views.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
from .models import ChangeLog
|
||||||
|
from .serializers import ChangeLogSerializer
|
||||||
|
|
||||||
|
class ChangeLogViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = ChangeLog.objects.all()
|
||||||
|
serializer_class = ChangeLogSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
|
since_id = self.request.query_params.get('since_id')
|
||||||
|
if since_id:
|
||||||
|
queryset = queryset.filter(id__gt=since_id)
|
||||||
|
return queryset
|
||||||
9
sync/apps.py
Normal file
9
sync/apps.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class SyncConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'sync'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from .signals import register_signals
|
||||||
|
register_signals()
|
||||||
28
sync/migrations/0001_initial.py
Normal file
28
sync/migrations/0001_initial.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2026-03-28 15:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ChangeLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('model_name', models.CharField(choices=[('Product', 'Product'), ('Comanda', 'Comanda'), ('Order', 'Order'), ('Client', 'Client'), ('Categories', 'Categories'), ('Mesa', 'Mesa'), ('Payments', 'Payments')], max_length=50)),
|
||||||
|
('object_id', models.IntegerField()),
|
||||||
|
('action', models.CharField(choices=[('SAVE', 'Save'), ('DELETE', 'Delete')], max_length=10)),
|
||||||
|
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['timestamp'],
|
||||||
|
'indexes': [models.Index(fields=['timestamp'], name='sync_change_timesta_c62f24_idx'), models.Index(fields=['model_name', 'object_id'], name='sync_change_model_n_3f4f8a_idx')],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
sync/migrations/__init__.py
Normal file
0
sync/migrations/__init__.py
Normal file
31
sync/models.py
Normal file
31
sync/models.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class ChangeLog(models.Model):
|
||||||
|
MODEL_CHOICES = [
|
||||||
|
('Product', 'Product'),
|
||||||
|
('Comanda', 'Comanda'),
|
||||||
|
('Order', 'Order'),
|
||||||
|
('Client', 'Client'),
|
||||||
|
('Categories', 'Categories'),
|
||||||
|
('Mesa', 'Mesa'),
|
||||||
|
('Payments', 'Payments'),
|
||||||
|
]
|
||||||
|
ACTION_CHOICES = [
|
||||||
|
('SAVE', 'Save'),
|
||||||
|
('DELETE', 'Delete'),
|
||||||
|
]
|
||||||
|
|
||||||
|
model_name = models.CharField(max_length=50, choices=MODEL_CHOICES)
|
||||||
|
object_id = models.IntegerField()
|
||||||
|
action = models.CharField(max_length=10, choices=ACTION_CHOICES)
|
||||||
|
timestamp = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['timestamp']
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['timestamp']),
|
||||||
|
models.Index(fields=['model_name', 'object_id']),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.model_name} {self.object_id} {self.action} at {self.timestamp}"
|
||||||
7
sync/serializers.py
Normal file
7
sync/serializers.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import ChangeLog
|
||||||
|
|
||||||
|
class ChangeLogSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ChangeLog
|
||||||
|
fields = '__all__'
|
||||||
35
sync/signals.py
Normal file
35
sync/signals.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from django.db.models.signals import post_save, post_delete
|
||||||
|
from .models import ChangeLog
|
||||||
|
|
||||||
|
from products.models import Product
|
||||||
|
from comandas.models import Comanda, ProductComanda
|
||||||
|
from orders.models import Order
|
||||||
|
from clients.models import Client
|
||||||
|
from categories.models import Categories
|
||||||
|
from mesas.models import Mesa
|
||||||
|
from payments.models import Payments
|
||||||
|
|
||||||
|
MODELS_TO_TRACK = [
|
||||||
|
Product, Comanda, ProductComanda, Order, Client, Categories, Mesa, Payments
|
||||||
|
]
|
||||||
|
|
||||||
|
def handle_save(sender, instance, created, **kwargs):
|
||||||
|
model_name = sender.__name__
|
||||||
|
ChangeLog.objects.create(
|
||||||
|
model_name=model_name,
|
||||||
|
object_id=instance.id,
|
||||||
|
action='SAVE'
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_delete(sender, instance, **kwargs):
|
||||||
|
model_name = sender.__name__
|
||||||
|
ChangeLog.objects.create(
|
||||||
|
model_name=model_name,
|
||||||
|
object_id=instance.id,
|
||||||
|
action='DELETE'
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_signals():
|
||||||
|
for model in MODELS_TO_TRACK:
|
||||||
|
post_save.connect(handle_save, sender=model, dispatch_uid=f"sync_save_{model.__name__}")
|
||||||
|
post_delete.connect(handle_delete, sender=model, dispatch_uid=f"sync_delete_{model.__name__}")
|
||||||
Reference in New Issue
Block a user