mirror of
https://github.com/welton89/RRBEC.git
synced 2026-06-13 04:23:01 +00:00
Compare commits
233 Commits
dev_2
...
dfe0894305
| Author | SHA1 | Date | |
|---|---|---|---|
| dfe0894305 | |||
| df83dac830 | |||
| aec59990a7 | |||
| 47821c4f71 | |||
| 405033d1bf | |||
| 19c6fe2ad8 | |||
| b73ba1ef10 | |||
| 6c4e95e579 | |||
| 645a5b4093 | |||
| 2fc4fafed7 | |||
| 7ddaa2d1f9 | |||
|
|
2160998c23 | ||
| 4ccd77466e | |||
| 37428e8cc7 | |||
| a9289cb28d | |||
| b74736f391 | |||
| f191b6a14b | |||
| a20fa6d583 | |||
|
|
3ad690345a | ||
| 672af857a0 | |||
| 760d9db57c | |||
| bb55dfdf77 | |||
| ba93424398 | |||
| 92ebad35fe | |||
| 6310e91659 | |||
| 853b62a6cd | |||
| ee75321b97 | |||
| 7355d546cd | |||
| 69a9e9e065 | |||
| 0b8614e684 | |||
| eb76e91066 | |||
| b969ea6663 | |||
|
|
5ccd1d84ab | ||
|
|
d74ed0a27c | ||
|
|
671fc5d34b | ||
|
|
a81916925f | ||
|
|
9bf2a69da1 | ||
|
|
86129037fb | ||
|
|
57c833fd66 | ||
|
|
b392a50774 | ||
|
|
c9def2fe2b | ||
|
|
45395ec1e2 | ||
|
|
f43029712b | ||
|
|
03ee9caaf6 | ||
| 7c6586bbce | |||
| 09c5fd1e98 | |||
| fe00c4da90 | |||
| 377fac4f1c | |||
| 7dd76db5ba | |||
| 8cb63832e5 | |||
| c8c3653bf8 | |||
| 1ef263d033 | |||
| f232090577 | |||
| 0911483e62 | |||
| 52ccb7801b | |||
| eaa7d42826 | |||
| a7cbf2da09 | |||
| d7013f0ab8 | |||
| b73e134d9e | |||
| 179342ff80 | |||
| 0e7c7d7c68 | |||
| a099952548 | |||
| d6a3b58f89 | |||
| 40951892fe | |||
| 782ea2bac5 | |||
| a476000c48 | |||
| b5a27884cd | |||
| 998c489dc3 | |||
| be907bfcd5 | |||
| 132c8dcfc6 | |||
| de0f6912e2 | |||
| 99d17db5e6 | |||
| da59da79ae | |||
| 7a6b69e5f6 | |||
| 2cc2795b03 | |||
| bfa293e062 | |||
| d739f0c39b | |||
| 4c57a356b1 | |||
| e33f9e2974 | |||
| d5d136fc5d | |||
| ec11ae7f7a | |||
| abe61440e5 | |||
| 3fe2ad1044 | |||
| 0d97208489 | |||
| 4d128290d6 | |||
| c3f0861255 | |||
| e13db8d469 | |||
| c1d88b586a | |||
| 81e6d3f8df | |||
| 0a47948aea | |||
| 44cfa49455 | |||
| 3a3c804b3c | |||
| 1f64d5ecf8 | |||
| 5617a68873 | |||
| 2f03c0e175 | |||
| 5b167f0ed4 | |||
| b35fc39a60 | |||
| ac2a0283d0 | |||
| bb51147d99 | |||
| 8d31a83b13 | |||
| ef24e3f3bc | |||
| a2c1c2bcf0 | |||
| add06a2a67 | |||
| cac46a3e52 | |||
| 0c4101d0c4 | |||
| 51a7743d47 | |||
| 53d156b919 | |||
| 725e566d74 | |||
| 6d9693bc26 | |||
| 9599b31d69 | |||
| d366d2db97 | |||
| 3a7cbfc413 | |||
| 76d2d4a879 | |||
| d15c58f4e7 | |||
| 8a58d0a0db | |||
| baafa4cdec | |||
| fdb9d9e453 | |||
| 46f0900763 | |||
| d2ab8212b9 | |||
| 456fba3d6a | |||
| f0c956d2ba | |||
| 44a9c694dc | |||
| aac7f57fbe | |||
| e6beb6f4b5 | |||
| a86bd2fb45 | |||
| ecbeba02e8 | |||
| 2da09a8a25 | |||
| 66fb4eb17b | |||
| cc375d5d29 | |||
| a0a898caee | |||
| 0534b896ad | |||
| c8119609ea | |||
| 7ccc505299 | |||
| 7005796e7a | |||
| 2b838cda4f | |||
| 1a83896449 | |||
| 56ca14e1cb | |||
| 412eb763db | |||
| 1747305c05 | |||
| 2d0cbae050 | |||
| de3f90f83b | |||
| eb424f90ff | |||
| 80aebd2030 | |||
| 04e78a0f79 | |||
| 1d96e8791e | |||
| 155cf44fec | |||
| 891f23ec2e | |||
| 88373b4570 | |||
| c06b8c5608 | |||
| 6673fac076 | |||
| 3ef4aefaa0 | |||
| 4df3c6e8a8 | |||
| 203c7345fb | |||
| dfc5072142 | |||
| 6b7bd1bbb2 | |||
| 89a4d0e4be | |||
| c7bc44aa54 | |||
| 1999701dcf | |||
| b403e5fc72 | |||
| cf73dba143 | |||
| 137f5510f8 | |||
| 966b5b2cf3 | |||
| 15af99f765 | |||
| dc21858b9a | |||
| c19b6e5fca | |||
| 0d6a164872 | |||
| 2ceae1c0e9 | |||
| 8a13a8806a | |||
| 43d9cd9c5b | |||
| a2fd5d12e3 | |||
| 14d04eb280 | |||
| aff5fe24d8 | |||
| b06e1d22e3 | |||
| 9b4e1cfca4 | |||
| 3b006e7a63 | |||
| 5040744c8a | |||
| 9961d080b6 | |||
| 3dae764fc8 | |||
| 956841ef2a | |||
| c7687f7b9a | |||
| 7b01316834 | |||
| dd158ff7b6 | |||
| 052bfb7fb4 | |||
| 5c1188ecde | |||
| bde7014717 | |||
| b84fd3bb69 | |||
| 0e096207a6 | |||
| faaf0d3576 | |||
| 1011624f03 | |||
| d874b94c16 | |||
| 57f6f3a7fa | |||
| a41960dcd5 | |||
| 1b5b02be0d | |||
| 6633110140 | |||
| b2641691fe | |||
| d5cba23d4d | |||
| c0e93980ac | |||
| 3a6d214356 | |||
| 03192adf94 | |||
| a761bd2db2 | |||
| 7ab0e99e6b | |||
| 26af988846 | |||
| f11686dbdb | |||
| 3b4a871166 | |||
| 8de4f74aeb | |||
| 0ac4609ce6 | |||
| 0460040af3 | |||
| a2d751e88f | |||
| 42f3e02b0d | |||
| 998fdaa516 | |||
| 4a29a85b55 | |||
| f8d1373454 | |||
| 79409843d0 | |||
| 7698cfae47 | |||
| eb55f60f7e | |||
| 31c0fe5daa | |||
| c9f7b33fde | |||
| 9cc0c72f10 | |||
| ac87c3a47d | |||
| 356d45c3e5 | |||
| 1beb2e00b2 | |||
| a1866c842c | |||
| e7212ab688 | |||
| df0d652a36 | |||
| a9896730eb | |||
| 63c9859557 | |||
| a21b038868 | |||
| 9d1d832045 | |||
| 8c41336029 | |||
| c5cd92a9c7 | |||
| 0ef1c0a61b | |||
| 59e4eb6039 | |||
|
|
978decb3c0 |
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.env
|
||||
db.sqlite3
|
||||
media/
|
||||
static/
|
||||
.python-version
|
||||
uv.lock
|
||||
pyproject.toml
|
||||
GUIA_POSTMAN_API.md
|
||||
brain/
|
||||
logs/
|
||||
walkthrough.md
|
||||
task.md
|
||||
implementation_plan*.md
|
||||
*.webp
|
||||
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*
|
||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM python:3
|
||||
|
||||
RUN git clone https://github.com/welton89/RRBEC.git
|
||||
RUN cd RRBEC
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN python gestaoRaul/manage.py collectstatic --noinput
|
||||
|
||||
# RUN python gestaoRaul/manage.py migrate --noinput
|
||||
WORKDIR /app/gestaoRaul
|
||||
|
||||
|
||||
|
||||
CMD [ "gunicorn", "-w", "4", "--timeout", "15", "gestaoRaul.wsgi:application", "--bind", "0.0.0.0:8000" ]
|
||||
|
||||
94
README.md
94
README.md
@@ -1,94 +0,0 @@
|
||||
# RRBEC - Gestão de Bares e Restaurantes
|
||||
|
||||
## Sobre o Projeto
|
||||
Este projeto é uma aplicação web desenvolvida em Django com o objetivo de explorar as funcionalidades e recursos desse framework. A aplicação visa simular um sistema de gestão para bares e restaurantes, abrangendo desde o cadastro de produtos e clientes até a geração de relatórios de vendas.
|
||||
|
||||
## Requisitos Funcionais
|
||||
|
||||
### Módulo de Produtos
|
||||
* [x] Cadastrar novos produtos.
|
||||
* [ ] Editar informações de produtos existentes.
|
||||
* [ ] Ativar/Desativar produtos.
|
||||
* [ ] Pesquisar produtos por nome.
|
||||
* [ ] Gerenciar o estoque de cada produto.
|
||||
|
||||
### Módulo de Comandas
|
||||
* [x] Abrir nova comanda(inserindo nome, associando ou não a mesa).
|
||||
* [ ] Editar informações da comanda.
|
||||
* [x] Adicionar produtos na comanda.
|
||||
* [x] Remover produtos da comanda.
|
||||
* [ ] Imprimir cupom de pagamento.
|
||||
* [x] Imprimir fichas dos produtos.
|
||||
* [x] Fechamento da comanda.
|
||||
* [ ] Receber pagamento
|
||||
|
||||
### Módulo de Mesa
|
||||
* [ ] Gerenciar mesas (ocupação, reserva).
|
||||
* [x] Associar pedidos e comandas a mesas.
|
||||
* [ ] Dividir contas.
|
||||
|
||||
### Módulo de Clientes
|
||||
* [ ] Cadastrar novos clientes (nome, endereço, telefone, email).
|
||||
* [ ] Editar informações de clientes existentes.
|
||||
* [ ] Excluir clientes.
|
||||
* [ ] Consultar o histórico de pedidos de um cliente.
|
||||
|
||||
### Módulo de Pedidos
|
||||
* [ ] Realizar novos pedidos (produtos, quantidade, cliente).
|
||||
* [ ] Editar pedidos (adicionar/remover itens, alterar quantidade).
|
||||
* [ ] Cancelar pedidos.
|
||||
* [ ] Consultar o status de um pedido (em aberto, em preparo, entregue).
|
||||
* [ ] Gerar nota fiscal para pedidos finalizados.
|
||||
|
||||
### Módulo de Funcionários
|
||||
* [ ] Cadastrar novos funcionários (nome, cargo, salário, data de admissão).
|
||||
* [ ] Editar informações de funcionários existentes.
|
||||
* [ ] Excluir funcionários.
|
||||
* [ ] Gerenciar as permissões de cada funcionário (acesso a módulos, funções).
|
||||
|
||||
### Módulo de Relatórios
|
||||
* [ ] Gerar relatório de vendas por período (diário, semanal, mensal).
|
||||
* [ ] Gerar relatório de estoque (produtos em falta, produtos com alta rotatividade).
|
||||
* [ ] Gerar relatório de clientes (mais ativos, menos ativos).
|
||||
* [ ] Gerar relatório de funcionários (horas trabalhadas, faltas).
|
||||
|
||||
### Módulo de Pagamentos
|
||||
* [ ] Integrar com gateways de pagamento (cartão de crédito, débito, dinheiro).
|
||||
* [x] Gerenciar formas de pagamento.
|
||||
* [ ] Emitir notas fiscais eletrônicas.
|
||||
|
||||
|
||||
### Módulo de Delivery (opcional para restaurantes)
|
||||
* [ ] Cadastrar entregadores.
|
||||
* [ ] Gerenciar rotas de entrega.
|
||||
* [ ] Acompanhar pedidos em tempo real.
|
||||
|
||||
### Módulo de Sistema
|
||||
* [ ] Gerenciar usuários do sistema (login, senha, permissões).
|
||||
* [ ] Realizar backups do sistema.
|
||||
|
||||
## Tecnologias Utilizadas
|
||||
* **Django:** Framework Python para desenvolvimento web.
|
||||
* **Python:** Linguagem de programação principal do projeto.
|
||||
* **HTML:** Linguagem de marcação para disponibilizar os elementos na pagina.
|
||||
* **HTMX:** Biblioteca para deixar a pagina mais dinâmica, reduzindo a necessidade do js.
|
||||
* **CSS:** Linguagem para estilizar a interface do usuário.
|
||||
* **JavaScript:** Linguagem que executa a lógica da pagina.
|
||||
|
||||
## Como Executar o Projeto
|
||||
1. **Clonar o repositório:**
|
||||
```bash
|
||||
git clone https://github.com/Pindoba/RRBEC.git
|
||||
2. **Criar um ambiente virtual:**
|
||||
```bash
|
||||
python -m venv [nome da sua preferencia]
|
||||
source venv/bin/activate
|
||||
3. **Instalar as dependências:**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
4. **Executar as migrations:**
|
||||
```bash
|
||||
python manage.py migrate
|
||||
5. **Iniciar o servidor de desenvolvimento:**
|
||||
```bash
|
||||
python manage.py runserver
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class {{ camel_case_app_name }}Config(AppConfig):
|
||||
class ComandasConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = '{{ app_name }}'
|
||||
name = 'balcao'
|
||||
125
balcao/htmx_views.py
Normal file
125
balcao/htmx_views.py
Normal file
@@ -0,0 +1,125 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.db.models import Count, F
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
|
||||
|
||||
from comandas.models import Comanda, ProductComanda, StockMovementType, StockMovement
|
||||
from mesas.models import Mesa
|
||||
from products.models import Product
|
||||
from payments.models import Payments
|
||||
from typePay.models import TypePay
|
||||
from gestaoRaul.decorators import group_required
|
||||
|
||||
def listProductBalcao(request, comanda_id, search_product):
|
||||
comanda_id = request.GET.get("id-comanda-balcao")
|
||||
if search_product == '*':
|
||||
products_ordenados = ProductComanda.maisVendidos()
|
||||
|
||||
return render(request, "htmx_components/htmx_list_products_balcao.html", {"products": products_ordenados,'comanda_id':comanda_id})
|
||||
else:
|
||||
product = search_product
|
||||
products = Product.objects.filter(name__icontains=product, active=True)
|
||||
return render(request, "htmx_components/htmx_list_products_balcao.html", {"products": products[:15],'comanda_id':comanda_id})
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def addProductBalcao(request, product_id, comanda_id, qtd):
|
||||
for i in range(qtd):
|
||||
product_comanda = ProductComanda(comanda_id=comanda_id, product_id=product_id)
|
||||
product_comanda.save()
|
||||
product = Product.objects.get(id=product_id)
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
user = User.objects.get(id=request.user.id)
|
||||
typeMovement = StockMovementType.objects.get(name="Venda - Balcao")
|
||||
|
||||
StockMovement.subTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=user,
|
||||
qtd=1,
|
||||
obs= "Vendido no balcão"
|
||||
)
|
||||
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
total = 0
|
||||
for produto in consumo:
|
||||
total += produto.product.price
|
||||
return render(request, "htmx_components/htmx_list_products_in_balcao.html",{'consumo': consumo, 'total': total})
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
@csrf_exempt
|
||||
def addProductBalcaoTeclado(request, product_id, comanda_id, qtd):
|
||||
for i in range(qtd):
|
||||
product_comanda = ProductComanda(comanda_id=comanda_id, product_id=product_id)
|
||||
product_comanda.save()
|
||||
product = Product.objects.get(id=product_id)
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
user = User.objects.get(id=request.user.id)
|
||||
typeMovement = StockMovementType.objects.get(name="Venda - Balcao")
|
||||
|
||||
StockMovement.subTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=user,
|
||||
qtd=1,
|
||||
obs= "Vendido no balcão"
|
||||
)
|
||||
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
total = 0
|
||||
for produto in consumo:
|
||||
total += produto.product.price
|
||||
return render(request, "htmx_components/htmx_list_products_in_balcao.html",{'consumo': consumo, 'total': total})
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def removeProductBalcao(request, productComanda_id):
|
||||
product_comanda = ProductComanda.objects.get(id=productComanda_id)
|
||||
comanda = product_comanda.comanda
|
||||
product = product_comanda.product
|
||||
user = User.objects.get(id=request.user.id)
|
||||
typeMovement = StockMovementType.objects.get(name="Estorno")
|
||||
|
||||
consumo = ProductComanda.objects.filter(comanda=product_comanda.comanda)
|
||||
StockMovement.sumTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=user,
|
||||
qtd=1,
|
||||
obs= "Excluido do balcão"
|
||||
)
|
||||
|
||||
|
||||
product_comanda.delete()
|
||||
|
||||
total = 0
|
||||
for produto in consumo:
|
||||
total += produto.product.price
|
||||
return render(request, "htmx_components/htmx_list_products_in_balcao.html",{'consumo': consumo, 'total': total})
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def paymentBalcao(request, comanda_id):
|
||||
typePayment = TypePay.objects.get(id=1)
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
user = User.objects.get(id=request.user.id)
|
||||
try:
|
||||
vendasBalcao = Comanda.objects.get(name='VENDAS BALCAO')
|
||||
except:
|
||||
mesa = Mesa.objects.get(id=1)
|
||||
vendasBalcao = Comanda(name='VENDAS BALCAO', mesa=mesa, user=request.user, status='CLOSED')
|
||||
vendasBalcao.save()
|
||||
comanda = Comanda.objects.get(name=f'{user.id} - BALCÃO - {user.first_name}')
|
||||
total = 0
|
||||
for produto in consumo:
|
||||
total += produto.product.price
|
||||
produto.comanda = vendasBalcao
|
||||
produto.save()
|
||||
pagamento = Payments(value=total, comanda=comanda, type_pay=typePayment,description=f'{user.id} - BALCÃO - {user.first_name}')
|
||||
pagamento.save()
|
||||
return redirect('viewBalcao')
|
||||
|
||||
27
balcao/models.py
Normal file
27
balcao/models.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# from django.db import models
|
||||
|
||||
# from clients.models import Client
|
||||
# from products.models import Product
|
||||
# from mesas.models import Mesa
|
||||
# from typePay.models import TypePay
|
||||
|
||||
# class Comanda(models.Model):
|
||||
# id = models.AutoField(primary_key=True)
|
||||
# mesa = models.ForeignKey(Mesa, on_delete=models.CASCADE)
|
||||
# type_pay = models.ForeignKey(TypePay, on_delete=models.SET_NULL, null=True)
|
||||
# dt_open = models.DateTimeField(auto_now_add=True)
|
||||
# dt_close = models.DateTimeField(null=True, blank=True)
|
||||
# client = models.ForeignKey(Client, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
# name = models.CharField(max_length=255)
|
||||
# status = models.CharField(max_length=255, default="OPEN")
|
||||
# def __str__(self) -> str:
|
||||
# return self.name
|
||||
|
||||
# class ProductComanda(models.Model):
|
||||
# id = models.AutoField(primary_key=True)
|
||||
# comanda = models.ForeignKey(Comanda, on_delete=models.CASCADE)
|
||||
# data_time = models.DateTimeField(auto_now_add=True)
|
||||
# product = models.ForeignKey(Product, on_delete=models.PROTECT)
|
||||
# applicant = models.CharField(max_length=255, null=True, blank=True)
|
||||
# def __str__(self) -> str:
|
||||
# return self.comanda.name + " - " + self.product.name
|
||||
142
balcao/templates/viewBalcao.html
Normal file
142
balcao/templates/viewBalcao.html
Normal file
@@ -0,0 +1,142 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block 'title' %}
|
||||
|
||||
{{comanda.name}}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block 'head' %}
|
||||
<link rel="stylesheet" href="{% static 'comandas/css/viewbalcao.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block 'body' %}
|
||||
|
||||
<body>
|
||||
<div class="grid-container">
|
||||
<div style="text-align: center;">
|
||||
<h1>Venda Balcão</h1>
|
||||
<input hidden type="text" name="id-comanda-balcao" id="id-comanda-balcao" value="{{comanda.id}}">
|
||||
<div>
|
||||
|
||||
<button id="pagarComanda" class="btn-secondary" onclick="modal_payment_comanda()"
|
||||
{% if total == 0 %}
|
||||
disabled
|
||||
{% endif %}>Receber</button>
|
||||
|
||||
<button class="btn-primary" id="imprimirFichas" onclick="imprimirFichas()"
|
||||
{% if total == 0 %}
|
||||
disabled
|
||||
{% endif %}
|
||||
>Imprimir Fichas</button>
|
||||
|
||||
</div>
|
||||
|
||||
<table id="list-products-balcao">
|
||||
<tr>
|
||||
<th style="text-align: left;"><b>Produto</b></th>
|
||||
<th style="text-align: left;"><b>Preço</b></th>
|
||||
</tr>
|
||||
{% for item in consumo%}
|
||||
|
||||
<tr>
|
||||
<td>{{item.product.name}}</td>
|
||||
<td>R$ {{item.product.price}} </td>
|
||||
<td><button class="btn-cancel" onclick="removeProductBalcao({{item.id}})">Excluir</button></td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2" style="text-align: center;">Total R$ {{total}}</td>
|
||||
</tr>
|
||||
<tr hidden>
|
||||
<td hidden id="total">{{total}}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="add-produto">
|
||||
<form id="productForm">
|
||||
<h2 style="text-align: center;">Buscar Produto </h2>
|
||||
<div class="grid-container">
|
||||
<input id="search-product" name="search-product" type="text" oninput="searchProduct()" autofocus
|
||||
placeholder="Buscar Produto">
|
||||
<input type="number" id="qtd-product" name="qtd-product" value="1" required min="1"><br>
|
||||
</div>
|
||||
<div id="product-list" class="grid-list-products">
|
||||
{% for product in products %}
|
||||
{% if forloop.counter0 == 0 %}
|
||||
|
||||
<article
|
||||
style="background-image: url('{{product.image}}');"
|
||||
name="productBox"
|
||||
id="productId-{{ product.id }}"
|
||||
class="card-product"
|
||||
onclick="addProductClick({{product.id}} )"
|
||||
>
|
||||
<p hidden id="{{forloop.counter0}}" >{{product.id}}</p>
|
||||
<p hidden id="comanda{{forloop.counter0}}" >{{comanda.id}}</p>
|
||||
<p class="card-product-p"> {{product.name}}</p>
|
||||
<p id="{{product.id}}"></p>
|
||||
<p class="card-product-p"> R$ {{product.price}}</p>
|
||||
</article >
|
||||
|
||||
{% else %}
|
||||
|
||||
<article
|
||||
style="background-image: url('{{product.image}}');"
|
||||
name="productBox"
|
||||
id="productId-{{ product.id }}"
|
||||
class="card-product"
|
||||
onclick="addProductClick({{product.id}})"
|
||||
>
|
||||
<p class="card-product-p"> {{product.name}}</p>
|
||||
<p class="card-product-p"> R$ {{product.price}}</p>
|
||||
</article >
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<dialog id="payment-comanda" style="display: none;">
|
||||
<article>
|
||||
<h2>Pagamento</h2>
|
||||
<h1 id="first-total">R$ {{ total }}</h1>
|
||||
<div>
|
||||
<p>Recebido:</p> <input id="recebido" type="number">
|
||||
<h4 id="troco">Troco: </h4>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button class="btn-secondary" hx-get="{% url 'paymentBalcao' comanda.id %} " hx-trigger="click" hx-swap="none"
|
||||
onclick="reloadPage()">
|
||||
Receber
|
||||
</button>
|
||||
|
||||
<button class="btn-cancel" onclick="close_modal_payment_comanda()">Cancelar</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
|
||||
<script src="{% static 'comandas/js/viewbalcao.js' %}"></script>
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
21
balcao/urls.py
Normal file
21
balcao/urls.py
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from balcao import htmx_views
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.viewBalcao, name='viewBalcao'),
|
||||
|
||||
]
|
||||
|
||||
|
||||
htmx_urlpatterns = [
|
||||
path('listProductBalcao/<int:comanda_id>/<str:search_product>/', htmx_views.listProductBalcao, name='listProductBalcao'),
|
||||
path('addProductBalcao<int:product_id>/<int:comanda_id>/<int:qtd>/', htmx_views.addProductBalcao, name='addProductBalcao'),
|
||||
path('addProductBalcaoTeclado<int:product_id>/<int:comanda_id>/<int:qtd>/', htmx_views.addProductBalcaoTeclado, name='addProductBalcaoTeclado'),
|
||||
path('removeProductBalcao<int:productComanda_id>/', htmx_views.removeProductBalcao, name='removeProductBalcao'),
|
||||
path('paymentBalcao<int:comanda_id>/', htmx_views.paymentBalcao, name='paymentBalcao'),
|
||||
]
|
||||
|
||||
urlpatterns += htmx_urlpatterns
|
||||
30
balcao/views.py
Normal file
30
balcao/views.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
from comandas.models import Comanda, ProductComanda
|
||||
from products.models import Product
|
||||
from mesas.models import Mesa
|
||||
from django.db.models import Count, F
|
||||
from django.contrib.auth.models import User
|
||||
from gestaoRaul.decorators import group_required
|
||||
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def viewBalcao(request):
|
||||
user = User.objects.get(id=request.user.id)
|
||||
try:
|
||||
comanda = Comanda.objects.get(name=f'{user.id} - BALCÃO - {user.first_name}')
|
||||
except:
|
||||
mesa = Mesa.objects.get(id=1)
|
||||
comanda = Comanda(name=f'{user.id} - BALCÃO - {user.first_name}', mesa=mesa, user=user,status='CLOSED')
|
||||
comanda.save()
|
||||
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda.id)
|
||||
products_ordenados = ProductComanda.maisVendidos()
|
||||
total = 0
|
||||
for produto in consumo:
|
||||
total += produto.product.price
|
||||
|
||||
return render(request, 'viewBalcao.html', {'comanda': comanda, 'consumo': consumo, 'total': total, 'products': products_ordenados})
|
||||
|
||||
|
||||
6
categories/admin.py
Normal file
6
categories/admin.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from categories.models import Categories
|
||||
|
||||
|
||||
admin.site.register(Categories)
|
||||
8
categories/api_views.py
Normal file
8
categories/api_views.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from rest_framework import viewsets, permissions
|
||||
from .models import Categories
|
||||
from .serializers import CategoriesSerializer
|
||||
|
||||
class CategoriesViewSet(viewsets.ModelViewSet):
|
||||
queryset = Categories.objects.all()
|
||||
serializer_class = CategoriesSerializer
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
7
categories/serializers.py
Normal file
7
categories/serializers.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Categories
|
||||
|
||||
class CategoriesSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Categories
|
||||
fields = '__all__'
|
||||
6
clients/admin.py
Normal file
6
clients/admin.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from clients.models import Client
|
||||
|
||||
|
||||
admin.site.register(Client)
|
||||
74
clients/api_views.py
Normal file
74
clients/api_views.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from rest_framework import viewsets, permissions, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from .models import Client
|
||||
from .serializers import ClientSerializer
|
||||
from comandas.models import Comanda, ProductComanda
|
||||
from comandas.serializers import ComandaSerializer
|
||||
from payments.models import Payments, somar
|
||||
from typePay.models import TypePay
|
||||
|
||||
class ClientViewSet(viewsets.ModelViewSet):
|
||||
queryset = Client.objects.all()
|
||||
serializer_class = ClientSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
return Client.objects.all()
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def fiados(self, request, pk=None):
|
||||
client = self.get_object()
|
||||
comandas = Comanda.objects.filter(client=client, status='FIADO')
|
||||
serializer = ComandaSerializer(comandas, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def pagar_fiados(self, request):
|
||||
comanda_ids = request.data.get('ids', [])
|
||||
if not comanda_ids:
|
||||
return Response({'error': 'Nenhum ID de comanda fornecido.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
results = []
|
||||
for comanda_id in comanda_ids:
|
||||
try:
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
|
||||
# Apenas processar se estiver como FIADO
|
||||
if comanda.status != 'FIADO':
|
||||
results.append({'id': comanda_id, 'status': 'erro', 'message': 'Status da comanda não é FIADO.'})
|
||||
continue
|
||||
|
||||
# 1. Fechar a comanda
|
||||
comanda.status = 'CLOSED'
|
||||
comanda.save()
|
||||
|
||||
# 2. Gerar o pagamento (Inspirado no payDebt original)
|
||||
try:
|
||||
type_pay = TypePay.objects.get(id=1) # Assume ID 1 como padrão para recebimento de fiado
|
||||
except TypePay.DoesNotExist:
|
||||
type_pay, _ = TypePay.objects.get_or_create(id=1, defaults={'name': 'Dinheiro'})
|
||||
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda)
|
||||
valores = somar(consumo, comanda)
|
||||
|
||||
payment = Payments.objects.create(
|
||||
value=valores['totalSemTaxa'], # Valor que falta pagar
|
||||
type_pay=type_pay,
|
||||
comanda=comanda,
|
||||
client=comanda.client,
|
||||
description='PAGAMENTO DE FIADO (via API)'
|
||||
)
|
||||
|
||||
results.append({'id': comanda_id, 'status': 'sucesso', 'payment_id': payment.id})
|
||||
|
||||
except Comanda.DoesNotExist:
|
||||
results.append({'id': comanda_id, 'status': 'erro', 'message': 'Comanda não encontrada.'})
|
||||
except Exception as e:
|
||||
results.append({'id': comanda_id, 'status': 'erro', 'message': str(e)})
|
||||
|
||||
return Response({
|
||||
'success': True,
|
||||
'message': f'{len(comanda_ids)} comandas processadas',
|
||||
'results': results
|
||||
}, status=status.HTTP_200_OK)
|
||||
26
clients/migrations/0002_client_created_at_client_debt.py
Normal file
26
clients/migrations/0002_client_created_at_client_debt.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-15 23:06
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('clients', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='client',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='client',
|
||||
name='debt',
|
||||
field=models.DecimalField(decimal_places=2, default=1, max_digits=10),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -4,5 +4,10 @@ from django.db import models
|
||||
class Client(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=255)
|
||||
debt = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
active = models.BooleanField(default=True)
|
||||
contact = models.CharField(max_length=255, null=True, blank=True)
|
||||
contact = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
23
clients/serializers.py
Normal file
23
clients/serializers.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Client
|
||||
from comandas.models import Comanda, ProductComanda
|
||||
from payments.models import Payments, somar
|
||||
from decimal import Decimal
|
||||
|
||||
class ClientSerializer(serializers.ModelSerializer):
|
||||
debt = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Client
|
||||
fields = ['id', 'name', 'created_at', 'active', 'contact', 'debt']
|
||||
|
||||
def get_debt(self, obj):
|
||||
comandas = Comanda.objects.filter(client=obj, status='FIADO')
|
||||
total_debt = Decimal(0)
|
||||
|
||||
for comanda in comandas:
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda)
|
||||
valores = somar(consumo, comanda)
|
||||
total_debt += valores['totalSemTaxa']
|
||||
|
||||
return total_debt
|
||||
96
clients/templates/clients.html
Normal file
96
clients/templates/clients.html
Normal file
@@ -0,0 +1,96 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load custom_filter_tag %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% block 'title' %}
|
||||
Clientes
|
||||
{% endblock %}
|
||||
|
||||
{% block 'head' %}
|
||||
<link rel="stylesheet" href="{% static 'clients/css/clients.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block 'body' %}
|
||||
|
||||
|
||||
<div class="grid-container">
|
||||
<div class="grid-top">
|
||||
<button class="btn-primary"
|
||||
|
||||
onclick="openModal()" id="openModal">Novo Cliente</button>
|
||||
</div>
|
||||
|
||||
<table id="client-list">
|
||||
<tr>
|
||||
<th style="text-align: left;">Cliente</th>
|
||||
<th style="text-align: left;width: 20%;">Débito</th>
|
||||
<th class="hide-on-mobile" style="text-align: left;">Contato</th>
|
||||
<th class="hide-on-mobile" style="text-align: left;width: 20%;">Ações</th>
|
||||
</tr>
|
||||
|
||||
{% for client in clients %}
|
||||
|
||||
<tr>
|
||||
<td ><a id="name-{{client.id}}" href="{% url 'viewClient' client.id %}">{{client.name}}</a></td>
|
||||
<td id="debt-{{client.id}}" >R$ {{client.id | totalFiado}}</td>
|
||||
<td class="hide-on-mobile" id="contact-{{client.id}}" >{{client.contact}}</td>
|
||||
<td hidden id="active-{{client.id}}" >{{client.active}}</td>
|
||||
<td>
|
||||
<div class="grid-buttons hide-on-mobile">
|
||||
|
||||
<img
|
||||
src="{% static 'midia/icons/edit.svg' %}"
|
||||
style=" width: 35px; height: 35px; cursor: pointer;"
|
||||
onclick="editclient({{client.id}})" >
|
||||
</img>
|
||||
|
||||
<input type="hidden" id="name-{{client.id}}" value="{{ client.name }}">
|
||||
<input type="hidden" id="contact-{{client.id}}" value="{{ client.contact }}">
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<dialog id='Modal-create-client' >
|
||||
<article >
|
||||
<form action="{% url 'createClient' %}" id="clientForm" method="post" >
|
||||
|
||||
{% csrf_token %}
|
||||
<h2 id="title-window">Cadastro Cliente</h2>
|
||||
<input type="text" id="clientId" name="clientId" hidden >
|
||||
|
||||
<input type="text" id="clientName" name="name" required placeholder="Nome">
|
||||
<input type="checkbox" id="active" name="active" placeholder="Ativo">Ativo
|
||||
|
||||
<input type="text" id="clientContact" name="contact" placeholder="Contato"></input>
|
||||
<footer class="grid-buttons">
|
||||
<button class="btn-primary" id="save" type="submit">Salvar</button>
|
||||
<button class="btn-primary" onclick="closeModal()" type="button" id="edit" hx-post="{% url 'editClient' %}" hx-trigger="click" hx-swap="none" style="width: 100%;">Alterar</button>
|
||||
<button class="btn-cancel" type="button" onclick="closeModal()" style="background-color:red;">Cancelar</button>
|
||||
</footer>
|
||||
</form>
|
||||
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="{% static 'clients/js/clients.js' %}"></script>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
70
clients/templates/viewclient.html
Normal file
70
clients/templates/viewclient.html
Normal file
@@ -0,0 +1,70 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load custom_filter_tag %}
|
||||
|
||||
|
||||
{% block 'title' %}
|
||||
Comandas
|
||||
{% endblock %}
|
||||
|
||||
{% block 'head' %}
|
||||
<link rel="stylesheet" href="{% static 'comandas/css/comandas.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block 'body' %}
|
||||
|
||||
<body>
|
||||
<div style="justify-self: center;">
|
||||
<h4>{{client.name}}</h4>
|
||||
<h4>R$ {{client.id | totalFiado}}</h4><br>
|
||||
<!-- <h4 id="total-selecionado">R$</h4> -->
|
||||
<button id="btn-fechar-comandas" class="btn-fechar" onclick="enviarComandasSelecionadas()">
|
||||
|
||||
Receber
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class=" ">
|
||||
<table>
|
||||
<tr>
|
||||
<th style="text-align: left;"><b>Nome</b></th>
|
||||
<th style="text-align: left;"><b>Atendente</b></th>
|
||||
<th style="text-align: left;"><b>Data abertura</b></th>
|
||||
<th style="text-align: left;"><b>Data fechamento</b></th>
|
||||
<th style="text-align: left;"><b><input id="selectAll" name="selectAll" type="checkbox"></b></th>
|
||||
<th style="text-align: left;"><b>Detalhes</b></th>
|
||||
<th style="text-align: left;"><b>Valor</b></th>
|
||||
</tr>
|
||||
{% for comanda in comandas %}
|
||||
<tr>
|
||||
<td>{{comanda.name}}</td>
|
||||
<td>{{comanda.user.first_name}} {{comanda.user.last_name}}</td>
|
||||
<td>{{comanda.dt_open}}</td>
|
||||
<td>{{comanda.dt_close}}</td>
|
||||
<td><input id="{{comanda.id}}" name="{{comanda.id}}" type="checkbox"></td>
|
||||
<td>
|
||||
<span data-tooltip="Visualizar Comanda" data-flow="top">
|
||||
<a href="{% url 'viewcomanda' %}?parametro={{ comanda.id }}">
|
||||
<img
|
||||
src="{% static 'midia/icons/view.svg' %}"
|
||||
style="height: 35px; cursor: pointer;">
|
||||
</img>
|
||||
</a>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ comanda.id | total }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script src="{% static 'comandas/js/comandas.js' %}"></script>
|
||||
<script src="{% static 'clients/js/clients.js' %}"></script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
14
clients/urls.py
Normal file
14
clients/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.clients, name='clients'),
|
||||
path('createClient', views.createClient, name='createClient'),
|
||||
path('editClient', views.editClient, name='editClient'),
|
||||
path('payDebt', views.payDebt, name='payDebt'),
|
||||
path('viewClient/<int:clientId>', views.viewClient, name='viewClient'),
|
||||
|
||||
|
||||
|
||||
]
|
||||
109
clients/views.py
Normal file
109
clients/views.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from decimal import Decimal
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
import json
|
||||
|
||||
from comandas.models import Comanda, ProductComanda
|
||||
from gestaoRaul.decorators import group_required
|
||||
from clients.models import Client
|
||||
from payments.models import Payments, somar
|
||||
from typePay.models import TypePay
|
||||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def clients(request):
|
||||
clients = Client.objects.all()
|
||||
return render(request, 'clients.html', {'clients': clients})
|
||||
|
||||
def viewClient(request,clientId):
|
||||
# config = {
|
||||
# 'taxa': False
|
||||
# }
|
||||
client = Client.objects.get(id=int(clientId))
|
||||
comandas = Comanda.objects.filter(client = client).filter(status = 'FIADO')
|
||||
total = Decimal(0)
|
||||
# for comanda in comandas:
|
||||
# totalConsumo = 0
|
||||
# totalParcial = 0
|
||||
# consumo = ProductComanda.objects.filter(comanda=comanda)
|
||||
# parcial = Payments.objects.filter(comanda=comanda)
|
||||
# for p in parcial:
|
||||
# totalParcial += p.value
|
||||
# for produto in consumo:
|
||||
# totalConsumo += produto.product.price
|
||||
# total+= (totalConsumo - totalParcial)
|
||||
# total = total + round(total * Decimal(0.1), 2) if config['taxa'] else total
|
||||
return render(request, 'viewclient.html', {'client': client, 'comandas': comandas})
|
||||
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def createClient(request):
|
||||
name = request.POST.get('name')
|
||||
contact = request.POST.get('contact')
|
||||
active = True if request.POST.get('active') else False
|
||||
# debt = request.POST.get('debt')
|
||||
client = Client(name=name, contact=contact,debt=0, active=active)
|
||||
client.save()
|
||||
return redirect('/clients')
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def editClient(request):
|
||||
client_id = int(request.POST.get('clientId'))
|
||||
client = Client.objects.get(id=client_id)
|
||||
client.name = request.POST.get('name')
|
||||
client.contact = request.POST.get('contact')
|
||||
client.active = True if request.POST.get('active') else False
|
||||
# client = Client(name=name, contact=contact,debt=0, active=active)
|
||||
client.save()
|
||||
return redirect('/clients')
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
def payDebt(request):
|
||||
try:
|
||||
# Verifica se é uma requisição AJAX
|
||||
if not request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({'error': 'Requisição inválida'}, status=400)
|
||||
|
||||
# Obter os IDs do corpo da requisição (não mais da URL)
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
comanda_ids = data.get('ids', [])
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'error': 'JSON inválido'}, status=400)
|
||||
|
||||
for comanda_id in comanda_ids:
|
||||
try:
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
comanda.status = 'CLOSED'
|
||||
comanda.save()
|
||||
|
||||
typePayment = TypePay.objects.get(id=1)
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
value = somar(consumo,comanda)
|
||||
description = 'PAGAMENTO DE FIADO'
|
||||
pagamento = Payments(value=value["totalSemTaxa"], comanda=comanda, type_pay=typePayment,description=description,client=comanda.client)
|
||||
pagamento.save()
|
||||
except Comanda.DoesNotExist:
|
||||
return JsonResponse({'error': f'Comanda com ID {comanda_id} não encontrada'}, status=404)
|
||||
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'{len(comanda_ids)} comandas processadas',
|
||||
'ids': comanda_ids
|
||||
}, status=200)
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
9
comandas/admin.py
Normal file
9
comandas/admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from comandas.models import Comanda, ProductComanda, StockMovement, StockMovementType
|
||||
|
||||
admin.site.register(Comanda)
|
||||
admin.site.register(ProductComanda)
|
||||
admin.site.register(StockMovementType)
|
||||
admin.site.register(StockMovement)
|
||||
|
||||
168
comandas/api_views.py
Normal file
168
comandas/api_views.py
Normal file
@@ -0,0 +1,168 @@
|
||||
from rest_framework import viewsets, permissions, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django.utils import timezone
|
||||
from .models import Comanda, ProductComanda, StockMovement, StockMovementType
|
||||
from .serializers import ComandaSerializer, ProductComandaSerializer
|
||||
from payments.models import Payments
|
||||
from typePay.models import TypePay
|
||||
from clients.models import Client
|
||||
|
||||
class ComandaViewSet(viewsets.ModelViewSet):
|
||||
queryset = Comanda.objects.all()
|
||||
serializer_class = ComandaSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
return Comanda.objects.all()
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def apagar(self, request, pk=None):
|
||||
comanda = self.get_object()
|
||||
|
||||
# 1. Recuperar os itens para devolver ao estoque
|
||||
itens = ProductComanda.objects.filter(comanda=comanda)
|
||||
|
||||
# Tipo de movimentação: Estorno/Cancelamento (ajustar conforme os tipos existentes)
|
||||
# Se não houver um tipo específico, podemos buscar um genérico ou usar o de Venda com sinal invertido via sumTransactionStock
|
||||
try:
|
||||
typeMovement = StockMovementType.objects.get(name="Estorno - Comanda Apagada")
|
||||
except StockMovementType.DoesNotExist:
|
||||
# Fallback para um tipo existente se o de estorno não existir
|
||||
typeMovement, _ = StockMovementType.objects.get_or_create(name="Ajuste de Estoque (Cancelamento)")
|
||||
|
||||
for item in itens:
|
||||
StockMovement.sumTransactionStock(
|
||||
product=item.product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=request.user,
|
||||
qtd=1,
|
||||
obs=f"Estorno: Comanda {comanda.name} apagada/limpa via API"
|
||||
)
|
||||
|
||||
# 2. Excluir os itens da comanda
|
||||
itens.delete()
|
||||
|
||||
# 3. Mudar o status para CLOSED
|
||||
comanda.status = 'CLOSED'
|
||||
comanda.save()
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def pagar(self, request, pk=None):
|
||||
comanda = self.get_object()
|
||||
|
||||
# Dados do pagamento vindos no request
|
||||
value = request.data.get('value')
|
||||
type_pay_id = request.data.get('type_pay')
|
||||
client_id = request.data.get('client')
|
||||
description = request.data.get('description', f"Pagamento da comanda {comanda.name}")
|
||||
|
||||
if not value or not type_pay_id:
|
||||
return Response(
|
||||
{'error': 'Campos "value" e "type_pay" são obrigatórios.'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
type_pay = TypePay.objects.get(id=type_pay_id)
|
||||
except TypePay.DoesNotExist:
|
||||
return Response({'error': 'Tipo de pagamento não encontrado.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
client = None
|
||||
if client_id:
|
||||
try:
|
||||
client = Client.objects.get(id=client_id)
|
||||
except Client.DoesNotExist:
|
||||
return Response({'error': 'Cliente não encontrado.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 1. Criar o registro de pagamento
|
||||
payment = Payments.objects.create(
|
||||
value=value,
|
||||
type_pay=type_pay,
|
||||
comanda=comanda,
|
||||
client=client,
|
||||
description=description
|
||||
)
|
||||
|
||||
# 2. Fechar a comanda
|
||||
comanda.status = 'CLOSED'
|
||||
comanda.dt_close = timezone.now()
|
||||
comanda.save()
|
||||
|
||||
return Response({
|
||||
'status': 'Pagamento registrado e comanda fechada com sucesso.',
|
||||
'payment_id': payment.id
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
class ProductComandaViewSet(viewsets.ModelViewSet):
|
||||
queryset = ProductComanda.objects.all()
|
||||
serializer_class = ProductComandaSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
# Salva o item na comanda
|
||||
product_comanda = serializer.save()
|
||||
|
||||
# Recupera os dados para a movimentação de estoque
|
||||
product = product_comanda.product
|
||||
comanda = product_comanda.comanda
|
||||
|
||||
# Tipo de movimentação: Venda - Comanda (como na view original)
|
||||
try:
|
||||
typeMovement = StockMovementType.objects.get(name="Venda - Comanda")
|
||||
except StockMovementType.DoesNotExist:
|
||||
typeMovement, _ = StockMovementType.objects.get_or_create(name="Saída de Estoque (API)")
|
||||
|
||||
StockMovement.subTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=self.request.user,
|
||||
qtd=1,
|
||||
obs="Adicionado na comanda via API"
|
||||
)
|
||||
|
||||
# 3. Criar Pedido (Order) automaticamente se for item de cozinha
|
||||
# (Lógica inspirada no addProduct original)
|
||||
if product.cuisine:
|
||||
from orders.models import Order
|
||||
Order.objects.create(
|
||||
id_comanda=comanda,
|
||||
id_product=product,
|
||||
productComanda=product_comanda,
|
||||
obs=self.request.data.get('obs', '')
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
instance = serializer.save()
|
||||
obs = self.request.data.get('obs')
|
||||
if obs is not None:
|
||||
order = instance.order_set.first()
|
||||
if order:
|
||||
order.obs = obs
|
||||
order.save()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
# Recupera os dados antes de deletar
|
||||
product = instance.product
|
||||
comanda = instance.comanda
|
||||
|
||||
# Tipo de movimentação: Estorno/Cancelamento
|
||||
try:
|
||||
typeMovement = StockMovementType.objects.get(name="Estorno - Item Removido")
|
||||
except StockMovementType.DoesNotExist:
|
||||
typeMovement, _ = StockMovementType.objects.get_or_create(name="Ajuste de Estoque (Cancelamento)")
|
||||
|
||||
# Realiza a devolução ao estoque
|
||||
StockMovement.sumTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=self.request.user,
|
||||
qtd=1,
|
||||
obs=f"Estorno: Item {product.name} removido da comanda {comanda.name} via API"
|
||||
)
|
||||
|
||||
# Deleta o registro definitivamente
|
||||
instance.delete()
|
||||
136
comandas/htmx_views.py
Normal file
136
comandas/htmx_views.py
Normal file
@@ -0,0 +1,136 @@
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
from comandas.models import Comanda, ProductComanda, StockMovementType, StockMovement
|
||||
from orders.models import Order
|
||||
from products.models import Product
|
||||
from payments.models import Payments, somar
|
||||
from typePay.models import TypePay
|
||||
from gestaoRaul.decorators import group_required
|
||||
from websocket_client.websocketClient import enviar_mensagem
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
# import asyncio
|
||||
|
||||
# import websockets
|
||||
|
||||
# async def enviar_mensagem(msg):
|
||||
# try:
|
||||
# uri = "ws://websocket_server:8765"
|
||||
# async with websockets.connect(uri) as websocket:
|
||||
# await websocket.send(msg)
|
||||
# # print(f"> Enviado: {msg}")
|
||||
|
||||
# # resposta = await websocket.recv()
|
||||
# # print(f"< Recebido: {resposta}")
|
||||
# except Exception as e:
|
||||
# print(f"Erro ao enviar mensagem via websocket: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def removeProductComanda(request, productComanda_id):
|
||||
config = {
|
||||
'taxa': False
|
||||
}
|
||||
product_comanda = ProductComanda.objects.get(id=productComanda_id)
|
||||
comanda = product_comanda.comanda
|
||||
product = product_comanda.product
|
||||
user = User.objects.get(id=request.user.id)
|
||||
typeMovement = StockMovementType.objects.get(name="Estorno")
|
||||
|
||||
if comanda.status == 'OPEN':
|
||||
parcial = Payments.objects.filter(comanda=comanda)
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda)
|
||||
valores = somar(consumo,comanda)
|
||||
if product_comanda.product.cuisine == True:
|
||||
order = Order.objects.get(productComanda=product_comanda)
|
||||
|
||||
StockMovement.sumTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=user,
|
||||
qtd=1,
|
||||
obs= "Excluido da comanda"
|
||||
)
|
||||
product_comanda.delete()
|
||||
|
||||
msg = JsonResponse({
|
||||
'type': 'broadcast',
|
||||
'message': 'Atenção! Pedido cancelado',
|
||||
'local':'cozinha',
|
||||
'tipo':'delete',
|
||||
'id':order.id,
|
||||
'speak': f'Pedido cancelado! {order.id_product.name}.'
|
||||
})
|
||||
# asyncio.run(enviar_mensagem(msg))
|
||||
# order.delete()
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda)
|
||||
valores = somar(consumo,comanda)
|
||||
else:
|
||||
StockMovement.sumTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=user,
|
||||
qtd=1,
|
||||
obs= "Excluido da comanda"
|
||||
)
|
||||
product_comanda.delete()
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda)
|
||||
valores = somar(consumo,comanda)
|
||||
|
||||
return render(request,
|
||||
"htmx_components/comandas/htmx_list_products_in_comanda.html",
|
||||
{'config':config,
|
||||
'valores': valores,
|
||||
'parcials':parcial,
|
||||
'consumo': consumo,
|
||||
'comanda':comanda})
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def reopenComanda(request, comanda_id):
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
if comanda.status == 'CLOSED':
|
||||
pass
|
||||
else:
|
||||
comanda.status = "OPEN"
|
||||
comanda.save()
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def paymentComanda(request, comanda_id):
|
||||
taxa = request.POST.get('taxa',False)
|
||||
typePayment = TypePay.objects.get(id=1)
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
valores = somar(consumo,comanda)
|
||||
if taxa == 'True':
|
||||
pagamento = Payments(value=valores['totalComTaxa'], comanda=comanda, type_pay=typePayment,description='tipo de pagamento mokado')
|
||||
pagamento.save()
|
||||
else:
|
||||
pagamento = Payments(value=valores['totalSemTaxa'], comanda=comanda, type_pay=typePayment,description='tipo de pagamento mokado')
|
||||
pagamento.save()
|
||||
|
||||
comanda.status = 'CLOSED'
|
||||
comanda.save()
|
||||
return redirect('/comandas')
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def paymentParcial(request, comanda_id):
|
||||
typePayment = TypePay.objects.get(id=1)
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
value = Decimal(request.POST.get('value-parcial'))
|
||||
description = request.POST.get('name-parcial')
|
||||
pagamento = Payments(value=value, comanda=comanda, type_pay=typePayment,description=description)
|
||||
pagamento.save()
|
||||
return redirect('/comandas')
|
||||
22
comandas/migrations/0004_comanda_user.py
Normal file
22
comandas/migrations/0004_comanda_user.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-15 12:43
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('comandas', '0003_comanda_status_alter_productcomanda_product'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='comanda',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
42
comandas/migrations/0005_stockmovementtype_stockmovement.py
Normal file
42
comandas/migrations/0005_stockmovementtype_stockmovement.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 5.1.4 on 2025-07-22 18:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('comandas', '0004_comanda_user'),
|
||||
('products', '0004_unitofmeasure_productcomponent_product_components_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StockMovementType',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(help_text="Ex: 'Entrada por Compra', 'Saída por Venda', 'Ajuste de Estoque'", max_length=100, unique=True)),
|
||||
('observation', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StockMovement',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('quantity', models.IntegerField(help_text='Quantidade movimentada. Use valores negativos para saídas.')),
|
||||
('observation', models.TextField(blank=True, null=True)),
|
||||
('movement_date', models.DateTimeField(auto_now_add=True)),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stock_movements', to='products.product')),
|
||||
('related_comanda', models.ForeignKey(blank=True, help_text='Comanda relacionada à movimentação (opcional).', null=True, on_delete=django.db.models.deletion.SET_NULL, to='comandas.comanda')),
|
||||
('user', models.ForeignKey(blank=True, help_text='Usuário que realizou a movimentação.', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
('movement_type', models.ForeignKey(help_text='Tipo de movimentação (entrada, saída, ajuste).', on_delete=django.db.models.deletion.PROTECT, to='comandas.stockmovementtype')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-movement_date'],
|
||||
},
|
||||
),
|
||||
]
|
||||
151
comandas/models.py
Normal file
151
comandas/models.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Count, F
|
||||
|
||||
|
||||
|
||||
from clients.models import Client
|
||||
from products.models import Product, ProductComponent
|
||||
from mesas.models import Mesa
|
||||
from typePay.models import TypePay
|
||||
|
||||
class Comanda(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
mesa = models.ForeignKey(Mesa, on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=True)
|
||||
type_pay = models.ForeignKey(TypePay, on_delete=models.SET_NULL, null=True)
|
||||
dt_open = models.DateTimeField(auto_now_add=True)
|
||||
dt_close = models.DateTimeField(null=True, blank=True)
|
||||
client = models.ForeignKey(Client, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
name = models.CharField(max_length=255)
|
||||
status = models.CharField(max_length=255, default="OPEN")
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
class ProductComanda(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
comanda = models.ForeignKey(Comanda, on_delete=models.CASCADE)
|
||||
data_time = models.DateTimeField(auto_now_add=True)
|
||||
product = models.ForeignKey(Product, on_delete=models.PROTECT)
|
||||
applicant = models.CharField(max_length=255, null=True, blank=True)
|
||||
def __str__(self) -> str:
|
||||
return self.comanda.name + " - " + self.product.name
|
||||
|
||||
|
||||
def maisVendidos():
|
||||
produtos_mais_vendidos = list(ProductComanda.objects.values('product').annotate(
|
||||
quantidade=Count('product'),
|
||||
nome=F('product__name') ).order_by('-quantidade'))
|
||||
|
||||
products = Product.objects.all()
|
||||
products_ordenados = []
|
||||
for produto in produtos_mais_vendidos:
|
||||
for p in products:
|
||||
if p.name == produto['nome'] and p.active == True:
|
||||
products_ordenados.append(p)
|
||||
return products_ordenados[:15]
|
||||
|
||||
|
||||
class StockMovementType(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=100, unique=True, help_text="Ex: 'Entrada por Compra', 'Saída por Venda', 'Ajuste de Estoque'")
|
||||
observation = models.TextField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class StockMovement(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='stock_movements')
|
||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, help_text="Usuário que realizou a movimentação.")
|
||||
related_comanda = models.ForeignKey(
|
||||
Comanda,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Comanda relacionada à movimentação (opcional)."
|
||||
)
|
||||
movement_type = models.ForeignKey(StockMovementType, on_delete=models.PROTECT, help_text="Tipo de movimentação (entrada, saída, ajuste).")
|
||||
quantity = models.IntegerField(help_text="Quantidade movimentada. Use valores negativos para saídas.")
|
||||
observation = models.TextField(null=True, blank=True)
|
||||
movement_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"Movimentação de {self.quantity} de {self.product.name} "
|
||||
f"({self.movement_type.name}) em {self.movement_date.strftime('%d/%m/%Y %H:%M')}"
|
||||
)
|
||||
|
||||
|
||||
def subTransactionStock(product:Product,
|
||||
movement_type:StockMovementType,
|
||||
comanda:Comanda,
|
||||
obs:str,
|
||||
user:User=None,
|
||||
qtd:int=1):
|
||||
|
||||
components = ProductComponent.objects.filter(composite_product=product)
|
||||
if components.exists():
|
||||
for component in components:
|
||||
movi = StockMovement.objects.create(
|
||||
product=component.component_product ,
|
||||
related_comanda=comanda,
|
||||
user=user,
|
||||
movement_type=movement_type,
|
||||
quantity=-component.quantity_required,
|
||||
observation=obs
|
||||
)
|
||||
movi.save()
|
||||
component.component_product.quantity -= component.quantity_required
|
||||
component.component_product.save()
|
||||
|
||||
movi = StockMovement.objects.create(
|
||||
product=product ,
|
||||
related_comanda=comanda,
|
||||
user=user,
|
||||
movement_type=movement_type,
|
||||
quantity=-qtd,
|
||||
observation=obs
|
||||
)
|
||||
movi.save()
|
||||
product.quantity -= qtd
|
||||
product.save()
|
||||
|
||||
|
||||
def sumTransactionStock(product:Product,
|
||||
movement_type:StockMovementType,
|
||||
comanda:Comanda,
|
||||
obs:str,
|
||||
user:User=None,
|
||||
qtd:int=1):
|
||||
|
||||
components = ProductComponent.objects.filter(composite_product=product)
|
||||
if components.exists():
|
||||
for component in components:
|
||||
movi = StockMovement.objects.create(
|
||||
product=component.component_product ,
|
||||
related_comanda=comanda,
|
||||
user=user,
|
||||
movement_type=movement_type,
|
||||
quantity=component.quantity_required,
|
||||
observation=obs
|
||||
)
|
||||
movi.save()
|
||||
component.component_product.quantity += component.quantity_required
|
||||
component.component_product.save()
|
||||
|
||||
movi = StockMovement.objects.create(
|
||||
product=product ,
|
||||
related_comanda=comanda,
|
||||
user=user,
|
||||
movement_type=movement_type,
|
||||
quantity=qtd,
|
||||
observation=obs
|
||||
)
|
||||
movi.save()
|
||||
product.quantity += qtd
|
||||
product.save()
|
||||
|
||||
class Meta:
|
||||
ordering = ['-movement_date']
|
||||
28
comandas/serializers.py
Normal file
28
comandas/serializers.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Comanda, ProductComanda
|
||||
|
||||
class ProductComandaSerializer(serializers.ModelSerializer):
|
||||
product_name = serializers.ReadOnlyField(source='product.name')
|
||||
obs = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = ProductComanda
|
||||
fields = ['id', 'comanda', 'data_time', 'product', 'product_name', 'applicant', 'obs']
|
||||
|
||||
def get_obs(self, obj):
|
||||
order = obj.order_set.first()
|
||||
return order.obs if order else ""
|
||||
|
||||
class ComandaSerializer(serializers.ModelSerializer):
|
||||
mesa_name = serializers.ReadOnlyField(source='mesa.name')
|
||||
client_name = serializers.ReadOnlyField(source='client.name')
|
||||
user_name = serializers.ReadOnlyField(source='user.username')
|
||||
items = ProductComandaSerializer(many=True, read_only=True, source='productcomanda_set')
|
||||
|
||||
class Meta:
|
||||
model = Comanda
|
||||
fields = [
|
||||
'id', 'mesa', 'mesa_name', 'user', 'user_name',
|
||||
'type_pay', 'dt_open', 'dt_close', 'client',
|
||||
'client_name', 'name', 'status', 'items'
|
||||
]
|
||||
78
comandas/templates/comandas.html
Normal file
78
comandas/templates/comandas.html
Normal file
@@ -0,0 +1,78 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load custom_filter_tag %}
|
||||
|
||||
|
||||
{% block 'title' %}
|
||||
Comandas
|
||||
{% endblock %}
|
||||
|
||||
{% block 'head' %}
|
||||
<link rel="stylesheet" href="{% static 'comandas/css/comandas.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block 'body' %}
|
||||
|
||||
<body>
|
||||
<div style="justify-self: center;">
|
||||
<button class="btn-primary" id="openModal">Abrir Comanda</button>
|
||||
</div>
|
||||
|
||||
<div class="grid-container ">
|
||||
|
||||
{% for comanda in comandas %}
|
||||
<div class="card-comanda">
|
||||
<a href="{% url 'viewcomanda' %}?parametro={{ comanda.id }}" style="text-decoration: none; ">
|
||||
<header
|
||||
{% if comanda.status == 'OPEN' %}
|
||||
style="padding: 5px; min-height: 60px;border-radius: 5px 5px 0px 0px; background-color: #036800;"
|
||||
{% elif comanda.status == 'PAYING' %}
|
||||
style="padding: 5px; min-height: 60px;border-radius: 5px 5px 0px 0px; background-color: rgb(165, 86, 33);"
|
||||
{% endif %}
|
||||
>
|
||||
<p>{{comanda.name}}</p>
|
||||
<p>{{comanda.mesa}}</p>
|
||||
</header>
|
||||
<div style="padding: 5px;">
|
||||
<p>
|
||||
Hora: {{comanda.dt_open|date:"H:i"}}
|
||||
</p>
|
||||
<p>
|
||||
{{ comanda.id | total }}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<dialog id="Modal-create-comanda">
|
||||
<article >
|
||||
<form id="form-comanda" method="post" action="{% url 'createComanda' %}">
|
||||
{% csrf_token %}
|
||||
<h2>Abrir Comanda</h2>
|
||||
<input type="text" id="name-comanda" name="name-comanda" required placeholder="Nome"><br>
|
||||
<select name="select-mesa" required>
|
||||
<option value="1">Sem Mesa</option>
|
||||
|
||||
{% for mesa in mesas %}
|
||||
<option value="{{mesa.id}}">{{mesa.name}}</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
<div style="display: flex;gap: 10px;">
|
||||
<button class="btn-primary" type="submit">Abrir</button>
|
||||
<button class="btn-cancel" type="button" onclick="closeModal()">Cancelar</button>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
</dialog >
|
||||
</body>
|
||||
<script src="{% static 'comandas/js/comandas.js' %}"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
338
comandas/templates/viewcomanda.html
Normal file
338
comandas/templates/viewcomanda.html
Normal file
@@ -0,0 +1,338 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load custom_filter_tag %}
|
||||
|
||||
|
||||
|
||||
{% block 'title' %}
|
||||
|
||||
Detalhes {{comanda.name}}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block 'head' %}
|
||||
<link rel="stylesheet" href="{% static 'comandas/css/viewcomanda.css' %}">
|
||||
<style>
|
||||
.swal2-popup {
|
||||
position: relative; /* Necessário para o posicionamento absoluto do botão */
|
||||
}
|
||||
.posi {
|
||||
position: absolute !important;
|
||||
background-color: rgba(72, 72, 72, 0.151);
|
||||
color: rgba(255, 255, 255, 0.452);
|
||||
border: 2px solid rgba(239, 239, 239, 0.107);
|
||||
border-radius: 35px;
|
||||
padding: 0px 15px 3px 15px ;
|
||||
align-self: center;
|
||||
font-size: 32px;
|
||||
top: 0.1em;
|
||||
right: 0.1em;
|
||||
scale: 0.8;
|
||||
}
|
||||
.posi:hover{
|
||||
background-color: rgba(183, 3, 3, 0.598);
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
.custom-toast-container {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
.swal2-container {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block 'body' %}
|
||||
|
||||
<body>
|
||||
<input hidden id="id-temp" type="number">
|
||||
<div class="grid-container" >
|
||||
<div style="display: flex;padding: 5px;gap:8px">
|
||||
<button class="btn-primary" id="openModal" onclick="modalAddProduct()"
|
||||
{% if comanda.status != 'OPEN'%}
|
||||
disabled
|
||||
{% endif %}
|
||||
>Add Produto</button>
|
||||
|
||||
<button class="btn-cancel" id="closeComanda" onclick="closeConta({{comanda.id}})"
|
||||
{% if comanda.status != 'OPEN' %}
|
||||
style="display: none;"
|
||||
{% endif %}
|
||||
>Fechar Conta</button>
|
||||
|
||||
{% if comanda.status == 'PAYING' or comanda.status == 'FIADO'%}
|
||||
<button class="btn-secondary" id="pagarComanda"
|
||||
onclick="modal_payment_comanda()"
|
||||
>Receber</button>
|
||||
|
||||
<button class="btn-primary" id="printComanda"
|
||||
style="display: flex;"
|
||||
onclick="imprimirConta()"
|
||||
>Imprimir Conta</button>
|
||||
{% else %}
|
||||
|
||||
<button class="btn-secondary" id="pagarComanda"
|
||||
onclick="modal_payment_comanda()"
|
||||
style="display: none;"
|
||||
>Receber</button>
|
||||
|
||||
<button class="btn-primary" id="printComanda"
|
||||
style="display: none;"
|
||||
onclick="imprimirConta()"
|
||||
>Imprimir Conta</button>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if user|groupUser:"Gerente" %}
|
||||
<button class="btn-primary" id="reOpenComanda" hx-get="{% url 'reopenComanda' comanda.id %} " hx-trigger="click" hx-swap="none" onclick="reloadPage()"
|
||||
{% if comanda.status == 'OPEN'%}
|
||||
style="display: none;"
|
||||
{% elif comanda.status == 'CLOSED' %}
|
||||
style="display: none;"
|
||||
{% endif %}
|
||||
>Reabrir</button>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<input hidden type="text" id="h-mesaId" value="{{comanda.mesa.id}}">
|
||||
<input hidden type="text" id="id-comanda" value="{{comanda.id}}">
|
||||
<span id="name-comanda">Nome: {{comanda.name}} | </span>
|
||||
<span id="mesa-comanda">Local: {{comanda.mesa}}</span>
|
||||
|
||||
<img
|
||||
onclick="openModalAlter()"
|
||||
src="{% static 'midia/icons/pen.svg' %}"
|
||||
style="width: 30px; height: 35px; cursor: pointer;">
|
||||
</img>
|
||||
|
||||
</div>
|
||||
<p id="open-comanda">Aberta em: {{comanda.dt_open|date:"D"}} {{comanda.dt_open|date:"d/m/Y - H:i"}}</p>
|
||||
|
||||
<img hidden src="{% static 'midia/logo.png' %}" style="width: 240px; height: 200px;">
|
||||
<table id="list-products-comanda">
|
||||
<tr>
|
||||
<th style="text-align: left;"><b>Produto</b></th>
|
||||
<th style="text-align: left;"><b>Preço</b></th>
|
||||
</tr>
|
||||
|
||||
{% for item in consumo%}
|
||||
|
||||
<tr id="item-{{item.id}}">
|
||||
<td id="id-for-print-{{item.id}}">
|
||||
<spam style="cursor: pointer;" onclick="inforOrders({{item.id}})">
|
||||
{{item.product.name}}</spam>
|
||||
{% if item.product.cuisine == True %}
|
||||
<input hidden id="{{item.id}}-obsOrder" type="order" value="{{item.id | obsOrder}}">
|
||||
|
||||
|
||||
<img
|
||||
onclick="openModalObs({{item.id}})"
|
||||
src="{% static 'midia/icons/note.svg' %}"
|
||||
style="width: 25px; height: 35px; cursor: pointer;">
|
||||
</img>
|
||||
|
||||
|
||||
<img
|
||||
onclick="printOrder({{item.id}})"
|
||||
src="{% static 'midia/icons/print.svg' %}"
|
||||
style="width: 35px; height: 35px; cursor: pointer;">
|
||||
</img>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>R$ {{item.product.price}}</td>
|
||||
|
||||
{% if comanda.status != 'OPEN'%}
|
||||
{% else %}
|
||||
<td>
|
||||
|
||||
<img
|
||||
onclick="removeProductComanda({{item.id}}, '{{item.product.name}}')"
|
||||
src="{% static 'midia/icons/delete.svg' %}"
|
||||
style="width: 35px; height: 35px; cursor: pointer;"
|
||||
>
|
||||
</img>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if config.taxa %}
|
||||
<tr>
|
||||
<td>
|
||||
Taxa de serviço 10%
|
||||
</td>
|
||||
<td>
|
||||
R$ {{valores.taxa}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if parcials%}
|
||||
<td colspan="2" style="text-align: center;"><b>Pagamentos parciais</b></td>
|
||||
{% endif %}
|
||||
{% for parcial in parcials %}
|
||||
<tr>
|
||||
<td>{{parcial.description}} ás {{parcial.datetime|date:"H:i"}}</td>
|
||||
<td>R$ -{{parcial.value}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
|
||||
<tfoot>
|
||||
<tr>
|
||||
{% if config.taxa %}
|
||||
<td colspan="2" style="text-align: center;"><b>Total R$ {{valores.totalComTaxa}}</b></td>
|
||||
{% else %}
|
||||
<td colspan="2" style="text-align: center;"><b>Total R$ {{valores.totalSemTaxa}}</b></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<button class="btn-secondary" onclick="modal_payment_parcial()">Pagamento Parcial</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div hidden id="addProduct" >
|
||||
{% csrf_token %}
|
||||
<input type="text" oninput="searchProduct()" id="search-product" name="search-product" placeholder="Buscar Produto" ><br>
|
||||
<div id="product-list" class="grid-list-products">
|
||||
{% for product in products %}
|
||||
<div
|
||||
style="background-image: url('{{product.image}}');"
|
||||
class="card-product"
|
||||
onclick="addProductComanda({{product.id}}, {{comanda.id}}, '{{product.cuisine}}')" >
|
||||
<p class="card-product-p"> {{product.name}}</p>
|
||||
<p class="card-product-p"> R$ {{product.price}}</p>
|
||||
</div >
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<dialog id="payment-comanda" style="display: none;" >
|
||||
<article>
|
||||
<form action="{% url 'paymentComanda' comanda.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<h2>Pagamento</h2>
|
||||
<div style="display: flex; align-content: space-around; align-items: center; justify-self: center;gap: 50px;">
|
||||
|
||||
{% if config.taxa %}
|
||||
<h1 id="first-total">R$ {{ valores.totalComTaxa }}</h1>
|
||||
<h1 hidden id="totalComTaxa">R$ {{ valores.totalComTaxa }}</h1>
|
||||
<h1 hidden id="totalSemTaxa">R$ {{ valores.totalSemTaxa }}</h1>
|
||||
<div>
|
||||
<input id="taxa" name="taxa" type="checkbox" value="True" label="Taxa de serviço" checked >Taxa de serviço
|
||||
{% else %}
|
||||
<h1 id="first-total">R$ {{ valores.totalSemTaxa }}</h1>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>Recebido:</p> <input id="recebido" oninput="troco()" name="recebido" type="number">
|
||||
<h4 id="troco">Troco: </h4>
|
||||
</div>
|
||||
<footer>
|
||||
<div style="display: flex;gap: 10px;">
|
||||
|
||||
<button type="submit" class="btn-secondary" onclick="backPage()">Receber</button>
|
||||
{% if comanda.status != 'FIADO' %}
|
||||
<button type="button" class="btn-primary" onclick=" modal_conta_client()">
|
||||
Conta
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button" class="btn-cancel" onclick="close_modal_payment_comanda()">Cancelar</button>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
|
||||
<dialog id="payment-parcial" style="display: none;" >
|
||||
<article>
|
||||
<h2>Pagamento Parcial</h2>
|
||||
<form method="post" action="{% url 'paymentParcial' comanda.id %} ">
|
||||
{% csrf_token %}
|
||||
<input required id="value-parcial" name="value-parcial" type="number" step="0.01" max="{{total}}" placeholder="Valor">
|
||||
<input id="name-parcial" name="name-parcial" type="text" placeholder="Nome" >
|
||||
<footer>
|
||||
<div style="display: flex;gap: 10px;">
|
||||
<button type="submit" class="btn-secondary" >Receber</button>
|
||||
<button type="button" class="btn-cancel" onclick="close_modal_payment_parcial()">Cancelar</button>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
|
||||
<dialog id="Modal-alter-comanda">
|
||||
<article >
|
||||
<form id="form-comanda" method="post" action="{% url 'editComanda' %}">
|
||||
{% csrf_token %}
|
||||
<h2>Editar Comanda</h2>
|
||||
<input hidden type="text" name="h-comandaId" id="h-comandaId" value="{{comanda.id}}">
|
||||
<input type="text" id="nameComanda" name="nameComanda" required placeholder="Nome"><br>
|
||||
<select name="select-mesa" id="select-mesa" required >
|
||||
|
||||
|
||||
{% for mesa in mesas %}
|
||||
<option value="{{mesa.id}}">{{mesa.name}}</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
<div style="display: flex;gap: 10px;">
|
||||
<button type="submit" class="btn-primary">Alterar</button>
|
||||
<button type="button" class="btn-cancel" onclick="closeModalAlter()">Cancelar</button>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
</dialog >
|
||||
|
||||
|
||||
|
||||
<dialog id="conta-cliente" style="display: none;" >
|
||||
<article>
|
||||
<form id="form-comanda" method="post" action="{% url 'addContaCliente' %}">
|
||||
{% csrf_token %}
|
||||
<h2>Adicionar na Conta</h2>
|
||||
<h1>R$ {{ valores.totalSemTaxa }}</h1>
|
||||
<div>
|
||||
<input hidden type="text" name="valor-conta" value="{{ total }}">
|
||||
<input hidden type="text" name="idComanda" value="{{ comanda.id }}">
|
||||
<p>Selecione o Cliente:</p>
|
||||
<select name="select-client" id="select-client" required>
|
||||
<option value="">Selecione um cliente</option>
|
||||
|
||||
{% for client in clients %}
|
||||
<option value="{{client.id}}">{{client.name}}</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
</div>
|
||||
<footer>
|
||||
<div style="display: flex;gap: 10px;">
|
||||
<button type="submit" class="btn-primary"> Adicionar a conta</button>
|
||||
<button type="button" class="btn-cancel" onclick="close_modal_conta_client()">Cancelar</button>
|
||||
</div>
|
||||
</form>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
|
||||
|
||||
<script src="{% static 'comandas/js/viewcomanda.js' %}"></script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
69
comandas/templatetags/custom_filter_tag.py
Normal file
69
comandas/templatetags/custom_filter_tag.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from decimal import Decimal
|
||||
from django import template
|
||||
|
||||
from orders.models import Order
|
||||
from comandas.models import Comanda, ProductComanda
|
||||
from clients.models import Client
|
||||
from payments.models import Payments
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter(name='total')
|
||||
def filter_total(value):
|
||||
config = {
|
||||
'taxa': False
|
||||
}
|
||||
id = value
|
||||
comanda_id = int(id)
|
||||
totalParcial = Decimal(0)
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
parcial = Payments.objects.filter(comanda=comanda)
|
||||
for p in parcial:
|
||||
totalParcial += p.value
|
||||
|
||||
total = Decimal(0)
|
||||
for produto in consumo:
|
||||
total += produto.product.price
|
||||
taxa = round(total * Decimal(0.1), 2)
|
||||
total = (total + taxa) - totalParcial if config['taxa'] else total - totalParcial
|
||||
return f'R$ {total}'
|
||||
|
||||
@register.filter(name='totalFiado')
|
||||
def viewClient(clientId):
|
||||
config = {
|
||||
'taxa': False
|
||||
}
|
||||
client = Client.objects.get(id=int(clientId))
|
||||
comandas = Comanda.objects.filter(client = client).filter(status = 'FIADO')
|
||||
total = Decimal(0)
|
||||
for comanda in comandas:
|
||||
totalConsumo = 0
|
||||
totalParcial = 0
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda)
|
||||
parcial = Payments.objects.filter(comanda=comanda)
|
||||
for p in parcial:
|
||||
totalParcial += p.value
|
||||
for produto in consumo:
|
||||
totalConsumo += produto.product.price
|
||||
total+= (totalConsumo - totalParcial)
|
||||
total = total + round(total * Decimal(0.1), 2) if config['taxa'] else total
|
||||
return total
|
||||
|
||||
|
||||
|
||||
@register.filter(name='groupUser')
|
||||
def has_group(user, group_name):
|
||||
return user.groups.filter(name=group_name).exists()
|
||||
|
||||
|
||||
@register.filter(name='obsOrder')
|
||||
def obsOrder(id):
|
||||
try:
|
||||
productComanda_obj = ProductComanda.objects.get(pk=id)
|
||||
order = Order.objects.get(productComanda=productComanda_obj)
|
||||
return order
|
||||
except:
|
||||
return ''
|
||||
|
||||
|
||||
1
comandas/tests/__init__.py
Normal file
1
comandas/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'comandas.apps.ComandasConfig' # Se você tiver um AppConfig
|
||||
28
comandas/tests/test_models.py
Normal file
28
comandas/tests/test_models.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
|
||||
class ComandaTestCase(TestCase):
|
||||
def setUp(self):
|
||||
# Setup code for creating test instances of Comanda and other related models
|
||||
pass
|
||||
|
||||
def test_comanda_creation(self):
|
||||
# Test the creation of a Comanda instance
|
||||
pass
|
||||
|
||||
def test_comanda_str_method(self):
|
||||
# Test the __str__ method of the Comanda model
|
||||
pass
|
||||
|
||||
def test_comanda_total_value(self):
|
||||
# Test the total value calculation of a Comanda instance
|
||||
pass
|
||||
|
||||
def test_comanda_status_choices(self):
|
||||
# Test the status choices for the Comanda model
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
# Cleanup code after tests
|
||||
pass
|
||||
119
comandas/tests/test_views.py
Normal file
119
comandas/tests/test_views.py
Normal file
@@ -0,0 +1,119 @@
|
||||
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.contrib.messages.middleware import MessageMiddleware
|
||||
|
||||
from ..views import createComanda
|
||||
from ..models import Mesa, Comanda
|
||||
from mesas.models import Mesa
|
||||
from gestaoRaul.decorators import group_required
|
||||
|
||||
|
||||
class CreateComandaViewTest(TestCase):
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.garcom_group = Group.objects.create(name='Garçom')
|
||||
self.outro_group = Group.objects.create(name='OutroGrupo')
|
||||
self.garcom_user = User.objects.create_user(username='garcom', password='password')
|
||||
self.garcom_user.groups.add(self.garcom_group)
|
||||
self.outro_user = User.objects.create_user(username='outro', password='password')
|
||||
self.outro_user.groups.add(self.outro_group)
|
||||
self.mesa = Mesa.objects.create(name='teste')
|
||||
|
||||
def add_middleware(self, request):
|
||||
"""Adiciona middlewares necessários para request.user e mensagens."""
|
||||
middleware = SessionMiddleware(lambda x: x)
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
message_middleware = MessageMiddleware(lambda x: x)
|
||||
message_middleware.process_request(request)
|
||||
return request
|
||||
|
||||
def test_garcom_can_access_and_create_comanda(self):
|
||||
"""Testa se um usuário no grupo 'Garçom' pode acessar e criar uma comanda."""
|
||||
request = self.factory.post(
|
||||
reverse('createComanda'), # Assumindo que sua URL para createComanda se chama 'createcomanda'
|
||||
{'name-comanda': 'Comanda Teste', 'select-mesa': self.mesa.id}
|
||||
)
|
||||
request.user = self.garcom_user
|
||||
request = self.add_middleware(request)
|
||||
|
||||
response = createComanda(request)
|
||||
|
||||
self.assertEqual(response.status_code, 302) # Verifica se houve um redirect
|
||||
self.assertIn(reverse('viewcomanda'), response.url) # Verifica se redirecionou para a view correta
|
||||
self.assertTrue(Comanda.objects.filter(name='Comanda Teste', mesa=self.mesa, user=self.garcom_user).exists())
|
||||
|
||||
def test_non_garcom_cannot_access_create_comanda(self):
|
||||
"""Testa se um usuário fora do grupo 'Garçom' não pode acessar a view."""
|
||||
request = self.factory.post(
|
||||
reverse('createComanda'),
|
||||
{'name-comanda': 'Comanda Teste', 'select-mesa': self.mesa.id}
|
||||
)
|
||||
request.user = self.outro_user
|
||||
request = self.add_middleware(request)
|
||||
|
||||
# Aplica o decorator diretamente para testar o acesso negado
|
||||
wrapped_view = group_required(groupName='Garçom')(createComanda)
|
||||
response = wrapped_view(request)
|
||||
|
||||
self.assertEqual(response.status_code, 403) # Verifica se o acesso foi negado (Forbidden)
|
||||
self.assertFalse(Comanda.objects.filter(name='Comanda Teste').exists())
|
||||
|
||||
def test_anonymous_user_cannot_access_create_comanda(self):
|
||||
"""Testa se um usuário anônimo não pode acessar a view."""
|
||||
request = self.factory.post(
|
||||
reverse('createComanda'),
|
||||
{'name-comanda': 'Comanda Teste', 'select-mesa': self.mesa.id}
|
||||
)
|
||||
request.user = None # Simula um usuário não autenticado
|
||||
request = self.add_middleware(request)
|
||||
|
||||
wrapped_view = group_required(groupName='Garçom')(createComanda)
|
||||
response = wrapped_view(request)
|
||||
|
||||
self.assertEqual(response.status_code, 302) # Deve redirecionar para a página de login
|
||||
self.assertTrue(response.url.startswith(reverse('login'))) # Verifica se redireciona para a página de login
|
||||
self.assertFalse(Comanda.objects.filter(name='Comanda Teste').exists())
|
||||
|
||||
def test_create_comanda_invalid_mesa(self):
|
||||
"""Testa o comportamento ao tentar criar uma comanda com uma mesa inválida."""
|
||||
request = self.factory.post(
|
||||
reverse('createComanda'),
|
||||
{'name-comanda': 'Comanda Inválida', 'select-mesa': 999} # ID de mesa inexistente
|
||||
)
|
||||
request.user = self.garcom_user
|
||||
request = self.add_middleware(request)
|
||||
# response = createComanda(request)
|
||||
|
||||
with self.assertRaises(Mesa.DoesNotExist):
|
||||
createComanda(request)
|
||||
# self.assertEqual(response.status_code, 400)
|
||||
self.assertFalse(Comanda.objects.filter(name='Comanda Inválida').exists())
|
||||
|
||||
def test_create_comanda_missing_data(self):
|
||||
"""Testa o comportamento ao tentar criar uma comanda com dados faltando."""
|
||||
request_sem_nome = self.factory.post(
|
||||
reverse('createComanda'),
|
||||
{'select-mesa': self.mesa.id}
|
||||
)
|
||||
request_sem_nome.user = self.garcom_user
|
||||
request_sem_nome = self.add_middleware(request_sem_nome)
|
||||
|
||||
# with self.assertRaises(TypeError): # A view espera 'name' e 'mesa_id' no POST
|
||||
# createComanda(request_sem_nome)
|
||||
self.assertFalse(Comanda.objects.exists())
|
||||
|
||||
# Sem 'select-mesa'
|
||||
request_sem_mesa = self.factory.post(
|
||||
reverse('createComanda'),
|
||||
{'name-comanda': 'Comanda Sem Mesa'}
|
||||
)
|
||||
request_sem_mesa.user = self.garcom_user
|
||||
request_sem_mesa = self.add_middleware(request_sem_mesa)
|
||||
|
||||
# with self.assertRaises(TypeError): # A view espera 'name' e 'mesa_id' no POST
|
||||
# createComanda(request_sem_mesa)
|
||||
self.assertFalse(Comanda.objects.exists())
|
||||
31
comandas/urls.py
Normal file
31
comandas/urls.py
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from comandas import htmx_views
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.comandas, name='comandas'),
|
||||
path('viewcomanda/', views.viewComanda, name='viewcomanda'),
|
||||
path('createComanda/', views.createComanda, name='createComanda'),
|
||||
path('editComanda/', views.editComanda, name='editComanda'),
|
||||
path('addContaCliente/', views.addContaCliente, name='addContaCliente'),
|
||||
path('notificacao/', views.notificacao, name='notificacao'),
|
||||
path('editOrders/<int:productComanda_id>/<str:obs>', views.editOrders, name='editOrders'),
|
||||
path('closeComanda/<int:comanda_id>/', views.closeComanda, name='closeComanda'),
|
||||
path('listProduct/<int:comanda_id>/<str:product>/', views.listProduct, name='listProduct'),
|
||||
path('product=<int:product_id>/comanda=<int:comanda_id>/', views.addProduct, name='addProduct'),
|
||||
|
||||
|
||||
|
||||
]
|
||||
|
||||
|
||||
htmx_urlpatterns = [
|
||||
path('removeProductComanda/<int:productComanda_id>/', htmx_views.removeProductComanda, name='removeProductComanda'),
|
||||
path('reopenComanda<int:comanda_id>/', htmx_views.reopenComanda, name='reopenComanda'),
|
||||
path('paymentComanda<int:comanda_id>/', htmx_views.paymentComanda, name='paymentComanda'),
|
||||
path('paymentParcial<int:comanda_id>/', htmx_views.paymentParcial, name='paymentParcial'),
|
||||
]
|
||||
|
||||
urlpatterns += htmx_urlpatterns
|
||||
214
comandas/views.py
Normal file
214
comandas/views.py
Normal file
@@ -0,0 +1,214 @@
|
||||
from decimal import Decimal
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from django.http import JsonResponse, HttpResponseRedirect
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from django.shortcuts import render, redirect
|
||||
from django.db.models import Count, F
|
||||
|
||||
from comandas.models import Comanda, ProductComanda, StockMovement, StockMovementType
|
||||
from clients.models import Client
|
||||
from payments.models import Payments, somar
|
||||
from orders.models import Order
|
||||
from products.models import Product, ProductComponent
|
||||
from mesas.models import Mesa
|
||||
from gestaoRaul.decorators import group_required
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def comandas(request):
|
||||
comandas = Comanda.objects.filter(status__in=["OPEN", "PAYING"])
|
||||
mesas = Mesa.objects.all()
|
||||
return render(request, 'comandas.html', {'comandas': comandas, 'mesas': mesas})
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def viewComanda(request):
|
||||
config = {
|
||||
'taxa': False
|
||||
}
|
||||
id = request.GET.get('parametro')
|
||||
comanda_id = int(id)
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
parcial = Payments.objects.filter(comanda=comanda_id)
|
||||
mesas = Mesa.objects.all()
|
||||
clients = Client.objects.filter(active=True)
|
||||
products_ordenados = ProductComanda.maisVendidos()
|
||||
valores = somar(consumo,comanda)
|
||||
return render(request, 'viewcomanda.html', {'config':config, 'valores':valores,'parcials':parcial,'clients':clients,'comanda': comanda, 'consumo': consumo, 'products': products_ordenados[:15],'mesas':mesas})
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def createComanda(request):
|
||||
name = request.POST.get('name-comanda')
|
||||
if name == '' or name == None or request.POST.get('select-mesa') == '' or request.POST.get('select-mesa') == None:
|
||||
return HttpResponseRedirect(reverse('comandas'), status=400)
|
||||
mesa_id = int(request.POST.get('select-mesa'))
|
||||
mesa = Mesa.objects.get(id=mesa_id)
|
||||
comanda = Comanda(name=name, mesa=mesa, user=request.user)
|
||||
comanda.save()
|
||||
return redirect(reverse('viewcomanda') + f'?parametro={comanda.id}')
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def editComanda(request):
|
||||
name = request.POST.get('nameComanda')
|
||||
comanda = Comanda.objects.get(id=int(request.POST.get('h-comandaId')))
|
||||
mesa = Mesa.objects.get(id=int(request.POST.get('select-mesa')))
|
||||
comanda.mesa = mesa
|
||||
comanda.name = name
|
||||
comanda.save()
|
||||
return redirect('comandas')
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def addContaCliente(request):
|
||||
|
||||
comandaId = int(request.POST.get('idComanda'))
|
||||
clientId = int(request.POST.get('select-client'))
|
||||
comanda = Comanda.objects.get(id=comandaId)
|
||||
client = Client.objects.get(id=clientId)
|
||||
comanda.client = client
|
||||
comanda.dt_close = timezone.now()
|
||||
comanda.status = 'FIADO'
|
||||
client.save()
|
||||
comanda.save()
|
||||
return redirect('comandas')
|
||||
|
||||
def notificacao(request):
|
||||
fifteen_hours_ago = timezone.now() - timezone.timedelta(hours=12)
|
||||
ordersPronto = Order.objects.filter(queue__gte=fifteen_hours_ago, finished__isnull=False)
|
||||
|
||||
grupoGarcom = request.user.groups.filter(name='Garçom').exists()
|
||||
grupoGerente = request.user.groups.filter(name='Gerente').exists()
|
||||
|
||||
if grupoGarcom == True and grupoGerente == False:
|
||||
if 'pronto' in request.COOKIES:
|
||||
cookiesPronto = int(request.COOKIES['pronto'])
|
||||
if len(ordersPronto) > cookiesPronto:
|
||||
return JsonResponse({
|
||||
'notificacao': 'true',
|
||||
'pronto':len(ordersPronto),
|
||||
'titulo': ordersPronto[len(ordersPronto)-1].id_comanda.name,
|
||||
'corpo': ordersPronto[len(ordersPronto)-1].id_product.name,
|
||||
})
|
||||
else:
|
||||
return JsonResponse({
|
||||
'notificacao': 'false',
|
||||
'pronto': len(ordersPronto),
|
||||
})
|
||||
else:
|
||||
return JsonResponse({
|
||||
'notificacao': 'true',
|
||||
'pronto':len(ordersPronto),
|
||||
'titulo': ordersPronto[len(ordersPronto)-1].id_comanda.name,
|
||||
'corpo': ordersPronto[len(ordersPronto)-1].id_product.name,
|
||||
})
|
||||
|
||||
|
||||
else:
|
||||
return JsonResponse({
|
||||
'notificacao': 'false',
|
||||
'pronto':len(ordersPronto),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def editOrders(request, productComanda_id, obs):
|
||||
order = Order.objects.get(productComanda=productComanda_id)
|
||||
order.obs = obs
|
||||
order.save()
|
||||
msg = JsonResponse({
|
||||
'type': 'broadcast',
|
||||
'message': obs,
|
||||
'local':'cozinha',
|
||||
'tipo':'edit',
|
||||
'id':order.id,
|
||||
'speak': f'Pedido alterado! {order.id_product.name}, é {obs}.'
|
||||
})
|
||||
return JsonResponse({'status': 'ok', 'obs':order.obs})
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def closeComanda(request, comanda_id):
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
comanda.status = "PAYING"
|
||||
comanda.save()
|
||||
return JsonResponse({'status': 'ok', 'obs':'order.obs'})
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def addProduct(request, product_id, comanda_id):
|
||||
config = {
|
||||
'taxa': False
|
||||
}
|
||||
obs = request.GET.get("obs")
|
||||
product_comanda = ProductComanda(comanda_id=comanda_id, product_id=product_id)
|
||||
product_comanda.save()
|
||||
|
||||
product = Product.objects.get(id=product_id)
|
||||
comanda = Comanda.objects.get(id=comanda_id)
|
||||
user = User.objects.get(id=request.user.id)
|
||||
typeMovement = StockMovementType.objects.get(name="Venda - Comanda")
|
||||
|
||||
StockMovement.subTransactionStock(
|
||||
product=product,
|
||||
movement_type=typeMovement,
|
||||
comanda=comanda,
|
||||
user=user,
|
||||
qtd=1,
|
||||
obs= "Adicionado na comanda"
|
||||
)
|
||||
|
||||
parcial = Payments.objects.filter(comanda=comanda)
|
||||
if product.cuisine == True:
|
||||
order = Order(id_comanda=comanda, id_product=product, productComanda=product_comanda, obs='')
|
||||
order.save()
|
||||
msg = JsonResponse({
|
||||
'type': 'broadcast',
|
||||
'message': f"""
|
||||
<div class="m-card" id="m-card-{order.id}">
|
||||
<h4>{product.name}</h4>
|
||||
<h4 id="obs-{order.id}"> {order.obs}</h4>
|
||||
<h4>{comanda.name} - {comanda.mesa.name}</h4>
|
||||
<h4> {order.queue.strftime("%d/%m/%Y - %H:%M")}</h4>
|
||||
<h4> Atendente: {comanda.user.first_name}</h4>
|
||||
<form method="path" action="/pedidos/preparing/{order.id}/">
|
||||
<button class="btn-primary" type="submit">Preparar</button>
|
||||
</form>
|
||||
</div>
|
||||
""",
|
||||
'local':'cozinha',
|
||||
'tipo':'add',
|
||||
'id':order.id,
|
||||
'speak': f'Novo pedido! {product.name}, para {comanda.name}.'
|
||||
})
|
||||
try:
|
||||
# Chama a função async dentro da view normal
|
||||
async_to_sync(enviar_mensagem)(mensagem)
|
||||
|
||||
# return JsonResponse({"status": "Mensagem enviada com sucesso"})
|
||||
|
||||
except Exception as e:
|
||||
print("Erro add product websocket: ",e)
|
||||
# return JsonResponse({"status": "Erro", "erro": str(e)}, status=500)
|
||||
# asyncio.run(enviar_mensagem(msg))
|
||||
consumo = ProductComanda.objects.filter(comanda=comanda_id)
|
||||
valores = somar(consumo,comanda)
|
||||
|
||||
return render(request, "htmx_components/comandas/htmx_list_products_in_comanda.html",{'config':config, 'valores':valores,'parcials':parcial,'consumo': consumo,'comanda':comanda})
|
||||
|
||||
|
||||
|
||||
def listProduct(request, comanda_id, product):
|
||||
if product == '*':
|
||||
allProducts = ProductComanda.maisVendidos()
|
||||
else:
|
||||
allProducts = Product.objects.filter(name__icontains=product)
|
||||
products = []
|
||||
for p in allProducts:
|
||||
if p.active == True:
|
||||
products.append(p)
|
||||
return render(request, "htmx_components/comandas/htmx_list_products.html", {"products": products[:15],'comanda_id':comanda_id})
|
||||
@@ -1,9 +0,0 @@
|
||||
mkdir djangotutorial
|
||||
python -m venv meu_ambiente
|
||||
django-admin startproject mysite djangotutorial
|
||||
meu_ambiente\Scripts\Activate.ps1
|
||||
python manage.py runserver
|
||||
python manage.py startapp polls
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py createsuperuser
|
||||
22
docker-compose.yml
Normal file
22
docker-compose.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
django_app:
|
||||
image: python:3.12-slim
|
||||
container_name: rrbec_api_django
|
||||
ports:
|
||||
- "8069:8000"
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
- DEBUG=True
|
||||
- ALLOWED_HOSTS=raulrockbar.com.br,api.raulrockbar.com.br
|
||||
- CSRF_TRUSTED_ORIGINS=https://raulrockbar.com.br,https://api.raulrockbar.com.br
|
||||
|
||||
volumes:
|
||||
- /DATA/AppData/rrbec-api-django:/app
|
||||
- /DATA/AppData/rrbec-api-django/static:/app/static
|
||||
- /DATA/AppData/rrbec-api-django/db:/app/db
|
||||
|
||||
working_dir: /app
|
||||
command: ["/bin/sh", "-c", "apt-get update && apt-get install -y git && rm -rf app_clone && git clone --branch api --depth 1 https://github.com/welton89/RRBEC.git app_clone && cp -r app_clone/* . && rm -rf app_clone && pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate --noinput && gunicorn gestaoRaul.wsgi:application --bind 0.0.0.0:8000"]
|
||||
Binary file not shown.
34
gestaoRaul/api_urls.py
Normal file
34
gestaoRaul/api_urls.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from orders.api_views import OrderViewSet
|
||||
from products.api_views import ProductViewSet
|
||||
from clients.api_views import ClientViewSet
|
||||
from mesas.api_views import MesaViewSet
|
||||
from comandas.api_views import ComandaViewSet, ProductComandaViewSet
|
||||
from categories.api_views import CategoriesViewSet
|
||||
from typePay.api_views import TypePayViewSet
|
||||
from payments.api_views import PaymentsViewSet
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenRefreshView,
|
||||
)
|
||||
from login.api_views import MyTokenObtainPairView, UserViewSet
|
||||
from sync.api_views import ChangeLogViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'orders', OrderViewSet, basename='order')
|
||||
router.register(r'products', ProductViewSet, basename='product')
|
||||
router.register(r'clients', ClientViewSet, basename='client')
|
||||
router.register(r'mesas', MesaViewSet, basename='mesa')
|
||||
router.register(r'comandas', ComandaViewSet, basename='comanda')
|
||||
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)),
|
||||
path('token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
|
||||
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||
]
|
||||
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.
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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user