Merge branch 'feature/wholesale' into develop
This commit is contained in:
commit
a87b8f1db1
@ -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": []
|
||||
}
|
||||
}]
|
||||
|
||||
@ -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",
|
||||
|
||||
41
core/migrations/0009_wholesaleorder.py
Normal file
41
core/migrations/0009_wholesaleorder.py
Normal file
@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
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',
|
||||
),
|
||||
]
|
||||
@ -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,79 @@ 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,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "wholesale order"
|
||||
verbose_name_plural = "wholesale orders"
|
||||
ordering = ['-created_at']
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -4,6 +4,7 @@ from django import forms
|
||||
from core import OrderStatus
|
||||
from core.models import (
|
||||
ProductVariant,
|
||||
WholesaleOrder,
|
||||
Order,
|
||||
OrderLine,
|
||||
ShippingRate,
|
||||
@ -118,7 +119,26 @@ 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
|
||||
fields = ('image',)
|
||||
|
||||
|
||||
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'
|
||||
),
|
||||
])),
|
||||
|
||||
# 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,
|
||||
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})
|
||||
|
||||
|
||||
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 |
@ -1134,3 +1134,22 @@ footer > section {
|
||||
.show-modal {
|
||||
white-space: unset;
|
||||
}
|
||||
|
||||
|
||||
#wholesale-faq {
|
||||
display: grid;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}),
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
41
storefront/templates/storefront/wholesale_order.html
Normal file
41
storefront/templates/storefront/wholesale_order.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>Wholesale</h1>
|
||||
</header>
|
||||
|
||||
{% if user.is_authenticated and perms.core.add_wholesaleorder %}
|
||||
<section>
|
||||
<p><a href="{% url 'storefront:wholesale-order-create' %}" class="btn">New wholesale order</a></p>
|
||||
<h3>Orders</h3>
|
||||
<table>
|
||||
{% for order in user.wholesale_orders.all %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'storefront:wholesale-order-detail' user.pk order.pk %}">
|
||||
No. {{ order.pk }} | {{ order.created_at }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td>No wholesale order placed yet.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<section id="wholesale-faq">
|
||||
<div>
|
||||
<h4>I would like to become a wholesale customer</h4>
|
||||
<p>You must be a wholesale customer to place an order. <a href="mailto:{{ site_settings.default_contact_email }}">Contact us</a> to become a wholesale customer.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>I'm already a wholesale customer</h4>
|
||||
<p>If you are already a wholesale customer, please <a href="{% url 'login' %}?next=/wholesale-orders/new/">login</a> and return to this page to place an order.<p>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -0,0 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>Place a wholesale order</h1>
|
||||
</header>
|
||||
<section>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="wholesale-fields">
|
||||
<div class="wholesale-field">
|
||||
<strong>Product</strong>
|
||||
<strong>16 oz.</strong>
|
||||
<strong>5 lb.</strong>
|
||||
<span></span>
|
||||
</div>
|
||||
{% for field in form %}
|
||||
<div class="wholesale-field">
|
||||
{{ field.label_tag }}
|
||||
{{ field }}
|
||||
<div>
|
||||
{{ field.errors }}
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text|safe }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="submit">Place order</button>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
72
storefront/templates/storefront/wholesale_order_detail.html
Normal file
72
storefront/templates/storefront/wholesale_order_detail.html
Normal file
@ -0,0 +1,72 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<p><a href="{% url 'storefront:wholesale-order' %}">← Back</a></p>
|
||||
<header>
|
||||
<h1>Wholesale Order No. {{order.pk}}</h1>
|
||||
<h3>Placed on {{order.created_at|date:"M j, Y"}}</h3>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<table class="wholesale-detail">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>16 oz.</th>
|
||||
<th>5 lb.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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 %}
|
||||
@ -99,6 +99,13 @@ urlpatterns = [
|
||||
views.OrderDetailView.as_view(),
|
||||
name='order-detail',
|
||||
),
|
||||
path('wholesale-orders/<int:order_pk>/', 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'
|
||||
),
|
||||
]
|
||||
|
||||
@ -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
|
||||
@ -37,10 +39,11 @@ 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,
|
||||
Subscription, SiteSettings
|
||||
Subscription, SiteSettings, WholesaleOrder
|
||||
)
|
||||
from core.forms import ShippingRateForm
|
||||
from core.shipping import get_shipping_cost
|
||||
@ -49,7 +52,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 +845,62 @@ def stripe_webhook(request):
|
||||
|
||||
return JsonResponse({'status': 'success'})
|
||||
|
||||
|
||||
class WholesaleOrderView(TemplateView):
|
||||
template_name = "storefront/wholesale_order.html"
|
||||
|
||||
|
||||
class WholesaleOrderCreateView(
|
||||
PermissionRequiredMixin, LoginRequiredMixin, SuccessMessageMixin, CreateView
|
||||
):
|
||||
permission_required = "core.add_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(
|
||||
'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
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -54,6 +54,7 @@
|
||||
<li><a class="nav__link" href="{% url 'storefront:category-detail' category.pk %}">{{ category }}</a></li>
|
||||
{% endfor %}
|
||||
<li><a class="nav__link" href="{% url 'storefront:subscription-form' %}">Subscribe</a></li>
|
||||
<li><a class="nav__link" href="{% url 'storefront:wholesale-order' %}">Wholesale</a></li>
|
||||
<li><a class="nav__link" href="{% url 'storefront:fair-trade' %}">Fair trade</a></li>
|
||||
<li><a class="nav__link" href="{% url 'storefront:reviews' %}">Reviews</a></li>
|
||||
<li><a class="nav__link" href="{% url 'storefront:about' %}">About</a></li>
|
||||
|
||||
@ -48,6 +48,10 @@
|
||||
<img src="{% static 'images/box.png' %}">
|
||||
Orders
|
||||
</a>
|
||||
<a href="{% url 'dashboard:wholesale-order-list' %}">
|
||||
<img src="{% static 'images/pallet.png' %}">
|
||||
Wholesale
|
||||
</a>
|
||||
<a href="{% url 'dashboard:customer-list' %}">
|
||||
<img src="{% static 'images/customer.png' %}">
|
||||
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