From e514d72c8ddb92ac6b656ec5f2c8f38097359995 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Thu, 17 Aug 2023 08:44:56 -0600 Subject: [PATCH 1/5] Remove username field as it is no longer used --- accounts/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/accounts/utils.py b/accounts/utils.py index 82a0144..8496e6a 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -8,7 +8,6 @@ def get_or_create_customer(request, shipping_address): user, u_created = User.objects.get_or_create( email=shipping_address['email'].lower(), defaults={ - 'username': shipping_address['email'].lower(), 'is_staff': False, 'is_active': True, 'is_superuser': False, From c598f805e20936b397218f7db2866263881a5c34 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sat, 29 Jul 2023 11:48:17 -0600 Subject: [PATCH 2/5] Add base for wholesale orders --- accounts/fixtures/accounts.json | 44 ++----- core/fixtures/orders.json | 27 ++++- core/migrations/0009_wholesaleorder.py | 41 +++++++ core/models.py | 75 ++++++++++++ dashboard/forms.py | 1 + static/styles/main.css | 6 + storefront/forms.py | 108 +++++++++++++++++- .../templates/storefront/wholesale_order.html | 35 ++++++ .../wholesale_order_create_form.html | 31 +++++ .../storefront/wholesale_order_detail.html | 73 ++++++++++++ storefront/urls.py | 19 +++ storefront/views.py | 50 +++++++- templates/base.html | 1 + 13 files changed, 469 insertions(+), 42 deletions(-) create mode 100644 core/migrations/0009_wholesaleorder.py create mode 100644 storefront/templates/storefront/wholesale_order.html create mode 100644 storefront/templates/storefront/wholesale_order_create_form.html create mode 100644 storefront/templates/storefront/wholesale_order_detail.html diff --git a/accounts/fixtures/accounts.json b/accounts/fixtures/accounts.json index 7087acb..7b717de 100644 --- a/accounts/fixtures/accounts.json +++ b/accounts/fixtures/accounts.json @@ -1,28 +1,4 @@ [{ - "model": "accounts.address", - "pk": 1, - "fields": { - "first_name": "Nathan", - "last_name": "Chapman", - "street_address_1": "1504 N 230 E", - "street_address_2": "", - "city": "North Logan", - "state": "UT", - "postal_code": "84341" - } -}, { - "model": "accounts.address", - "pk": 2, - "fields": { - "first_name": "John", - "last_name": "Doe", - "street_address_1": "90415 Pollich Skyway", - "street_address_2": "", - "city": "Jaskolskiburgh", - "state": "MS", - "postal_code": "32715" - } -}, { "model": "accounts.user", "pk": 1, "fields": { @@ -36,11 +12,13 @@ "is_staff": true, "is_active": true, "date_joined": "2022-04-28T01:24:47.591Z", - "default_shipping_address": 1, - "default_billing_address": null, + "shipping_street_address_1": "1504 N 230 E", + "shipping_street_address_2": "", + "shipping_city": "North Logan", + "shipping_state": "UT", + "shipping_postal_code": "84341", "groups": [], - "user_permissions": [], - "addresses": [] + "user_permissions": [] } }, { "model": "accounts.user", @@ -56,10 +34,12 @@ "is_staff": false, "is_active": true, "date_joined": "2022-05-04T00:00:11Z", - "default_shipping_address": 2, - "default_billing_address": null, + "shipping_street_address_1": "90415 Pollich Skyway", + "shipping_street_address_2": "", + "shipping_city": "Jaskolskiburgh", + "shipping_state": "MS", + "shipping_postal_code": "32715", "groups": [], - "user_permissions": [], - "addresses": [] + "user_permissions": [] } }] diff --git a/core/fixtures/orders.json b/core/fixtures/orders.json index 1fbc41e..a596e4b 100644 --- a/core/fixtures/orders.json +++ b/core/fixtures/orders.json @@ -5,8 +5,13 @@ "fields": { "customer": 1, "status": "unfulfilled", - "billing_address": null, - "shipping_address": 1, + "shipping_first_name": "Nathan", + "shipping_last_name": "Chapman", + "shipping_street_address_1": "1504 N 230 E", + "shipping_street_address_2": "", + "shipping_city": "North Logan", + "shipping_state": "UT", + "shipping_postal_code": "84341", "coupon": null, "subtotal_amount": "26.80", "coupon_amount": "0.00", @@ -22,8 +27,13 @@ "fields": { "customer": 1, "status": "unfulfilled", - "billing_address": null, - "shipping_address": 1, + "shipping_first_name": "Nathan", + "shipping_last_name": "Chapman", + "shipping_street_address_1": "1504 N 230 E", + "shipping_street_address_2": "", + "shipping_city": "North Logan", + "shipping_state": "UT", + "shipping_postal_code": "84341", "coupon": null, "subtotal_amount": "26.80", "coupon_amount": "0.00", @@ -39,8 +49,13 @@ "fields": { "customer": 2, "status": "unfulfilled", - "billing_address": null, - "shipping_address": 1, + "shipping_first_name": "John", + "shipping_last_name": "Doe", + "shipping_street_address_1": "90415 Pollich Skyway", + "shipping_street_address_2": "", + "shipping_city": "Jaskolskiburgh", + "shipping_state": "MS", + "shipping_postal_code": "32715", "coupon": null, "subtotal_amount": "26.80", "coupon_amount": "0.00", diff --git a/core/migrations/0009_wholesaleorder.py b/core/migrations/0009_wholesaleorder.py new file mode 100644 index 0000000..ee5af24 --- /dev/null +++ b/core/migrations/0009_wholesaleorder.py @@ -0,0 +1,41 @@ +# Generated by Django 4.1.6 on 2023-07-29 02:28 + +from django.conf import settings +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0008_remove_order_billing_address_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='WholesaleOrder', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('brazil', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('dantes_tornado', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('decaf', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('ethiopia', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('loop_d_loop', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('moka_java', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('nicaragua', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('pantomime', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('sumatra', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(50)]), size=2)), + ('fulfilled', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('customer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wholesale_orders', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'wholesale order', + 'verbose_name_plural': 'wholesale orders', + }, + ), + ] diff --git a/core/models.py b/core/models.py index f265bd2..68d940c 100644 --- a/core/models.py +++ b/core/models.py @@ -14,6 +14,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator from django.core.serializers.json import DjangoJSONEncoder from django.contrib.postgres.fields import ArrayField, HStoreField from django.forms.models import model_to_dict +from django.contrib.postgres.fields import ArrayField from django_measurement.models import MeasurementField from localflavor.us.us_states import USPS_CHOICES @@ -688,3 +689,77 @@ class SiteSettings(SingletonBase): class Meta: verbose_name = 'Site Settings' verbose_name_plural = 'Site Settings' + + +class WholesaleOrder(models.Model): + customer = models.ForeignKey( + User, + related_name='wholesale_orders', + on_delete=models.SET_NULL, + null=True + ) + + brazil = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + dantes_tornado = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + decaf = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + ethiopia = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + loop_d_loop = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + moka_java = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + nicaragua = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + pantomime = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + sumatra = ArrayField( + models.PositiveSmallIntegerField(default=0, validators=[ + MinValueValidator(0), MaxValueValidator(50) + ]), + size=2, + ) + + fulfilled = models.BooleanField(default=False) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "wholesale order" + verbose_name_plural = "wholesale orders" + diff --git a/dashboard/forms.py b/dashboard/forms.py index ae168ab..ea2e5fc 100644 --- a/dashboard/forms.py +++ b/dashboard/forms.py @@ -122,3 +122,4 @@ class ProductPhotoForm(forms.ModelForm): class Meta: model = ProductPhoto fields = ('image',) + diff --git a/static/styles/main.css b/static/styles/main.css index ebe5083..a58acf0 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -1134,3 +1134,9 @@ footer > section { .show-modal { white-space: unset; } + + +#wholesale-faq { + display: flex; + gap: 3rem; +} diff --git a/storefront/forms.py b/storefront/forms.py index 7b3c377..f3b840a 100644 --- a/storefront/forms.py +++ b/storefront/forms.py @@ -11,12 +11,27 @@ from localflavor.us.us_states import USPS_CHOICES from usps import USPSApi, Address from django_measurement.forms import MeasurementField -from core.models import Order, ProductVariant, Subscription, SiteSettings +from core.models import ( + Order, ProductVariant, Subscription, SiteSettings, WholesaleOrder +) from core import CoffeeGrind, ShippingContainer logger = logging.getLogger(__name__) + +class SplitWidget(forms.MultiWidget): + def decompress(self, value): + if value: + return [value[0], value[1]] + return [0, 0] + + def value_from_datadict(self, data, files, name): + sixteen, five = super().value_from_datadict(data, files, name) + return [sixteen, five] + + + class VariantChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): return f'{obj.name} | ${obj.price}' @@ -142,3 +157,94 @@ class SubscriptionForm(forms.Form): stripe_price_id = forms.CharField(widget=forms.HiddenInput()) total_quantity = forms.IntegerField(widget=forms.HiddenInput()) total_weight = forms.CharField(widget=forms.HiddenInput()) + + +class WholesaleOrderCreateForm(forms.ModelForm): + class Meta: + model = WholesaleOrder + fields = [ + "brazil", + "dantes_tornado", + "decaf", + "ethiopia", + "loop_d_loop", + "moka_java", + "nicaragua", + "pantomime", + "sumatra", + ] + widgets = { + "brazil": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "dantes_tornado": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "decaf": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "ethiopia": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "loop_d_loop": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "moka_java": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "nicaragua": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "pantomime": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + "sumatra": SplitWidget(widgets={ + "16": forms.NumberInput(attrs={ + "size": 5 + }), + "5": forms.NumberInput(attrs={ + "size": 5 + }), + }), + } + diff --git a/storefront/templates/storefront/wholesale_order.html b/storefront/templates/storefront/wholesale_order.html new file mode 100644 index 0000000..7cdc9f5 --- /dev/null +++ b/storefront/templates/storefront/wholesale_order.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Wholesale

+
+ + {% if user.is_authenticated %} +
+

New wholesale order

+ + {% for order in user.wholesale_orders.all %} + + + + + {% endfor %} +
{{ order.created_at }} + +
+ {% else %} +
+
+

I'm already a wholesale customer

+

If you are already a wholesale customer, please login to place an order.

+

+
+

I would like to become a wholesale customer

+

You must become a wholesale customer to place an order. Contact us to become a wholesale customer.

+
+
+ {% endif %} +
+{% endblock %} diff --git a/storefront/templates/storefront/wholesale_order_create_form.html b/storefront/templates/storefront/wholesale_order_create_form.html new file mode 100644 index 0000000..99fe83a --- /dev/null +++ b/storefront/templates/storefront/wholesale_order_create_form.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Place a wholesale order

+
+
+
+ {% csrf_token %} + + + + + + + {{ form.as_table }} + + + + + + + +
Product16 oz Qty / 5 lb Qty
+ +
+
+
+
+{% endblock %} diff --git a/storefront/templates/storefront/wholesale_order_detail.html b/storefront/templates/storefront/wholesale_order_detail.html new file mode 100644 index 0000000..b6d53a1 --- /dev/null +++ b/storefront/templates/storefront/wholesale_order_detail.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +
+

← Back

+
+

Wholesale Order No. {{order.pk}}

+

Placed on {{order.created_at|date:"M j, Y"}}

+
+ +
+ + + + + + + + + + + + {% for qty in order.brazil %} + + {% endfor %} + + + + {% for qty in order.dantes_tornado %} + + {% endfor %} + + + {% for qty in order.decaf %} + + {% endfor %} + + + {% for qty in order.ethiopia %} + + {% endfor %} + + + {% for qty in order.loop_d_loop %} + + {% endfor %} + + + {% for qty in order.moka_java %} + + {% endfor %} + + + {% for qty in order.nicaragua %} + + {% endfor %} + + + {% for qty in order.pantomime %} + + {% endfor %} + + + {% for qty in order.sumatra %} + + {% endfor %} + + +
Product16 oz5 lb
Brazil{% if qty %}Qty: {{ qty }}{% endif %}
Dante's Tornado{% if qty %}Qty: {{ qty }}{% endif %}
Decaf{% if qty %}Qty: {{ qty }}{% endif %}
Ethiopia{% if qty %}Qty: {{ qty }}{% endif %}
Loop d' Loop{% if qty %}Qty: {{ qty }}{% endif %}
Moka Java{% if qty %}Qty: {{ qty }}{% endif %}
Nicaragua{% if qty %}Qty: {{ qty }}{% endif %}
Pantomime{% if qty %}Qty: {{ qty }}{% endif %}
Sumatra{% if qty %}Qty: {{ qty }}{% endif %}
+
+
+{% endblock content %} diff --git a/storefront/urls.py b/storefront/urls.py index bf34d62..9372ca9 100644 --- a/storefront/urls.py +++ b/storefront/urls.py @@ -99,6 +99,13 @@ urlpatterns = [ views.OrderDetailView.as_view(), name='order-detail', ), + path('wholesale-orders//', include([ + path( + '', + views.WholesaleOrderDetailView.as_view(), + name='wholesale-order-detail' + ), + ])), ])), path( @@ -130,4 +137,16 @@ urlpatterns = [ name='subscription-done' ), ])), + + # Wholesale Orders + path( + 'wholesale-orders/', + views.WholesaleOrderView.as_view(), + name='wholesale-order' + ), + path( + 'wholesale-orders/new/', + views.WholesaleOrderCreateView.as_view(), + name='wholesale-order-create' + ), ] diff --git a/storefront/views.py b/storefront/views.py index cd541c3..a3ab0c4 100644 --- a/storefront/views.py +++ b/storefront/views.py @@ -19,7 +19,9 @@ from django.views.generic.edit import ( from django.views.generic.detail import DetailView, SingleObjectMixin from django.views.generic.list import ListView from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.contrib.auth.mixins import ( + PermissionRequiredMixin, LoginRequiredMixin, UserPassesTestMixin +) from django.contrib.messages.views import SuccessMessageMixin from django.contrib import messages from django.views.decorators.csrf import csrf_exempt @@ -40,7 +42,7 @@ from accounts.forms import CustomerUpdateForm, CustomerShippingAddressUpdateForm from core.models import ( ProductCategory, Product, ProductVariant, ProductOption, Order, Transaction, OrderLine, Coupon, ShippingRate, - Subscription, SiteSettings + Subscription, SiteSettings, WholesaleOrder ) from core.forms import ShippingRateForm from core.shipping import get_shipping_cost @@ -49,7 +51,7 @@ from core import OrderStatus, ShippingContainer, TransactionStatus from .forms import ( AddToCartForm, CartItemUpdateForm, OrderCreateForm, AddressForm, CouponApplyForm, CheckoutShippingForm, - SubscriptionForm + SubscriptionForm, WholesaleOrderCreateForm ) from .cart import CartItem, Cart from .payments import CaptureOrder @@ -842,3 +844,45 @@ def stripe_webhook(request): return JsonResponse({'status': 'success'}) + +class WholesaleOrderView(TemplateView): + template_name = "storefront/wholesale_order.html" + + +class WholesaleOrderCreateView( + PermissionRequiredMixin, LoginRequiredMixin, CreateView +): + permission_required = "core.create_wholesaleorder" + model = WholesaleOrder + template_name = 'storefront/wholesale_order_create_form.html' + form_class = WholesaleOrderCreateForm + + def get_success_url(self): + return reverse( + 'storefront:wholesale-order-detail', kwargs={ + 'pk': self.object.customer.pk, 'order_pk': self.object.pk + } + ) + + def form_valid(self, form): + form.instance.customer = self.request.user + return super().form_valid(form) + + +class WholesaleOrderDetailView( + PermissionRequiredMixin, LoginRequiredMixin, DetailView +): + permission_required = "core.view_wholesaleorder" + model = WholesaleOrder + context_object_name = 'order' + pk_url_kwarg = 'order_pk' + template_name = 'storefront/wholesale_order_detail.html' + + def test_func(self): + return self.request.user.pk == self.get_object().customer.pk + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['customer'] = User.objects.get(pk=self.kwargs['pk']) + return context + diff --git a/templates/base.html b/templates/base.html index dfd0def..e3568f6 100644 --- a/templates/base.html +++ b/templates/base.html @@ -54,6 +54,7 @@
  • {{ category }}
  • {% endfor %}
  • Subscribe
  • +
  • Wholesale
  • Fair trade
  • Reviews
  • About
  • From f5ae448c988d7c4b1f67b5bd1265958fbc412de8 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sun, 30 Jul 2023 20:29:45 -0600 Subject: [PATCH 3/5] Add intermediary page for wholesale --- static/styles/main.css | 3 ++- storefront/templates/storefront/wholesale_order.html | 8 ++++---- storefront/views.py | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/static/styles/main.css b/static/styles/main.css index a58acf0..3deccbd 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -1137,6 +1137,7 @@ footer > section { #wholesale-faq { - display: flex; + display: grid; + grid-template-columns: repeat(2, 1fr); gap: 3rem; } diff --git a/storefront/templates/storefront/wholesale_order.html b/storefront/templates/storefront/wholesale_order.html index 7cdc9f5..83052a6 100644 --- a/storefront/templates/storefront/wholesale_order.html +++ b/storefront/templates/storefront/wholesale_order.html @@ -22,12 +22,12 @@ {% else %}
    -

    I'm already a wholesale customer

    -

    If you are already a wholesale customer, please login to place an order.

    +

    I would like to become a wholesale customer

    +

    You must be a wholesale customer to place an order. Contact us to become a wholesale customer.

    -

    I would like to become a wholesale customer

    -

    You must become a wholesale customer to place an order. Contact us to become a wholesale customer.

    +

    I'm already a wholesale customer

    +

    If you are already a wholesale customer, please login and return to this page to place an order.

    {% endif %} diff --git a/storefront/views.py b/storefront/views.py index a3ab0c4..d9df71b 100644 --- a/storefront/views.py +++ b/storefront/views.py @@ -850,12 +850,13 @@ class WholesaleOrderView(TemplateView): class WholesaleOrderCreateView( - PermissionRequiredMixin, LoginRequiredMixin, CreateView + PermissionRequiredMixin, LoginRequiredMixin, CreateView, SuccessMessageMixin ): permission_required = "core.create_wholesaleorder" model = WholesaleOrder template_name = 'storefront/wholesale_order_create_form.html' form_class = WholesaleOrderCreateForm + success_message = "Wholesale order placed. Thank you." def get_success_url(self): return reverse( From 10cd9cf0c53080fcfe79d28b69b86f67d59cdbac Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Tue, 1 Aug 2023 22:21:16 -0600 Subject: [PATCH 4/5] Update form --- static/styles/main.css | 5 +++ .../templates/storefront/wholesale_order.html | 2 +- .../wholesale_order_create_form.html | 38 ++++++++++--------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/static/styles/main.css b/static/styles/main.css index 3deccbd..1d2db15 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -1141,3 +1141,8 @@ footer > section { grid-template-columns: repeat(2, 1fr); gap: 3rem; } +.wholesale-field { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.5rem; +} diff --git a/storefront/templates/storefront/wholesale_order.html b/storefront/templates/storefront/wholesale_order.html index 83052a6..dc97cce 100644 --- a/storefront/templates/storefront/wholesale_order.html +++ b/storefront/templates/storefront/wholesale_order.html @@ -27,7 +27,7 @@

    I'm already a wholesale customer

    -

    If you are already a wholesale customer, please login and return to this page to place an order.

    +

    If you are already a wholesale customer, please login and return to this page to place an order.

    {% endif %} diff --git a/storefront/templates/storefront/wholesale_order_create_form.html b/storefront/templates/storefront/wholesale_order_create_form.html index 99fe83a..2d39111 100644 --- a/storefront/templates/storefront/wholesale_order_create_form.html +++ b/storefront/templates/storefront/wholesale_order_create_form.html @@ -8,23 +8,27 @@
    {% csrf_token %} - - - - - - - {{ form.as_table }} - - - - - - - -
    Product16 oz Qty / 5 lb Qty
    - -
    +
    +
    + Product + 16 oz. + 5 lb. + +
    + {% for field in form %} +
    + {{ field.label_tag }} + {{ field }} +
    + {{ field.errors }} + {% if field.help_text %} +

    {{ field.help_text|safe }}

    + {% endif %} +
    +
    + {% endfor %} +
    +
    From b61114babb5cb2eccad11bb8cd329760400079e1 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Tue, 22 Aug 2023 21:57:28 -0600 Subject: [PATCH 5/5] Finalize wholesale orders --- .../0010_wholesaleorder_is_cancelled.py | 18 +++ .../0011_alter_wholesaleorder_options.py | 17 +++ ...e_fulfilled_wholesaleorder_is_fulfilled.py | 18 +++ core/models.py | 4 +- core/tasks.py | 15 ++- dashboard/forms.py | 19 +++ .../dashboard/wholesale_order/_table.html | 28 +++++ .../wholesale_order/cancel_form.html | 26 +++++ .../dashboard/wholesale_order/detail.html | 108 ++++++++++++++++++ .../wholesale_order/fulfill_form.html | 26 +++++ .../dashboard/wholesale_order/list.html | 40 +++++++ dashboard/urls.py | 24 ++++ dashboard/views.py | 53 +++++++++ static/images/pallet.png | Bin 0 -> 4419 bytes static/styles/main.css | 7 ++ .../templates/storefront/wholesale_order.html | 12 +- .../storefront/wholesale_order_detail.html | 29 +++-- storefront/views.py | 21 +++- templates/base.html | 10 +- templates/dashboard.html | 4 + .../wholesale_order_confirmation.email | 84 ++++++++++++++ 21 files changed, 536 insertions(+), 27 deletions(-) create mode 100644 core/migrations/0010_wholesaleorder_is_cancelled.py create mode 100644 core/migrations/0011_alter_wholesaleorder_options.py create mode 100644 core/migrations/0012_rename_fulfilled_wholesaleorder_is_fulfilled.py create mode 100644 dashboard/templates/dashboard/wholesale_order/_table.html create mode 100644 dashboard/templates/dashboard/wholesale_order/cancel_form.html create mode 100644 dashboard/templates/dashboard/wholesale_order/detail.html create mode 100644 dashboard/templates/dashboard/wholesale_order/fulfill_form.html create mode 100644 dashboard/templates/dashboard/wholesale_order/list.html create mode 100644 static/images/pallet.png create mode 100644 templates/templated_email/storefront/wholesale_order_confirmation.email diff --git a/core/migrations/0010_wholesaleorder_is_cancelled.py b/core/migrations/0010_wholesaleorder_is_cancelled.py new file mode 100644 index 0000000..0d8db2a --- /dev/null +++ b/core/migrations/0010_wholesaleorder_is_cancelled.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.6 on 2023-08-06 18:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_wholesaleorder'), + ] + + operations = [ + migrations.AddField( + model_name='wholesaleorder', + name='is_cancelled', + field=models.BooleanField(default=False), + ), + ] diff --git a/core/migrations/0011_alter_wholesaleorder_options.py b/core/migrations/0011_alter_wholesaleorder_options.py new file mode 100644 index 0000000..16460f2 --- /dev/null +++ b/core/migrations/0011_alter_wholesaleorder_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.6 on 2023-08-06 18:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0010_wholesaleorder_is_cancelled'), + ] + + operations = [ + migrations.AlterModelOptions( + name='wholesaleorder', + options={'ordering': ['-created_at'], 'verbose_name': 'wholesale order', 'verbose_name_plural': 'wholesale orders'}, + ), + ] diff --git a/core/migrations/0012_rename_fulfilled_wholesaleorder_is_fulfilled.py b/core/migrations/0012_rename_fulfilled_wholesaleorder_is_fulfilled.py new file mode 100644 index 0000000..d9d9ab5 --- /dev/null +++ b/core/migrations/0012_rename_fulfilled_wholesaleorder_is_fulfilled.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.6 on 2023-08-23 01:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0011_alter_wholesaleorder_options'), + ] + + operations = [ + migrations.RenameField( + model_name='wholesaleorder', + old_name='fulfilled', + new_name='is_fulfilled', + ), + ] diff --git a/core/models.py b/core/models.py index 68d940c..0de6882 100644 --- a/core/models.py +++ b/core/models.py @@ -754,7 +754,8 @@ class WholesaleOrder(models.Model): size=2, ) - fulfilled = models.BooleanField(default=False) + is_fulfilled = models.BooleanField(default=False) + is_cancelled = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -762,4 +763,5 @@ class WholesaleOrder(models.Model): class Meta: verbose_name = "wholesale order" verbose_name_plural = "wholesale orders" + ordering = ['-created_at'] diff --git a/core/tasks.py b/core/tasks.py index 02fa9d3..13e60ae 100644 --- a/core/tasks.py +++ b/core/tasks.py @@ -11,8 +11,9 @@ logger = get_task_logger(__name__) CONFIRM_ORDER_TEMPLATE = 'storefront/order_confirmation' SHIP_ORDER_TEMPLATE = 'storefront/order_shipped' -ORDER_CANCEl_TEMPLATE = 'storefront/order_cancel' +ORDER_CANCEL_TEMPLATE = 'storefront/order_cancel' ORDER_REFUND_TEMPLATE = 'storefront/order_refund' +CONFIRM_WHOLESALE_ORDER_TEMPLATE = 'storefront/wholesale_order_confirmation' @shared_task(name='send_order_confirmation_email') @@ -27,6 +28,18 @@ def send_order_confirmation_email(order): logger.info(f"Order confirmation email sent to {order['email']}") +@shared_task(name='send_wholesale_order_confirmation_email') +def send_wholesale_order_confirmation_email(order): + send_templated_mail( + template_name=CONFIRM_WHOLESALE_ORDER_TEMPLATE, + from_email=SiteSettings.load().order_from_email, + recipient_list=[order['email']], + context=order + ) + + logger.info(f"WholesaleOrder confirmation email sent to {order['email']}") + + @shared_task(name='send_order_shipped_email') def send_order_shipped_email(data): send_templated_mail( diff --git a/dashboard/forms.py b/dashboard/forms.py index ea2e5fc..4f3351e 100644 --- a/dashboard/forms.py +++ b/dashboard/forms.py @@ -4,6 +4,7 @@ from django import forms from core import OrderStatus from core.models import ( ProductVariant, + WholesaleOrder, Order, OrderLine, ShippingRate, @@ -118,6 +119,24 @@ OrderTrackingFormset = forms.inlineformset_factory( ) +class WholesaleOrderCancelForm(forms.ModelForm): + class Meta: + model = WholesaleOrder + fields = ['is_cancelled'] + widgets = { + 'is_cancelled': forms.HiddenInput() + } + + +class WholesaleOrderFulfillForm(forms.ModelForm): + class Meta: + model = WholesaleOrder + fields = ['is_fulfilled'] + widgets = { + 'is_fulfilled': forms.HiddenInput() + } + + class ProductPhotoForm(forms.ModelForm): class Meta: model = ProductPhoto diff --git a/dashboard/templates/dashboard/wholesale_order/_table.html b/dashboard/templates/dashboard/wholesale_order/_table.html new file mode 100644 index 0000000..0545927 --- /dev/null +++ b/dashboard/templates/dashboard/wholesale_order/_table.html @@ -0,0 +1,28 @@ + + + Order No. + Date + Customer + Fulfillment + + + + {% for order in order_list %} + + No. {{ order.pk }} + {{ order.created_at|date:"D, M j Y" }} + {{ order.customer.get_full_name }} + +
    + {% if order.is_fulfilled %} + Fulfilled + {% elif order.is_cancelled %} + Cancelled + {% else %} + Unfulfilled + {% endif %} +
    + + + {% endfor %} + diff --git a/dashboard/templates/dashboard/wholesale_order/cancel_form.html b/dashboard/templates/dashboard/wholesale_order/cancel_form.html new file mode 100644 index 0000000..f8eb1c9 --- /dev/null +++ b/dashboard/templates/dashboard/wholesale_order/cancel_form.html @@ -0,0 +1,26 @@ +{% extends 'dashboard.html' %} + +{% block head_title %}Cancel Wholesale Order No. {{ order.pk }} | {% endblock %} + +{% block content %} +
    +

    + ← Back +

    +
    +

    Cancel Wholesale Order No. {{order.pk}}

    +
    +
    +
    +

    Are you sure you want to cancel Wholesale Order No. {{ order.pk }}?

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

    + +

    +
    +
    +
    +{% endblock content %} diff --git a/dashboard/templates/dashboard/wholesale_order/detail.html b/dashboard/templates/dashboard/wholesale_order/detail.html new file mode 100644 index 0000000..21c663b --- /dev/null +++ b/dashboard/templates/dashboard/wholesale_order/detail.html @@ -0,0 +1,108 @@ +{% extends 'dashboard.html' %} +{% load static %} + +{% block head_title %}Order No. {{ order.pk }} | {% endblock %} + +{% block content %} +
    +

    + ← Back to wholesale +

    +
    +

    Wholesale Order No. {{order.pk}}

    + {% if perms.core.cancel_order and not order.is_cancelled %} + Cancel order + {% endif %} +
    +
    +
    +

    Details

    +
    +
    +
    Date
    +
    {{ order.created_at }}
    + +
    Customer
    +
    + {{order.customer.get_full_name}}  + {{order.customer.email}} ↗ +
    + +
    Status
    +
    + {% if order.is_fulfilled %} + Fulfilled + {% elif order.is_cancelled %} + Cancelled + {% else %} + Unfulfilled + {% endif %} +
    +
    +
    +
    +
    +

    Items

    + Fulfill order → +
    + + + + + + + + + + + + {% for qty in order.brazil %} + + {% endfor %} + + + + {% for qty in order.dantes_tornado %} + + {% endfor %} + + + {% for qty in order.decaf %} + + {% endfor %} + + + {% for qty in order.ethiopia %} + + {% endfor %} + + + {% for qty in order.loop_d_loop %} + + {% endfor %} + + + {% for qty in order.moka_java %} + + {% endfor %} + + + {% for qty in order.nicaragua %} + + {% endfor %} + + + {% for qty in order.pantomime %} + + {% endfor %} + + + {% for qty in order.sumatra %} + + {% endfor %} + + +
    Product16 oz.5 lb.
    Brazil{% if qty %}Qty: {{ qty }}{% endif %}
    Dante's Tornado{% if qty %}Qty: {{ qty }}{% endif %}
    Decaf{% if qty %}Qty: {{ qty }}{% endif %}
    Ethiopia{% if qty %}Qty: {{ qty }}{% endif %}
    Loop d' Loop{% if qty %}Qty: {{ qty }}{% endif %}
    Moka Java{% if qty %}Qty: {{ qty }}{% endif %}
    Nicaragua{% if qty %}Qty: {{ qty }}{% endif %}
    Pantomime{% if qty %}Qty: {{ qty }}{% endif %}
    Sumatra{% if qty %}Qty: {{ qty }}{% endif %}
    +
    +
    +{% endblock content %} diff --git a/dashboard/templates/dashboard/wholesale_order/fulfill_form.html b/dashboard/templates/dashboard/wholesale_order/fulfill_form.html new file mode 100644 index 0000000..db2c147 --- /dev/null +++ b/dashboard/templates/dashboard/wholesale_order/fulfill_form.html @@ -0,0 +1,26 @@ +{% extends 'dashboard.html' %} + +{% block head_title %}Fulfill Wholesale Order No. {{ order.pk }} | {% endblock %} + +{% block content %} +
    +

    + ← Back +

    +
    +

    Fulfill Wholesale Order No. {{order.pk}}

    +
    +
    +
    +

    Are you sure you want to fulfill Wholesale Order No. {{ order.pk }}?

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

    + +

    +
    +
    +
    +{% endblock content %} diff --git a/dashboard/templates/dashboard/wholesale_order/list.html b/dashboard/templates/dashboard/wholesale_order/list.html new file mode 100644 index 0000000..ee8a9f0 --- /dev/null +++ b/dashboard/templates/dashboard/wholesale_order/list.html @@ -0,0 +1,40 @@ +{% extends 'dashboard.html' %} +{% load static %} + +{% block head_title %}Wholesale Orders | {% endblock %} + +{% block content %} +
    +
    +

    Wholesale Orders

    +
    +
    +
    +
    + + {% include 'dashboard/wholesale_order/_table.html' with order_list=order_list %} + + + + + +
    + +
    +
    +
    +{% endblock content %} diff --git a/dashboard/urls.py b/dashboard/urls.py index f2ab9d6..11813c2 100644 --- a/dashboard/urls.py +++ b/dashboard/urls.py @@ -254,4 +254,28 @@ urlpatterns = [ name='customer-update' ), ])), + + # Wholesale Orders + path( + 'wholesale-orders/', + views.WholesaleOrderListView.as_view(), + name='wholesale-order-list' + ), + path('wholesale-orders//', include([ + path( + '', + views.WholesaleOrderDetailView.as_view(), + name='wholesale-order-detail' + ), + path( + 'fulfill/', + views.WholesaleOrderFulfillView.as_view(), + name='wholesale-order-fulfill' + ), + path( + 'cancel/', + views.WholesaleOrderCancelView.as_view(), + name='wholesale-order-cancel' + ), + ])), ] diff --git a/dashboard/views.py b/dashboard/views.py index ffffee5..b589202 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -43,6 +43,7 @@ from core.models import ( Transaction, TrackingNumber, Coupon, + WholesaleOrder, SiteSettings ) @@ -57,6 +58,8 @@ from .forms import ( OrderLineFormset, OrderCancelForm, OrderTrackingFormset, + WholesaleOrderFulfillForm, + WholesaleOrderCancelForm, CouponForm, ProductPhotoForm ) @@ -723,3 +726,53 @@ class CustomerUpdateView( def get_success_url(self): return reverse('dashboard:customer-detail', kwargs={'pk': self.object.pk}) + + +class WholesaleOrderListView(LoginRequiredMixin, ListView): + model = WholesaleOrder + template_name = 'dashboard/wholesale_order/list.html' + context_object_name = 'order_list' + paginate_by = 50 + + +class WholesaleOrderDetailView( + LoginRequiredMixin, DetailView +): + model = WholesaleOrder + context_object_name = 'order' + template_name = 'dashboard/wholesale_order/detail.html' + + +class WholesaleOrderCancelView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView +): + permission_required = 'core.cancel_order' + model = WholesaleOrder + context_object_name = 'order' + template_name = 'dashboard/wholesale_order/cancel_form.html' + form_class = WholesaleOrderCancelForm + success_message = 'Wholesale Order canceled.' + initial = { + 'is_cancelled': True + } + + def get_success_url(self): + return reverse('dashboard:wholesale-order-detail', kwargs={'pk': self.object.pk}) + + +class WholesaleOrderFulfillView( + LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView +): + permission_required = 'core.change_wholesaleorder' + model = WholesaleOrder + context_object_name = 'order' + template_name = 'dashboard/wholesale_order/fulfill_form.html' + form_class = WholesaleOrderFulfillForm + success_message = 'Wholesale Order fulfilled.' + initial = { + 'is_fulfilled': True + } + + def get_success_url(self): + return reverse('dashboard:wholesale-order-detail', kwargs={'pk': self.object.pk}) + diff --git a/static/images/pallet.png b/static/images/pallet.png new file mode 100644 index 0000000000000000000000000000000000000000..8bc9599bd5a651deae1a8603f66255bb243674ee GIT binary patch literal 4419 zcmeHKdr(x@8NZ7Nl*q;hND_6rF11nW-TT=0?XC#y0vlPgDk$X9=I(vqmEFC|-3z;r z2|=x*ZKl>qYOS+LY2vFylL0C& zg-8}W08*2VXcP=I%{V(vGYy#rV_Tuk23x@>?IF!{wSNxmc}e>PuoovWiVvYlLvo;x zLnFb?g}xJ-(4JW9_h2iZEcJUK4MFC>(`aAr@%nV6fxrz$6Zqq#k-}{hZqX4WWwKB> z0qbiz#v)RfHayrj1o3SVN=tp7(o$Vm5_uuOAxJF#%uZ)T&+_?afBg6{cj2a{nGJou zCj%UtP;yoJL*o!Nr7J#cOjf8)r@ySH8+ zKGDA8^=QLOL(lx}{XZVat;`rsA58Dh`C076SWEvU`|Ua9HMOg^y}zBwc=8v+JOA4C zt{7jT{j!-q)5l-iR&b`|cbkW_JXgBtCH>C}JIlTpiNBkD9lfw&-?_)`UBzbPA01ls zTL1jw?E7kdlBFRxe7t=6+`*$8_U`Vx*m|j_=;p9nI)C*~NI|co)3?yxdZ%dkFNbU6 z@$mC+dpf>6d%UP@;}yfjLj(W3_eRkAXj`{2yzkgoA7flBNM?I?&GxR39G@(> zar4$?m5+a-2EZso*;sv5b*TS+lu&_J`IpEJaQVdcaSM^P+HGxeOT^K*4V z$f>`5@(+5Qz&iEo%wF6ZF6Gt>6)h6Cre$Rn(^AjaS$)y6Y)6a&f*>c;x>zs}icm49 zUgf30R)#UXPKC(zPJIm|QmH6$I+MXCQvTsaDWGnLWiT$l1pyj?Rf!2pDpxM$p-50C1jDKqh&=%*3;dW^6Jk?l)a{H9 z1l&`+6VRi3SHXbtdMTI4G%DeFTu!~RKE;ZRz*4Gdx8j72H?ye4%rK~l=S`@cVeP2d zgj;BW^V1A(A4la0MPxd}a0(TW8wB8?jf9mmTKp*M=lrP2!V{?9Oj=NaF`8{gqXpR6 zaTJeA0%Ro}7$22F#R3&eGk$_IK@6mY11dj((tfiar7gIPG@C8F)rzZBEJKxvQjmt@ z6oPaehlN9R>VzUVRqXRP^`s#IRY!aQTIRvQsjn15jj=Ikl@R3C$h4v+VX+fNlhte? zEG8pvH;#eUa#92`QQ=gJr#hn?3k912VQD2#fj}LHy-=kRN6VsAC5i#3K2>&8(_Xkv zEG^S6TIPT>Rfwxb3o(jAIZlMH(0N5x;G4e*ty~_R1MqO<6+#5oZ&rsAH);*nka$Ww z1_bpg>2&I)plBwsg$UimB@PR?5>w22I#kC&^+=}c=(zB8sX(v8bvS(k)gGYRN=&bUmk>0tklUHW2B* z1;dgX#*~^dx?>0SKbkmHz_=mm+Hzo00*EamNaWCy%xW%yT=yO4h?-;PrU zo`;d|gez(z2%_y#nxWzyu1g|P zO@FYjfjv0m?5cC*{(n8YKYL_dx8?NdSp}89-e}ld9@$mvE4(ta?bfFU7reT?WM0qG z2L^Z7oz8o!_t*!H`PH8-+3bvSbC(|JFlaukdG&>NKKNwq(-|d)`s3|8 section { grid-template-columns: repeat(2, 1fr); gap: 3rem; } + +.wholesale-detail, +.wholesale-fields { + width: fit-content; +} .wholesale-field { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; + margin-bottom: 1rem; } + diff --git a/storefront/templates/storefront/wholesale_order.html b/storefront/templates/storefront/wholesale_order.html index dc97cce..d97ab13 100644 --- a/storefront/templates/storefront/wholesale_order.html +++ b/storefront/templates/storefront/wholesale_order.html @@ -6,17 +6,23 @@

    Wholesale

    - {% if user.is_authenticated %} + {% if user.is_authenticated and perms.core.add_wholesaleorder %}

    New wholesale order

    +

    Orders

    {% for order in user.wholesale_orders.all %} - + {% empty %} + + + {% endfor %}
    {{ order.created_at }} - + + No. {{ order.pk }} | {{ order.created_at }} +
    No wholesale order placed yet.
    {% else %} diff --git a/storefront/templates/storefront/wholesale_order_detail.html b/storefront/templates/storefront/wholesale_order_detail.html index b6d53a1..afffd10 100644 --- a/storefront/templates/storefront/wholesale_order_detail.html +++ b/storefront/templates/storefront/wholesale_order_detail.html @@ -3,65 +3,64 @@ {% block content %}
    -

    ← Back

    +

    ← Back

    Wholesale Order No. {{order.pk}}

    Placed on {{order.created_at|date:"M j, Y"}}

    - +
    - - + + - + {% for qty in order.brazil %} {% endfor %} - - - + + {% for qty in order.dantes_tornado %} {% endfor %} - + {% for qty in order.decaf %} {% endfor %} - + {% for qty in order.ethiopia %} {% endfor %} - + {% for qty in order.loop_d_loop %} {% endfor %} - + {% for qty in order.moka_java %} {% endfor %} - + {% for qty in order.nicaragua %} {% endfor %} - + {% for qty in order.pantomime %} {% endfor %} - + {% for qty in order.sumatra %} {% endfor %} diff --git a/storefront/views.py b/storefront/views.py index d9df71b..06ff166 100644 --- a/storefront/views.py +++ b/storefront/views.py @@ -39,6 +39,7 @@ from moneyed import Money, USD from accounts.models import User from accounts.utils import get_or_create_customer from accounts.forms import CustomerUpdateForm, CustomerShippingAddressUpdateForm +from core.tasks import send_wholesale_order_confirmation_email from core.models import ( ProductCategory, Product, ProductVariant, ProductOption, Order, Transaction, OrderLine, Coupon, ShippingRate, @@ -850,9 +851,9 @@ class WholesaleOrderView(TemplateView): class WholesaleOrderCreateView( - PermissionRequiredMixin, LoginRequiredMixin, CreateView, SuccessMessageMixin + PermissionRequiredMixin, LoginRequiredMixin, SuccessMessageMixin, CreateView ): - permission_required = "core.create_wholesaleorder" + permission_required = "core.add_wholesaleorder" model = WholesaleOrder template_name = 'storefront/wholesale_order_create_form.html' form_class = WholesaleOrderCreateForm @@ -867,6 +868,22 @@ class WholesaleOrderCreateView( def form_valid(self, form): form.instance.customer = self.request.user + self.object = form.save() + order = { + 'order_id': self.object.pk, + 'full_name': self.object.customer.get_full_name(), + 'email': self.object.customer.email, + 'brazil': self.object.brazil, + 'dantes_tornado': self.object.dantes_tornado, + 'decaf': self.object.decaf, + 'ethiopia': self.object.ethiopia, + 'loop_d_loop': self.object.loop_d_loop, + 'moka_java': self.object.moka_java, + 'nicaragua': self.object.nicaragua, + 'pantomime': self.object.pantomime, + 'sumatra': self.object.sumatra + } + send_wholesale_order_confirmation_email.delay(order) return super().form_valid(form) diff --git a/templates/base.html b/templates/base.html index e3568f6..f65242d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -87,11 +87,11 @@
    {% if messages %} -
    - {% for message in messages %} -

    {{ message }}

    - {% endfor %} -
    +
    + {% for message in messages %} +

    {{ message }}

    + {% endfor %} +
    {% endif %} {% block content %} {% endblock content %} diff --git a/templates/dashboard.html b/templates/dashboard.html index 0ed2c7f..5b7d2c6 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -48,6 +48,10 @@ Orders + + + Wholesale + Customers diff --git a/templates/templated_email/storefront/wholesale_order_confirmation.email b/templates/templated_email/storefront/wholesale_order_confirmation.email new file mode 100644 index 0000000..e85a4e5 --- /dev/null +++ b/templates/templated_email/storefront/wholesale_order_confirmation.email @@ -0,0 +1,84 @@ +{% block subject %}PT Coffee: Confirmation for Wholesale Order No. {{ order_id }}{% endblock %} +{% block html %} + + + +

    Thank you for your order!

    + +

    Hi {{ full_name }}, your order details are below.

    + +

    Order Summary

    +
    Product16 oz5 lb16 oz.5 lb.
    BrazilBrazil{% if qty %}Qty: {{ qty }}{% endif %}
    Dante's Tornado
    Dante's Tornado{% if qty %}Qty: {{ qty }}{% endif %}
    DecafDecaf{% if qty %}Qty: {{ qty }}{% endif %}
    EthiopiaEthiopia{% if qty %}Qty: {{ qty }}{% endif %}
    Loop d' LoopLoop d' Loop{% if qty %}Qty: {{ qty }}{% endif %}
    Moka JavaMoka Java{% if qty %}Qty: {{ qty }}{% endif %}
    NicaraguaNicaragua{% if qty %}Qty: {{ qty }}{% endif %}
    PantomimePantomime{% if qty %}Qty: {{ qty }}{% endif %}
    SumatraSumatra{% if qty %}Qty: {{ qty }}{% endif %}
    + + + + + + + + + + + {% for qty in brazil %} + + {% endfor %} + + + {% for qty in dantes_tornado %} + + {% endfor %} + + + {% for qty in decaf %} + + {% endfor %} + + + {% for qty in ethiopia %} + + {% endfor %} + + + {% for qty in loop_d_loop %} + + {% endfor %} + + + {% for qty in moka_java %} + + {% endfor %} + + + {% for qty in nicaragua %} + + {% endfor %} + + + {% for qty in pantomime %} + + {% endfor %} + + + {% for qty in sumatra %} + + {% endfor %} + + +
    Product16 oz.5 lb.
    Brazil{% if qty %}Qty: {{ qty }}{% endif %}
    Dante's Tornado{% if qty %}Qty: {{ qty }}{% endif %}
    Decaf{% if qty %}Qty: {{ qty }}{% endif %}
    Ethiopia{% if qty %}Qty: {{ qty }}{% endif %}
    Loop d' Loop{% if qty %}Qty: {{ qty }}{% endif %}
    Moka Java{% if qty %}Qty: {{ qty }}{% endif %}
    Nicaragua{% if qty %}Qty: {{ qty }}{% endif %}
    Pantomime{% if qty %}Qty: {{ qty }}{% endif %}
    Sumatra{% if qty %}Qty: {{ qty }}{% endif %}
    + +

    If you have any questions, send us an email at support@ptcoffee.com.

    + +

    Thanks,
    + Port Townsend Roasting Co.

    +{% endblock %}