mirror of
https://github.com/welton89/RRBEC.git
synced 2026-04-05 21:45:41 +00:00
chore: Delete numerous application modules, templates, static assets, documentation, and build files.
This commit is contained in:
0
comandas/__init__.py
Normal file
0
comandas/__init__.py
Normal file
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()
|
||||
6
comandas/apps.py
Normal file
6
comandas/apps.py
Normal 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
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')
|
||||
30
comandas/migrations/0001_initial.py
Normal file
30
comandas/migrations/0001_initial.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
25
comandas/migrations/0002_productcomanda.py
Normal file
25
comandas/migrations/0002_productcomanda.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
comandas/migrations/__init__.py
Normal file
0
comandas/migrations/__init__.py
Normal file
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 %}
|
||||
0
comandas/templatetags/__init__.py
Normal file
0
comandas/templatetags/__init__.py
Normal file
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 ''
|
||||
|
||||
|
||||
3
comandas/tests.py
Normal file
3
comandas/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
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
|
||||
215
comandas/views.py
Normal file
215
comandas/views.py
Normal 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})
|
||||
Reference in New Issue
Block a user