Exemplo n.º 1
0
def test_compare_taxed_money_field_with_same_type_field():
    field_1 = TaxedMoneyField(net_field='price_net', gross_field='price_gross')
    field_2 = TaxedMoneyField(net_field='price_net', gross_field='price_gross')

    # Comparision is based on creation_counter attribute
    assert field_1 < field_2
    field_2.creation_counter -= 1
    assert field_1 == field_2
Exemplo n.º 2
0
class OrderLine(models.Model):
    order = models.ForeignKey(
        Order, related_name='lines', editable=False, on_delete=models.CASCADE)
    variant = models.ForeignKey(
        ProductVariant, related_name='+', on_delete=models.SET_NULL,
        blank=True, null=True)
    # max_length is as produced by ProductVariant's display_product method
    product_name = models.CharField(max_length=386)
    product_sku = models.CharField(max_length=32)
    is_shipping_required = models.BooleanField()
    quantity = models.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(999)])
    quantity_fulfilled = models.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(999)], default=0)
    unit_price_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES)
    unit_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES)
    unit_price = TaxedMoneyField(
        net_field='unit_price_net', gross_field='unit_price_gross')
    tax_rate = models.DecimalField(
        max_digits=5, decimal_places=2, default='0.0')

    def __str__(self):
        return self.product_name

    def get_total(self):
        return self.unit_price * self.quantity

    @property
    def quantity_unfulfilled(self):
        return self.quantity - self.quantity_fulfilled
Exemplo n.º 3
0
class OrderLine(models.Model):
    delivery_group = models.ForeignKey(
        DeliveryGroup, related_name='lines', editable=False,
        on_delete=models.CASCADE)
    product = models.ForeignKey(
        Product, blank=True, null=True, related_name='+',
        on_delete=models.SET_NULL)
    product_name = models.CharField(max_length=128)
    product_sku = models.CharField(max_length=32)
    is_shipping_required = models.BooleanField()
    stock_location = models.CharField(max_length=100, default='')
    stock = models.ForeignKey(
        'product.Stock', on_delete=models.SET_NULL, null=True)
    quantity = models.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(999)])
    unit_price_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=4)
    unit_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=4)
    unit_price = TaxedMoneyField(
        net_field='unit_price_net', gross_field='unit_price_gross')

    def __str__(self):
        return self.product_name

    def get_total(self):
        return self.unit_price * self.quantity
Exemplo n.º 4
0
class OrderLine(models.Model):
    order = models.ForeignKey(Order,
                              related_name="lines",
                              editable=False,
                              on_delete=models.CASCADE)
    variant = models.ForeignKey(
        "product.ProductVariant",
        related_name="order_lines",
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    # max_length is as produced by ProductVariant's display_product method
    product_name = models.CharField(max_length=386)
    translated_product_name = models.CharField(max_length=386,
                                               default="",
                                               blank=True)
    product_sku = models.CharField(max_length=32)
    is_shipping_required = models.BooleanField()
    quantity = models.IntegerField(validators=[MinValueValidator(1)])
    quantity_fulfilled = models.IntegerField(validators=[MinValueValidator(0)],
                                             default=0)
    unit_price_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    unit_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    unit_price = TaxedMoneyField(net_field="unit_price_net",
                                 gross_field="unit_price_gross")
    tax_rate = models.DecimalField(max_digits=5,
                                   decimal_places=2,
                                   default=Decimal("0.0"))

    objects = OrderLineQueryset.as_manager()

    class Meta:
        ordering = ("pk", )

    def __str__(self):
        return self.product_name

    def get_total(self):
        return self.unit_price * self.quantity

    @property
    def quantity_unfulfilled(self):
        return self.quantity - self.quantity_fulfilled

    @property
    def is_digital(self) -> bool:
        """Return true if product variant is a digital type and has assigned
        digital content"""
        is_digital = self.variant.is_digital()
        has_digital = hasattr(self.variant, "digital_content")
        return is_digital and has_digital
Exemplo n.º 5
0
def test_compare_taxed_money_field_with_same_type_field():
    field_1 = TaxedMoneyField(
        net_amount_field="price_net",
        gross_amount_field="price_gross",
        currency="currency",
    )
    field_2 = TaxedMoneyField(
        net_amount_field="price_net",
        gross_amount_field="price_gross",
        currency="currency",
    )

    # Comparision is based on creation_counter attribute
    assert field_1 < field_2
    field_2.creation_counter -= 1
    assert field_1 == field_2
Exemplo n.º 6
0
def test_taxed_money_field_init():
    field = TaxedMoneyField(
        net_amount_field="price_net",
        gross_amount_field="price_gross",
        currency="currency",
    )
    assert field.net_amount_field == "price_net"
    assert field.gross_amount_field == "price_gross"
    assert field.currency == "currency"
Exemplo n.º 7
0
def test_compare_money_field_with_taxed_money_field():
    field_1 = MoneyField(amount_field="money_net_amount",
                         currency_field="currency")
    field_2 = TaxedMoneyField(net_field="price_net", gross_field="price_gross")

    # Comparision is based on creation_counter attribute
    assert field_1 < field_2
    assert not field_1 > field_2
    field_2.creation_counter -= 1
    assert field_1 == field_2
Exemplo n.º 8
0
def test_compare_taxed_money_field_with_django_field():
    field_1 = TaxedMoneyField(net_field='price_net', gross_field='price_gross')
    field_2 = MoneyField(currency='BTC',
                         default='5',
                         max_digits=9,
                         decimal_places=2)

    # Comparision is based on creation_counter attribute
    assert field_1 < field_2
    field_2.creation_counter -= 1
    assert field_1 == field_2
Exemplo n.º 9
0
def test_compare_taxed_money_field_with_django_field():
    field_1 = TaxedMoneyField(
        net_amount_field="price_net",
        gross_amount_field="price_gross",
        currency="currency",
    )
    field_2 = DecimalField(default="5", max_digits=9, decimal_places=2)

    # Comparision is based on creation_counter attribute
    assert field_1 < field_2
    assert not field_1 > field_2
    field_2.creation_counter -= 1
    assert field_1 == field_2
Exemplo n.º 10
0
class TaskLine(models.Model):
    task = models.ForeignKey(Task,
                             related_name='lines',
                             editable=False,
                             on_delete=models.CASCADE)
    variant = models.ForeignKey('skill.SkillVariant',
                                related_name='task_lines',
                                on_delete=models.SET_NULL,
                                blank=True,
                                null=True)
    # max_length is as produced by SkillVariant's display_skill method
    skill_name = models.CharField(max_length=386)
    translated_skill_name = models.CharField(max_length=386,
                                             default='',
                                             blank=True)
    skill_sku = models.CharField(max_length=32)
    is_delivery_required = models.BooleanField()
    quantity = models.IntegerField(validators=[MinValueValidator(1)])
    quantity_fulfilled = models.IntegerField(validators=[MinValueValidator(0)],
                                             default=0)
    unit_price_net = MoneyField(currency=settings.DEFAULT_CURRENCY,
                                max_digits=settings.DEFAULT_MAX_DIGITS,
                                decimal_places=settings.DEFAULT_DECIMAL_PLACES)
    unit_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES)
    unit_price = TaxedMoneyField(net_field='unit_price_net',
                                 gross_field='unit_price_gross')
    tax_rate = models.DecimalField(max_digits=5,
                                   decimal_places=2,
                                   default=Decimal('0.0'))

    class Meta:
        ordering = ('pk', )

    def __str__(self):
        return self.skill_name

    def get_total(self):
        return self.unit_price * self.quantity

    @property
    def quantity_unfulfilled(self):
        return self.quantity - self.quantity_fulfilled
Exemplo n.º 11
0
class Model(models.Model):
    DEFAULT_NET = "5"
    DEFAULT_GROSS = "5"
    DEFAULT_CURRENCY = "BTC"

    currency = models.CharField(max_length=3,
                                default=DEFAULT_CURRENCY,
                                choices=AVAILABLE_CURRENCIES)
    price_net_amount = models.DecimalField(max_digits=9,
                                           decimal_places=2,
                                           default=DEFAULT_NET)
    price_net = MoneyField(amount_field="price_net_amount",
                           currency_field="currency")
    price_gross_amount = models.DecimalField(max_digits=9,
                                             decimal_places=2,
                                             default=DEFAULT_GROSS)
    price_gross = MoneyField(amount_field="price_gross_amount",
                             currency_field="currency")
    price = TaxedMoneyField(
        net_amount_field="price_net_amount",
        gross_amount_field="price_gross_amount",
        currency="currency",
    )
Exemplo n.º 12
0
class OrderLine(models.Model):
    order = models.ForeignKey(
        Order, related_name="lines", editable=False, on_delete=models.CASCADE
    )
    variant = models.ForeignKey(
        "product.ProductVariant",
        related_name="order_lines",
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    # max_length is as produced by ProductVariant's display_product method
    product_name = models.CharField(max_length=386)
    variant_name = models.CharField(max_length=255, default="", blank=True)
    translated_product_name = models.CharField(max_length=386, default="", blank=True)
    translated_variant_name = models.CharField(max_length=255, default="", blank=True)
    product_sku = models.CharField(max_length=255)
    is_shipping_required = models.BooleanField()
    quantity = models.IntegerField(validators=[MinValueValidator(1)])
    quantity_fulfilled = models.IntegerField(
        validators=[MinValueValidator(0)], default=0
    )

    currency = models.CharField(max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH,)

    unit_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    unit_price_net = MoneyField(
        amount_field="unit_price_net_amount", currency_field="currency"
    )

    unit_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    unit_price_gross = MoneyField(
        amount_field="unit_price_gross_amount", currency_field="currency"
    )

    unit_price = TaxedMoneyField(
        net_amount_field="unit_price_net_amount",
        gross_amount_field="unit_price_gross_amount",
        currency="currency",
    )

    total_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    total_price_net = MoneyField(
        amount_field="total_price_net_amount", currency_field="currency",
    )

    total_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    total_price_gross = MoneyField(
        amount_field="total_price_gross_amount", currency_field="currency",
    )

    total_price = TaxedMoneyField(
        net_amount_field="total_price_net_amount",
        gross_amount_field="total_price_gross_amount",
        currency="currency",
    )

    tax_rate = models.DecimalField(
        max_digits=5, decimal_places=4, default=Decimal("0.0")
    )

    objects = OrderLineQueryset.as_manager()

    class Meta:
        ordering = ("pk",)

    def __str__(self):
        return (
            f"{self.product_name} ({self.variant_name})"
            if self.variant_name
            else self.product_name
        )

    @property
    def quantity_unfulfilled(self):
        return self.quantity - self.quantity_fulfilled

    @property
    def is_digital(self) -> Optional[bool]:
        """Check if a variant is digital and contains digital content."""
        if not self.variant:
            return None
        is_digital = self.variant.is_digital()
        has_digital = hasattr(self.variant, "digital_content")
        return is_digital and has_digital
Exemplo n.º 13
0
class Order(ModelWithMetadata):
    created = models.DateTimeField(default=now, editable=False)
    status = models.CharField(
        max_length=32, default=OrderStatus.UNFULFILLED, choices=OrderStatus.CHOICES
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        blank=True,
        null=True,
        related_name="orders",
        on_delete=models.SET_NULL,
    )
    language_code = models.CharField(max_length=35, default=settings.LANGUAGE_CODE)
    tracking_client_id = models.CharField(max_length=36, blank=True, editable=False)
    billing_address = models.ForeignKey(
        Address, related_name="+", editable=False, null=True, on_delete=models.SET_NULL
    )
    shipping_address = models.ForeignKey(
        Address, related_name="+", editable=False, null=True, on_delete=models.SET_NULL
    )
    user_email = models.EmailField(blank=True, default="")

    currency = models.CharField(max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH,)

    shipping_method = models.ForeignKey(
        ShippingMethod,
        blank=True,
        null=True,
        related_name="orders",
        on_delete=models.SET_NULL,
    )
    shipping_method_name = models.CharField(
        max_length=255, null=True, default=None, blank=True, editable=False
    )
    channel = models.ForeignKey(
        Channel, related_name="orders", on_delete=models.PROTECT,
    )
    shipping_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
        editable=False,
    )
    shipping_price_net = MoneyField(
        amount_field="shipping_price_net_amount", currency_field="currency"
    )

    shipping_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
        editable=False,
    )
    shipping_price_gross = MoneyField(
        amount_field="shipping_price_gross_amount", currency_field="currency"
    )

    shipping_price = TaxedMoneyField(
        net_amount_field="shipping_price_net_amount",
        gross_amount_field="shipping_price_gross_amount",
        currency_field="currency",
    )

    token = models.CharField(max_length=36, unique=True, blank=True)
    # Token of a checkout instance that this order was created from
    checkout_token = models.CharField(max_length=36, blank=True)

    total_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    total_net = MoneyField(amount_field="total_net_amount", currency_field="currency")

    total_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    total_gross = MoneyField(
        amount_field="total_gross_amount", currency_field="currency"
    )

    total = TaxedMoneyField(
        net_amount_field="total_net_amount",
        gross_amount_field="total_gross_amount",
        currency_field="currency",
    )

    voucher = models.ForeignKey(
        Voucher, blank=True, null=True, related_name="+", on_delete=models.SET_NULL
    )
    gift_cards = models.ManyToManyField(GiftCard, blank=True, related_name="orders")
    discount_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    discount = MoneyField(amount_field="discount_amount", currency_field="currency")
    discount_name = models.CharField(max_length=255, blank=True, null=True)
    translated_discount_name = models.CharField(max_length=255, blank=True, null=True)
    display_gross_prices = models.BooleanField(default=True)
    customer_note = models.TextField(blank=True, default="")
    weight = MeasurementField(
        measurement=Weight, unit_choices=WeightUnits.CHOICES, default=zero_weight
    )
    redirect_url = models.URLField(blank=True, null=True)
    objects = OrderQueryset.as_manager()

    class Meta:
        ordering = ("-pk",)
        permissions = ((OrderPermissions.MANAGE_ORDERS.codename, "Manage orders."),)

    def save(self, *args, **kwargs):
        if not self.token:
            self.token = str(uuid4())
        return super().save(*args, **kwargs)

    def is_fully_paid(self):
        total_paid = self._total_paid()
        return total_paid.gross >= self.total.gross

    def is_partly_paid(self):
        total_paid = self._total_paid()
        return total_paid.gross.amount > 0

    def get_customer_email(self):
        return self.user.email if self.user else self.user_email

    def _total_paid(self):
        # Get total paid amount from partially charged,
        # fully charged and partially refunded payments
        payments = self.payments.filter(
            charge_status__in=[
                ChargeStatus.PARTIALLY_CHARGED,
                ChargeStatus.FULLY_CHARGED,
                ChargeStatus.PARTIALLY_REFUNDED,
            ]
        )
        total_captured = [payment.get_captured_amount() for payment in payments]
        total_paid = sum(total_captured, zero_taxed_money(currency=self.currency))
        return total_paid

    def _index_billing_phone(self):
        return self.billing_address.phone

    def _index_shipping_phone(self):
        return self.shipping_address.phone

    def __iter__(self):
        return iter(self.lines.all())

    def __repr__(self):
        return "<Order #%r>" % (self.id,)

    def __str__(self):
        return "#%d" % (self.id,)

    def get_last_payment(self):
        return max(self.payments.all(), default=None, key=attrgetter("pk"))

    def get_payment_status(self):
        last_payment = self.get_last_payment()
        if last_payment:
            return last_payment.charge_status
        return ChargeStatus.NOT_CHARGED

    def get_payment_status_display(self):
        last_payment = self.get_last_payment()
        if last_payment:
            return last_payment.get_charge_status_display()
        return dict(ChargeStatus.CHOICES).get(ChargeStatus.NOT_CHARGED)

    def is_pre_authorized(self):
        return (
            self.payments.filter(
                is_active=True,
                transactions__kind=TransactionKind.AUTH,
                transactions__action_required=False,
            )
            .filter(transactions__is_success=True)
            .exists()
        )

    def is_captured(self):
        return (
            self.payments.filter(
                is_active=True,
                transactions__kind=TransactionKind.CAPTURE,
                transactions__action_required=False,
            )
            .filter(transactions__is_success=True)
            .exists()
        )

    @property
    def quantity_fulfilled(self):
        return sum([line.quantity_fulfilled for line in self])

    def is_shipping_required(self):
        return any(line.is_shipping_required for line in self)

    def get_subtotal(self):
        subtotal_iterator = (line.total_price for line in self)
        return sum(subtotal_iterator, zero_taxed_money(currency=self.currency))

    def get_total_quantity(self):
        return sum([line.quantity for line in self])

    def is_draft(self):
        return self.status == OrderStatus.DRAFT

    def is_open(self):
        statuses = {OrderStatus.UNFULFILLED, OrderStatus.PARTIALLY_FULFILLED}
        return self.status in statuses

    def can_cancel(self):
        return (
            not self.fulfillments.exclude(status=FulfillmentStatus.CANCELED).exists()
        ) and self.status not in {OrderStatus.CANCELED, OrderStatus.DRAFT}

    def can_capture(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        order_status_ok = self.status not in {OrderStatus.DRAFT, OrderStatus.CANCELED}
        return payment.can_capture() and order_status_ok

    def can_void(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        return payment.can_void()

    def can_refund(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        return payment.can_refund()

    def can_mark_as_paid(self):
        return len(self.payments.all()) == 0

    @property
    def total_authorized(self):
        payment = self.get_last_payment()
        if payment:
            return payment.get_authorized_amount()
        return zero_money(self.currency)

    @property
    def total_captured(self):
        payment = self.get_last_payment()
        if payment and payment.charge_status in (
            ChargeStatus.PARTIALLY_CHARGED,
            ChargeStatus.FULLY_CHARGED,
            ChargeStatus.PARTIALLY_REFUNDED,
        ):
            return Money(payment.captured_amount, payment.currency)
        return zero_money(self.currency)

    @property
    def total_balance(self):
        return self.total_captured - self.total.gross

    def get_total_weight(self, *_args):
        return self.weight
Exemplo n.º 14
0
class Order(models.Model):
    created = models.DateTimeField(
        default=now, editable=False)
    last_status_change = models.DateTimeField(
        default=now, editable=False)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name='orders',
        on_delete=models.SET_NULL)
    language_code = models.CharField(
        max_length=35, default=settings.LANGUAGE_CODE)
    tracking_client_id = models.CharField(
        max_length=36, blank=True, editable=False)
    billing_address = models.ForeignKey(
        Address, related_name='+', editable=False,
        on_delete=models.PROTECT)
    shipping_address = models.ForeignKey(
        Address, related_name='+', editable=False, null=True,
        on_delete=models.PROTECT)
    user_email = models.EmailField(
        blank=True, default='', editable=False)
    shipping_price_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2,
        default=0, editable=False)
    shipping_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2,
        default=0, editable=False)
    shipping_price = TaxedMoneyField(
        net_field='shipping_price_net', gross_field='shipping_price_gross')
    token = models.CharField(max_length=36, unique=True)
    total_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2,
        blank=True, null=True)
    total_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2,
        blank=True, null=True)
    total = TaxedMoneyField(net_field='total_net', gross_field='total_gross')
    voucher = models.ForeignKey(
        Voucher, null=True, related_name='+', on_delete=models.SET_NULL)
    discount_amount = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2,
        blank=True, null=True)
    discount_name = models.CharField(max_length=255, default='', blank=True)

    objects = OrderQuerySet.as_manager()

    class Meta:
        ordering = ('-last_status_change',)
        permissions = (
            ('view_order',
             pgettext_lazy('Permission description', 'Can view orders')),
            ('edit_order',
             pgettext_lazy('Permission description', 'Can edit orders')))

    def save(self, *args, **kwargs):
        if not self.token:
            self.token = str(uuid4())
        return super().save(*args, **kwargs)

    def get_lines(self):
        return OrderLine.objects.filter(delivery_group__order=self)

    def is_fully_paid(self):
        total_paid = sum(
            [
                payment.get_total_price() for payment in
                self.payments.filter(status=PaymentStatus.CONFIRMED)],
            TaxedMoney(
                net=Money(0, currency=settings.DEFAULT_CURRENCY),
                gross=Money(0, currency=settings.DEFAULT_CURRENCY)))
        return total_paid.gross >= self.total.gross

    def get_user_current_email(self):
        return self.user.email if self.user else self.user_email

    def _index_billing_phone(self):
        return self.billing_address.phone

    def _index_shipping_phone(self):
        return self.shipping_address.phone

    def __iter__(self):
        return iter(self.groups.all())

    def __repr__(self):
        return '<Order #%r>' % (self.id,)

    def __str__(self):
        return '#%d' % (self.id,)

    def get_absolute_url(self):
        return reverse('order:details', kwargs={'token': self.token})

    def get_last_payment_status(self):
        last_payment = self.payments.last()
        if last_payment:
            return last_payment.status
        return None

    def get_last_payment_status_display(self):
        last_payment = self.payments.last()
        if last_payment:
            return last_payment.get_status_display()
        return None

    def is_pre_authorized(self):
        return self.payments.filter(status=PaymentStatus.PREAUTH).exists()

    def is_shipping_required(self):
        return any(group.is_shipping_required() for group in self.groups.all())

    @property
    def status(self):
        """Order status deduced from shipment groups."""
        statuses = set([group.status for group in self.groups.all()])
        return (
            OrderStatus.OPEN if GroupStatus.NEW in statuses
            else OrderStatus.CLOSED)

    @property
    def is_open(self):
        return self.status == OrderStatus.OPEN

    def get_status_display(self):
        """Order status display text."""
        return dict(OrderStatus.CHOICES)[self.status]

    def get_subtotal(self):
        subtotal_iterator = (line.get_total() for line in self.get_lines())
        return sum(subtotal_iterator, ZERO_TAXED_MONEY)

    def can_cancel(self):
        return self.status == OrderStatus.OPEN
Exemplo n.º 15
0
class OrderLine(models.Model):
    order = models.ForeignKey(
        Order, related_name='lines', editable=False, on_delete=models.CASCADE)
    variant = models.ForeignKey(
        'product.ProductVariant',
        related_name='+',
        on_delete=models.SET_NULL,
        blank=True,
        null=True)
    # max_length is as produced by ProductVariant's display_product method
    product_name = models.CharField(max_length=386)
    translated_product_name = models.CharField(max_length=386, default='')
    product_sku = models.CharField(max_length=32)
    is_shipping_required = models.BooleanField()
    quantity = models.IntegerField(
        validators=[MinValueValidator(0),
                    MaxValueValidator(999)])
    quantity_fulfilled = models.IntegerField(
        validators=[MinValueValidator(0),
                    MaxValueValidator(999)], default=0)
    unit_price_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES)
    unit_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES)
    unit_price = TaxedMoneyField(
        net_field='unit_price_net', gross_field='unit_price_gross')
    tax_rate = models.DecimalField(
        max_digits=5, decimal_places=2, default='0.0')
    work_dir = models.CharField(max_length=255, blank=True, null=True)
    param_file = models.CharField(max_length=255, blank=True, null=True)
    result_file = models.CharField(max_length=255, blank=True, null=True)
    upload_name = models.CharField(max_length=255, blank=True, null=True)
    download_name = models.CharField(
        max_length=255, blank=True, null=True, default='results.zip')
    status = models.CharField(
        max_length=32, default=JobStatus.DRAFT, choices=JobStatus.CHOICES)
    exe_name = models.CharField(max_length=255, default='exe.sh')
    work_base = models.CharField(max_length=255, blank=True, null=True)
    source_file = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return self.product_name

    def get_total(self):
        return self.unit_price * self.quantity

    @property
    def quantity_unfulfilled(self):
        return self.quantity - self.quantity_fulfilled

    def set_work_dir(self, id):
        now_time = now().strftime("%Y-%m-%d")
        self.work_dir = self.work_base + str(id) + '/' + now_time + '/'
        os.makedirs(self.work_dir)
        return self.work_dir

    def set_param_file(self, param_f):
        self.param_file = self.work_dir + self.upload_name
        #need to be altered accordingly
        try:
            shutil.copy(settings.PROJECT_ROOT + param_f.url, self.param_file)
        except:
            pass
        return self.param_file

    def set_result_file(self, id):
        #need to be altered accordingly
        self.result_file = settings.MEDIA_ROOT + '/saved_files/result_files/' + str(
            id) + '/' + self.download_name
        return self.result_file

    def set_source_file(self, source_f):
        self.source_file = self.work_dir + source_f.split('/')[-1]
        #need to be altered accordingly
        try:
            shutil.copy(source_f, self.source_file)
            os.chdir(self.work_dir)
            os.system('/usr/bin/unzip -q ' + self.source_file)
        except:
            pass
        return self.source_file

    def get_result_file_link(self):
        return self.result_file.replace(settings.PROJECT_ROOT, '')
Exemplo n.º 16
0
class Order(models.Model):
    created = models.DateTimeField(default=now, editable=False)
    status = models.CharField(max_length=32,
                              default=OrderStatus.UNFULFILLED,
                              choices=OrderStatus.CHOICES)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             blank=True,
                             null=True,
                             related_name='orders',
                             on_delete=models.SET_NULL)
    language_code = models.CharField(max_length=35,
                                     default=settings.LANGUAGE_CODE)
    tracking_client_id = models.CharField(max_length=36,
                                          blank=True,
                                          editable=False)
    billing_address = models.ForeignKey(Address,
                                        related_name='+',
                                        editable=False,
                                        null=True,
                                        on_delete=models.SET_NULL)
    shipping_address = models.ForeignKey(Address,
                                         related_name='+',
                                         editable=False,
                                         null=True,
                                         on_delete=models.SET_NULL)
    user_email = models.EmailField(blank=True, default='')
    shipping_method = models.ForeignKey(ShippingMethod,
                                        blank=True,
                                        null=True,
                                        related_name='orders',
                                        on_delete=models.SET_NULL)
    shipping_price_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
        editable=False)
    shipping_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
        editable=False)
    shipping_price = TaxedMoneyField(net_field='shipping_price_net',
                                     gross_field='shipping_price_gross')
    shipping_method_name = models.CharField(max_length=255,
                                            null=True,
                                            default=None,
                                            blank=True,
                                            editable=False)
    token = models.CharField(max_length=36, unique=True, blank=True)
    # Token of a checkout instance that this order was created from
    checkout_token = models.CharField(max_length=36, blank=True)
    total_net = MoneyField(currency=settings.DEFAULT_CURRENCY,
                           max_digits=settings.DEFAULT_MAX_DIGITS,
                           decimal_places=settings.DEFAULT_DECIMAL_PLACES,
                           default=zero_money)
    total_gross = MoneyField(currency=settings.DEFAULT_CURRENCY,
                             max_digits=settings.DEFAULT_MAX_DIGITS,
                             decimal_places=settings.DEFAULT_DECIMAL_PLACES,
                             default=zero_money)
    total = TaxedMoneyField(net_field='total_net', gross_field='total_gross')
    voucher = models.ForeignKey(Voucher,
                                blank=True,
                                null=True,
                                related_name='+',
                                on_delete=models.SET_NULL)
    discount_amount = MoneyField(
        currency=settings.DEFAULT_CURRENCY,
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=zero_money)
    discount_name = models.CharField(max_length=255, default='', blank=True)
    translated_discount_name = models.CharField(max_length=255,
                                                default='',
                                                blank=True)
    display_gross_prices = models.BooleanField(default=True)
    customer_note = models.TextField(blank=True, default='')
    weight = MeasurementField(measurement=Weight,
                              unit_choices=WeightUnits.CHOICES,
                              default=zero_weight)
    objects = OrderQueryset.as_manager()

    class Meta:
        ordering = ('-pk', )
        permissions = (('manage_orders',
                        pgettext_lazy('Permission description',
                                      'Manage orders.')), )

    def save(self, *args, **kwargs):
        if not self.token:
            self.token = str(uuid4())
        return super().save(*args, **kwargs)

    def is_fully_paid(self):
        total_paid = self._total_paid()
        return total_paid.gross >= self.total.gross

    def is_partly_paid(self):
        total_paid = self._total_paid()
        return total_paid.gross.amount > 0

    def get_user_current_email(self):
        return self.user.email if self.user else self.user_email

    def _total_paid(self):
        # Get total paid amount from partially charged,
        # fully charged and partially refunded payments
        payments = self.payments.filter(charge_status__in=[
            ChargeStatus.PARTIALLY_CHARGED, ChargeStatus.FULLY_CHARGED,
            ChargeStatus.PARTIALLY_REFUNDED
        ])
        total_captured = [
            payment.get_captured_amount() for payment in payments
        ]
        total_paid = sum(total_captured, ZERO_TAXED_MONEY)
        return total_paid

    def _index_billing_phone(self):
        return self.billing_address.phone

    def _index_shipping_phone(self):
        return self.shipping_address.phone

    def __iter__(self):
        return iter(self.lines.all())

    def __repr__(self):
        return '<Order #%r>' % (self.id, )

    def __str__(self):
        return '#%d' % (self.id, )

    def get_absolute_url(self):
        return reverse('order:details', kwargs={'token': self.token})

    def get_last_payment(self):
        return max(self.payments.all(), default=None, key=attrgetter('pk'))

    def get_payment_status(self):
        last_payment = self.get_last_payment()
        if last_payment:
            return last_payment.charge_status
        return ChargeStatus.NOT_CHARGED

    def get_payment_status_display(self):
        last_payment = self.get_last_payment()
        if last_payment:
            return last_payment.get_charge_status_display()
        return dict(ChargeStatus.CHOICES).get(ChargeStatus.NOT_CHARGED)

    def is_pre_authorized(self):
        return self.payments.filter(
            is_active=True, transactions__kind=TransactionKind.AUTH).filter(
                transactions__is_success=True).exists()

    @property
    def quantity_fulfilled(self):
        return sum([line.quantity_fulfilled for line in self])

    def is_shipping_required(self):
        return any(line.is_shipping_required for line in self)

    def get_subtotal(self):
        subtotal_iterator = (line.get_total() for line in self)
        return sum(subtotal_iterator, ZERO_TAXED_MONEY)

    def get_total_quantity(self):
        return sum([line.quantity for line in self])

    def is_draft(self):
        return self.status == OrderStatus.DRAFT

    def is_open(self):
        statuses = {OrderStatus.UNFULFILLED, OrderStatus.PARTIALLY_FULFILLED}
        return self.status in statuses

    def can_cancel(self):
        return self.status not in {OrderStatus.CANCELED, OrderStatus.DRAFT}

    def can_capture(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        order_status_ok = self.status not in {
            OrderStatus.DRAFT, OrderStatus.CANCELED
        }
        return payment.can_capture() and order_status_ok

    def can_charge(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        order_status_ok = self.status not in {
            OrderStatus.DRAFT, OrderStatus.CANCELED
        }
        return payment.can_charge() and order_status_ok

    def can_void(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        return payment.can_void()

    def can_refund(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        return payment.can_refund()

    def can_mark_as_paid(self):
        return len(self.payments.all()) == 0

    @property
    def total_authorized(self):
        payment = self.get_last_payment()
        if payment:
            return payment.get_authorized_amount()
        return zero_money()

    @property
    def total_captured(self):
        payment = self.get_last_payment()
        if payment and payment.charge_status in (
                ChargeStatus.PARTIALLY_CHARGED, ChargeStatus.FULLY_CHARGED,
                ChargeStatus.PARTIALLY_REFUNDED):
            return Money(payment.captured_amount, payment.currency)
        return zero_money()

    @property
    def total_balance(self):
        return self.total_captured - self.total.gross

    def get_total_weight(self):
        return self.weight
Exemplo n.º 17
0
def test_price_field_init():
    field = TaxedMoneyField(net_field='price_net', gross_field='price_gross')
    assert field.net_field == 'price_net'
    assert field.gross_field == 'price_gross'
Exemplo n.º 18
0
class Order(models.Model):
    created = models.DateTimeField(default=timezone.now, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             blank=True,
                             null=True,
                             related_name='invoices',
                             on_delete=models.SET_NULL)

    first_name = models.CharField(max_length=256, blank=True)
    last_name = models.CharField(max_length=256, blank=True)
    company_name = models.CharField(max_length=256, blank=True)
    street_address_1 = models.CharField(max_length=256, blank=True)
    street_address_2 = models.CharField(max_length=256, blank=True)
    city = models.CharField(max_length=256, blank=True)
    postcode = models.CharField(max_length=20, blank=True)
    country = CountryField()

    user_email = models.EmailField(blank=True, default='')

    total_net = MoneyField(currency=settings.DEFAULT_CURRENCY,
                           max_digits=12,
                           decimal_places=settings.DEFAULT_DECIMAL_PLACES,
                           default=0)
    total_gross = MoneyField(currency=settings.DEFAULT_CURRENCY,
                             max_digits=12,
                             decimal_places=settings.DEFAULT_DECIMAL_PLACES,
                             default=0)
    total = TaxedMoneyField(net_field='total_net', gross_field='total_gross')

    is_donation = models.BooleanField(default=False)
    description = models.CharField(max_length=255, blank=True)
    customer_note = models.TextField(blank=True, default='')
    kind = models.CharField(max_length=255, default='', blank=True)

    token = models.UUIDField(default=uuid.uuid4, db_index=True)

    def __str__(self):
        return self.description

    @property
    def currency(self):
        return settings.DEFAULT_CURRENCY

    @property
    def amount(self):
        return self.total.gross.amount

    @property
    def amount_cents(self):
        return int(self.total.gross.amount * 100)

    @property
    def address(self):
        return '\n'.join(x for x in [
            self.street_address_1, self.street_address_2, '{} {}'.format(
                self.postcode, self.city), self.country.name
        ] if x)

    def is_fully_paid(self):
        total_paid = sum([
            payment.get_captured_amount()
            for payment in self.payments.filter(status=PaymentStatus.CONFIRMED)
        ], ZERO_TAXED_MONEY)
        return total_paid.gross >= self.total.gross

    def get_absolute_url(self):
        return reverse('froide_payment:order-detail',
                       kwargs={'token': self.token})

    def get_failure_url(self):
        obj = self.get_domain_object()
        if obj is None:
            return '/'
        return obj.get_failure_url()

    def get_success_url(self):
        obj = self.get_domain_object()
        if obj is None:
            return '/'
        return obj.get_success_url()

    def get_domain_object(self):
        model = self.get_domain_model()
        if model is None:
            return None
        try:
            return model.objects.get(order=self)
        except model.DoesNotExist:
            return None

    def get_domain_model(self):
        if not self.kind:
            return None
        try:
            return apps.get_model(self.kind)
        except LookupError:
            return None
Exemplo n.º 19
0
class Order(ModelWithMetadata):
    id = models.UUIDField(primary_key=True,
                          editable=False,
                          unique=True,
                          default=uuid4)
    number = models.IntegerField(unique=True,
                                 default=get_order_number,
                                 editable=False)
    use_old_id = models.BooleanField(default=False)

    created_at = models.DateTimeField(default=now, editable=False)
    updated_at = models.DateTimeField(auto_now=True,
                                      editable=False,
                                      db_index=True)
    status = models.CharField(max_length=32,
                              default=OrderStatus.UNFULFILLED,
                              choices=OrderStatus.CHOICES)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        blank=True,
        null=True,
        related_name="orders",
        on_delete=models.SET_NULL,
    )
    language_code = models.CharField(max_length=35,
                                     choices=settings.LANGUAGES,
                                     default=settings.LANGUAGE_CODE)
    tracking_client_id = models.CharField(max_length=36,
                                          blank=True,
                                          editable=False)
    billing_address = models.ForeignKey(
        "account.Address",
        related_name="+",
        editable=False,
        null=True,
        on_delete=models.SET_NULL,
    )
    shipping_address = models.ForeignKey(
        "account.Address",
        related_name="+",
        editable=False,
        null=True,
        on_delete=models.SET_NULL,
    )
    user_email = models.EmailField(blank=True, default="")
    original = models.ForeignKey("self",
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL)
    origin = models.CharField(max_length=32, choices=OrderOrigin.CHOICES)

    currency = models.CharField(
        max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH, )

    shipping_method = models.ForeignKey(
        ShippingMethod,
        blank=True,
        null=True,
        related_name="orders",
        on_delete=models.SET_NULL,
    )
    collection_point = models.ForeignKey(
        "warehouse.Warehouse",
        blank=True,
        null=True,
        related_name="orders",
        on_delete=models.SET_NULL,
    )
    shipping_method_name = models.CharField(max_length=255,
                                            null=True,
                                            default=None,
                                            blank=True,
                                            editable=False)
    collection_point_name = models.CharField(max_length=255,
                                             null=True,
                                             default=None,
                                             blank=True,
                                             editable=False)

    channel = models.ForeignKey(
        Channel,
        related_name="orders",
        on_delete=models.PROTECT,
    )
    shipping_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
        editable=False,
    )
    shipping_price_net = MoneyField(amount_field="shipping_price_net_amount",
                                    currency_field="currency")

    shipping_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
        editable=False,
    )
    shipping_price_gross = MoneyField(
        amount_field="shipping_price_gross_amount", currency_field="currency")

    shipping_price = TaxedMoneyField(
        net_amount_field="shipping_price_net_amount",
        gross_amount_field="shipping_price_gross_amount",
        currency_field="currency",
    )
    shipping_tax_rate = models.DecimalField(max_digits=5,
                                            decimal_places=4,
                                            default=Decimal("0.0"))

    # Token of a checkout instance that this order was created from
    checkout_token = models.CharField(max_length=36, blank=True)

    total_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    undiscounted_total_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )

    total_net = MoneyField(amount_field="total_net_amount",
                           currency_field="currency")
    undiscounted_total_net = MoneyField(
        amount_field="undiscounted_total_net_amount",
        currency_field="currency")

    total_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    undiscounted_total_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )

    total_gross = MoneyField(amount_field="total_gross_amount",
                             currency_field="currency")
    undiscounted_total_gross = MoneyField(
        amount_field="undiscounted_total_gross_amount",
        currency_field="currency")

    total = TaxedMoneyField(
        net_amount_field="total_net_amount",
        gross_amount_field="total_gross_amount",
        currency_field="currency",
    )
    undiscounted_total = TaxedMoneyField(
        net_amount_field="undiscounted_total_net_amount",
        gross_amount_field="undiscounted_total_gross_amount",
        currency_field="currency",
    )

    total_paid_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    total_paid = MoneyField(amount_field="total_paid_amount",
                            currency_field="currency")

    voucher = models.ForeignKey(Voucher,
                                blank=True,
                                null=True,
                                related_name="+",
                                on_delete=models.SET_NULL)
    gift_cards = models.ManyToManyField(GiftCard,
                                        blank=True,
                                        related_name="orders")

    display_gross_prices = models.BooleanField(default=True)
    customer_note = models.TextField(blank=True, default="")
    weight = MeasurementField(
        measurement=Weight,
        unit_choices=WeightUnits.CHOICES,  # type: ignore
        default=zero_weight,
    )
    redirect_url = models.URLField(blank=True, null=True)
    search_document = models.TextField(blank=True, default="")

    objects = models.Manager.from_queryset(OrderQueryset)()

    class Meta:
        ordering = ("-number", )
        permissions = ((OrderPermissions.MANAGE_ORDERS.codename,
                        "Manage orders."), )
        indexes = [
            *ModelWithMetadata.Meta.indexes,
            GinIndex(
                name="order_search_gin",
                # `opclasses` and `fields` should be the same length
                fields=["search_document"],
                opclasses=["gin_trgm_ops"],
            ),
            GinIndex(
                name="order_email_search_gin",
                # `opclasses` and `fields` should be the same length
                fields=["user_email"],
                opclasses=["gin_trgm_ops"],
            ),
        ]

    def is_fully_paid(self):
        return self.total_paid >= self.total.gross

    def is_partly_paid(self):
        return self.total_paid_amount > 0

    def get_customer_email(self):
        return self.user.email if self.user_id else self.user_email

    def update_total_paid(self):
        self.total_paid_amount = (sum(
            self.payments.values_list("captured_amount", flat=True)) or 0)
        self.save(update_fields=["total_paid_amount", "updated_at"])

    def _index_billing_phone(self):
        return self.billing_address.phone

    def _index_shipping_phone(self):
        return self.shipping_address.phone

    def __repr__(self):
        return "<Order #%r>" % (self.id, )

    def __str__(self):
        return "#%d" % (self.id, )

    def get_last_payment(self):
        # Skipping a partial payment is a temporary workaround for storing a basic data
        # about partial payment from Adyen plugin. This is something that will removed
        # in 3.1 by introducing a partial payments feature.
        payments = [
            payment for payment in self.payments.all() if not payment.partial
        ]
        return max(payments, default=None, key=attrgetter("pk"))

    def is_pre_authorized(self):
        return (self.payments.filter(
            is_active=True,
            transactions__kind=TransactionKind.AUTH,
            transactions__action_required=False,
        ).filter(transactions__is_success=True).exists())

    def is_captured(self):
        return (self.payments.filter(
            is_active=True,
            transactions__kind=TransactionKind.CAPTURE,
            transactions__action_required=False,
        ).filter(transactions__is_success=True).exists())

    def is_shipping_required(self):
        return any(line.is_shipping_required for line in self.lines.all())

    def get_subtotal(self):
        return get_subtotal(self.lines.all(), self.currency)

    def get_total_quantity(self):
        return sum([line.quantity for line in self.lines.all()])

    def is_draft(self):
        return self.status == OrderStatus.DRAFT

    def is_unconfirmed(self):
        return self.status == OrderStatus.UNCONFIRMED

    def is_open(self):
        statuses = {OrderStatus.UNFULFILLED, OrderStatus.PARTIALLY_FULFILLED}
        return self.status in statuses

    def can_cancel(self):
        statuses_allowed_to_cancel = [
            FulfillmentStatus.CANCELED,
            FulfillmentStatus.REFUNDED,
            FulfillmentStatus.REPLACED,
            FulfillmentStatus.REFUNDED_AND_RETURNED,
            FulfillmentStatus.RETURNED,
        ]
        return (not self.fulfillments.exclude(
            status__in=statuses_allowed_to_cancel).exists()
                ) and self.status not in {
                    OrderStatus.CANCELED, OrderStatus.DRAFT
                }

    def can_capture(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        order_status_ok = self.status not in {
            OrderStatus.DRAFT, OrderStatus.CANCELED
        }
        return payment.can_capture() and order_status_ok

    def can_void(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        return payment.can_void()

    def can_refund(self, payment=None):
        if not payment:
            payment = self.get_last_payment()
        if not payment:
            return False
        return payment.can_refund()

    def can_mark_as_paid(self, payments=None):
        if not payments:
            payments = self.payments.all()
        return len(payments) == 0

    @property
    def total_authorized(self):
        return get_total_authorized(self.payments.all(), self.currency)

    @property
    def total_captured(self):
        return self.total_paid

    @property
    def total_balance(self):
        return self.total_captured - self.total.gross

    def get_total_weight(self, _lines=None):
        return self.weight
Exemplo n.º 20
0
class OrderLine(models.Model):
    id = models.UUIDField(primary_key=True,
                          editable=False,
                          unique=True,
                          default=uuid4)
    old_id = models.PositiveIntegerField(unique=True, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    order = models.ForeignKey(
        Order,
        related_name="lines",
        editable=False,
        on_delete=models.CASCADE,
    )
    variant = models.ForeignKey(
        "product.ProductVariant",
        related_name="order_lines",
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    # max_length is as produced by ProductVariant's display_product method
    product_name = models.CharField(max_length=386)
    variant_name = models.CharField(max_length=255, default="", blank=True)
    translated_product_name = models.CharField(max_length=386,
                                               default="",
                                               blank=True)
    translated_variant_name = models.CharField(max_length=255,
                                               default="",
                                               blank=True)
    product_sku = models.CharField(max_length=255, null=True, blank=True)
    # str with GraphQL ID used as fallback when product SKU is not available
    product_variant_id = models.CharField(max_length=255,
                                          null=True,
                                          blank=True)
    is_shipping_required = models.BooleanField()
    is_gift_card = models.BooleanField()
    quantity = models.IntegerField(validators=[MinValueValidator(1)])
    quantity_fulfilled = models.IntegerField(validators=[MinValueValidator(0)],
                                             default=0)

    currency = models.CharField(
        max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH, )

    unit_discount_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    unit_discount = MoneyField(amount_field="unit_discount_amount",
                               currency_field="currency")
    unit_discount_type = models.CharField(
        max_length=10,
        choices=DiscountValueType.CHOICES,
        default=DiscountValueType.FIXED,
    )
    unit_discount_reason = models.TextField(blank=True, null=True)

    unit_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    # stores the value of the applied discount. Like 20 of %
    unit_discount_value = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    unit_price_net = MoneyField(amount_field="unit_price_net_amount",
                                currency_field="currency")

    unit_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    unit_price_gross = MoneyField(amount_field="unit_price_gross_amount",
                                  currency_field="currency")

    unit_price = TaxedMoneyField(
        net_amount_field="unit_price_net_amount",
        gross_amount_field="unit_price_gross_amount",
        currency="currency",
    )

    total_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    total_price_net = MoneyField(
        amount_field="total_price_net_amount",
        currency_field="currency",
    )

    total_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
    )
    total_price_gross = MoneyField(
        amount_field="total_price_gross_amount",
        currency_field="currency",
    )

    total_price = TaxedMoneyField(
        net_amount_field="total_price_net_amount",
        gross_amount_field="total_price_gross_amount",
        currency="currency",
    )

    undiscounted_unit_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    undiscounted_unit_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    undiscounted_unit_price = TaxedMoneyField(
        net_amount_field="undiscounted_unit_price_net_amount",
        gross_amount_field="undiscounted_unit_price_gross_amount",
        currency="currency",
    )

    undiscounted_total_price_gross_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    undiscounted_total_price_net_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    undiscounted_total_price = TaxedMoneyField(
        net_amount_field="undiscounted_total_price_net_amount",
        gross_amount_field="undiscounted_total_price_gross_amount",
        currency="currency",
    )

    base_unit_price_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    base_unit_price = MoneyField(amount_field="base_unit_price_amount",
                                 currency_field="currency")

    undiscounted_base_unit_price_amount = models.DecimalField(
        max_digits=settings.DEFAULT_MAX_DIGITS,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0,
    )
    undiscounted_base_unit_price = MoneyField(
        amount_field="undiscounted_base_unit_price_amount",
        currency_field="currency")

    tax_rate = models.DecimalField(max_digits=5,
                                   decimal_places=4,
                                   default=Decimal("0.0"))

    # Fulfilled when voucher code was used for product in the line
    voucher_code = models.CharField(max_length=255, null=True, blank=True)

    # Fulfilled when sale was applied to product in the line
    sale_id = models.CharField(max_length=255, null=True, blank=True)

    objects = models.Manager.from_queryset(OrderLineQueryset)()

    class Meta:
        ordering = ("created_at", )

    def __str__(self):
        return (f"{self.product_name} ({self.variant_name})"
                if self.variant_name else self.product_name)

    @property
    def quantity_unfulfilled(self):
        return self.quantity - self.quantity_fulfilled

    @property
    def is_digital(self) -> Optional[bool]:
        """Check if a variant is digital and contains digital content."""
        if not self.variant:
            return None
        is_digital = self.variant.is_digital()
        has_digital = hasattr(self.variant, "digital_content")
        return is_digital and has_digital
Exemplo n.º 21
0
class Order(models.Model):
    created = models.DateTimeField(
        default=now, editable=False)
    status = models.CharField(
        max_length=32, default=OrderStatus.UNFULFILLED,
        choices=OrderStatus.CHOICES)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name='orders',
        on_delete=models.SET_NULL)
    language_code = models.CharField(
        max_length=35, default=settings.LANGUAGE_CODE)
    tracking_client_id = models.CharField(
        max_length=36, blank=True, editable=False)
    billing_address = models.ForeignKey(
        Address, related_name='+', editable=False, null=True,
        on_delete=models.SET_NULL)
    shipping_address = models.ForeignKey(
        Address, related_name='+', editable=False, null=True,
        on_delete=models.SET_NULL)
    user_email = models.EmailField(blank=True, default='')
    shipping_method = models.ForeignKey(
        ShippingMethodCountry, blank=True, null=True, related_name='orders',
        on_delete=models.SET_NULL)
    shipping_price_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0, editable=False)
    shipping_price_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0, editable=False)
    shipping_price = TaxedMoneyField(
        net_field='shipping_price_net', gross_field='shipping_price_gross')
    shipping_method_name = models.CharField(
        max_length=255, null=True, default=None, blank=True, editable=False)
    token = models.CharField(max_length=36, unique=True, blank=True)
    total_net = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES, default=0)
    total_gross = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES, default=0)
    total = TaxedMoneyField(net_field='total_net', gross_field='total_gross')
    voucher = models.ForeignKey(
        Voucher, blank=True, null=True, related_name='+',
        on_delete=models.SET_NULL)
    discount_amount = MoneyField(
        currency=settings.DEFAULT_CURRENCY, max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES, default=0)
    discount_name = models.CharField(max_length=255, default='', blank=True)
    display_gross_prices = models.BooleanField(default=True)

    objects = OrderQueryset.as_manager()

    class Meta:
        ordering = ('-pk',)
        permissions = ((
            'manage_orders',
            pgettext_lazy('Permission description', 'Manage orders.')),)

    def save(self, *args, **kwargs):
        if not self.token:
            self.token = str(uuid4())
        return super().save(*args, **kwargs)

    def is_fully_paid(self):
        total_paid = sum(
            [
                payment.get_total_price() for payment in
                self.payments.filter(status=PaymentStatus.CONFIRMED)],
            ZERO_TAXED_MONEY)
        return total_paid.gross >= self.total.gross

    def get_user_current_email(self):
        return self.user.email if self.user else self.user_email

    def _index_billing_phone(self):
        return self.billing_address.phone

    def _index_shipping_phone(self):
        return self.shipping_address.phone

    def __iter__(self):
        return iter(self.lines.all())

    def __repr__(self):
        return '<Order #%r>' % (self.id,)

    def __str__(self):
        return '#%d' % (self.id,)

    def get_absolute_url(self):
        return reverse('order:details', kwargs={'token': self.token})

    def get_last_payment(self):
        return max(self.payments.all(), default=None, key=attrgetter('pk'))

    def get_last_payment_status(self):
        last_payment = self.get_last_payment()
        if last_payment:
            return last_payment.status
        return None

    def get_last_payment_status_display(self):
        last_payment = max(
            self.payments.all(), default=None, key=attrgetter('pk'))
        if last_payment:
            return last_payment.get_status_display()
        return None

    def is_pre_authorized(self):
        return self.payments.filter(status=PaymentStatus.PREAUTH).exists()

    @property
    def quantity_fulfilled(self):
        return sum([line.quantity_fulfilled for line in self])

    def is_shipping_required(self):
        return any(line.is_shipping_required for line in self)

    def get_subtotal(self):
        subtotal_iterator = (line.get_total() for line in self)
        return sum(subtotal_iterator, ZERO_TAXED_MONEY)

    def get_total_quantity(self):
        return sum([line.quantity for line in self])

    def is_draft(self):
        return self.status == OrderStatus.DRAFT

    def is_open(self):
        statuses = {OrderStatus.UNFULFILLED, OrderStatus.PARTIALLY_FULFILLED}
        return self.status in statuses

    def can_cancel(self):
        return self.status not in {OrderStatus.CANCELED, OrderStatus.DRAFT}
Exemplo n.º 22
0
class Order(models.Model):
    created = models.DateTimeField(default=timezone.now)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             blank=True,
                             null=True,
                             related_name='invoices',
                             on_delete=models.SET_NULL)
    customer = models.ForeignKey(Customer,
                                 blank=True,
                                 null=True,
                                 on_delete=models.SET_NULL,
                                 related_name='orders')
    subscription = models.ForeignKey(
        Subscription,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='orders',
    )

    first_name = models.CharField(max_length=256, blank=True)
    last_name = models.CharField(max_length=256, blank=True)
    company_name = models.CharField(max_length=256, blank=True)
    street_address_1 = models.CharField(max_length=256, blank=True)
    street_address_2 = models.CharField(max_length=256, blank=True)
    city = models.CharField(max_length=256, blank=True)
    postcode = models.CharField(max_length=20, blank=True)
    country = CountryField(blank=True)

    user_email = models.EmailField(blank=True, default='')

    currency = models.CharField(max_length=3,
                                default=settings.DEFAULT_CURRENCY)
    total_net = models.DecimalField(
        max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0)
    total_gross = models.DecimalField(
        max_digits=12,
        decimal_places=settings.DEFAULT_DECIMAL_PLACES,
        default=0)
    total = TaxedMoneyField(
        net_amount_field='total_net',
        gross_amount_field='total_gross',
        currency="currency",
    )

    # FIXME: https://github.com/mirumee/django-prices/issues/71
    total.unique = False

    is_donation = models.BooleanField(default=False)
    description = models.CharField(max_length=255, blank=True)
    customer_note = models.TextField(blank=True, default='')
    kind = models.CharField(max_length=255, default='', blank=True)

    remote_reference = models.CharField(max_length=256, blank=True)
    token = models.UUIDField(default=uuid.uuid4, db_index=True)

    service_start = models.DateTimeField(null=True, blank=True)
    service_end = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.description

    @property
    def currency(self):
        return settings.DEFAULT_CURRENCY

    @property
    def amount(self):
        return self.total.gross.amount

    @property
    def amount_cents(self):
        return int(self.total.gross.amount * 100)

    @property
    def email(self):
        return self.user_email

    @property
    def address(self):
        return '\n'.join(x for x in [
            self.street_address_1, self.street_address_2, '{} {}'.format(
                self.postcode, self.city), self.country.name
        ] if x)

    @property
    def is_recurring(self):
        return bool(self.subscription_id)

    def get_service_label(self):
        if not self.subscription:
            return self.description
        return order_service_description(self, self.subscription.plan.interval)

    def get_user_or_order(self):
        if self.user:
            return self.user
        return self

    def get_full_name(self):
        return '{} {}'.format(self.first_name, self.last_name).strip()

    def is_fully_paid(self):
        total_paid = sum([
            payment.get_captured_amount()
            for payment in self.payments.filter(status=PaymentStatus.CONFIRMED)
        ], ZERO_TAXED_MONEY)
        return total_paid.gross >= self.total.gross

    def is_tentatively_paid(self):
        tentatively_paid = sum([
            payment.total for payment in self.payments.filter(
                status__in=(PaymentStatus.CONFIRMED, PaymentStatus.PENDING,
                            PaymentStatus.PREAUTH))
        ], ZERO_TAXED_MONEY)
        return tentatively_paid.gross >= self.total.gross

    def get_payment_amounts(self):
        tentative_status = (PaymentStatus.CONFIRMED, PaymentStatus.PENDING,
                            PaymentStatus.PREAUTH)

        payments = self.payments.filter(status__in=tentative_status)

        amounts = defaultdict(lambda: ZERO_MONEY)
        for payment in payments:
            amounts['tentative'] += payment.get_amount()
            if payment.status in (PaymentStatus.CONFIRMED, ):
                amounts['total'] += payment.get_captured_amount()
        return amounts

    def get_absolute_url(self):
        return reverse('froide_payment:order-detail',
                       kwargs={'token': str(self.token)})

    def get_absolute_payment_url(self, variant):
        return reverse('froide_payment:start-payment',
                       kwargs={
                           'token': str(self.token),
                           'variant': variant
                       })

    def get_failure_url(self):
        obj = self.get_domain_object()
        if obj is not None and hasattr(obj, 'get_failure_url'):
            return obj.get_failure_url()
        return '/'

    def get_success_check_url(self):
        return reverse('froide_payment:order-success',
                       kwargs={'token': str(self.token)})

    def get_success_url(self):
        obj = self.get_domain_object()
        if obj is not None and hasattr(obj, 'get_success_url'):
            return obj.get_success_url()
        return self.get_absolute_url() + '?result=success'

    def get_domain_object(self):
        model = self.get_domain_model()
        if model is None:
            return None
        try:
            if self.subscription_id and hasattr(model, 'subscription'):
                return model.objects.get(subscription=self.subscription)
            return model.objects.get(order=self)
        except model.DoesNotExist:
            return None

    def get_domain_model(self):
        if not self.kind:
            return None
        try:
            return apps.get_model(self.kind)
        except LookupError:
            return None

    def get_interval_description(self):
        if not self.subscription:
            return ''
        return self.subscription.plan.get_interval_description()

    def get_or_create_payment(self, variant, request=None):
        defaults = get_payment_defaults(self, request=request)
        with transaction.atomic():
            payment, created = Payment.objects.filter(
                models.Q(status=PaymentStatus.WAITING)
                | models.Q(status=PaymentStatus.INPUT)
                | models.Q(status=PaymentStatus.PENDING)).get_or_create(
                    variant=variant, order=self, defaults=defaults)
            if created:
                # Delete waiting/input payments from before
                Payment.objects.filter(
                    models.Q(status=PaymentStatus.WAITING)
                    | models.Q(status=PaymentStatus.INPUT), ).filter(
                        order=self).exclude(id=payment.id).delete()
        # Trigger signal
        payment.change_status(payment.status)
        return payment