diff --git a/tienda_ilusion/don_confiao/admin.py b/tienda_ilusion/don_confiao/admin.py index 83d4e1c..ee26dae 100644 --- a/tienda_ilusion/don_confiao/admin.py +++ b/tienda_ilusion/don_confiao/admin.py @@ -1,13 +1,21 @@ from django.contrib import admin from .models.sales import Sale, SaleLine from .models.customers import Customer -from .models.sales import Sale, SaleLine, Payment +from .models.sales import ( + Sale, + SaleLine, + CatalogSale, + CatalogSaleLine, + Payment, +) from .models.products import Product, ProductCategory from .models.payments import ReconciliationJar admin.site.register(Customer) admin.site.register(Sale) admin.site.register(SaleLine) +admin.site.register(CatalogSale) +admin.site.register(CatalogSaleLine) admin.site.register(Product) admin.site.register(ProductCategory) admin.site.register(Payment) diff --git a/tienda_ilusion/don_confiao/api/__init__.py b/tienda_ilusion/don_confiao/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tienda_ilusion/don_confiao/api/sale_views.py b/tienda_ilusion/don_confiao/api/sale_views.py deleted file mode 100644 index fa93046..0000000 --- a/tienda_ilusion/don_confiao/api/sale_views.py +++ /dev/null @@ -1,5 +0,0 @@ -from rest_framework import viewsets - - -class CatalogSaleView(viewsets.ViewSet): - pass diff --git a/tienda_ilusion/don_confiao/api_views.py b/tienda_ilusion/don_confiao/api_views.py index 0109cc3..02ad601 100644 --- a/tienda_ilusion/don_confiao/api_views.py +++ b/tienda_ilusion/don_confiao/api_views.py @@ -7,13 +7,20 @@ from rest_framework.permissions import IsAuthenticated from .models.sales import Sale, SaleLine from .models.customers import Customer -from .models.sales import Sale, SaleLine, Payment +from .models.sales import ( + Sale, + SaleLine, + CatalogSale, + CatalogSaleLine, + Payment, +) from .models.products import Product, ProductCategory from .models.payments import PaymentMethods, ReconciliationJar from .models.admin import AdminCode from .serializers import ( SaleSerializer, + CatalogSaleSerializer, ProductSerializer, CustomerSerializer, ReconciliationJarSerializer, @@ -79,6 +86,11 @@ class SaleView(viewsets.ModelViewSet): ) +class CatalogSaleView(viewsets.ModelViewSet): + queryset = CatalogSale.objects.all() + serializer_class = CatalogSaleSerializer + + class ProductView(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer diff --git a/tienda_ilusion/don_confiao/migrations/0045_catalogsale_catalogsaleline.py b/tienda_ilusion/don_confiao/migrations/0045_catalogsale_catalogsaleline.py new file mode 100644 index 0000000..d060562 --- /dev/null +++ b/tienda_ilusion/don_confiao/migrations/0045_catalogsale_catalogsaleline.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.6 on 2026-05-28 21:29 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('don_confiao', '0044_alter_payment_type_payment_alter_sale_payment_method'), + ] + + operations = [ + migrations.CreateModel( + name='CatalogSale', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField(verbose_name='Date')), + ('phone', models.CharField(blank=True, max_length=13, null=True)), + ('description', models.CharField(blank=True, max_length=255, null=True)), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='don_confiao.customer')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='CatalogSaleLine', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.DecimalField(decimal_places=2, max_digits=10, null=True)), + ('unit_price', models.DecimalField(decimal_places=2, max_digits=9)), + ('description', models.CharField(blank=True, max_length=255, null=True)), + ('catalog_sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='don_confiao.catalogsale')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='don_confiao.product')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/tienda_ilusion/don_confiao/models/sales.py b/tienda_ilusion/don_confiao/models/sales.py index 1cf8f2f..17061b5 100644 --- a/tienda_ilusion/don_confiao/models/sales.py +++ b/tienda_ilusion/don_confiao/models/sales.py @@ -6,11 +6,31 @@ from django.core.exceptions import ValidationError from datetime import datetime -class Sale(models.Model): +class SaleAbstractModel(models.Model): customer = models.ForeignKey(Customer, on_delete=models.PROTECT) date = models.DateTimeField("Date") phone = models.CharField(max_length=13, null=True, blank=True) description = models.CharField(max_length=255, null=True, blank=True) + + class Meta: + abstract = True + + +class SaleLineAbstractModel(models.Model): + product = models.ForeignKey( + Product, null=False, blank=False, on_delete=models.CASCADE + ) + quantity = models.DecimalField( + max_digits=10, decimal_places=2, null=True + ) + unit_price = models.DecimalField(max_digits=9, decimal_places=2) + description = models.CharField(max_length=255, null=True, blank=True) + + class Meta: + abstract = True + + +class Sale(SaleAbstractModel): payment_method = models.CharField( max_length=30, choices=PaymentMethods.choices, @@ -46,22 +66,31 @@ class Sale(models.Model): return sale_header_csv -class SaleLine(models.Model): +class SaleLine(SaleLineAbstractModel): sale = models.ForeignKey(Sale, on_delete=models.CASCADE) - product = models.ForeignKey( - Product, null=False, blank=False, on_delete=models.CASCADE - ) - quantity = models.DecimalField( - max_digits=10, decimal_places=2, null=True - ) - unit_price = models.DecimalField(max_digits=9, decimal_places=2) - description = models.CharField(max_length=255, null=True, blank=True) def __str__(self): return f"{self.sale} - {self.product}" +class CatalogSale(SaleAbstractModel): + + def __str__(self): + return f"{self.date} {self.customer}" + + def get_total(self): + lines = self.catalogsaleline_set.all() + return sum([l.quantity * l.unit_price for l in lines]) + + +class CatalogSaleLine(SaleLineAbstractModel): + catalog_sale = models.ForeignKey(CatalogSale, on_delete=models.CASCADE) + + def __str__(self): + return f"{self.catalog_sale} - {self.product}" + + class Payment(models.Model): date_time = models.DateTimeField() type_payment = models.CharField( diff --git a/tienda_ilusion/don_confiao/serializers.py b/tienda_ilusion/don_confiao/serializers.py index 0fa113a..ccf992e 100644 --- a/tienda_ilusion/don_confiao/serializers.py +++ b/tienda_ilusion/don_confiao/serializers.py @@ -2,7 +2,13 @@ from rest_framework import serializers from .models.sales import Sale, SaleLine from .models.customers import Customer -from .models.sales import Sale, SaleLine, Payment +from .models.sales import ( + Sale, + SaleLine, + CatalogSale, + CatalogSaleLine, + Payment, +) from .models.products import Product, ProductCategory from .models.payments import ReconciliationJar @@ -29,6 +35,44 @@ class SaleSerializer(serializers.ModelSerializer): ] +class CatalogSaleLineSerializer(serializers.ModelSerializer): + class Meta: + model = CatalogSaleLine + fields = [ + "id", + "catalog_sale", + "product", + "unit_price", + "quantity", + ] + + +class CatalogSaleSerializer(serializers.ModelSerializer): + catalogsaleline_set = CatalogSaleLineSerializer( + many=True, required=False + ) + total = serializers.ReadOnlyField(source="get_total") + + class Meta: + model = CatalogSale + fields = [ + "id", + "customer", + "date", + "catalogsaleline_set", + "total", + ] + + def create(self, validated_data): + lines_data = validated_data.pop("catalogsaleline_set", []) + catalog_sale = CatalogSale.objects.create(**validated_data) + for line_data in lines_data: + CatalogSaleLine.objects.create( + catalog_sale=catalog_sale, **line_data + ) + return catalog_sale + + class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product diff --git a/tienda_ilusion/don_confiao/tests/test_api.py b/tienda_ilusion/don_confiao/tests/test_api.py index ccaf3dd..902e8e6 100644 --- a/tienda_ilusion/don_confiao/tests/test_api.py +++ b/tienda_ilusion/don_confiao/tests/test_api.py @@ -34,6 +34,16 @@ class TestAPI(APITestCase, LoginMixin): self.assertEqual(sale.id, content["id"]) self.assertIsNone(sale.external_id) + def test_create_catalog_sale(self): + response = self._create_catalog_sale() + content = json.loads(response.content.decode("utf-8")) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + # self.assertEqual(Sale.objects.count(), 1) + # sale = Sale.objects.all()[0] + # self.assertEqual(sale.customer.name, self.customer.name) + # self.assertEqual(sale.id, content["id"]) + # self.assertTrue(sale.catalog_sale) + def test_create_sale_with_decimal(self): response = self._create_sale_with_decimal() content = json.loads(response.content.decode("utf-8")) diff --git a/tienda_ilusion/don_confiao/urls.py b/tienda_ilusion/don_confiao/urls.py index 8df75b1..a381834 100644 --- a/tienda_ilusion/don_confiao/urls.py +++ b/tienda_ilusion/don_confiao/urls.py @@ -8,6 +8,9 @@ app_name = "don_confiao" router = DefaultRouter() router.register(r"sales", api_views.SaleView, basename="sale") +router.register( + r"catalog_sales", api_views.CatalogSaleView, basename="catalog_sale" +) router.register(r"customers", api_views.CustomerView, basename="customer") router.register(r"products", api_views.ProductView, basename="product") router.register( @@ -23,6 +26,17 @@ urlpatterns = [ api_views.SaleSummary.as_view(), name="purchase_json_summary", ), + path( + "payment_methods/all/select_format", + api_views.PaymentMethodView.as_view(), + name="payment_methods_to_select", + ), + path( + "purchases/for_reconciliation", + api_views.SalesForReconciliationView.as_view(), + name="sales_for_reconciliation", + ), + path("reconciliate_jar", api_views.ReconciliateJarView.as_view()), path("api/", include(router.urls)), path( "api/importar_productos_de_tryton", @@ -39,17 +53,6 @@ urlpatterns = [ api_views.SalesToTrytonView.as_view(), name="send_tryton", ), - path( - "payment_methods/all/select_format", - api_views.PaymentMethodView.as_view(), - name="payment_methods_to_select", - ), - path( - "purchases/for_reconciliation", - api_views.SalesForReconciliationView.as_view(), - name="sales_for_reconciliation", - ), - path("reconciliate_jar", api_views.ReconciliateJarView.as_view()), path( "api/admin_code/validate/", api_views.AdminCodeValidateView.as_view(),