chore: Delete numerous application modules, templates, static assets, documentation, and build files.

This commit is contained in:
2026-02-25 17:09:27 -03:00
parent 7ddaa2d1f9
commit 2fc4fafed7
562 changed files with 17 additions and 6810 deletions

0
comandas/__init__.py Normal file
View File

9
comandas/admin.py Normal file
View 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
View 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()

6
comandas/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ComandasConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'comandas'

136
comandas/htmx_views.py Normal file
View 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')

View File

@@ -0,0 +1,30 @@
# Generated by Django 5.1.4 on 2024-12-10 01:19
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('clients', '0001_initial'),
('mesas', '0001_initial'),
('typePay', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Comanda',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('dt_open', models.DateTimeField(auto_now_add=True)),
('dt_close', models.DateTimeField(blank=True, null=True)),
('name', models.CharField(max_length=255)),
('client', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='clients.client')),
('mesa', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mesas.mesa')),
('type_pay', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='typePay.typepay')),
],
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.1.4 on 2024-12-10 01:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comandas', '0001_initial'),
('products', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ProductComanda',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('data_time', models.DateTimeField(auto_now_add=True)),
('applicant', models.CharField(blank=True, max_length=255, null=True)),
('comanda', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='comandas.comanda')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product')),
],
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.1.4 on 2024-12-20 12:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comandas', '0002_productcomanda'),
('products', '0002_product_image_product_quantity'),
]
operations = [
migrations.AddField(
model_name='comanda',
name='status',
field=models.CharField(default='OPEN', max_length=255),
),
migrations.AlterField(
model_name='productcomanda',
name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='products.product'),
),
]

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

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

View File

151
comandas/models.py Normal file
View 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
View 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'
]

View 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 %}

View 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 %}

View File

View 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 ''

3
comandas/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1 @@
default_app_config = 'comandas.apps.ComandasConfig' # Se você tiver um AppConfig

View 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

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

215
comandas/views.py Normal file
View File

@@ -0,0 +1,215 @@
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}.'
})
# asyncio.run(enviar_mensagem(msg))
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})