diff --git a/DOCUMENTACAO_PROJETO.md b/DOCUMENTACAO_PROJETO.md new file mode 100644 index 0000000..2fbf3d6 --- /dev/null +++ b/DOCUMENTACAO_PROJETO.md @@ -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 `. + +### 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* diff --git a/SYNC_GO_MIDDLEWARE.md b/SYNC_GO_MIDDLEWARE.md new file mode 100644 index 0000000..d12fbdd --- /dev/null +++ b/SYNC_GO_MIDDLEWARE.md @@ -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* diff --git a/gestaoRaul/api_urls.py b/gestaoRaul/api_urls.py index 5dbc306..6dc126b 100644 --- a/gestaoRaul/api_urls.py +++ b/gestaoRaul/api_urls.py @@ -11,7 +11,8 @@ from payments.api_views import PaymentsViewSet from rest_framework_simplejwt.views import ( TokenRefreshView, ) -from login.api_views import MyTokenObtainPairView +from login.api_views import MyTokenObtainPairView, UserViewSet +from sync.api_views import ChangeLogViewSet router = DefaultRouter() 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'payment-types', TypePayViewSet, basename='payment-type') router.register(r'payments', PaymentsViewSet, basename='payment') +router.register(r'users', UserViewSet, basename='user') +router.register(r'sync', ChangeLogViewSet, basename='sync') urlpatterns = [ path('', include(router.urls)), diff --git a/gestaoRaul/settings.py b/gestaoRaul/settings.py index 05fe078..a91aaa8 100644 --- a/gestaoRaul/settings.py +++ b/gestaoRaul/settings.py @@ -60,6 +60,7 @@ INSTALLED_APPS = [ 'balcao', 'orders', 'login', + 'sync', 'django_extensions', 'pwa', 'rest_framework', diff --git a/login/api_views.py b/login/api_views.py index bbd407d..8e7bbae 100644 --- a/login/api_views.py +++ b/login/api_views.py @@ -1,5 +1,11 @@ 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): serializer_class = MyTokenObtainPairSerializer + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer diff --git a/login/serializers.py b/login/serializers.py index efa55ce..433d4a0 100644 --- a/login/serializers.py +++ b/login/serializers.py @@ -1,4 +1,31 @@ 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): @classmethod diff --git a/sync/api_views.py b/sync/api_views.py new file mode 100644 index 0000000..0193a13 --- /dev/null +++ b/sync/api_views.py @@ -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 diff --git a/sync/apps.py b/sync/apps.py new file mode 100644 index 0000000..8a592ca --- /dev/null +++ b/sync/apps.py @@ -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() diff --git a/sync/migrations/0001_initial.py b/sync/migrations/0001_initial.py new file mode 100644 index 0000000..a01b361 --- /dev/null +++ b/sync/migrations/0001_initial.py @@ -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')], + }, + ), + ] diff --git a/sync/migrations/__init__.py b/sync/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sync/models.py b/sync/models.py new file mode 100644 index 0000000..8b7db0c --- /dev/null +++ b/sync/models.py @@ -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}" diff --git a/sync/serializers.py b/sync/serializers.py new file mode 100644 index 0000000..6c50ff0 --- /dev/null +++ b/sync/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import ChangeLog + +class ChangeLogSerializer(serializers.ModelSerializer): + class Meta: + model = ChangeLog + fields = '__all__' diff --git a/sync/signals.py b/sync/signals.py new file mode 100644 index 0000000..01d0aa5 --- /dev/null +++ b/sync/signals.py @@ -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__}")