feat: add user management viewset and sync module to API routes

This commit is contained in:
2026-04-04 17:44:05 -03:00
parent 645a5b4093
commit 6c4e95e579
13 changed files with 328 additions and 2 deletions

111
DOCUMENTACAO_PROJETO.md Normal file
View 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
View 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*

View File

@@ -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)),

View File

@@ -60,6 +60,7 @@ INSTALLED_APPS = [
'balcao', 'balcao',
'orders', 'orders',
'login', 'login',
'sync',
'django_extensions', 'django_extensions',
'pwa', 'pwa',
'rest_framework', 'rest_framework',

View File

@@ -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

View File

@@ -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
View 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
View 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()

View 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')],
},
),
]

View File

31
sync/models.py Normal file
View 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
View 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
View 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__}")