From 0e1f32d5b9fba54329d0100d8edf21206909be92 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sat, 29 Oct 2022 22:53:32 -0600 Subject: [PATCH] Finalize stock feature --- .../templates/dashboard/order_detail.html | 7 ++- .../templates/dashboard/rate_form.html | 2 +- src/dashboard/templates/dashboard/stock.html | 31 ++++++++----- .../templates/dashboard/variant_restock.html | 19 ++++++++ src/dashboard/urls.py | 5 ++ src/dashboard/views.py | 46 +++++++++++++++++-- src/storefront/cart.py | 18 +++++--- src/storefront/forms.py | 2 +- .../templates/storefront/order_detail.html | 2 +- src/storefront/views.py | 5 ++ src/templates/dashboard.html | 2 - 11 files changed, 111 insertions(+), 28 deletions(-) create mode 100644 src/dashboard/templates/dashboard/variant_restock.html diff --git a/src/dashboard/templates/dashboard/order_detail.html b/src/dashboard/templates/dashboard/order_detail.html index 0c325b8..c530b70 100644 --- a/src/dashboard/templates/dashboard/order_detail.html +++ b/src/dashboard/templates/dashboard/order_detail.html @@ -4,7 +4,10 @@ {% block content %}
-

Order #{{order.pk}}

+
+

Order #{{order.pk}}

+

Date: {{ order.created_at }}

+
Cancel order {{order.get_status_display}} ({{order.total_quantity_fulfilled}} / {{order.total_quantity_ordered}}) @@ -102,7 +105,7 @@ Discount: {{order.coupon.discount_value}} {{order.coupon.get_discount_value_type_display}}
{% endif %} Shipping: ${{order.shipping_total}}
- Total: ${{order.get_total_price_after_discount}} + Total: ${{order.total_amount}}

diff --git a/src/dashboard/templates/dashboard/rate_form.html b/src/dashboard/templates/dashboard/rate_form.html index 81e364e..0947361 100644 --- a/src/dashboard/templates/dashboard/rate_form.html +++ b/src/dashboard/templates/dashboard/rate_form.html @@ -10,7 +10,7 @@ {% csrf_token %} {{form.as_p}}

- or cancel + or cancel

diff --git a/src/dashboard/templates/dashboard/stock.html b/src/dashboard/templates/dashboard/stock.html index fe545ed..b846316 100644 --- a/src/dashboard/templates/dashboard/stock.html +++ b/src/dashboard/templates/dashboard/stock.html @@ -5,21 +5,30 @@

Stock

+

Total in warehouse = available stock + unfulfilled

-
-
-

Products

+
+
+ Product + SKU + Available Stock + Total in warehouse
-
- {% for variant in variant_list %} -
-

{{ variant }}

-

Variant ID: {{ variant.pk }}

-

Total in warehouse: {{ variant.stock }}

-
- {% endfor %} + {% for variant in variant_list %} +
+ {% with product=variant.product %} +
+ {{product.get_first_img.image}} +
{{variant}}
+
+ {{ variant.sku }} + {{ variant.stock }} + {{ variant.total_in_warehouse }} + Restock → + {% endwith %}
+ {% endfor %}
{% endblock %} diff --git a/src/dashboard/templates/dashboard/variant_restock.html b/src/dashboard/templates/dashboard/variant_restock.html new file mode 100644 index 0000000..c75f805 --- /dev/null +++ b/src/dashboard/templates/dashboard/variant_restock.html @@ -0,0 +1,19 @@ +{% extends "dashboard.html" %} + +{% block content %} +
+
+

Restock variant

+
+
+
+ {% csrf_token %} + {{form.as_p}} +

Total in warehouse: {{ variant.total_in_warehouse }}

+

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/dashboard/urls.py b/src/dashboard/urls.py index c2b6fdf..dc2d70c 100644 --- a/src/dashboard/urls.py +++ b/src/dashboard/urls.py @@ -191,6 +191,11 @@ urlpatterns = [ views.ProductVariantDeleteView.as_view(), name='variant-delete' ), + path( + 'restock/', + views.ProductVariantStockUpdateView.as_view(), + name='variant-restock' + ), ])), ])), ])), diff --git a/src/dashboard/views.py b/src/dashboard/views.py index 7e5814e..1ceb8cd 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -18,7 +18,8 @@ from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from django.db.models import ( - Exists, OuterRef, Prefetch, Subquery, Count, Sum, Avg, F, Q, Value + Exists, OuterRef, Prefetch, Subquery, Count, Sum, Avg, F, Q, Value, + ExpressionWrapper, IntegerField ) from django.db.models.functions import Coalesce @@ -109,9 +110,14 @@ class StockView(ListView): def get_queryset(self): object_list = ProductVariant.objects.filter( track_inventory=True + ).prefetch_related('order_lines', 'product').annotate( + total_in_warehouse=F('stock') + Coalesce(Sum('order_lines__quantity', filter=Q( + order_lines__order__status=OrderStatus.UNFULFILLED) | Q( + order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED) + ) - Sum('order_lines__quantity_fulfilled', filter=Q( + order_lines__order__status=OrderStatus.UNFULFILLED) | Q( + order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED)), 0) ).order_by('product') - # quantity - # quantity_fulfilled return object_list @@ -460,6 +466,38 @@ class ProductVariantDeleteView(SuccessMessageMixin, DeleteView): return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']}) +class ProductVariantStockUpdateView(LoginRequiredMixin, UpdateView): + model = ProductVariant + pk_url_kwarg = 'variant_pk' + success_message = 'ProductVariant saved.' + success_url = reverse_lazy('dashboard:stock') + template_name = 'dashboard/variant_restock.html' + fields = [ + 'stock', + ] + context_object_name = 'variant' + + def get_queryset(self): + queryset = ProductVariant.objects.annotate( + total_in_warehouse=F('stock') + Coalesce(Sum('order_lines__quantity', filter=Q( + order_lines__order__status=OrderStatus.UNFULFILLED) | Q( + order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED) + ) - Sum('order_lines__quantity_fulfilled', filter=Q( + order_lines__order__status=OrderStatus.UNFULFILLED) | Q( + order_lines__order__status=OrderStatus.PARTIALLY_FULFILLED)), 0) + ).prefetch_related('order_lines', 'product') + return queryset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['product'] = Product.objects.get(pk=self.kwargs['pk']) + return context + + def form_valid(self, form): + form.instance.product = Product.objects.get(pk=self.kwargs['pk']) + return super().form_valid(form) + + class ProductOptionDetailView(LoginRequiredMixin, DetailView): model = ProductOption template_name = 'dashboard/option_detail.html' @@ -512,7 +550,7 @@ class CustomerListView(LoginRequiredMixin, ListView): 'orders' ).annotate( num_orders=Count('orders') - ) + ).order_by('first_name', 'last_name') return object_list diff --git a/src/storefront/cart.py b/src/storefront/cart.py index 8e6d381..ff0c03e 100644 --- a/src/storefront/cart.py +++ b/src/storefront/cart.py @@ -76,6 +76,7 @@ class Cart: # TODO: abstract this to a function that will check the max amount of item in the cart if len(self) <= 20: + self.check_item_stock_quantities(request) self.save() else: messages.warning(request, "Cart is full: 20 items or less.") @@ -96,6 +97,14 @@ class Cart: self.session.modified = True logger.info(f'\nCart:\n{self.cart}\n') + def check_item_stock_quantities(self, request): + for item in self: + if item['variant'].track_inventory: + if item['quantity'] > item['variant'].stock: + messages.warning(request, 'Quantity added exceeds available stock.') + item['quantity'] = item['variant'].stock + self.save() + def remove(self, pk): self.cart.pop(pk) self.save() @@ -160,10 +169,7 @@ class Cart: container, str(self.session.get('shipping_address')['postal_code']) ) - try: - logger.info('wafd') - except TypeError as e: - return Decimal('0.00') + usps = USPSApi(settings.USPS_USER_ID, test=True) try: @@ -173,12 +179,12 @@ class Cart: 'Could not connect to USPS, try again.' ) - logger.error(validation.result) + logger.info(validation.result) package = dict(validation.result['RateV4Response']['Package']) if 'Error' not in package: rate = package['Postage']['CommercialRate'] else: - logger.error("USPS Rate error") + logger.error('USPS Rate error') rate = '0.00' return Decimal(rate) else: diff --git a/src/storefront/forms.py b/src/storefront/forms.py index 940fced..fbe7a1c 100644 --- a/src/storefront/forms.py +++ b/src/storefront/forms.py @@ -11,7 +11,7 @@ from localflavor.us.us_states import USPS_CHOICES from usps import USPSApi, Address from captcha.fields import CaptchaField -from core.models import Order +from core.models import Order, ProductVariant from core import CoffeeGrind, ShippingContainer logger = logging.getLogger(__name__) diff --git a/src/storefront/templates/storefront/order_detail.html b/src/storefront/templates/storefront/order_detail.html index d5eb3ca..3ee8ba9 100644 --- a/src/storefront/templates/storefront/order_detail.html +++ b/src/storefront/templates/storefront/order_detail.html @@ -62,7 +62,7 @@ Total - ${{order.get_total_price_after_discount}} + ${{order.total_amount}} diff --git a/src/storefront/views.py b/src/storefront/views.py index a332159..7e46c41 100644 --- a/src/storefront/views.py +++ b/src/storefront/views.py @@ -290,6 +290,11 @@ class CheckoutShippingView(FormView): return HttpResponseRedirect( reverse('storefront:order-create') ) + elif len(self.get_containers(request)) == 1: + self.request.session['shipping_container'] = self.get_containers(request)[0] + return HttpResponseRedirect( + reverse('storefront:order-create') + ) return super().get(request, *args, **kwargs) def get_form(self, form_class=None): diff --git a/src/templates/dashboard.html b/src/templates/dashboard.html index 6409bb3..0f6734a 100644 --- a/src/templates/dashboard.html +++ b/src/templates/dashboard.html @@ -33,12 +33,10 @@ Catalog - Orders