Source code for shoop.core.models._order_lines

# -*- 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, with_statement

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from enumfields import Enum, EnumIntegerField
from jsonfield import JSONField

from shoop.core.fields import MoneyValueField, QuantityField, UnsavedForeignKey
from shoop.core.pricing import Priceful
from shoop.core.taxing import LineTax
from shoop.utils.money import Money
from shoop.utils.properties import MoneyProperty, MoneyPropped, PriceProperty

from ._base import ShoopModel


class OrderLineType(Enum):
    PRODUCT = 1
    SHIPPING = 2
    PAYMENT = 3
    DISCOUNT = 4
    OTHER = 5
    REFUND = 6

    class Labels:
        PRODUCT = _('product')
        SHIPPING = _('shipping')
        PAYMENT = _('payment')
        DISCOUNT = _('discount')
        OTHER = _('other')
        REFUND = _('refund')


class OrderLineManager(models.Manager):

    def products(self):  # pragma: no cover
        return self.filter(type=OrderLineType.PRODUCT)

    def shipping(self):  # pragma: no cover
        return self.filter(type=OrderLineType.SHIPPING)

    def payment(self):  # pragma: no cover
        return self.filter(type=OrderLineType.PAYMENT)

    def discounts(self):
        return self.filter(type=OrderLineType.DISCOUNT)

    def refunds(self):
        return self.filter(type=OrderLineType.REFUND)

    def other(self):  # pragma: no cover
        return self.filter(type=OrderLineType.OTHER)


@python_2_unicode_compatible
class OrderLine(MoneyPropped, models.Model, Priceful):
    order = UnsavedForeignKey("Order", related_name='lines', on_delete=models.PROTECT, verbose_name=_('order'))
    product = UnsavedForeignKey(
        "Product", blank=True, null=True, related_name="order_lines",
        on_delete=models.PROTECT, verbose_name=_('product')
    )
    supplier = UnsavedForeignKey(
        "Supplier", blank=True, null=True, related_name="order_lines",
        on_delete=models.PROTECT, verbose_name=_('supplier')
    )

    parent_line = UnsavedForeignKey(
        "self", related_name="child_lines", blank=True, null=True,
        on_delete=models.PROTECT, verbose_name=_('parent line')
    )
    ordering = models.IntegerField(default=0, verbose_name=_('ordering'))
    type = EnumIntegerField(OrderLineType, default=OrderLineType.PRODUCT, verbose_name=_('line type'))
    sku = models.CharField(max_length=48, blank=True, verbose_name=_('line SKU'))
    text = models.CharField(max_length=256, verbose_name=_('line text'))
    accounting_identifier = models.CharField(max_length=32, blank=True, verbose_name=_('accounting identifier'))
    require_verification = models.BooleanField(default=False, verbose_name=_('require verification'))
    verified = models.BooleanField(default=False, verbose_name=_('verified'))
    extra_data = JSONField(blank=True, null=True, verbose_name=_('extra data'))

    # The following fields govern calculation of the prices
    quantity = QuantityField(verbose_name=_('quantity'), default=1)
    base_unit_price = PriceProperty('base_unit_price_value', 'order.currency', 'order.prices_include_tax')
    discount_amount = PriceProperty('discount_amount_value', 'order.currency', 'order.prices_include_tax')

    base_unit_price_value = MoneyValueField(verbose_name=_('unit price amount (undiscounted)'), default=0)
    discount_amount_value = MoneyValueField(verbose_name=_('total amount of discount'), default=0)

    objects = OrderLineManager()

    class Meta:
        verbose_name = _('order line')
        verbose_name_plural = _('order lines')

    def __str__(self):
        return "%dx %s (%s)" % (self.quantity, self.text, self.get_type_display())

    @property
    def tax_amount(self):
        """
        :rtype: shoop.utils.money.Money
        """
        zero = Money(0, self.order.currency)
        return sum((x.amount for x in self.taxes.all()), zero)

    @property
    def max_refundable_amount(self):
        """
        :rtype: shoop.utils.money.Money
        """
        refunds = self.order.lines.refunds().filter(parent_line=self)
        refund_total_value = sum(refund.taxful_price.amount.value for refund in refunds)
        return (self.taxful_price.amount + Money(refund_total_value, self.order.currency))

[docs] def save(self, *args, **kwargs): if not self.sku: self.sku = u"" if self.type == OrderLineType.PRODUCT and not self.product_id: raise ValidationError("Product-type order line can not be saved without a set product") if self.product_id and self.type != OrderLineType.PRODUCT: raise ValidationError("Order line has product but is not of Product type") if self.product_id and not self.supplier_id: raise ValidationError("Order line has product but no supplier") super(OrderLine, self).save(*args, **kwargs) if self.product_id: self.supplier.module.update_stock(self.product_id)
@python_2_unicode_compatible class OrderLineTax(MoneyPropped, ShoopModel, LineTax): order_line = models.ForeignKey( OrderLine, related_name='taxes', on_delete=models.PROTECT, verbose_name=_('order line')) tax = models.ForeignKey( "Tax", related_name="order_line_taxes", on_delete=models.PROTECT, verbose_name=_('tax')) name = models.CharField(max_length=200, verbose_name=_('tax name')) amount = MoneyProperty('amount_value', 'order_line.order.currency') base_amount = MoneyProperty('base_amount_value', 'order_line.order.currency') amount_value = MoneyValueField(verbose_name=_('tax amount')) base_amount_value = MoneyValueField( verbose_name=_('base amount'), help_text=_('Amount that this tax is calculated from')) ordering = models.IntegerField(default=0, verbose_name=_('ordering')) class Meta: ordering = ["ordering"] def __str__(self): return "%s: %s on %s" % (self.name, self.amount, self.base_amount)