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
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
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
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
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
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"
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
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
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
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
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", )
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
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
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
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, '')
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
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'
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
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
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
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}
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