diff --git a/shop/admin.py b/shop/admin.py index 557e7b3..baed5ef 100644 --- a/shop/admin.py +++ b/shop/admin.py @@ -13,6 +13,8 @@ from shop.models import ( Provider, Tag, Tax, + ShippingMethod, + CustomerAddress, ) @@ -115,3 +117,28 @@ class ProductBatchAdmin(ModelAdmin): "product", "quantity", ) + + +@admin.register(ShippingMethod) +class ShippingMethodAdmin(ModelAdmin): + list_display = ( + "id", + "name", + "description", + ) + + +@admin.register(CustomerAddress) +class CustomerAddressAdmin(ModelAdmin): + list_display = ( + "id", + "user", + "vat_id", + "address", + "address_state", + "address_zip", + ) + + def get_queryset(self, request): + qs = super().get_queryset(request) + return qs.filter(hidden=False) diff --git a/shop/migrations/0001_initial.py b/shop/migrations/0001_initial.py index 75729a7..dc4c69b 100644 --- a/shop/migrations/0001_initial.py +++ b/shop/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.3 on 2024-12-07 10:15 +# Generated by Django 5.1.3 on 2024-12-23 12:01 import django.db.models.deletion import django.utils.timezone @@ -233,6 +233,10 @@ class Migration(migrations.Migration): verbose_name="número de identificación fiscal", ), ), + ( + "full_name", + models.CharField(max_length=128, verbose_name="dirección"), + ), ("address", models.CharField(max_length=128, verbose_name="dirección")), ( "address_town", @@ -271,10 +275,20 @@ class Migration(migrations.Migration): verbose_name="tipo de dirección", ), ), - ("default", models.BooleanField(default=True)), + ( + "default", + models.BooleanField(default=True, verbose_name="por defecto"), + ), + ( + "hidden", + models.BooleanField( + db_index=True, default=False, verbose_name="oculto" + ), + ), ( "user", models.ForeignKey( + null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name="usuario", @@ -471,6 +485,12 @@ class Migration(migrations.Migration): verbose_name="Slug", ), ), + ( + "hidden", + models.BooleanField( + db_index=True, default=False, verbose_name="oculto" + ), + ), ( "brand", models.ForeignKey( @@ -697,6 +717,47 @@ class Migration(migrations.Migration): "verbose_name_plural": "remesas de productos", }, ), + migrations.CreateModel( + name="ShippingMethod", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=64, verbose_name="nombre")), + ( + "description", + models.CharField( + blank=True, + default="", + max_length=256, + verbose_name="description", + ), + ), + ("url", models.URLField(blank=True, default="", verbose_name="url")), + ( + "enabled", + models.BooleanField(default=True, verbose_name="habilitado"), + ), + ( + "shipping_product", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="shop.product", + verbose_name="producto de envío", + ), + ), + ], + options={ + "verbose_name": "método de envío", + "verbose_name_plural": "métodos de envío", + }, + ), migrations.CreateModel( name="ProductPrice", fields=[ diff --git a/shop/models.py b/shop/models.py index b42f03d..32d2844 100644 --- a/shop/models.py +++ b/shop/models.py @@ -78,6 +78,7 @@ class Product(TimestampedModel): slug = models.SlugField( default="", blank=True, null=True, max_length=128, verbose_name=_("Slug") ) + hidden = models.BooleanField(default=False, db_index=True, verbose_name=_("oculto")) def __str__(self): return self.name @@ -414,7 +415,7 @@ class CustomerAddress(models.Model): BILLING = "BILL", _("facturación") SHIPPING = "SHIP", _("envío") - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("usuario")) + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, verbose_name=_("usuario")) vat_id = models.CharField( max_length=16, blank=True, @@ -422,6 +423,9 @@ class CustomerAddress(models.Model): default="", verbose_name=_("número de identificación fiscal"), ) + full_name = models.CharField( + max_length=128, blank=False, null=False, verbose_name=_("dirección") + ) address = models.CharField( max_length=128, blank=False, null=False, verbose_name=_("dirección") ) @@ -451,8 +455,25 @@ class CustomerAddress(models.Model): default=Types.SHIPPING, verbose_name=_("tipo de dirección"), ) - default = models.BooleanField(default=True) + default = models.BooleanField(default=True, verbose_name=_("por defecto")) + hidden = models.BooleanField(default=False, db_index=True, verbose_name=_("oculto")) class Meta: verbose_name = _("dirección de cliente") verbose_name_plural = _("direcciones de cliente") + + +class ShippingMethod(models.Model): + name = models.CharField(max_length=64, verbose_name=_("nombre")) + description = models.CharField( + max_length=256, default="", blank=True, verbose_name=_("description") + ) + url = models.URLField(default="", blank=True, verbose_name=_("url")) + shipping_product = models.ForeignKey( + "shop.Product", on_delete=models.CASCADE, verbose_name=_("producto de envío") + ) + enabled = models.BooleanField(default=True, verbose_name=_("habilitado")) + + class Meta: + verbose_name = _("método de envío") + verbose_name_plural = _("métodos de envío") diff --git a/shop/utils.py b/shop/utils.py index bfcb16d..9787990 100644 --- a/shop/utils.py +++ b/shop/utils.py @@ -1,6 +1,15 @@ from decimal import Decimal -from shop.models import Order, OrderLine, Product, ProductBatch +from shop.models import ( + Order, + OrderLine, + Product, + ProductBatch, + Cart, + CustomerAddress, + ShippingMethod, + ProductPrice, +) from django.contrib.auth import get_user_model User = get_user_model() @@ -59,3 +68,67 @@ def delete_product_batch(batch: ProductBatch): product.stock = max(product.stock - batch.quantity, 0) product.save() batch.delete() + + +def create_order_from_cart( + cart: Cart, + billing_address: CustomerAddress, + shipping_address: CustomerAddress, + shipping_method: ShippingMethod, +): + order = Order.objects.create( + billing_address=billing_address.address, + billing_city=billing_address.address_town, + billing_state=billing_address.address_state, + billing_country=billing_address.address_country, + billing_zip=billing_address.address_zip, + shipping_address=shipping_address.address, + shipping_city=shipping_address.address_town, + shipping_state=shipping_address.address_state, + shipping_country=shipping_address.address_country, + shipping_zip=shipping_address.address_zip, + ) + + for item in cart.items.all(): + product = item.product + price: ProductPrice = product.price + base_total = price.price * item.quantity + tax_value = price.tax.value + taxes = base_total * (tax_value / 100) + total = base_total + taxes + + OrderLine.objects.create( + order=order, + product=product, + quantity=item.quantity, + price=price.price, + base_total=base_total, + tax_value=tax_value, + taxes=taxes, + total=total, + ) + + order.calculate_total_from_lines() + add_shipping_order_line(order, shipping_method) + + return order + + +def add_shipping_order_line(order, shipping_method): + shipping_price: ProductPrice = shipping_method.shipping_product.price + shipping_base_total = shipping_price.price + shipping_tax_value = shipping_price.tax.value + shipping_taxes = shipping_base_total * (shipping_tax_value / 100) + shipping_total = shipping_base_total + shipping_taxes + + OrderLine.objects.create( + order=order, + product=shipping_method.shipping_product, + quantity=1, + price=shipping_price.price, + base_total=shipping_base_total, + tax_value=shipping_tax_value, + taxes=shipping_taxes, + total=shipping_total, + ) + order.calculate_total_from_lines() diff --git a/web/templates/users/user_details.html b/web/templates/users/user_details.html index a658b4c..946f8fc 100644 --- a/web/templates/users/user_details.html +++ b/web/templates/users/user_details.html @@ -2,435 +2,100 @@ {% load i18n %} {% block main %} {% include 'components/generic/navbar.html' %} -
-
-

{% translate 'Mi cuenta' %}

+
+
+

+ {% translate 'Mi cuenta' %} +

-
-
-
-
-
-

Helene Engels

-
-
-
-
Email Address
-
helene@example.com
-
-
-
Home Address
-
- - 2 Miles Drive, NJ 071, New York, United States of America -
-
-
-
Delivery Address
-
- - 9th St. PATH Station, New York, United States of America -
-
-
-
-
-
Phone Number
-
+1234 567 890 / +12 345 678
-
-
-
Favorite pick-up point
-
- - Herald Square, 2, New York, United States of America -
-
-
-
My Companies
-
FLOWBITE LLC, Fiscal code: 18673557
-
-
-
Payment Methods
-
-
- - -
-
-
-

Visa ending in 7658

-

Expiry 10/2024

-
-
-
-
+
+
+

+ {{ user.first_name }} {{ user.last_name }}

- -
-
- -
+
{% endblock %} \ No newline at end of file diff --git a/web/views/components.py b/web/views/components.py index 948a6d7..a3fc175 100644 --- a/web/views/components.py +++ b/web/views/components.py @@ -1,6 +1,5 @@ from decimal import Decimal -from django.db.models import Sum from django.shortcuts import render from django.views.generic import TemplateView @@ -8,12 +7,12 @@ from shop.filters import ProductFilter from shop.models import CartItem, Product, ProductPrice from web.mixins import FilteredQuerysetMixin, PaginatedQuerysetMixin from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME -from web.utils import get_cart_total, get_or_create_cart +from web.utils import get_or_create_cart class ListProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): template_name = "web/list_products.html" - queryset = Product.objects.all().prefetch_related( + queryset = Product.objects.filter(hidden=False).prefetch_related( "images", ) filter_class = ProductFilter diff --git a/web/views/users.py b/web/views/users.py index e49d4d3..8981b24 100644 --- a/web/views/users.py +++ b/web/views/users.py @@ -4,6 +4,7 @@ from django.shortcuts import redirect, render, reverse from django.utils.text import gettext_lazy as _ from django.views.generic import TemplateView, View +from shop.models import CustomerAddress from users.forms import LoginForm from web.models import WebSettings @@ -77,6 +78,12 @@ class ResetPasswordView(TemplateView): class MyAccountView(TemplateView): template_name = "users/user_details.html" + def get_context_data(self, **kwargs): + addresses = CustomerAddress.objects.filter(user=self.request.user) + return { + "customer_addresses": addresses, + } + login = LoginView.as_view() logout = LogoutView.as_view()