# -*- coding: utf-8 -*-
# This file is part of Shoop.
#
# Copyright (c) 2012-2016, Shoop Ltd. All rights reserved.
#
# This source code is licensed under the AGPLv3 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals
from decimal import Decimal
from django.db import models
from django.utils.crypto import get_random_string
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from enumfields import Enum, EnumIntegerField
from shoop.core.fields import (
InternalIdentifierField, MeasurementField, QuantityField
)
__all__ = ("Shipment", "ShipmentProduct")
CUBIC_MM_TO_CUBIC_METERS_DIVISOR = Decimal("1000000000")
GRAMS_TO_KILOGRAMS_DIVISOR = 1000
class ShipmentStatus(Enum):
NOT_SENT = 0
SENT = 1
RECEIVED = 2 # if the customer deigns to tell us
ERROR = 10
class Labels:
NOT_SENT = _("not sent")
SENT = _("sent")
RECEIVED = _("received")
ERROR = _("error")
class Shipment(models.Model):
order = models.ForeignKey("Order", related_name='shipments', on_delete=models.PROTECT, verbose_name=_("order"))
supplier = models.ForeignKey(
"Supplier", related_name='shipments', on_delete=models.PROTECT, verbose_name=_("supplier"))
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"))
description = models.CharField(max_length=255, blank=True, verbose_name=_("description"))
volume = MeasurementField(unit="m3", verbose_name=_("volume"))
weight = MeasurementField(unit="kg", verbose_name=_("weight"))
identifier = InternalIdentifierField(unique=True)
# TODO: documents = models.ManyToManyField(FilerFile)
class Meta:
verbose_name = _('shipment')
verbose_name_plural = _('shipments')
def __init__(self, *args, **kwargs):
super(Shipment, self).__init__(*args, **kwargs)
if not self.identifier:
if self.order and self.order.pk:
prefix = '%s/%s/' % (self.order.pk, self.order.shipments.count())
else:
prefix = ''
self.identifier = prefix + get_random_string(32)
def __repr__(self): # pragma: no cover
return "<Shipment %s for order %s (tracking %r, created %s)>" % (
self.pk, self.order_id, self.tracking_code, self.created_on
)
[docs] def save(self, *args, **kwargs):
super(Shipment, self).save(*args, **kwargs)
for product_id in self.products.values_list("product_id", flat=True):
self.supplier.module.update_stock(product_id=product_id)
[docs] def cache_values(self):
"""
(Re)cache `.volume` and `.weight` for this Shipment from the ShipmentProducts within.
"""
total_volume = 0
total_weight = 0
for quantity, volume, weight in self.products.values_list("quantity", "unit_volume", "unit_weight"):
total_volume += quantity * volume
total_weight += quantity * weight
self.volume = total_volume
self.weight = total_weight / GRAMS_TO_KILOGRAMS_DIVISOR
@property
def total_products(self):
return (self.products.aggregate(quantity=models.Sum("quantity"))["quantity"] or 0)
@python_2_unicode_compatible
class ShipmentProduct(models.Model):
shipment = models.ForeignKey(
Shipment, related_name='products', on_delete=models.PROTECT, verbose_name=_("shipment")
)
product = models.ForeignKey(
"Product", related_name='shipments', on_delete=models.CASCADE, verbose_name=_("product")
)
quantity = QuantityField(verbose_name=_("quantity"))
# volume is m^3, not mm^3, because mm^3 are tiny. like ants.
unit_volume = MeasurementField(unit="m3", verbose_name=_("unit volume"))
unit_weight = MeasurementField(unit="g", verbose_name=_("unit weight"))
class Meta:
verbose_name = _('sent product')
verbose_name_plural = _('sent products')
def __str__(self): # pragma: no cover
return "%(quantity)s of '%(product)s' in Shipment #%(shipment_pk)s" % {
'product': self.product,
'quantity': self.quantity,
'shipment_pk': self.shipment_id,
}
[docs] def cache_values(self):
prod = self.product
self.unit_volume = (prod.width * prod.height * prod.depth) / CUBIC_MM_TO_CUBIC_METERS_DIVISOR
self.unit_weight = prod.gross_weight