Merge branch 'feature/track-stock-on-checkout' into develop

This commit is contained in:
Nathan Chapman 2022-10-29 22:53:48 -06:00
commit ec8a518a7f
19 changed files with 310 additions and 44 deletions

View File

@ -19,10 +19,10 @@ class Address(models.Model):
def __str__(self): def __str__(self):
return f""" return f"""
{first_name} {last_name} {self.first_name} {self.last_name}
{street_address_1} {self.street_address_1}
{street_address_2} {self.street_address_2}
{city}, {state}, {postal_code} {self.city}, {self.state}, {self.postal_code}
""" """

View File

@ -0,0 +1,24 @@
# Generated by Django 4.0.2 on 2022-10-29 14:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='shippingrate',
name='is_selectable',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='sitesettings',
name='default_shipping_rate',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='core.shippingrate'),
),
]

View File

@ -56,17 +56,6 @@ class SingletonBase(models.Model):
abstract = True abstract = True
class SiteSettings(SingletonBase):
usps_user_id = models.CharField(max_length=255)
def __str__(self):
return 'Site Settings'
class Meta:
verbose_name = 'Site Settings'
verbose_name_plural = 'Site Settings'
class ProductCategory(models.Model): class ProductCategory(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
main_category = models.BooleanField(default=True) main_category = models.BooleanField(default=True)
@ -271,6 +260,7 @@ class ShippingRate(models.Model):
blank=True, blank=True,
null=True null=True
) )
is_selectable = models.BooleanField(default=True)
def get_absolute_url(self): def get_absolute_url(self):
return reverse('dashboard:rate-detail', kwargs={'pk': self.pk}) return reverse('dashboard:rate-detail', kwargs={'pk': self.pk})
@ -366,6 +356,14 @@ class Order(models.Model):
objects = OrderManager() objects = OrderManager()
def minus_stock(self):
for line in self.lines.all():
line.minus_stock()
def add_stock(self):
for line in self.lines.all():
line.add_stock()
def get_total_quantity(self): def get_total_quantity(self):
return sum([line.quantity for line in self]) return sum([line.quantity for line in self])
@ -443,6 +441,16 @@ class OrderLine(models.Model):
def quantity_unfulfilled(self): def quantity_unfulfilled(self):
return self.quantity - self.quantity_fulfilled return self.quantity - self.quantity_fulfilled
def minus_stock(self):
if self.variant.track_inventory:
self.variant.stock -= self.quantity
self.variant.save()
def add_stock(self):
if self.variant.track_inventory:
self.variant.stock += self.quantity
self.variant.save()
class TrackingNumber(models.Model): class TrackingNumber(models.Model):
order = models.ForeignKey( order = models.ForeignKey(
@ -472,3 +480,21 @@ class Subscription(models.Model):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True null=True
) )
class SiteSettings(SingletonBase):
usps_user_id = models.CharField(max_length=255)
default_shipping_rate = models.ForeignKey(
ShippingRate,
blank=True,
null=True,
related_name='+',
on_delete=models.SET_NULL
)
def __str__(self):
return 'Site Settings'
class Meta:
verbose_name = 'Site Settings'
verbose_name_plural = 'Site Settings'

View File

@ -22,5 +22,24 @@
<span class="object__item">No customers</span> <span class="object__item">No customers</span>
{% endfor %} {% endfor %}
</section> </section>
<section>
<div class="pagination">
<p class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">&laquo; first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</p>
</div>
</section>
</article> </article>
{% endblock content %} {% endblock content %}

View File

@ -0,0 +1,18 @@
{% extends "dashboard.html" %}
{% block content %}
<article class="product">
<header class="object__header">
<h1>Update option</h1>
</header>
<section class="object__panel">
<form class="panel__item" method="POST" action="{% url 'dashboard:option-update' option.pk %}">
{% csrf_token %}
{{form.as_p}}
<p class="form__submit">
<input class="action-button" type="submit" value="Create option"> or <a href="{% url 'dashboard:option-detail' option.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -4,7 +4,10 @@
{% block content %} {% block content %}
<article> <article>
<header class="object__header"> <header class="object__header">
<h1><img src="{% static 'images/box.png' %}" alt=""> Order #{{order.pk}}</h1> <div>
<h1><img src="{% static 'images/box.png' %}" alt=""> Order #{{order.pk}}</h1>
<p>Date: {{ order.created_at }}</p>
</div>
<div class="object__menu"> <div class="object__menu">
<a class="action-button action-button--warning" href="{% url 'dashboard:order-cancel' order.pk %}">Cancel order</a> <a class="action-button action-button--warning" href="{% url 'dashboard:order-cancel' order.pk %}">Cancel order</a>
<span class="order__status order__status--{{order.status}}">{{order.get_status_display}} ({{order.total_quantity_fulfilled}} / {{order.total_quantity_ordered}})</span> <span class="order__status order__status--{{order.status}}">{{order.get_status_display}} ({{order.total_quantity_fulfilled}} / {{order.total_quantity_ordered}})</span>
@ -102,7 +105,7 @@
<span>Discount: {{order.coupon.discount_value}} {{order.coupon.get_discount_value_type_display}}</span><br> <span>Discount: {{order.coupon.discount_value}} {{order.coupon.get_discount_value_type_display}}</span><br>
{% endif %} {% endif %}
<span>Shipping: ${{order.shipping_total}}</span><br> <span>Shipping: ${{order.shipping_total}}</span><br>
<span>Total: ${{order.get_total_price_after_discount}}</span> <span>Total: ${{order.total_amount}}</span>
</p> </p>
</div> </div>
</section> </section>

View File

@ -10,7 +10,7 @@
{% csrf_token %} {% csrf_token %}
{{form.as_p}} {{form.as_p}}
<p class="form__submit"> <p class="form__submit">
<input class="action-button" type="submit" value="Create rate"> or <a href="{% url 'dashboard:rate-detail' rate.pk %}">cancel</a> <input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'dashboard:rate-detail' rate.pk %}">cancel</a>
</p> </p>
</form> </form>
</section> </section>

View File

@ -0,0 +1,34 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static 'images/warehouse.png' %}" alt=""> Stock</h1>
<p><strong>Total in warehouse</strong> = available stock + unfulfilled</p>
</header>
<section class="object__list">
<div class="object__item panel__header object__item--col5">
<span>Product</span>
<span>SKU</span>
<span>Available Stock</span>
<span>Total in warehouse</span>
</div>
{% for variant in variant_list %}
<div class="object__item object__item--col5">
{% with product=variant.product %}
<figure class="item__figure">
<img class="product__image product__image--small" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
<figcaption><strong>{{variant}}</strong></figcaption>
</figure>
<span>{{ variant.sku }}</span>
<span>{{ variant.stock }}</span>
<span>{{ variant.total_in_warehouse }}</span>
<a href="{% url 'dashboard:variant-restock' product.pk variant.pk %}" class="action-button">Restock &rarr;</a>
{% endwith %}
</div>
{% endfor %}
</section>
</article>
{% endblock %}

View File

@ -11,7 +11,7 @@
{% csrf_token %} {% csrf_token %}
{{form.as_p}} {{form.as_p}}
<p class="form__submit"> <p class="form__submit">
<input class="action-button" type="submit" value="Create variant"> or <a href="{% url 'dashboard:product-detail' product.pk %}">cancel</a> <input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'dashboard:product-detail' product.pk %}">cancel</a>
</p> </p>
</form> </form>
</section> </section>

View File

@ -0,0 +1,19 @@
{% extends "dashboard.html" %}
{% block content %}
<article class="product">
<header class="object__header">
<h1>Restock variant</h1>
</header>
<section class="object__panel">
<form class="panel__item" method="POST" action="{% url 'dashboard:variant-restock' product.pk variant.pk %}">
{% csrf_token %}
{{form.as_p}}
<p>Total in warehouse: {{ variant.total_in_warehouse }}</p>
<p class="form__submit">
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'dashboard:stock' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -17,6 +17,11 @@ urlpatterns = [
views.CatalogView.as_view(), views.CatalogView.as_view(),
name='catalog' name='catalog'
), ),
path(
'stock/',
views.StockView.as_view(),
name='stock'
),
path( path(
'shipping-rates/new/', 'shipping-rates/new/',
@ -186,6 +191,11 @@ urlpatterns = [
views.ProductVariantDeleteView.as_view(), views.ProductVariantDeleteView.as_view(),
name='variant-delete' name='variant-delete'
), ),
path(
'restock/',
views.ProductVariantStockUpdateView.as_view(),
name='variant-restock'
),
])), ])),
])), ])),
])), ])),

View File

@ -18,7 +18,8 @@ from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import ( 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 from django.db.models.functions import Coalesce
@ -101,6 +102,25 @@ class CatalogView(ListView):
return context return context
class StockView(ListView):
model = ProductVariant
context_object_name = 'variant_list'
template_name = 'dashboard/stock.html'
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')
return object_list
class ShippingRateDetailView(LoginRequiredMixin, DetailView): class ShippingRateDetailView(LoginRequiredMixin, DetailView):
model = ShippingRate model = ShippingRate
context_object_name = 'rate' context_object_name = 'rate'
@ -234,6 +254,11 @@ class OrderCancelView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
'status': OrderStatus.CANCELED 'status': OrderStatus.CANCELED
} }
def form_valid(self, form):
form.instance.add_stock()
form.instance.save()
return super().form_valid(form)
def get_success_url(self): def get_success_url(self):
return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk}) return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk})
@ -441,6 +466,38 @@ class ProductVariantDeleteView(SuccessMessageMixin, DeleteView):
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']}) 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): class ProductOptionDetailView(LoginRequiredMixin, DetailView):
model = ProductOption model = ProductOption
template_name = 'dashboard/option_detail.html' template_name = 'dashboard/option_detail.html'
@ -482,6 +539,7 @@ class ProductOptionDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteVie
class CustomerListView(LoginRequiredMixin, ListView): class CustomerListView(LoginRequiredMixin, ListView):
model = User model = User
template_name = 'dashboard/customer_list.html' template_name = 'dashboard/customer_list.html'
paginate_by = 100
def get_queryset(self): def get_queryset(self):
object_list = User.objects.filter( object_list = User.objects.filter(
@ -492,7 +550,7 @@ class CustomerListView(LoginRequiredMixin, ListView):
'orders' 'orders'
).annotate( ).annotate(
num_orders=Count('orders') num_orders=Count('orders')
) ).order_by('first_name', 'last_name')
return object_list return object_list

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -72,19 +72,39 @@ class Cart:
if update_quantity: if update_quantity:
self.cart[item['variant']]['quantity'] = item['quantity'] self.cart[item['variant']]['quantity'] = item['quantity']
else: else:
self.cart.append(item) self.add_or_update_item(item)
# TODO: abstract this to a function that will check the max amount of item in the cart # TODO: abstract this to a function that will check the max amount of item in the cart
if len(self) <= 20: if len(self) <= 20:
self.check_item_stock_quantities(request)
self.save() self.save()
else: else:
messages.warning(request, "Cart is full: 20 items or less.") messages.warning(request, "Cart is full: 20 items or less.")
def add_or_update_item(self, new_item):
new_item_pk = int(new_item['variant'])
for item in self:
if new_item_pk == item['variant'].pk:
if new_item['options'] == item['options']:
item['quantity'] += new_item['quantity']
return
else:
continue
self.cart.append(new_item)
def save(self): def save(self):
self.session[settings.CART_SESSION_ID] = self.cart self.session[settings.CART_SESSION_ID] = self.cart
self.session.modified = True self.session.modified = True
logger.info(f'\nCart:\n{self.cart}\n') 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): def remove(self, pk):
self.cart.pop(pk) self.cart.pop(pk)
self.save() self.save()
@ -123,6 +143,9 @@ class Cart:
return 0 return 0
def get_shipping_container_choices(self): def get_shipping_container_choices(self):
is_selectable = Q(
is_selectable=True
)
min_weight_matched = Q( min_weight_matched = Q(
min_order_weight__lte=self.get_total_weight()) | Q( min_order_weight__lte=self.get_total_weight()) | Q(
min_order_weight__isnull=True min_order_weight__isnull=True
@ -132,7 +155,7 @@ class Cart:
max_order_weight__isnull=True max_order_weight__isnull=True
) )
containers = ShippingRate.objects.filter( containers = ShippingRate.objects.filter(
min_weight_matched & max_weight_matched is_selectable & min_weight_matched & max_weight_matched
) )
return containers return containers
@ -146,10 +169,7 @@ class Cart:
container, container,
str(self.session.get('shipping_address')['postal_code']) 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) usps = USPSApi(settings.USPS_USER_ID, test=True)
try: try:
@ -159,12 +179,12 @@ class Cart:
'Could not connect to USPS, try again.' 'Could not connect to USPS, try again.'
) )
logger.error(validation.result) logger.info(validation.result)
package = dict(validation.result['RateV4Response']['Package']) package = dict(validation.result['RateV4Response']['Package'])
if 'Error' not in package: if 'Error' not in package:
rate = package['Postage']['CommercialRate'] rate = package['Postage']['CommercialRate']
else: else:
logger.error("USPS Rate error") logger.error('USPS Rate error')
rate = '0.00' rate = '0.00'
return Decimal(rate) return Decimal(rate)
else: else:

View File

@ -11,7 +11,7 @@ from localflavor.us.us_states import USPS_CHOICES
from usps import USPSApi, Address from usps import USPSApi, Address
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from core.models import Order from core.models import Order, ProductVariant
from core import CoffeeGrind, ShippingContainer from core import CoffeeGrind, ShippingContainer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -10,7 +10,6 @@
<h1>Welcome to our new website!</h1> <h1>Welcome to our new website!</h1>
<h4>NEW COOL LOOK, SAME GREAT COFFEE</h4> <h4>NEW COOL LOOK, SAME GREAT COFFEE</h4>
</div> </div>
{# Home > Category > "Coffee/Merchandise" #}
<article> <article>
<div class="breadcrumbs"> <div class="breadcrumbs">
<menu> <menu>

View File

@ -62,7 +62,7 @@
</tr> </tr>
<tr> <tr>
<th>Total</th> <th>Total</th>
<td><strong>${{order.get_total_price_after_discount}}</strong></td> <td><strong>${{order.total_amount}}</strong></td>
</tr> </tr>
</table> </table>
</section> </section>

View File

@ -7,6 +7,7 @@ from django.utils import timezone
from django.shortcuts import render, reverse, redirect, get_object_or_404 from django.shortcuts import render, reverse, redirect, get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.http import JsonResponse, HttpResponseRedirect from django.http import JsonResponse, HttpResponseRedirect
from django.views.generic.base import View, RedirectView, TemplateView from django.views.generic.base import View, RedirectView, TemplateView
@ -35,8 +36,9 @@ from accounts.forms import (
AddressForm as AccountAddressForm, CustomerUpdateForm AddressForm as AccountAddressForm, CustomerUpdateForm
) )
from core.models import ( from core.models import (
ProductCategory, Product, ProductOption, ProductCategory, Product, ProductVariant, ProductOption,
Order, Transaction, OrderLine, Coupon, ShippingRate Order, Transaction, OrderLine, Coupon, ShippingRate,
SiteSettings
) )
from core.forms import ShippingRateForm from core.forms import ShippingRateForm
from core import OrderStatus, ShippingContainer from core import OrderStatus, ShippingContainer
@ -79,7 +81,12 @@ class CartAddProductView(SingleObjectMixin, FormView):
return reverse('storefront:cart-detail') return reverse('storefront:cart-detail')
def get_form(self, form_class=None): def get_form(self, form_class=None):
variants = self.get_object().variants.all() variants = self.get_object().variants.filter(
Q(track_inventory=False) | Q(
track_inventory=True,
stock__gt=0
)
)
options = ProductOption.objects.filter(products__pk=self.get_object().pk) options = ProductOption.objects.filter(products__pk=self.get_object().pk)
if form_class is None: if form_class is None:
form_class = self.get_form_class() form_class = self.get_form_class()
@ -168,8 +175,10 @@ class ProductCategoryDetailView(DetailView):
Prefetch( Prefetch(
'product_set', 'product_set',
queryset=Product.objects.filter( queryset=Product.objects.filter(
visible_in_listings=True Q(visible_in_listings=True),
) Q(variants__track_inventory=False) |
Q(variants__track_inventory=True) & Q(variants__stock__gt=0)
).distinct()
) )
) )
return object_list return object_list
@ -192,7 +201,12 @@ class ProductDetailView(FormMixin, DetailView):
form_class = AddToCartForm form_class = AddToCartForm
def get_form(self, form_class=None): def get_form(self, form_class=None):
variants = self.object.variants.all() variants = self.object.variants.filter(
Q(track_inventory=False) | Q(
track_inventory=True,
stock__gt=0
)
)
options = ProductOption.objects.filter(products__pk=self.object.pk) options = ProductOption.objects.filter(products__pk=self.object.pk)
if form_class is None: if form_class is None:
form_class = self.get_form_class() form_class = self.get_form_class()
@ -210,7 +224,7 @@ class CheckoutAddressView(FormView):
if user.is_authenticated and user.default_shipping_address: if user.is_authenticated and user.default_shipping_address:
address = user.default_shipping_address address = user.default_shipping_address
initial = { initial = {
'full_name': address.first_name+' '+address.last_name, 'full_name': address.first_name + ' ' + address.last_name,
'email': user.email, 'email': user.email,
'street_address_1': address.street_address_1, 'street_address_1': address.street_address_1,
'street_address_2': address.street_address_2, 'street_address_2': address.street_address_2,
@ -221,7 +235,7 @@ class CheckoutAddressView(FormView):
elif self.request.session.get('shipping_address'): elif self.request.session.get('shipping_address'):
address = self.request.session.get('shipping_address') address = self.request.session.get('shipping_address')
initial = { initial = {
'full_name': address['first_name']+' '+address['last_name'], 'full_name': address['first_name'] + ' ' + address['last_name'],
'email': address['email'], 'email': address['email'],
'street_address_1': address['street_address_1'], 'street_address_1': address['street_address_1'],
'street_address_2': address['street_address_2'], 'street_address_2': address['street_address_2'],
@ -255,6 +269,13 @@ class CheckoutShippingView(FormView):
template_name = 'storefront/checkout_shipping_form.html' template_name = 'storefront/checkout_shipping_form.html'
form_class = CheckoutShippingForm form_class = CheckoutShippingForm
success_url = reverse_lazy('storefront:order-create') success_url = reverse_lazy('storefront:order-create')
containers = None
def get_containers(self, request):
if self.containers is None:
cart = Cart(request)
self.containers = cart.get_shipping_container_choices()
return self.containers
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if not self.request.session.get('shipping_address'): if not self.request.session.get('shipping_address'):
@ -262,16 +283,27 @@ class CheckoutShippingView(FormView):
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('storefront:checkout-address') reverse('storefront:checkout-address')
) )
site_settings = cache.get('SiteSettings')
cart = Cart(self.request)
if len(self.get_containers(request)) == 0:
self.request.session['shipping_container'] = site_settings.default_shipping_rate
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) return super().get(request, *args, **kwargs)
def get_form(self, form_class=None): def get_form(self, form_class=None):
cart = Cart(self.request) cart = Cart(self.request)
containers = cart.get_shipping_container_choices() for container in self.get_containers(self.request):
for container in containers:
container.s_cost = cart.get_shipping_cost(container.container) container.s_cost = cart.get_shipping_cost(container.container)
if form_class is None: if form_class is None:
form_class = self.get_form_class() form_class = self.get_form_class()
return form_class(containers, **self.get_form_kwargs()) return form_class(self.get_containers(self.request), **self.get_form_kwargs())
def form_valid(self, form): def form_valid(self, form):
shipping_container = ShippingRate.objects.get( shipping_container = ShippingRate.objects.get(
@ -370,6 +402,7 @@ def paypal_order_transaction_capture(request, transaction_id):
cart = Cart(request) cart = Cart(request)
order = Order.objects.get(pk=request.session.get('order_id')) order = Order.objects.get(pk=request.session.get('order_id'))
order.status = OrderStatus.UNFULFILLED order.status = OrderStatus.UNFULFILLED
order.minus_stock()
try: try:
coupon = Coupon.objects.get( coupon = Coupon.objects.get(
code=request.session.get('coupon_code') code=request.session.get('coupon_code')
@ -387,7 +420,6 @@ def paypal_order_transaction_capture(request, transaction_id):
transaction.save() transaction.save()
cart.clear() cart.clear()
logger.debug(f'\nPayPal Response data: {data}\n') logger.debug(f'\nPayPal Response data: {data}\n')
return JsonResponse(data) return JsonResponse(data)
else: else:
return JsonResponse({'details': 'invalid request'}) return JsonResponse({'details': 'invalid request'})

View File

@ -33,6 +33,10 @@
<img src="{% static 'images/cubes.png' %}" alt=""> <img src="{% static 'images/cubes.png' %}" alt="">
Catalog Catalog
</a> </a>
<a href="{% url 'dashboard:stock' %}">
<img src="{% static 'images/warehouse.png' %}" alt="">
Stock
</a>
<a href="{% url 'dashboard:order-list' %}"> <a href="{% url 'dashboard:order-list' %}">
<img src="{% static 'images/box.png' %}" alt=""> <img src="{% static 'images/box.png' %}" alt="">
Orders Orders