Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,24 +1,3 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Product, Customer, Seller, Sale, SaleItem
|
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
@admin.register(Product)
|
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("name", "price", "stock_quantity")
|
|
||||||
|
|
||||||
@admin.register(Customer)
|
|
||||||
class CustomerAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("name", "email", "phone")
|
|
||||||
|
|
||||||
@admin.register(Seller)
|
|
||||||
class SellerAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("name", "email", "phone", "commission")
|
|
||||||
|
|
||||||
class SaleItemInline(admin.TabularInline):
|
|
||||||
model = SaleItem
|
|
||||||
extra = 1
|
|
||||||
|
|
||||||
@admin.register(Sale)
|
|
||||||
class SaleAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("id", "customer", "sale_date", "total_amount")
|
|
||||||
inlines = [SaleItemInline]
|
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
from .models import Product, Customer, Seller, Sale, SaleItem
|
|
||||||
|
|
||||||
class SellerForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Seller
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
class SaleForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Sale
|
|
||||||
fields = ['customer', 'seller']
|
|
||||||
labels = {
|
|
||||||
'customer': 'Cliente',
|
|
||||||
'seller': 'Vendedor',
|
|
||||||
}
|
|
||||||
|
|
||||||
class SaleItemForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = SaleItem
|
|
||||||
fields = ['product', 'quantity', 'lote']
|
|
||||||
labels = {
|
|
||||||
'product': 'Produto',
|
|
||||||
'quantity': 'Quantidade',
|
|
||||||
'lote': 'Lote',
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Product
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
class CustomerForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = Customer
|
|
||||||
fields = '__all__'
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 14:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Product',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200)),
|
|
||||||
('description', models.TextField()),
|
|
||||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
|
||||||
('stock_quantity', models.PositiveIntegerField(default=0)),
|
|
||||||
('image_url', models.URLField(blank=True, max_length=1024, null=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 14:49
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Customer',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200)),
|
|
||||||
('email', models.EmailField(max_length=254, unique=True)),
|
|
||||||
('phone', models.CharField(blank=True, max_length=20, null=True)),
|
|
||||||
('address', models.TextField(blank=True, null=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 14:53
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0002_customer'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Supplier',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200)),
|
|
||||||
('contact_person', models.CharField(blank=True, max_length=200, null=True)),
|
|
||||||
('email', models.EmailField(max_length=254, unique=True)),
|
|
||||||
('phone', models.CharField(blank=True, max_length=20, null=True)),
|
|
||||||
('address', models.TextField(blank=True, null=True)),
|
|
||||||
('website', models.URLField(blank=True, max_length=1024, null=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 14:58
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0003_supplier'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Sale',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('sale_date', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('total_amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)),
|
|
||||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.customer')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SaleItem',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('quantity', models.PositiveIntegerField(default=1)),
|
|
||||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
|
||||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')),
|
|
||||||
('sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.sale')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 15:04
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0004_sale_saleitem'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sale',
|
|
||||||
name='lote',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 16:43
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0005_sale_lote'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='customer',
|
|
||||||
options={'verbose_name': 'Cliente', 'verbose_name_plural': 'Clientes'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='product',
|
|
||||||
options={'verbose_name': 'Produto', 'verbose_name_plural': 'Produtos'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='sale',
|
|
||||||
options={'verbose_name': 'Venda', 'verbose_name_plural': 'Vendas'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='saleitem',
|
|
||||||
options={'verbose_name': 'Item da Venda', 'verbose_name_plural': 'Itens da Venda'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='supplier',
|
|
||||||
options={'verbose_name': 'Fornecedor', 'verbose_name_plural': 'Fornecedores'},
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='customer',
|
|
||||||
name='address',
|
|
||||||
field=models.TextField(blank=True, null=True, verbose_name='Endereço'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='customer',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='customer',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Nome'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='customer',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Telefone'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(verbose_name='Descrição'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='image_url',
|
|
||||||
field=models.URLField(blank=True, max_length=1024, null=True, verbose_name='URL da Imagem'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Nome'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='price',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Preço'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='stock_quantity',
|
|
||||||
field=models.PositiveIntegerField(default=0, verbose_name='Quantidade em Estoque'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sale',
|
|
||||||
name='customer',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.customer', verbose_name='Cliente'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sale',
|
|
||||||
name='lote',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Lote'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sale',
|
|
||||||
name='sale_date',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Data da Venda'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='sale',
|
|
||||||
name='total_amount',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='Valor Total'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='saleitem',
|
|
||||||
name='price',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Preço'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='saleitem',
|
|
||||||
name='product',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product', verbose_name='Produto'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='saleitem',
|
|
||||||
name='quantity',
|
|
||||||
field=models.PositiveIntegerField(default=1, verbose_name='Quantidade'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='saleitem',
|
|
||||||
name='sale',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.sale', verbose_name='Venda'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='address',
|
|
||||||
field=models.TextField(blank=True, null=True, verbose_name='Endereço'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='contact_person',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Pessoa de Contato'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(max_length=254, unique=True, verbose_name='Email'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Nome'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='phone',
|
|
||||||
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='Telefone'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='website',
|
|
||||||
field=models.URLField(blank=True, max_length=1024, null=True, verbose_name='Website'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 17:08
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0006_alter_customer_options_alter_product_options_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='Supplier',
|
|
||||||
new_name='Seller',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 16:49
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0007_rename_supplier_to_seller'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='seller',
|
|
||||||
options={'verbose_name': 'Vendedor', 'verbose_name_plural': 'Vendedores'},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='seller',
|
|
||||||
name='contact_person',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='seller',
|
|
||||||
name='commission',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=5, verbose_name='Comissão'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='seller',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(blank=True, null=True, verbose_name='Descrição'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 16:55
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0008_alter_seller_options_remove_seller_contact_person_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='commission_rate',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=5.0, max_digits=5, verbose_name='Taxa de Comissão'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sale',
|
|
||||||
name='commission_amount',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10, verbose_name='Valor da Comissão'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='sale',
|
|
||||||
name='seller',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.seller', verbose_name='Vendedor'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 17:32
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0009_product_commission_rate_sale_commission_amount_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleitem',
|
|
||||||
name='commission_rate',
|
|
||||||
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=5, verbose_name='Taxa de Comissão'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-12-23 17:48
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('core', '0010_saleitem_commission_rate'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='saleitem',
|
|
||||||
name='lote',
|
|
||||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Lote'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,77 +1,3 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
class Product(models.Model):
|
# Create your models here.
|
||||||
name = models.CharField("Nome", max_length=200)
|
|
||||||
description = models.TextField("Descrição")
|
|
||||||
price = models.DecimalField("Preço", max_digits=10, decimal_places=2)
|
|
||||||
stock_quantity = models.PositiveIntegerField("Quantidade em Estoque", default=0)
|
|
||||||
image_url = models.URLField("URL da Imagem", max_length=1024, blank=True, null=True)
|
|
||||||
commission_rate = models.DecimalField("Taxa de Comissão", max_digits=5, decimal_places=2, default=5.00)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Produto"
|
|
||||||
verbose_name_plural = "Produtos"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Customer(models.Model):
|
|
||||||
name = models.CharField("Nome", max_length=200)
|
|
||||||
email = models.EmailField("Email", max_length=254, unique=True)
|
|
||||||
phone = models.CharField("Telefone", max_length=20, blank=True, null=True)
|
|
||||||
address = models.TextField("Endereço", blank=True, null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Cliente"
|
|
||||||
verbose_name_plural = "Clientes"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Seller(models.Model):
|
|
||||||
name = models.CharField("Nome", max_length=200)
|
|
||||||
description = models.TextField("Descrição", blank=True, null=True)
|
|
||||||
email = models.EmailField("Email", max_length=254, unique=True)
|
|
||||||
phone = models.CharField("Telefone", max_length=20, blank=True, null=True)
|
|
||||||
address = models.TextField("Endereço", blank=True, null=True)
|
|
||||||
website = models.URLField("Website", max_length=1024, blank=True, null=True)
|
|
||||||
commission = models.DecimalField("Comissão", max_digits=5, decimal_places=2, default=0.00)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Vendedor"
|
|
||||||
verbose_name_plural = "Vendedores"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Sale(models.Model):
|
|
||||||
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name="Cliente")
|
|
||||||
seller = models.ForeignKey(Seller, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Vendedor")
|
|
||||||
sale_date = models.DateTimeField("Data da Venda", auto_now_add=True)
|
|
||||||
total_amount = models.DecimalField("Valor Total", max_digits=10, decimal_places=2, default=0.00)
|
|
||||||
commission_amount = models.DecimalField("Valor da Comissão", max_digits=10, decimal_places=2, default=0.00)
|
|
||||||
lote = models.CharField("Lote", max_length=200, blank=True, null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Venda"
|
|
||||||
verbose_name_plural = "Vendas"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Venda #{self.pk} - {self.customer.name}"
|
|
||||||
|
|
||||||
|
|
||||||
class SaleItem(models.Model):
|
|
||||||
sale = models.ForeignKey(Sale, related_name='items', on_delete=models.CASCADE, verbose_name="Venda")
|
|
||||||
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name="Produto")
|
|
||||||
lote = models.CharField("Lote", max_length=200, blank=True, null=True)
|
|
||||||
quantity = models.PositiveIntegerField("Quantidade", default=1)
|
|
||||||
price = models.DecimalField("Preço", max_digits=10, decimal_places=2)
|
|
||||||
commission_rate = models.DecimalField("Taxa de Comissão", max_digits=5, decimal_places=2, default=0.00)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "Item da Venda"
|
|
||||||
verbose_name_plural = "Itens da Venda"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.quantity} de {self.product.name} na Venda #{self.sale.pk}"
|
|
||||||
|
|||||||
@ -1,16 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="pt-BR">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
||||||
<meta name="description" content="Meta description for the page">
|
|
||||||
<title>EletroNorteAr</title>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
|
|
||||||
{% if project_description %}
|
{% if project_description %}
|
||||||
<meta name="description" content="{{ project_description }}">
|
<meta name="description" content="{{ project_description }}">
|
||||||
<meta property="og:description" content="{{ project_description }}">
|
<meta property="og:description" content="{{ project_description }}">
|
||||||
@ -26,39 +19,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<a class="navbar-brand" href="{% url 'index' %}">EletroNorteAr</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Alternar navegação">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'product_list' %}">Produtos</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'customer_list' %}">Clientes</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'seller-list' %}">Vendedores</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="{% url 'sale_list' %}">Vendas</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin">Administração</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
|
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<h1>{{ article.title }}</h1>
|
<h1>{{ article.title }}</h1>
|
||||||
<p class="text-muted">Publicado em {{ article.created_at|date:"d \\de F \\de Y" }}</p>
|
<p class="text-muted">Published on {{ article.created_at|date:"F d, Y" }}</p>
|
||||||
<hr>
|
<hr>
|
||||||
<div>
|
<div>
|
||||||
{{ article.content|safe }}
|
{{ article.content|safe }}
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Excluir Cliente</h1>
|
|
||||||
<p>Tem certeza que deseja excluir "{{ object.name }}"?</p>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-danger">Excluir</button>
|
|
||||||
<a href="{% url 'customer_list' %}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% if object %}Atualizar Cliente{% else %}Novo Cliente{% endif %}</h1>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<button type="submit" class="btn btn-primary">Salvar</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Clientes</h1>
|
|
||||||
<a href="{% url 'customer_create' %}" class="btn btn-primary mb-3">Novo Cliente</a>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Nome</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>Telefone</th>
|
|
||||||
<th>Endereço</th>
|
|
||||||
<th>Ações</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for customer in customers %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ customer.name }}</td>
|
|
||||||
<td>{{ customer.email }}</td>
|
|
||||||
<td>{{ customer.phone }}</td>
|
|
||||||
<td>{{ customer.address }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'customer_update' customer.pk %}" class="btn btn-sm btn-info">Editar</a>
|
|
||||||
<a href="{% url 'customer_delete' customer.pk %}" class="btn btn-sm btn-danger">Excluir</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,42 +1,145 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
|
||||||
|
{% block title %}{{ project_name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-color-start: #6a11cb;
|
||||||
|
--bg-color-end: #2575fc;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||||
|
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
|
||||||
|
animation: bg-pan 20s linear infinite;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bg-pan {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--card-bg-color);
|
||||||
|
border: 1px solid var(--card-border-color);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 2.5rem 2rem;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0 0 1.2rem;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.25);
|
||||||
|
border-top-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtime code {
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
padding: 0.15rem 0.45rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Hero Section -->
|
<main>
|
||||||
<section class="hero-section text-center">
|
<div class="card">
|
||||||
<div class="container">
|
<h1>Analyzing your requirements and generating your app…</h1>
|
||||||
<h1 class="display-4">Gestão Inteligente de Climatização</h1>
|
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||||
<p class="lead">Controle seu estoque e vendas de forma eficiente e moderna.</p>
|
<span class="sr-only">Loading…</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
|
||||||
|
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
||||||
<!-- Product Section -->
|
<p class="runtime">
|
||||||
<section class="product-section">
|
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
||||||
<div class="container">
|
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
||||||
<h2 class="text-center mb-5">Nossos Produtos</h2>
|
</p>
|
||||||
<div class="row">
|
</div>
|
||||||
{% if products %}
|
</main>
|
||||||
{% for product in products %}
|
<footer>
|
||||||
<div class="col-md-4 mb-4">
|
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
||||||
<div class="card product-card h-100">
|
</footer>
|
||||||
<img src="{{ product.image_url|default:'https://via.placeholder.com/300x200.png?text=Sem+Imagem' }}" class="card-img-top product-card-img" alt="{{ product.name }}">
|
|
||||||
<div class="card-body d-flex flex-column">
|
|
||||||
<h5 class="card-title">{{ product.name }}</h5>
|
|
||||||
<p class="card-text text-muted flex-grow-1">{{ product.description|truncatewords:20 }}</p>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
||||||
<p class="mb-0 product-price">R$ {{ product.price }}</p>
|
|
||||||
<p class="mb-0 text-muted stock-level">Estoque: {{ product.stock_quantity }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<div class="col-12">
|
|
||||||
<p class="text-center">Nenhum produto cadastrado ainda. Adicione produtos no <a href="/admin">painel de administração</a>.</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Excluir Produto</h1>
|
|
||||||
<p>Tem certeza que deseja excluir "{{ object.name }}"?</p>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-danger">Excluir</button>
|
|
||||||
<a href="{% url 'product_list' %}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% if object %}Atualizar Produto{% else %}Novo Produto{% endif %}</h1>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<button type="submit" class="btn btn-primary">Salvar</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Produtos</h1>
|
|
||||||
<a href="{% url 'product_create' %}" class="btn btn-primary mb-3">Novo Produto</a>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Nome</th>
|
|
||||||
<th>Preço</th>
|
|
||||||
<th>Estoque</th>
|
|
||||||
<th>Ações</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for product in products %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ product.name }}</td>
|
|
||||||
<td>{{ product.price }}</td>
|
|
||||||
<td>{{ product.stock_quantity }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'product_update' product.pk %}" class="btn btn-sm btn-info">Editar</a>
|
|
||||||
<a href="{% url 'product_delete' product.pk %}" class="btn btn-sm btn-danger">Excluir</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Excluir Venda</h1>
|
|
||||||
<p>Tem certeza que deseja excluir a venda #{{ object.id }}?</p>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-danger">Excluir</button>
|
|
||||||
<a href="{% url 'sale_list' %}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="hero-section text-center">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="display-4">Criar Venda</h1>
|
|
||||||
<p class="lead">Preencha os detalhes para criar uma nova venda.</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="product-section">
|
|
||||||
<div class="container">
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
|
|
||||||
{{ field }}
|
|
||||||
{% if field.help_text %}
|
|
||||||
<small class="form-text text-muted">{{ field.help_text }}</small>
|
|
||||||
{% endif %}
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<div class="alert alert-danger mt-1">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<h2>Itens da Venda</h2>
|
|
||||||
{{ formset.management_form }}
|
|
||||||
{% if formset.non_form_errors %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ formset.non_form_errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Produto</th>
|
|
||||||
<th>Quantidade</th>
|
|
||||||
<th>Lote</th>
|
|
||||||
<th>Excluir</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="item-forms">
|
|
||||||
{% for form in formset.forms %}
|
|
||||||
<tr class="item-form">
|
|
||||||
{% for field in form.hidden_fields %}{{ field }}{% endfor %}
|
|
||||||
<td>
|
|
||||||
{{ form.product }}
|
|
||||||
{% for error in form.product.errors %}<div class="alert alert-danger mt-1">{{ error }}</div>{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ form.quantity }}
|
|
||||||
{% for error in form.quantity.errors %}<div class="alert alert-danger mt-1">{{ error }}</div>{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ form.lote }}
|
|
||||||
{% for error in form.lote.errors %}<div class="alert alert-danger mt-1">{{ error }}</div>{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if form.instance.pk %}{{ form.DELETE }}{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<button type="button" id="add-item" class="btn btn-info">Adicionar Produto</button>
|
|
||||||
<br><br>
|
|
||||||
<button type="submit" class="btn btn-primary">Salvar Venda</button>
|
|
||||||
<a href="{% url 'sale_list' %}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<script type="text/template" id="item-form-template">
|
|
||||||
<tr class="item-form" id="{{ formset.prefix }}-__prefix__">
|
|
||||||
{% for field in formset.empty_form.hidden_fields %}{{ field }}{% endfor %}
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.product }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.quantity }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.lote }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.DELETE }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const addItemButton = document.getElementById('add-item');
|
|
||||||
const formsetContainer = document.getElementById('item-forms');
|
|
||||||
const formTemplate = document.getElementById('item-form-template').innerHTML;
|
|
||||||
const totalFormsInput = document.querySelector('#id_items-TOTAL_FORMS');
|
|
||||||
|
|
||||||
addItemButton.addEventListener('click', function() {
|
|
||||||
let formNum = parseInt(totalFormsInput.value);
|
|
||||||
let newForm = formTemplate.replace(/__prefix__/g, formNum);
|
|
||||||
formsetContainer.insertAdjacentHTML('beforeend', newForm);
|
|
||||||
totalFormsInput.value = formNum + 1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section class="hero-section text-center">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="display-4">{% if sale %}Editar Venda{% else %}Criar Venda{% endif %}</h1>
|
|
||||||
<p class="lead">{% if sale %}Atualize os detalhes da venda.{% else %}Preencha os detalhes para criar uma nova venda.{% endif %}</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="product-section">
|
|
||||||
<div class="container">
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
|
|
||||||
{{ field }}
|
|
||||||
{% if field.help_text %}
|
|
||||||
<small class="form-text text-muted">{{ field.help_text }}</small>
|
|
||||||
{% endif %}
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<div class="alert alert-danger mt-1">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<h2>Itens da Venda</h2>
|
|
||||||
{{ formset.management_form }}
|
|
||||||
{% if formset.non_form_errors %}
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
{{ formset.non_form_errors }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Produto</th>
|
|
||||||
<th>Quantidade</th>
|
|
||||||
<th>Lote</th>
|
|
||||||
<th>Excluir</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="item-forms">
|
|
||||||
{% for form in formset.forms %}
|
|
||||||
<tr class="item-form">
|
|
||||||
{% for field in form.hidden_fields %}{{ field }}{% endfor %}
|
|
||||||
<td>
|
|
||||||
{{ form.product }}
|
|
||||||
{% for error in form.product.errors %}<div class="alert alert-danger mt-1">{{ error }}</div>{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ form.quantity }}
|
|
||||||
{% for error in form.quantity.errors %}<div class="alert alert-danger mt-1">{{ error }}</div>{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ form.lote }}
|
|
||||||
{% for error in form.lote.errors %}<div class="alert alert-danger mt-1">{{ error }}</div>{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if form.instance.pk %}{{ form.DELETE }}{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<button type="button" id="add-item" class="btn btn-info">Adicionar Produto</button>
|
|
||||||
<br><br>
|
|
||||||
<button type="submit" class="btn btn-primary">Salvar Alterações</button>
|
|
||||||
<a href="{% url 'sale_list' %}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<script type="text/template" id="item-form-template">
|
|
||||||
<tr class="item-form" id="{{ formset.prefix }}-__prefix__">
|
|
||||||
{% for field in formset.empty_form.hidden_fields %}{{ field }}{% endfor %}
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.product }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.quantity }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.lote }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ formset.empty_form.DELETE }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const addItemButton = document.getElementById('add-item');
|
|
||||||
const formsetContainer = document.getElementById('item-forms');
|
|
||||||
const formTemplate = document.getElementById('item-form-template').innerHTML;
|
|
||||||
const totalFormsInput = document.querySelector('#id_items-TOTAL_FORMS');
|
|
||||||
const formsetPrefix = '{{ formset.prefix }}';
|
|
||||||
|
|
||||||
addItemButton.addEventListener('click', function() {
|
|
||||||
let formNum = parseInt(totalFormsInput.value);
|
|
||||||
let newForm = formTemplate.replace(/__prefix__/g, formNum);
|
|
||||||
formsetContainer.insertAdjacentHTML('beforeend', newForm);
|
|
||||||
totalFormsInput.value = formNum + 1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container mt-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1 class="display-4">Vendas</h1>
|
|
||||||
<a href="{% url 'sale_create' %}" class="btn btn-primary">Nova Venda</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="lead">Gerencie suas vendas.</p>
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Cliente</th>
|
|
||||||
<th>Vendedor</th>
|
|
||||||
<th>Valor Total</th>
|
|
||||||
<th>Comissão</th>
|
|
||||||
<th>Data</th>
|
|
||||||
<th>Ações</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for sale in sales %}
|
|
||||||
<tr>
|
|
||||||
<td>#{{ sale.id }}</td>
|
|
||||||
<td>{{ sale.customer.name }}</td>
|
|
||||||
<td>{{ sale.seller.name|default:"N/A" }}</td>
|
|
||||||
<td>R$ {{ sale.total_amount|floatformat:2 }}</td>
|
|
||||||
<td>R$ {{ sale.commission_amount|floatformat:2 }}</td>
|
|
||||||
<td>{{ sale.sale_date|date:"d/m/Y" }}</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<a href="{% url 'sale_update' sale.id %}" class="btn btn-sm btn-warning">Editar</a>
|
|
||||||
<a href="{% url 'sale_delete' sale.id %}" class="btn btn-sm btn-danger">Excluir</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="7" class="text-center">Nenhuma venda cadastrada.</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>Excluir Vendedor</h2>
|
|
||||||
<p>Tem certeza que deseja excluir o vendedor "{{ seller.name }}"?</p>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-danger">Excluir</button>
|
|
||||||
<a href="{% url 'seller-list' %}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>{% if form.instance.pk %}Editar Vendedor{% else %}Adicionar Vendedor{% endif %}</h2>
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ form.as_p }}
|
|
||||||
<button type="submit" class="btn btn-primary">Salvar</button>
|
|
||||||
<a href="{% url 'seller-list' %}" class="btn btn-secondary">Cancelar</a>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container mt-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1 class="display-4">Vendedores</h1>
|
|
||||||
<a href="{% url 'seller_create' %}" class="btn btn-primary">Adicionar Vendedor</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="lead">Gerencie seus vendedores.</p>
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Nome</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>Telefone</th>
|
|
||||||
<th>Comissão (%)</th>
|
|
||||||
<th>Ações</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for seller in sellers %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ seller.name }}</td>
|
|
||||||
<td>{{ seller.email }}</td>
|
|
||||||
<td>{{ seller.phone }}</td>
|
|
||||||
<td>{{ seller.commission }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'seller_update' seller.pk %}" class="btn btn-sm btn-info">Editar</a>
|
|
||||||
<a href="{% url 'seller_delete' seller.pk %}" class="btn btn-sm btn-danger">Excluir</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="text-center">Nenhum vendedor cadastrado.</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
38
core/urls.py
38
core/urls.py
@ -1,41 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import (
|
from .views import home
|
||||||
index,
|
|
||||||
customer_list,
|
|
||||||
seller_list,
|
|
||||||
sale_list,
|
|
||||||
sale_create,
|
|
||||||
sale_update,
|
|
||||||
SaleDeleteView,
|
|
||||||
product_list,
|
|
||||||
ProductCreateView,
|
|
||||||
ProductUpdateView,
|
|
||||||
ProductDeleteView,
|
|
||||||
CustomerCreateView,
|
|
||||||
CustomerUpdateView,
|
|
||||||
CustomerDeleteView,
|
|
||||||
SellerCreateView,
|
|
||||||
SellerUpdateView,
|
|
||||||
SellerDeleteView,
|
|
||||||
)
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", index, name="index"),
|
path("", home, name="home"),
|
||||||
path("customers/", customer_list, name="customer_list"),
|
|
||||||
path("customers/create/", CustomerCreateView.as_view(), name="customer_create"),
|
|
||||||
path("customers/<int:pk>/update/", CustomerUpdateView.as_view(), name="customer_update"),
|
|
||||||
path("customers/<int:pk>/delete/", CustomerDeleteView.as_view(), name="customer_delete"),
|
|
||||||
path("sellers/", seller_list, name="seller-list"),
|
|
||||||
path("sellers/create/", SellerCreateView.as_view(), name="seller_create"),
|
|
||||||
path("sellers/<int:pk>/update/", SellerUpdateView.as_view(), name="seller_update"),
|
|
||||||
path("sellers/<int:pk>/delete/", SellerDeleteView.as_view(), name="seller_delete"),
|
|
||||||
path("sales/", sale_list, name="sale_list"),
|
|
||||||
path("sales/create/", sale_create, name="sale_create"),
|
|
||||||
path("sales/<int:pk>/update/", sale_update, name="sale_update"),
|
|
||||||
path("sales/<int:pk>/delete/", SaleDeleteView.as_view(), name="sale_delete"),
|
|
||||||
path("products/", product_list, name="product_list"),
|
|
||||||
path("products/create/", ProductCreateView.as_view(), name="product_create"),
|
|
||||||
path("products/<int:pk>/update/", ProductUpdateView.as_view(), name="product_update"),
|
|
||||||
path("products/<int:pk>/delete/", ProductDeleteView.as_view(), name="product_delete"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
228
core/views.py
228
core/views.py
@ -2,28 +2,17 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
|
|
||||||
from django import get_version as django_version
|
from django import get_version as django_version
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.generic import CreateView, UpdateView, DeleteView
|
|
||||||
|
|
||||||
from .forms import SaleForm, ProductForm, CustomerForm, SellerForm
|
|
||||||
from .models import Product, Customer, Seller, Sale, SaleItem
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def home(request):
|
||||||
"""Render the landing screen with loader and environment details."""
|
"""Render the landing screen with loader and environment details."""
|
||||||
|
|
||||||
host_name = request.get_host().lower()
|
host_name = request.get_host().lower()
|
||||||
|
|
||||||
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
||||||
|
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
products = Product.objects.all()
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"products": products,
|
|
||||||
"project_name": "New Style",
|
"project_name": "New Style",
|
||||||
"agent_brand": agent_brand,
|
"agent_brand": agent_brand,
|
||||||
"django_version": django_version(),
|
"django_version": django_version(),
|
||||||
@ -33,217 +22,4 @@ def index(request):
|
|||||||
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
||||||
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "core/index.html", context)
|
return render(request, "core/index.html", context)
|
||||||
|
|
||||||
|
|
||||||
def customer_list(request):
|
|
||||||
customers = Customer.objects.all()
|
|
||||||
return render(request, 'core/customer_list.html', {'customers': customers})
|
|
||||||
|
|
||||||
|
|
||||||
class CustomerCreateView(CreateView):
|
|
||||||
model = Customer
|
|
||||||
form_class = CustomerForm
|
|
||||||
template_name = 'core/customer_form.html'
|
|
||||||
success_url = reverse_lazy('customer_list')
|
|
||||||
|
|
||||||
|
|
||||||
class CustomerUpdateView(UpdateView):
|
|
||||||
model = Customer
|
|
||||||
form_class = CustomerForm
|
|
||||||
template_name = 'core/customer_form.html'
|
|
||||||
success_url = reverse_lazy('customer_list')
|
|
||||||
|
|
||||||
|
|
||||||
class CustomerDeleteView(DeleteView):
|
|
||||||
model = Customer
|
|
||||||
template_name = 'core/customer_confirm_delete.html'
|
|
||||||
success_url = reverse_lazy('customer_list')
|
|
||||||
|
|
||||||
|
|
||||||
class SellerCreateView(CreateView):
|
|
||||||
model = Seller
|
|
||||||
form_class = SellerForm
|
|
||||||
template_name = 'core/seller_form.html'
|
|
||||||
success_url = reverse_lazy('seller-list')
|
|
||||||
|
|
||||||
|
|
||||||
class SellerUpdateView(UpdateView):
|
|
||||||
model = Seller
|
|
||||||
form_class = SellerForm
|
|
||||||
template_name = 'core/seller_form.html'
|
|
||||||
success_url = reverse_lazy('seller-list')
|
|
||||||
|
|
||||||
|
|
||||||
class SellerDeleteView(DeleteView):
|
|
||||||
model = Seller
|
|
||||||
template_name = 'core/seller_confirm_delete.html'
|
|
||||||
success_url = reverse_lazy('seller-list')
|
|
||||||
|
|
||||||
|
|
||||||
def seller_list(request):
|
|
||||||
"""Render the seller list page."""
|
|
||||||
|
|
||||||
sellers = Seller.objects.all()
|
|
||||||
|
|
||||||
context = {"sellers": sellers}
|
|
||||||
|
|
||||||
return render(request, "core/seller_list.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
def sale_list(request):
|
|
||||||
"""Render the sale list page."""
|
|
||||||
|
|
||||||
logging.info("Sale list view called")
|
|
||||||
|
|
||||||
sales = Sale.objects.select_related('customer', 'seller').all()
|
|
||||||
logging.info(f"Sales: {sales}")
|
|
||||||
|
|
||||||
context = {"sales": sales}
|
|
||||||
|
|
||||||
return render(request, "core/sale_list.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
from django.forms import inlineformset_factory
|
|
||||||
from django.db import transaction
|
|
||||||
from .forms import SaleForm, ProductForm, CustomerForm, SellerForm, SaleItemForm
|
|
||||||
def sale_create(request):
|
|
||||||
SaleItemFormSet = inlineformset_factory(
|
|
||||||
Sale, SaleItem, form=SaleItemForm, extra=1, can_delete=True
|
|
||||||
)
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = SaleForm(request.POST)
|
|
||||||
formset = SaleItemFormSet(request.POST)
|
|
||||||
|
|
||||||
if form.is_valid() and formset.is_valid():
|
|
||||||
with transaction.atomic():
|
|
||||||
sale = form.save(commit=False)
|
|
||||||
# Initialize calculated fields
|
|
||||||
sale.total_amount = 0
|
|
||||||
sale.commission_amount = 0
|
|
||||||
sale.save() # Save sale to get an ID for the formset
|
|
||||||
|
|
||||||
formset.instance = sale
|
|
||||||
sale_items = formset.save(commit=False)
|
|
||||||
|
|
||||||
total_amount = 0
|
|
||||||
commission_amount = 0
|
|
||||||
|
|
||||||
for item in sale_items:
|
|
||||||
# Freeze price at the time of sale
|
|
||||||
item.price = item.product.price
|
|
||||||
|
|
||||||
# Determine commission rate: Use seller's rate if > 0, otherwise fallback to product's rate
|
|
||||||
if sale.seller and sale.seller.commission > 0:
|
|
||||||
item.commission_rate = sale.seller.commission
|
|
||||||
else:
|
|
||||||
item.commission_rate = item.product.commission_rate
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
# Calculate totals
|
|
||||||
item_total = item.price * item.quantity
|
|
||||||
total_amount += item_total
|
|
||||||
commission_amount += item_total * (item.commission_rate / 100)
|
|
||||||
|
|
||||||
# Update the sale instance with calculated totals
|
|
||||||
sale.total_amount = total_amount
|
|
||||||
sale.commission_amount = commission_amount
|
|
||||||
sale.save()
|
|
||||||
|
|
||||||
return redirect('sale_list')
|
|
||||||
else:
|
|
||||||
form = SaleForm()
|
|
||||||
formset = SaleItemFormSet()
|
|
||||||
|
|
||||||
return render(request, 'core/sale_create.html', {'form': form, 'formset': formset})
|
|
||||||
|
|
||||||
def sale_update(request, pk):
|
|
||||||
sale = get_object_or_404(Sale, pk=pk)
|
|
||||||
SaleItemFormSet = inlineformset_factory(
|
|
||||||
Sale, SaleItem, form=SaleItemForm, extra=1, can_delete=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = SaleForm(request.POST, instance=sale)
|
|
||||||
formset = SaleItemFormSet(request.POST, instance=sale)
|
|
||||||
|
|
||||||
if form.is_valid() and formset.is_valid():
|
|
||||||
with transaction.atomic():
|
|
||||||
sale = form.save()
|
|
||||||
|
|
||||||
# Handle deleted items first
|
|
||||||
for form in formset.deleted_forms:
|
|
||||||
if form.instance.pk:
|
|
||||||
form.instance.delete()
|
|
||||||
|
|
||||||
# Handle new and changed items
|
|
||||||
sale_items = formset.save(commit=False)
|
|
||||||
for item in sale_items:
|
|
||||||
if not item.pk: # If it's a new item
|
|
||||||
item.price = item.product.price
|
|
||||||
# Commission rate will be set in the recalculation step
|
|
||||||
item.save()
|
|
||||||
formset.save_m2m()
|
|
||||||
|
|
||||||
# Recalculate total amount and commission from all valid items
|
|
||||||
total_amount = 0
|
|
||||||
commission_amount = 0
|
|
||||||
for item in sale.items.all():
|
|
||||||
# Determine and save the correct commission rate for every item
|
|
||||||
if sale.seller and sale.seller.commission > 0:
|
|
||||||
item.commission_rate = sale.seller.commission
|
|
||||||
else:
|
|
||||||
item.commission_rate = item.product.commission_rate
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
item_total = item.price * item.quantity
|
|
||||||
total_amount += item_total
|
|
||||||
commission_amount += item_total * (item.commission_rate / 100)
|
|
||||||
|
|
||||||
sale.total_amount = total_amount
|
|
||||||
sale.commission_amount = commission_amount
|
|
||||||
sale.save()
|
|
||||||
|
|
||||||
return redirect('sale_list')
|
|
||||||
else:
|
|
||||||
form = SaleForm(instance=sale)
|
|
||||||
formset = SaleItemFormSet(instance=sale)
|
|
||||||
|
|
||||||
return render(request, 'core/sale_form.html', {'form': form, 'formset': formset, 'sale': sale})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def product_list(request):
|
|
||||||
products = Product.objects.all()
|
|
||||||
return render(request, 'core/product_list.html', {'products': products})
|
|
||||||
|
|
||||||
|
|
||||||
class ProductCreateView(CreateView):
|
|
||||||
model = Product
|
|
||||||
form_class = ProductForm
|
|
||||||
template_name = 'core/product_form.html'
|
|
||||||
success_url = reverse_lazy('product_list')
|
|
||||||
|
|
||||||
|
|
||||||
class ProductUpdateView(UpdateView):
|
|
||||||
model = Product
|
|
||||||
form_class = ProductForm
|
|
||||||
template_name = 'core/product_form.html'
|
|
||||||
success_url = reverse_lazy('product_list')
|
|
||||||
|
|
||||||
|
|
||||||
class ProductDeleteView(DeleteView):
|
|
||||||
model = Product
|
|
||||||
template_name = 'core/product_confirm_delete.html'
|
|
||||||
success_url = reverse_lazy('product_list')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SaleDeleteView(DeleteView):
|
|
||||||
model = Sale
|
|
||||||
template_name = 'core/sale_confirm_delete.html'
|
|
||||||
success_url = reverse_lazy('sale_list')
|
|
||||||
@ -1,116 +1,4 @@
|
|||||||
/*
|
/* Custom styles for the application */
|
||||||
Custom Styles for ACME Climsoft
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--primary-color: #007BFF;
|
|
||||||
--secondary-color: #343A40;
|
|
||||||
--accent-color: #17A2B8;
|
|
||||||
--background-color: #F8F9FA;
|
|
||||||
--text-color: #212529;
|
|
||||||
--heading-font: 'Poppins', sans-serif;
|
|
||||||
--body-font: 'Roboto', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: var(--body-font);
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-family: var(--heading-font);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
border-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-accent {
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
border-color: var(--accent-color);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-accent:hover {
|
|
||||||
background-color: #138496;
|
|
||||||
border-color: #138496;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hero Section */
|
|
||||||
.hero-section {
|
|
||||||
background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
|
|
||||||
padding: 6rem 0;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section h1 {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section .lead {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Product Section */
|
|
||||||
.product-section {
|
|
||||||
padding: 4rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card {
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 8px 12px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card-img {
|
|
||||||
height: 200px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .card-body {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .card-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .card-text {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock-level {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navbar */
|
|
||||||
.navbar {
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
@ -1,116 +1,21 @@
|
|||||||
/*
|
|
||||||
Custom Styles for ACME Climsoft
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #007BFF;
|
--bg-color-start: #6a11cb;
|
||||||
--secondary-color: #343A40;
|
--bg-color-end: #2575fc;
|
||||||
--accent-color: #17A2B8;
|
--text-color: #ffffff;
|
||||||
--background-color: #F8F9FA;
|
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||||
--text-color: #212529;
|
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||||
--heading-font: 'Poppins', sans-serif;
|
|
||||||
--body-font: 'Roboto', sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: var(--body-font);
|
margin: 0;
|
||||||
background-color: var(--background-color);
|
font-family: 'Inter', sans-serif;
|
||||||
|
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-family: var(--heading-font);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--secondary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
border-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-accent {
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
border-color: var(--accent-color);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-accent:hover {
|
|
||||||
background-color: #138496;
|
|
||||||
border-color: #138496;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hero Section */
|
|
||||||
.hero-section {
|
|
||||||
background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
|
|
||||||
padding: 6rem 0;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section h1 {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-section .lead {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Product Section */
|
|
||||||
.product-section {
|
|
||||||
padding: 4rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card {
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 8px 12px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card-img {
|
|
||||||
height: 200px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .card-body {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .card-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .card-text {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock-level {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navbar */
|
|
||||||
.navbar {
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user