Skip to content

Commit

Permalink
Merge 4a7bdde into 2c64837
Browse files Browse the repository at this point in the history
  • Loading branch information
chessbr committed Feb 27, 2021
2 parents 2c64837 + 4a7bdde commit 434661d
Show file tree
Hide file tree
Showing 19 changed files with 264 additions and 61 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

List all changes after the last release here (newer on top). Each change on a separate bullet point line

### Added

- Front: create shipment sent notify event
- Core: add shipment tracking url to shipment model
- Admin: add shipment action to mark a shipment as sent

## [2.3.17] - 2021-02-23

### Fixed
Expand Down
5 changes: 2 additions & 3 deletions shuup/admin/base.py
Expand Up @@ -298,7 +298,7 @@ class Section(object):
order = 0

@classmethod
def visible_for_object(cls, obj, request=None):
def visible_for_object(cls, obj, request):
"""
Returns whether this sections must be visible for the provided object (e.g. `order`).
Expand All @@ -310,7 +310,7 @@ def visible_for_object(cls, obj, request=None):
return False

@classmethod
def get_context_data(cls, obj, request=None):
def get_context_data(cls, obj, request):
"""
Returns additional information to be used in the template.
Expand All @@ -319,7 +319,6 @@ def get_context_data(cls, obj, request=None):
e.g. `context[admin_order_section.identifier] =
admin_order_section.get_context_data(self.object)`
:type object: e.g. shuup.core.models.Order
:type request: HttpRequest
:return additional context data
Expand Down
5 changes: 5 additions & 0 deletions shuup/admin/modules/orders/__init__.py
Expand Up @@ -37,6 +37,11 @@ def get_urls(self):
"shuup.admin.modules.orders.views.ShipmentDeleteView",
name="order.delete-shipment"
),
admin_url(
r"^shipments/(?P<pk>\d+)/set-sent/$",
"shuup.admin.modules.orders.views.ShipmentSetSentView",
name="order.set-shipment-sent"
),
admin_url(
r"^orders/(?P<pk>\d+)/create-payment/$",
"shuup.admin.modules.orders.views.OrderCreatePaymentView",
Expand Down
22 changes: 15 additions & 7 deletions shuup/admin/modules/orders/sections.py
Expand Up @@ -79,23 +79,31 @@ def get_context_data(order, request=None):
suppliers = Supplier.objects.filter(order_lines__order=order).distinct()
create_permission = "order.create-shipment"
delete_permission = "order.delete-shipment"
missing_permissions = get_missing_permissions(request.user, [create_permission, delete_permission])
set_sent_permission = "order.set-shipment-sent"
missing_permissions = get_missing_permissions(
request.user,
[create_permission, delete_permission, set_sent_permission]
)
create_urls = {}
delete_urls = {}
set_sent_urls = {}

if create_permission not in missing_permissions:
for supplier in suppliers:
create_urls[supplier.pk] = reverse(
"shuup_admin:order.create-shipment", kwargs={"pk": order.pk, "supplier_pk": supplier.pk})

delete_urls = {}
if delete_permission not in missing_permissions:
for shipment_id in order.shipments.all_except_deleted().values_list("id", flat=True):
delete_urls[shipment_id] = reverse(
"shuup_admin:order.delete-shipment", kwargs={"pk": shipment_id})
for shipment_id in order.shipments.all_except_deleted().values_list("id", flat=True):
if delete_permission not in missing_permissions:
delete_urls[shipment_id] = reverse("shuup_admin:order.delete-shipment", kwargs={"pk": shipment_id})
if set_sent_permission not in missing_permissions:
set_sent_urls[shipment_id] = reverse("shuup_admin:order.set-shipment-sent", kwargs={"pk": shipment_id})

return {
"suppliers": suppliers,
"create_urls": create_urls,
"delete_urls": delete_urls
"delete_urls": delete_urls,
"set_sent_urls": set_sent_urls,
}


Expand Down
6 changes: 4 additions & 2 deletions shuup/admin/modules/orders/views/__init__.py
Expand Up @@ -15,13 +15,15 @@
OrderCreatePaymentView, OrderDeletePaymentView, OrderSetPaidView
)
from .refund import OrderCreateFullRefundView, OrderCreateRefundView
from .shipment import OrderCreateShipmentView, ShipmentDeleteView
from .shipment import (
OrderCreateShipmentView, ShipmentDeleteView, ShipmentSetSentView
)
from .status import OrderStatusEditView, OrderStatusListView

__all__ = [
"NewLogEntryView", "OrderAddressEditView", "OrderDetailView", "OrderEditView",
"OrderListView", "OrderCreatePaymentView", "OrderCreateFullRefundView",
"OrderCreateRefundView", "OrderCreateShipmentView", "OrderSetPaidView",
"OrderSetStatusView", "OrderStatusEditView", "OrderStatusListView", "ShipmentDeleteView",
"UpdateAdminCommentView", "OrderDeletePaymentView"
"UpdateAdminCommentView", "OrderDeletePaymentView", "ShipmentSetSentView"
]
23 changes: 23 additions & 0 deletions shuup/admin/modules/orders/views/shipment.py
Expand Up @@ -30,6 +30,7 @@ class ShipmentForm(ModifiableFormMixin, forms.Form):

description = forms.CharField(required=False)
tracking_code = forms.CharField(required=False)
tracking_url = forms.URLField(required=False, label=_("Tracking URL"))


class OrderCreateShipmentView(ModifiableViewMixin, UpdateView):
Expand Down Expand Up @@ -146,6 +147,7 @@ def form_valid(self, form):
order=order,
supplier_id=self._get_supplier_id(),
tracking_code=form.cleaned_data.get("tracking_code"),
tracking_url=form.cleaned_data.get("tracking_url"),
description=form.cleaned_data.get("description"))
has_extension_errors = self.form_valid_hook(form, unsaved_shipment)

Expand Down Expand Up @@ -191,3 +193,24 @@ def post(self, request, *args, **kwargs):
shipment.soft_delete()
messages.success(request, _("Shipment %s has been deleted.") % shipment.pk)
return HttpResponseRedirect(self.get_success_url())


class ShipmentSetSentView(DetailView):
model = Shipment
context_object_name = "shipment"

def get_queryset(self):
shop_ids = Shop.objects.get_for_user(self.request.user).values_list("id", flat=True)
return Shipment.objects.filter(order__shop_id__in=shop_ids)

def get_success_url(self):
return get_model_url(self.get_object().order)

def get(self, request, *args, **kwargs):
return HttpResponseRedirect(self.get_success_url())

def post(self, request, *args, **kwargs):
shipment = self.get_object()
shipment.set_sent()
messages.success(request, _("Shipment has been marked as sent."))
return HttpResponseRedirect(self.get_success_url())
1 change: 1 addition & 0 deletions shuup/admin/static_src/base/scss/shuup/shipments.scss
@@ -1,6 +1,7 @@
#order-shipment-info-for-supplier {
margin-left: 20px;
margin-bottom: 40px;
padding-right: 20px;

a.btn {
margin-bottom: 20px;
Expand Down
Expand Up @@ -97,7 +97,7 @@
<h4 class="pt-3 pb-3"><strong>{% trans %}Billing address{% endtrans %}</strong></h4>
<div class="list-group">
{% for line in order.billing_address or [] %}
<address class="list-group-item">{{ line }}</address>
<div class="list-group-item">{{ line }}</div>
{% else %}
<p><i class="fa fa-warning text-warning"></i> {% trans %}No billing address defined.{% endtrans %}</p>
{% endfor %}
Expand All @@ -107,7 +107,7 @@
<h4 class="pt-3 pb-3"><strong>{% trans %}Shipping address{% endtrans %}</strong></h4>
<div class="list-group">
{% for line in order.shipping_address or [] %}
<address class="list-group-item">{{ line }}</address>
<div class="list-group-item">{{ line }}</div>
{% else %}
<p><i class="fa fa-warning text-warning"></i> {% trans %}No shipping address defined.{% endtrans %}</p>
{% endfor %}
Expand Down
50 changes: 40 additions & 10 deletions shuup/admin/templates/shuup/admin/orders/_order_shipments.jinja
Expand Up @@ -6,8 +6,8 @@
{%- if not loop.last %}, {% endif -%}
{%- endfor -%}
{% endmacro %}
{% macro render_supplier_info(supplier, create_urls, delete_urls) %}

{% macro render_supplier_info(supplier, create_urls, delete_urls, set_sent_urls) %}
{% set product_summary = order.get_product_summary(supplier) %}
{% set ordered_total = product_summary.values()|sum("ordered")|number %}
{% set shipped_total = product_summary.values()|sum("shipped")|number %}
Expand Down Expand Up @@ -59,22 +59,35 @@
<tr>
<th class="text-center">{% trans %}Products{% endtrans %}</th>
<th class="text-center">{% trans %}Tracking Code{% endtrans %}</th>
<th class="text-center">{% trans %}Status{% endtrans %}</th>
<th class="text-center">{% trans %}Description{% endtrans %}</th>
<th class="text-center">{% trans %}Created{% endtrans %}</th>
<th class="text-center">{% trans %}Delete{% endtrans %}</th>
<th class="text-center">{% trans %}Actions{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for shipment in shipments %}
<tr>
<td class="text-center">{{ render_products_cell(shipment) }}</td>
<td class="text-center">{{ shipment.tracking_code }}</td>
<td class="text-center">
{%- if shipment.tracking_url %}
<a href="{{ shipment.tracking_url }}" target="_blank" rel="noopener">
{% endif -%}
{{- shipment.tracking_code -}}
{%- if shipment.tracking_url %}</a>{% endif -%}
</td>
<td class="text-center">{{ shipment.status }}</td>
<td class="text-center">{{ shipment.description }}</td>
<td class="text-center">{{ shipment.created_on|datetime }}</td>
<td class="text-center">{{ shipment.created_on|datetime(format="short") }}</td>
<td class="text-center">
{% if delete_urls.get(shipment.id) and not shipment.is_deleted() %}
<a href="{{ delete_urls.get(shipment.id) }}" onclick="handleDelete(event, this)">
<i class="fa fa-trash"></i>
<a class="btn btn-danger btn-sm" href="{{ delete_urls.get(shipment.id) }}" onclick="handleDelete(event, this)">
<i class="fa fa-trash"></i> {{ _("Delete") }}
</a>
{% endif %}
{% if set_sent_urls.get(shipment.id) and not shipment.is_sent() %}
<a class="btn btn-info btn-sm" href="{{ set_sent_urls.get(shipment.id) }}" onclick="handleSetSent(event, this)">
<i class="fa fa-truck"></i> {{ _("Mark as sent") }}
</a>
{% endif %}
</td>
Expand All @@ -87,14 +100,13 @@
</div>
{% endmacro %}

<div class="container">
{% set create_urls = shipments_data.create_urls %}
{% set delete_urls = shipments_data.delete_urls %}
{% set set_sent_urls = shipments_data.set_sent_urls %}
{% for supplier in shipments_data.suppliers %}
{{ render_supplier_info(supplier, create_urls, delete_urls) }}
{{ render_supplier_info(supplier, create_urls, delete_urls, set_sent_urls) }}
{%- if not loop.last %}<hr>{% endif -%}
{% endfor %}
</div>

{% block extra_js %}
<script>
Expand All @@ -116,5 +128,23 @@
}
return false;
}
function handleSetSent(event, link) {
event.preventDefault();
var warningMessage = "{% trans %}Do you want to mark this shipment as sent?{% endtrans %}";
if (confirm(warningMessage)) {
var form = document.createElement("form");
form.method = "POST";
form.action = $(link).attr("href");
var input = document.createElement("input");
input.type = "hidden";
input.name = "csrfmiddlewaretoken";
input.id = "language-field";
input.value = "{{ csrf_token }}";
form.appendChild(input);
document.body.appendChild(form);
form.submit();
}
return false;
}
</script>
{% endblock %}
18 changes: 18 additions & 0 deletions shuup/core/migrations/0084_tracking_url.py
@@ -0,0 +1,18 @@
# Generated by Django 2.2.15 on 2021-02-26 18:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('shuup', '0083_make_attribute_name_256_chars'),
]

operations = [
migrations.AddField(
model_name='shipment',
name='tracking_url',
field=models.URLField(blank=True, verbose_name='tracking url'),
),
]
3 changes: 3 additions & 0 deletions shuup/core/models/_orders.py
Expand Up @@ -1201,6 +1201,9 @@ def get_shipping_method_display(self):
def get_tracking_codes(self):
return [shipment.tracking_code for shipment in self.shipments.all_except_deleted() if shipment.tracking_code]

def get_sent_shipments(self):
return self.shipments.all_except_deleted().sent()

def can_edit(self):
return (
settings.SHUUP_ALLOW_EDITING_ORDER
Expand Down
36 changes: 26 additions & 10 deletions shuup/core/models/_shipments.py
Expand Up @@ -19,7 +19,7 @@
InternalIdentifierField, MeasurementField, QuantityField
)
from shuup.core.models import ShuupModel
from shuup.core.signals import shipment_deleted
from shuup.core.signals import shipment_deleted, shipment_sent
from shuup.core.utils.units import get_shuup_volume_unit
from shuup.utils.analog import define_log_model

Expand All @@ -34,11 +34,11 @@ class ShipmentStatus(Enum):
DELETED = 20

class Labels:
NOT_SENT = _("not sent")
SENT = _("sent")
RECEIVED = _("received")
ERROR = _("error")
DELETED = _("deleted")
NOT_SENT = _("Not sent")
SENT = _("Sent")
RECEIVED = _("Received")
ERROR = _("Error")
DELETED = _("Deleted")


class ShipmentType(Enum):
Expand All @@ -50,11 +50,13 @@ class Labels:
IN = _("incoming")


class ShipmentManager(models.Manager):

class ShipmentQueryset(models.QuerySet):
def all_except_deleted(self, language=None, shop=None):
return self.exclude(status=ShipmentStatus.DELETED)

def sent(self):
return self.filter(status=ShipmentStatus.SENT)


class Shipment(ShuupModel):
order = models.ForeignKey(
Expand All @@ -66,6 +68,7 @@ class Shipment(ShuupModel):
created_on = models.DateTimeField(auto_now_add=True, verbose_name=_("created on"))
status = EnumIntegerField(ShipmentStatus, default=ShipmentStatus.NOT_SENT, verbose_name=_("status"))
tracking_code = models.CharField(max_length=64, blank=True, verbose_name=_("tracking code"))
tracking_url = models.URLField(blank=True, verbose_name=_("tracking url"))
description = models.CharField(max_length=255, blank=True, verbose_name=_("description"))
volume = MeasurementField(
unit=get_shuup_volume_unit(),
Expand All @@ -77,9 +80,8 @@ class Shipment(ShuupModel):
)
identifier = InternalIdentifierField(unique=True)
type = EnumIntegerField(ShipmentType, default=ShipmentType.OUT, verbose_name=_("type"))
# TODO: documents = models.ManyToManyField(FilerFile)

objects = ShipmentManager()
objects = ShipmentQueryset.as_manager()

class Meta:
verbose_name = _('shipment')
Expand Down Expand Up @@ -122,6 +124,9 @@ def soft_delete(self, user=None):
def is_deleted(self):
return bool(self.status == ShipmentStatus.DELETED)

def is_sent(self):
return bool(self.status == ShipmentStatus.SENT)

def cache_values(self):
"""
(Re)cache `.volume` and `.weight` for this Shipment from within the ShipmentProducts.
Expand All @@ -138,6 +143,17 @@ def cache_values(self):
def total_products(self):
return (self.products.aggregate(quantity=models.Sum("quantity"))["quantity"] or 0)

def set_sent(self):
"""
Mark the shipment as sent.
"""
if self.status == ShipmentStatus.SENT:
return

self.status = ShipmentStatus.SENT
self.save()
shipment_sent.send(sender=type(self), order=self.order, shipment=self)

def set_received(self, purchase_prices=None, created_by=None):
"""
Mark the shipment as received.
Expand Down
1 change: 1 addition & 0 deletions shuup/core/signals.py
Expand Up @@ -12,6 +12,7 @@
get_orderability_errors = Signal(providing_args=["shop_product", "customer", "supplier", "quantity"], use_caching=True)
shipment_created = Signal(providing_args=["order", "shipment"], use_caching=True)
shipment_created_and_processed = Signal(providing_args=["order", "shipment"], use_caching=True)
shipment_sent = Signal(providing_args=["order", "shipment"], use_caching=True)
refund_created = Signal(providing_args=["order", "refund_lines"], use_caching=True)
category_deleted = Signal(providing_args=["category"], use_caching=True)
shipment_deleted = Signal(providing_args=["shipment"], use_caching=True)
Expand Down

0 comments on commit 434661d

Please sign in to comment.