Finalize wholesale orders
This commit is contained in:
parent
10cd9cf0c5
commit
b61114babb
18
core/migrations/0010_wholesaleorder_is_cancelled.py
Normal file
18
core/migrations/0010_wholesaleorder_is_cancelled.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
17
core/migrations/0011_alter_wholesaleorder_options.py
Normal file
17
core/migrations/0011_alter_wholesaleorder_options.py
Normal file
@ -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'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -754,7 +754,8 @@ class WholesaleOrder(models.Model):
|
|||||||
size=2,
|
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)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
@ -762,4 +763,5 @@ class WholesaleOrder(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "wholesale order"
|
verbose_name = "wholesale order"
|
||||||
verbose_name_plural = "wholesale orders"
|
verbose_name_plural = "wholesale orders"
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
|||||||
@ -11,8 +11,9 @@ logger = get_task_logger(__name__)
|
|||||||
|
|
||||||
CONFIRM_ORDER_TEMPLATE = 'storefront/order_confirmation'
|
CONFIRM_ORDER_TEMPLATE = 'storefront/order_confirmation'
|
||||||
SHIP_ORDER_TEMPLATE = 'storefront/order_shipped'
|
SHIP_ORDER_TEMPLATE = 'storefront/order_shipped'
|
||||||
ORDER_CANCEl_TEMPLATE = 'storefront/order_cancel'
|
ORDER_CANCEL_TEMPLATE = 'storefront/order_cancel'
|
||||||
ORDER_REFUND_TEMPLATE = 'storefront/order_refund'
|
ORDER_REFUND_TEMPLATE = 'storefront/order_refund'
|
||||||
|
CONFIRM_WHOLESALE_ORDER_TEMPLATE = 'storefront/wholesale_order_confirmation'
|
||||||
|
|
||||||
|
|
||||||
@shared_task(name='send_order_confirmation_email')
|
@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']}")
|
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')
|
@shared_task(name='send_order_shipped_email')
|
||||||
def send_order_shipped_email(data):
|
def send_order_shipped_email(data):
|
||||||
send_templated_mail(
|
send_templated_mail(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from django import forms
|
|||||||
from core import OrderStatus
|
from core import OrderStatus
|
||||||
from core.models import (
|
from core.models import (
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
|
WholesaleOrder,
|
||||||
Order,
|
Order,
|
||||||
OrderLine,
|
OrderLine,
|
||||||
ShippingRate,
|
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 ProductPhotoForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProductPhoto
|
model = ProductPhoto
|
||||||
|
|||||||
28
dashboard/templates/dashboard/wholesale_order/_table.html
Normal file
28
dashboard/templates/dashboard/wholesale_order/_table.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order No.</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Customer</th>
|
||||||
|
<th>Fulfillment</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for order in order_list %}
|
||||||
|
<tr class="is-link" onclick="window.location='{% url 'dashboard:wholesale-order-detail' order.pk %}'">
|
||||||
|
<td>No. {{ order.pk }}</td>
|
||||||
|
<td>{{ order.created_at|date:"D, M j Y" }}</td>
|
||||||
|
<td>{{ order.customer.get_full_name }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="status-display">
|
||||||
|
{% if order.is_fulfilled %}
|
||||||
|
<span class="status-dot {% if order.is_fulfilled %} status-success {% else %} status-info {% endif %}"></span> Fulfilled
|
||||||
|
{% elif order.is_cancelled %}
|
||||||
|
Cancelled
|
||||||
|
{% else %}
|
||||||
|
<span class="status-dot status-error"></span> Unfulfilled
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
{% extends 'dashboard.html' %}
|
||||||
|
|
||||||
|
{% block head_title %}Cancel Wholesale Order No. {{ order.pk }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'dashboard:wholesale-order-detail' order.pk %}">← Back</a>
|
||||||
|
</p>
|
||||||
|
<header>
|
||||||
|
<h1>Cancel Wholesale Order No. {{order.pk}}</h1>
|
||||||
|
</header>
|
||||||
|
<section class="panel">
|
||||||
|
<header class="panel-header">
|
||||||
|
<h4>Are you sure you want to cancel Wholesale Order No. {{ order.pk }}?</h4>
|
||||||
|
</header>
|
||||||
|
<form method="POST" class="panel-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<p>
|
||||||
|
<input class="btn btn-warning" type="submit" value="Cancel order">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock content %}
|
||||||
108
dashboard/templates/dashboard/wholesale_order/detail.html
Normal file
108
dashboard/templates/dashboard/wholesale_order/detail.html
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
{% extends 'dashboard.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block head_title %}Order No. {{ order.pk }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'dashboard:wholesale-order-list' %}">← Back to wholesale</a>
|
||||||
|
</p>
|
||||||
|
<header class="object-header">
|
||||||
|
<h1><img src="{% static 'images/pallet.png' %}"> Wholesale Order No. {{order.pk}}</h1>
|
||||||
|
{% if perms.core.cancel_order and not order.is_cancelled %}
|
||||||
|
<a class="btn btn-warning" href="{% url 'dashboard:wholesale-order-cancel' order.pk %}">Cancel order</a>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
<section class="panel">
|
||||||
|
<header class="panel-header">
|
||||||
|
<h4>Details</h4>
|
||||||
|
</header>
|
||||||
|
<dl class="panel-datalist">
|
||||||
|
<dt>Date</dt>
|
||||||
|
<dd>{{ order.created_at }}</dd>
|
||||||
|
|
||||||
|
<dt>Customer</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="{% url 'dashboard:customer-detail' order.customer.pk %}">{{order.customer.get_full_name}}</a> 
|
||||||
|
<a href="mailto:{{order.customer.email}}">{{order.customer.email}} ↗</a>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Status</dt>
|
||||||
|
<dd>
|
||||||
|
{% if order.is_fulfilled %}
|
||||||
|
<span class="status status-success">Fulfilled</span>
|
||||||
|
{% elif order.is_cancelled %}
|
||||||
|
Cancelled
|
||||||
|
{% else %}
|
||||||
|
<span class="status status-error">Unfulfilled</span>
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</section>
|
||||||
|
<section class="panel">
|
||||||
|
<header class="panel-header">
|
||||||
|
<h4>Items</h4>
|
||||||
|
<a href="{% url 'dashboard:wholesale-order-fulfill' order.pk %}" class="btn">Fulfill order →</a>
|
||||||
|
</header>
|
||||||
|
<table>
|
||||||
|
<tdead>
|
||||||
|
<tr>
|
||||||
|
<th>Product</th>
|
||||||
|
<th>16 oz.</th>
|
||||||
|
<th>5 lb.</th>
|
||||||
|
</tr>
|
||||||
|
</tdead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Brazil</td>
|
||||||
|
{% for qty in order.brazil %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Dante's Tornado</td>
|
||||||
|
{% for qty in order.dantes_tornado %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Decaf</td>
|
||||||
|
{% for qty in order.decaf %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Ethiopia</td>
|
||||||
|
{% for qty in order.ethiopia %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Loop d' Loop</td>
|
||||||
|
{% for qty in order.loop_d_loop %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Moka Java</td>
|
||||||
|
{% for qty in order.moka_java %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Nicaragua</td>
|
||||||
|
{% for qty in order.nicaragua %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Pantomime</td>
|
||||||
|
{% for qty in order.pantomime %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Sumatra</td>
|
||||||
|
{% for qty in order.sumatra %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock content %}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
{% extends 'dashboard.html' %}
|
||||||
|
|
||||||
|
{% block head_title %}Fulfill Wholesale Order No. {{ order.pk }} | {% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'dashboard:wholesale-order-detail' order.pk %}">← Back</a>
|
||||||
|
</p>
|
||||||
|
<header>
|
||||||
|
<h1>Fulfill Wholesale Order No. {{order.pk}}</h1>
|
||||||
|
</header>
|
||||||
|
<section class="panel">
|
||||||
|
<header class="panel-header">
|
||||||
|
<h4>Are you sure you want to fulfill Wholesale Order No. {{ order.pk }}?</h4>
|
||||||
|
</header>
|
||||||
|
<form method="POST" class="panel-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<p>
|
||||||
|
<input class="btn" type="submit" value="Fulfill order">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock content %}
|
||||||
40
dashboard/templates/dashboard/wholesale_order/list.html
Normal file
40
dashboard/templates/dashboard/wholesale_order/list.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{% extends 'dashboard.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block head_title %}Wholesale Orders | {% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1><img src="{% static "images/pallet.png" %}"> Wholesale Orders</h1>
|
||||||
|
</header>
|
||||||
|
<section class="panel">
|
||||||
|
<header class="panel-header">
|
||||||
|
</header>
|
||||||
|
<table>
|
||||||
|
{% include 'dashboard/wholesale_order/_table.html' with order_list=order_list %}
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">
|
||||||
|
<div class="pagination">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page=1">« 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 »</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock content %}
|
||||||
@ -254,4 +254,28 @@ urlpatterns = [
|
|||||||
name='customer-update'
|
name='customer-update'
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
# Wholesale Orders
|
||||||
|
path(
|
||||||
|
'wholesale-orders/',
|
||||||
|
views.WholesaleOrderListView.as_view(),
|
||||||
|
name='wholesale-order-list'
|
||||||
|
),
|
||||||
|
path('wholesale-orders/<int:pk>/', 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'
|
||||||
|
),
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -43,6 +43,7 @@ from core.models import (
|
|||||||
Transaction,
|
Transaction,
|
||||||
TrackingNumber,
|
TrackingNumber,
|
||||||
Coupon,
|
Coupon,
|
||||||
|
WholesaleOrder,
|
||||||
SiteSettings
|
SiteSettings
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,6 +58,8 @@ from .forms import (
|
|||||||
OrderLineFormset,
|
OrderLineFormset,
|
||||||
OrderCancelForm,
|
OrderCancelForm,
|
||||||
OrderTrackingFormset,
|
OrderTrackingFormset,
|
||||||
|
WholesaleOrderFulfillForm,
|
||||||
|
WholesaleOrderCancelForm,
|
||||||
CouponForm,
|
CouponForm,
|
||||||
ProductPhotoForm
|
ProductPhotoForm
|
||||||
)
|
)
|
||||||
@ -723,3 +726,53 @@ class CustomerUpdateView(
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('dashboard:customer-detail', kwargs={'pk': self.object.pk})
|
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})
|
||||||
|
|
||||||
|
|||||||
BIN
static/images/pallet.png
Normal file
BIN
static/images/pallet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@ -1141,8 +1141,15 @@ footer > section {
|
|||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 3rem;
|
gap: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wholesale-detail,
|
||||||
|
.wholesale-fields {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
.wholesale-field {
|
.wholesale-field {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,17 +6,23 @@
|
|||||||
<h1>Wholesale</h1>
|
<h1>Wholesale</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated and perms.core.add_wholesaleorder %}
|
||||||
<section>
|
<section>
|
||||||
<p><a href="{% url 'storefront:wholesale-order-create' %}" class="btn">New wholesale order</a></p>
|
<p><a href="{% url 'storefront:wholesale-order-create' %}" class="btn">New wholesale order</a></p>
|
||||||
|
<h3>Orders</h3>
|
||||||
<table>
|
<table>
|
||||||
{% for order in user.wholesale_orders.all %}
|
{% for order in user.wholesale_orders.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ order.created_at }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'storefront:wholesale-order-detail' user.pk order.pk %}"No. {{ order.pk }}</a>
|
<a href="{% url 'storefront:wholesale-order-detail' user.pk order.pk %}">
|
||||||
|
No. {{ order.pk }} | {{ order.created_at }}
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td>No wholesale order placed yet.</td>
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@ -3,65 +3,64 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article>
|
||||||
<p><a href="{% url 'storefront:customer-detail' customer.pk %}">← Back</a></p>
|
<p><a href="{% url 'storefront:wholesale-order' %}">← Back</a></p>
|
||||||
<header>
|
<header>
|
||||||
<h1>Wholesale Order No. {{order.pk}}</h1>
|
<h1>Wholesale Order No. {{order.pk}}</h1>
|
||||||
<h3>Placed on {{order.created_at|date:"M j, Y"}}</h3>
|
<h3>Placed on {{order.created_at|date:"M j, Y"}}</h3>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<table>
|
<table class="wholesale-detail">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Product</th>
|
<th>Product</th>
|
||||||
<th>16 oz</th>
|
<th>16 oz.</th>
|
||||||
<th>5 lb</th>
|
<th>5 lb.</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Brazil</th>
|
<td>Brazil</td>
|
||||||
{% for qty in order.brazil %}
|
{% for qty in order.brazil %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr><tr>
|
||||||
<tr>
|
<td>Dante's Tornado</td>
|
||||||
<th>Dante's Tornado</th>
|
|
||||||
{% for qty in order.dantes_tornado %}
|
{% for qty in order.dantes_tornado %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Decaf</th>
|
<td>Decaf</td>
|
||||||
{% for qty in order.decaf %}
|
{% for qty in order.decaf %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Ethiopia</th>
|
<td>Ethiopia</td>
|
||||||
{% for qty in order.ethiopia %}
|
{% for qty in order.ethiopia %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Loop d' Loop</th>
|
<td>Loop d' Loop</td>
|
||||||
{% for qty in order.loop_d_loop %}
|
{% for qty in order.loop_d_loop %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Moka Java</th>
|
<td>Moka Java</td>
|
||||||
{% for qty in order.moka_java %}
|
{% for qty in order.moka_java %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Nicaragua</th>
|
<td>Nicaragua</td>
|
||||||
{% for qty in order.nicaragua %}
|
{% for qty in order.nicaragua %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Pantomime</th>
|
<td>Pantomime</td>
|
||||||
{% for qty in order.pantomime %}
|
{% for qty in order.pantomime %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Sumatra</th>
|
<td>Sumatra</td>
|
||||||
{% for qty in order.sumatra %}
|
{% for qty in order.sumatra %}
|
||||||
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ from moneyed import Money, USD
|
|||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
from accounts.utils import get_or_create_customer
|
from accounts.utils import get_or_create_customer
|
||||||
from accounts.forms import CustomerUpdateForm, CustomerShippingAddressUpdateForm
|
from accounts.forms import CustomerUpdateForm, CustomerShippingAddressUpdateForm
|
||||||
|
from core.tasks import send_wholesale_order_confirmation_email
|
||||||
from core.models import (
|
from core.models import (
|
||||||
ProductCategory, Product, ProductVariant, ProductOption,
|
ProductCategory, Product, ProductVariant, ProductOption,
|
||||||
Order, Transaction, OrderLine, Coupon, ShippingRate,
|
Order, Transaction, OrderLine, Coupon, ShippingRate,
|
||||||
@ -850,9 +851,9 @@ class WholesaleOrderView(TemplateView):
|
|||||||
|
|
||||||
|
|
||||||
class WholesaleOrderCreateView(
|
class WholesaleOrderCreateView(
|
||||||
PermissionRequiredMixin, LoginRequiredMixin, CreateView, SuccessMessageMixin
|
PermissionRequiredMixin, LoginRequiredMixin, SuccessMessageMixin, CreateView
|
||||||
):
|
):
|
||||||
permission_required = "core.create_wholesaleorder"
|
permission_required = "core.add_wholesaleorder"
|
||||||
model = WholesaleOrder
|
model = WholesaleOrder
|
||||||
template_name = 'storefront/wholesale_order_create_form.html'
|
template_name = 'storefront/wholesale_order_create_form.html'
|
||||||
form_class = WholesaleOrderCreateForm
|
form_class = WholesaleOrderCreateForm
|
||||||
@ -867,6 +868,22 @@ class WholesaleOrderCreateView(
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.customer = self.request.user
|
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)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -87,11 +87,11 @@
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<section class="messages">
|
<section class="messages">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<p {% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</p>
|
<p {% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@ -48,6 +48,10 @@
|
|||||||
<img src="{% static 'images/box.png' %}">
|
<img src="{% static 'images/box.png' %}">
|
||||||
Orders
|
Orders
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url 'dashboard:wholesale-order-list' %}">
|
||||||
|
<img src="{% static 'images/pallet.png' %}">
|
||||||
|
Wholesale
|
||||||
|
</a>
|
||||||
<a href="{% url 'dashboard:customer-list' %}">
|
<a href="{% url 'dashboard:customer-list' %}">
|
||||||
<img src="{% static 'images/customer.png' %}">
|
<img src="{% static 'images/customer.png' %}">
|
||||||
Customers
|
Customers
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
{% block subject %}PT Coffee: Confirmation for Wholesale Order No. {{ order_id }}{% endblock %}
|
||||||
|
{% block html %}
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
th, td, tr {
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<h3>Thank you for your order!</h3>
|
||||||
|
|
||||||
|
<p>Hi {{ full_name }}, your order details are below.</p>
|
||||||
|
|
||||||
|
<h4>Order Summary</h4>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Product</th>
|
||||||
|
<th>16 oz.</th>
|
||||||
|
<th>5 lb.</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Brazil</td>
|
||||||
|
{% for qty in brazil %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Dante's Tornado</td>
|
||||||
|
{% for qty in dantes_tornado %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Decaf</td>
|
||||||
|
{% for qty in decaf %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Ethiopia</td>
|
||||||
|
{% for qty in ethiopia %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Loop d' Loop</td>
|
||||||
|
{% for qty in loop_d_loop %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Moka Java</td>
|
||||||
|
{% for qty in moka_java %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Nicaragua</td>
|
||||||
|
{% for qty in nicaragua %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Pantomime</td>
|
||||||
|
{% for qty in pantomime %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr><tr>
|
||||||
|
<td>Sumatra</td>
|
||||||
|
{% for qty in sumatra %}
|
||||||
|
<td>{% if qty %}Qty: {{ qty }}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>If you have any questions, send us an email at <a href="mailto:support@ptcoffee.com">support@ptcoffee.com</a>.</p>
|
||||||
|
|
||||||
|
<p>Thanks,<br>
|
||||||
|
Port Townsend Roasting Co.</p>
|
||||||
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user