mirror of
https://github.com/welton89/RRBEC.git
synced 2026-04-05 13:35:42 +00:00
chore: Delete numerous application modules, templates, static assets, documentation, and build files.
This commit is contained in:
0
products/__init__.py
Normal file
0
products/__init__.py
Normal file
11
products/admin.py
Normal file
11
products/admin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from products.models import Product, ProductComponent, UnitOfMeasure
|
||||
|
||||
|
||||
|
||||
admin.site.register(Product)
|
||||
admin.site.register(ProductComponent)
|
||||
admin.site.register(UnitOfMeasure)
|
||||
|
||||
|
||||
11
products/api_views.py
Normal file
11
products/api_views.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from rest_framework import viewsets, permissions
|
||||
from .models import Product
|
||||
from .serializers import ProductSerializer
|
||||
|
||||
class ProductViewSet(viewsets.ModelViewSet):
|
||||
queryset = Product.objects.all()
|
||||
serializer_class = ProductSerializer
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
return Product.objects.all()
|
||||
6
products/apps.py
Normal file
6
products/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProductsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'products'
|
||||
27
products/migrations/0001_initial.py
Normal file
27
products/migrations/0001_initial.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.1.4 on 2024-12-10 01:18
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('categories', '0002_rename_category_categories'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Product',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('active', models.BooleanField(default=True)),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='categories.categories')),
|
||||
],
|
||||
),
|
||||
]
|
||||
23
products/migrations/0002_product_image_product_quantity.py
Normal file
23
products/migrations/0002_product_image_product_quantity.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.4 on 2024-12-20 12:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='image',
|
||||
field=models.ImageField(blank=True, null=True, upload_to=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='quantity',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
18
products/migrations/0003_product_cuisine.py
Normal file
18
products/migrations/0003_product_cuisine.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-10 16:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0002_product_image_product_quantity'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='cuisine',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 5.1.4 on 2025-07-22 18:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0003_product_cuisine'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UnitOfMeasure',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(help_text="Ex: 'Unidade', 'Kg', 'Litro'", max_length=50, unique=True)),
|
||||
('abbreviation', models.CharField(help_text="Ex: 'un', 'kg', 'L'", max_length=10, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductComponent',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity_required', models.PositiveIntegerField(default=1, help_text='Quantidade deste componente necessária para o produto composto.')),
|
||||
('component_product', models.ForeignKey(help_text='Um produto que é componente de outro produto.', on_delete=django.db.models.deletion.CASCADE, related_name='used_as_component_in', to='products.product')),
|
||||
('composite_product', models.ForeignKey(help_text='O produto que é composto por outros produtos.', on_delete=django.db.models.deletion.CASCADE, related_name='composition_entries', to='products.product')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('composite_product', 'component_product')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='components',
|
||||
field=models.ManyToManyField(related_name='is_component_of', through='products.ProductComponent', to='products.product'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='unit_of_measure',
|
||||
field=models.ForeignKey(blank=True, help_text='Unidade de medida para este produto.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='products.unitofmeasure'),
|
||||
),
|
||||
]
|
||||
0
products/migrations/__init__.py
Normal file
0
products/migrations/__init__.py
Normal file
80
products/models.py
Normal file
80
products/models.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from categories.models import Categories
|
||||
|
||||
|
||||
|
||||
class UnitOfMeasure(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=50, unique=True, help_text="Ex: 'Unidade', 'Kg', 'Litro'")
|
||||
abbreviation = models.CharField(max_length=10, unique=True, help_text="Ex: 'un', 'kg', 'L'")
|
||||
|
||||
def __str__(self):
|
||||
return self.abbreviation
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class Product(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
image = models.ImageField(null=True, blank=True)
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
quantity = models.IntegerField(null=False, default=0)
|
||||
category = models.ForeignKey(Categories, on_delete=models.CASCADE)
|
||||
cuisine = models.BooleanField(default=False)
|
||||
active = models.BooleanField(default=True)
|
||||
unit_of_measure = models.ForeignKey(
|
||||
UnitOfMeasure,
|
||||
on_delete=models.SET_NULL, # Define como NULL se a unidade de medida for excluída
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Unidade de medida para este produto."
|
||||
)
|
||||
|
||||
# Campo de composição (mantido da resposta anterior)
|
||||
components = models.ManyToManyField(
|
||||
'self',
|
||||
through='ProductComponent',
|
||||
through_fields=('composite_product', 'component_product'),
|
||||
symmetrical=False,
|
||||
related_name='is_component_of'
|
||||
)
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name}"
|
||||
|
||||
|
||||
|
||||
class ProductComponent(models.Model):
|
||||
composite_product = models.ForeignKey(
|
||||
Product,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='composition_entries',
|
||||
help_text="O produto que é composto por outros produtos."
|
||||
)
|
||||
component_product = models.ForeignKey(
|
||||
Product,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='used_as_component_in',
|
||||
help_text="Um produto que é componente de outro produto."
|
||||
)
|
||||
quantity_required = models.PositiveIntegerField(
|
||||
default=1,
|
||||
help_text="Quantidade deste componente necessária para o produto composto."
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('composite_product', 'component_product')
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
f"{self.composite_product.name} requer "
|
||||
f"{self.quantity_required} de {self.component_product.name}"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
19
products/serializers.py
Normal file
19
products/serializers.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Product, UnitOfMeasure
|
||||
|
||||
class UnitOfMeasureSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UnitOfMeasure
|
||||
fields = '__all__'
|
||||
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
category_name = serializers.ReadOnlyField(source='category.name')
|
||||
unit_of_measure_name = serializers.ReadOnlyField(source='unit_of_measure.name')
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = [
|
||||
'id', 'name', 'description', 'image', 'price',
|
||||
'quantity', 'category', 'category_name',
|
||||
'cuisine', 'active', 'unit_of_measure', 'unit_of_measure_name'
|
||||
]
|
||||
136
products/templates/products.html
Normal file
136
products/templates/products.html
Normal file
@@ -0,0 +1,136 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block 'head' %}
|
||||
<link rel="stylesheet" href="{% static 'products/css/products.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block 'title' %}
|
||||
Produtos
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block 'body' %}
|
||||
|
||||
<body>
|
||||
<div class="grid-container">
|
||||
<div class="grid-top">
|
||||
<button class="btn-primary"
|
||||
onclick="openModal()" id="openModal">Novo Produto</button>
|
||||
<input onclick="listerSortTeable()" type="text" id="search-product" name="search-product" placeholder="Buscar Produto" hx-get="{% url 'searchProduct' %}" hx-trigger="keyup" hx-target="#product-list">
|
||||
<a href="https://raulrockbar.blogspot.com/p/cardapio.html" target="_blank">Cardápio Digital</a>
|
||||
</div>
|
||||
|
||||
<table id="product-list">
|
||||
<thead> <tr>
|
||||
<th style="text-align: left;" data-col-type="text">Produto</th>
|
||||
<th style="text-align: left;width: 20%;" data-col-type="number">Preço</th>
|
||||
<th class="hide-on-mobile" style="text-align: left;" data-col-type="number">Quantidade</th>
|
||||
<th class="hide-on-mobile" style="text-align: left;" data-col-type="text">Categoria</th>
|
||||
<th style="text-align: left;width: 20%;">Ações</th> </tr>
|
||||
</thead>
|
||||
<tbody> {% for product in products %}
|
||||
<tr>
|
||||
<td id="name-{{product.id}}" >{{product.name}}</td>
|
||||
<td id="price-{{product.id}}" >R$ {{product.price}}</td>
|
||||
|
||||
{% if product.quantity > 20 %}
|
||||
<td class="hide-on-mobile" id="quantity-{{product.id}}" >{{product.quantity}}</td>
|
||||
{% else %}
|
||||
<td class="hide-on-mobile" id="quantity-{{product.id}}" style="background-color: brown;" >{{product.quantity}}</td>
|
||||
{% endif %}
|
||||
<td hidden class="hide-on-mobile" id="image-{{product.id}}" >{{product.image}}</td>
|
||||
<td class="hide-on-mobile" id="category-{{product.id}}" >{{product.category.name}}</td>
|
||||
<td>
|
||||
<div class="grid-buttons">
|
||||
<img
|
||||
src="{% static 'midia/icons/edit.svg' %}"
|
||||
style="width: 35px; height: 35px; cursor: pointer;"
|
||||
onclick="editProduct({{product.id}})"
|
||||
>
|
||||
</img>
|
||||
|
||||
<input type="hidden" id="h-category-{{product.id}}" value="{{ product.category.id }}">
|
||||
<input type="hidden" id="description-{{product.id}}" value="{{ product.description }}">
|
||||
<input type="hidden" id="cuisine-{{product.id}}" value="{{ product.cuisine }}">
|
||||
|
||||
<form hx-post="{% url 'onOffproduct' %}" hx-trigger="click" hx-target="#product-list">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="id-product" id="id-{{product.id}}" value="{{ product.id }}">
|
||||
|
||||
{% if product.active == True %}
|
||||
<button style="background-color: rgba(255, 0, 0, 0); padding: 0px;border: 0px;">
|
||||
<span data-tooltip="Ativar ou Desativar Produto" data-flow="top">
|
||||
<img
|
||||
src="{% static 'midia/icons/toggle-on.svg' %}"
|
||||
style="width: 35px; height: 35px; cursor: pointer;"
|
||||
>
|
||||
</img>
|
||||
</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button style="background-color: rgba(0, 128, 0, 0); padding: 0px;border: 0px;" >
|
||||
<span data-tooltip="Ativar ou Desativar Produto" data-flow="top">
|
||||
<img
|
||||
src="{% static 'midia/icons/toggle-off.svg' %}"
|
||||
style="width: 35px; height: 35px; cursor: pointer;"
|
||||
>
|
||||
</img>
|
||||
<span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<dialog id='Modal-create-product' >
|
||||
<article id="modal-product" class="modal-product">
|
||||
<form style="z-index: 1000; position: relative;" action="{% url 'create_product' %}" id="productForm" method="post" >
|
||||
|
||||
{% csrf_token %}
|
||||
<h2>Cadastro de Produto</h2>
|
||||
<!-- <div style="height: 200px;border-radius: 15px;">
|
||||
<img id="image-product" src="" alt="">
|
||||
</div> -->
|
||||
<input type="text" id="productId" name="productId" hidden >
|
||||
<input type="text" id="productName" name="name" required placeholder="Nome">
|
||||
<input type="number" step="0.01" id="productPrice" name="price" required placeholder="Preço">
|
||||
<input type="number" step="1" id="productqtd" name="qtd" placeholder="Quantidade">
|
||||
<input type="text" id="url-image" name="image" required placeholder="URL da imagem">
|
||||
|
||||
<div>
|
||||
<input type="checkbox" id="cuisine" name="cuisine" placeholder="Cozinha">Cozinha
|
||||
</div>
|
||||
<select id="select-categorie" name="select-categorie" >
|
||||
|
||||
{% for categorie in categories %}
|
||||
<option value="{{categorie.id}}">{{categorie.name}}</option>
|
||||
{% endfor %}
|
||||
|
||||
</select>
|
||||
<textarea id="productDescription" name="description" rows="4" placeholder="Descrição"></textarea>
|
||||
<footer>
|
||||
<div style="display: flex;gap: 10px;">
|
||||
|
||||
|
||||
<button class="btn-primary" id="save" type="submit">Salvar</button>
|
||||
<button class="btn-primary" type="submit" onclick="closeModal()" type="button" id="edit" hx-post="{% url 'editProduct' 1 %}" hx-trigger="click" hx-target="#product-list" >Alterar</button>
|
||||
<button class="btn-cancel" type="button" onclick="closeModal()">Cancelar</button>
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
|
||||
</article>
|
||||
</dialog>
|
||||
|
||||
|
||||
<script src="{% static 'products/js/products.js' %}"></script>
|
||||
|
||||
{% endblock %}
|
||||
3
products/tests.py
Normal file
3
products/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
14
products/urls.py
Normal file
14
products/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.products, name='products'),
|
||||
path('create_product', views.createProduct, name='create_product'),
|
||||
path('onOffproduct', views.onOffProduct, name='onOffproduct'),
|
||||
path('searchProduct', views.searchProduct, name='searchProduct'),
|
||||
path('editProduct/<int:productId>/', views.editProduct, name='editProduct'),
|
||||
path('createJson', views.createJson, name='createJson'),
|
||||
|
||||
|
||||
]
|
||||
115
products/views.py
Normal file
115
products/views.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import HttpResponse
|
||||
import json
|
||||
from django.db.models import Q, Count, F
|
||||
|
||||
|
||||
from categories.models import Categories
|
||||
from comandas.models import ProductComanda
|
||||
from products.models import Product
|
||||
from gestaoRaul.decorators import group_required
|
||||
|
||||
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def products(request):
|
||||
protucts = Product.objects.all().order_by('-active', 'category')
|
||||
categories = Categories.objects.all()
|
||||
return render(request, 'products.html', {'products': protucts, 'categories': categories})
|
||||
|
||||
@group_required(groupName='Garçom')
|
||||
def searchProduct(request):
|
||||
product = request.GET.get("search-product")
|
||||
products = Product.objects.filter(name__icontains=product).order_by('-active', 'category')
|
||||
return render(request, "htmx_components/products/htmx_search_products.html", {"products": products})
|
||||
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def createProduct(request):
|
||||
name = request.POST.get('name')
|
||||
description = request.POST.get('description')
|
||||
price = request.POST.get('price')
|
||||
category = Categories.objects.get(id = int(request.POST.get('select-categorie')))
|
||||
product = Product(name=name, description=description, price=price, category=category)
|
||||
product.save()
|
||||
return redirect('/products')
|
||||
|
||||
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def onOffProduct(request):
|
||||
id = request.POST.get('id-product')
|
||||
product_id = int(id)
|
||||
product = Product.objects.get(id=product_id)
|
||||
product.active = not product.active
|
||||
product.save()
|
||||
products = Product.objects.all().order_by('-active', 'category')
|
||||
return render(request, "htmx_components/products/htmx_search_products.html", {"products": products})
|
||||
|
||||
|
||||
@group_required(groupName='Gerente')
|
||||
def editProduct(request, productId):
|
||||
product_id = int(request.POST.get('productId'))
|
||||
# product_id = productId
|
||||
product = Product.objects.get(id=product_id)
|
||||
product.name = request.POST.get('name')
|
||||
product.description = request.POST.get('description')
|
||||
product.price = request.POST.get('price')
|
||||
product.quantity = request.POST.get('qtd')
|
||||
product.image = request.POST.get('image')
|
||||
product.cuisine = True if request.POST.get('cuisine') else False
|
||||
product.category = Categories.objects.get(id = int(request.POST.get('select-categorie')))
|
||||
product.save()
|
||||
product = request.GET.get("search-product")
|
||||
if product == None:
|
||||
product = ''
|
||||
products = Product.objects.filter(name__icontains=product)
|
||||
return render(request, "htmx_components/products/htmx_search_products.html", {"products": products})
|
||||
|
||||
def createJson(request):
|
||||
produtos_mais_vendidos = list(ProductComanda.objects.values('product').annotate(
|
||||
quantidade=Count('product'),
|
||||
nome=F('product__name') ).order_by('-quantidade'))
|
||||
|
||||
products = Product.objects.filter(active=True).exclude(
|
||||
category__name__in=['Insumos',
|
||||
'Adicional',
|
||||
'Bomboniere',
|
||||
'Lojinha',
|
||||
'Utensilios',
|
||||
'Litros',
|
||||
'Ingressos',
|
||||
'Cigarros',
|
||||
'Outros']
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
products_ordenados = []
|
||||
for produto in produtos_mais_vendidos:
|
||||
for p in products:
|
||||
if p.name == produto['nome']:
|
||||
products_ordenados.append(p)
|
||||
|
||||
|
||||
|
||||
|
||||
product_list = []
|
||||
for product in products_ordenados:
|
||||
product_data = {
|
||||
"id": product.id,
|
||||
"name": product.name,
|
||||
"description": product.description or "",
|
||||
"price": float(product.price),
|
||||
"category": product.category.name if product.category else "",
|
||||
"image": str(product.image) if product.image else f"https://placehold.co/400x250/efc7b8/49291c?text={product.name.replace(' ', '+')}"
|
||||
}
|
||||
product_list.append(product_data)
|
||||
|
||||
# Retorna como JSON em texto simples
|
||||
return HttpResponse(
|
||||
json.dumps(product_list, indent=4, ensure_ascii=False),
|
||||
content_type="application/json; charset=utf-8"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user