def get_price_per_item(self, discounts=None): price = self.price_override or self.product.price price = TaxedMoney(net=price, gross=price) price = calculate_discounted_price(self.product, price, discounts) return price
def sum_order_totals(qs): zero = Money(0, currency=settings.DEFAULT_CURRENCY) taxed_zero = TaxedMoney(zero, zero) return sum([order.total for order in qs], taxed_zero)
def test_zero_clipping(): price = TaxedMoney(Money(10, 'USD'), Money(10, 'USD')) result = fixed_discount(price, Money(30, 'USD')) assert result.net == Money(0, 'USD') assert result.gross == Money(0, 'USD')
def test_get_tax_rate_by_name_empty_taxes(product): rate_name = "unexisting tax rate" tax_rate = get_tax_rate_by_name(rate_name) assert tax_rate == 0 @pytest.mark.parametrize( "price, charge_taxes, expected_price", [ ( Money(10, "USD"), False, TaxedMoney(net=Money(10, "USD"), gross=Money(10, "USD")), ), ( Money(10, "USD"), True, TaxedMoney(net=Money("8.13", "USD"), gross=Money(10, "USD")), ), ], ) def test_get_taxed_shipping_price(site_settings, vatlayer, price, charge_taxes, expected_price): site_settings.charge_taxes_on_shipping = charge_taxes site_settings.save() shipping_price = get_taxed_shipping_price(price, taxes=vatlayer)
def test_discount_from_net(): price = TaxedMoney(Money(100, 'PLN'), Money(200, 'PLN')) result = fractional_discount(price, Decimal('0.5'), from_gross=False) assert result.net == Money(50, 'PLN') assert result.gross == Money(150, 'PLN')
def test_manager_apply_taxes_to_shipping(shipping_method, address, plugins, price_amount): expected_price = Money(price_amount, "USD") taxed_price = PluginsManager(plugins=plugins).apply_taxes_to_shipping( shipping_method.price, address) assert TaxedMoney(expected_price, expected_price) == taxed_price
def sum_order_totals(qs, currency_code): zero = Money(0, currency=currency_code) taxed_zero = TaxedMoney(zero, zero) return sum([order.total for order in qs], taxed_zero)
from typing import Union from django.conf import settings from django.contrib.sites.models import Site from django_countries.fields import Country from django_prices_vatlayer.utils import get_tax_for_rate, get_tax_rates_for_country from prices import Money, MoneyRange, TaxedMoney, TaxedMoneyRange from ...core import TaxRateType DEFAULT_TAX_RATE_NAME = TaxRateType.STANDARD ZERO_MONEY = Money(0, settings.DEFAULT_CURRENCY) ZERO_TAXED_MONEY = TaxedMoney(net=ZERO_MONEY, gross=ZERO_MONEY) def zero_money(): """Function used as a model's default.""" return ZERO_MONEY def apply_tax_to_price(taxes, rate_name, base): if not taxes or not rate_name: # Naively convert Money to TaxedMoney for consistency with price # handling logic across the codebase, passthrough other money types if isinstance(base, Money): return TaxedMoney(net=base, gross=base) if isinstance(base, MoneyRange): return TaxedMoneyRange( apply_tax_to_price(taxes, rate_name, base.start), apply_tax_to_price(taxes, rate_name, base.stop),
def apply_taxes_to_shipping(self, price, shipping_address, previous_value) -> TaxedMoney: price = Money("1.0", price.currency) return TaxedMoney(price, price)
def test_fulfillment_refund_products_fulfillment_lines_and_order_lines( mocked_refund, warehouse, variant, channel_USD, staff_api_client, permission_manage_orders, fulfilled_order, payment_dummy, ): payment_dummy.total = fulfilled_order.total_gross_amount payment_dummy.captured_amount = payment_dummy.total payment_dummy.charge_status = ChargeStatus.FULLY_CHARGED payment_dummy.save() fulfilled_order.payments.add(payment_dummy) stock = Stock.objects.create(warehouse=warehouse, product_variant=variant, quantity=5) channel_listing = variant.channel_listings.get() net = variant.get_price(variant.product, [], channel_USD, channel_listing) gross = Money(amount=net.amount * Decimal(1.23), currency=net.currency) variant.track_inventory = False variant.save() unit_price = TaxedMoney(net=net, gross=gross) quantity = 5 order_line = fulfilled_order.lines.create( product_name=str(variant.product), variant_name=str(variant), product_sku=variant.sku, product_variant_id=variant.get_global_id(), is_shipping_required=variant.is_shipping_required(), is_gift_card=variant.is_gift_card(), quantity=quantity, quantity_fulfilled=2, variant=variant, unit_price=unit_price, total_price=unit_price * quantity, tax_rate=Decimal("0.23"), ) fulfillment = fulfilled_order.fulfillments.get() fulfillment.lines.create(order_line=order_line, quantity=2, stock=stock) fulfillment_line_to_refund = fulfilled_order.fulfillments.first( ).lines.first() order_id = graphene.Node.to_global_id("Order", fulfilled_order.pk) fulfillment_line_id = graphene.Node.to_global_id( "FulfillmentLine", fulfillment_line_to_refund.pk) order_line_from_fulfillment_line = graphene.Node.to_global_id( "OrderLine", fulfillment_line_to_refund.order_line.pk) order_line_id = graphene.Node.to_global_id("OrderLine", order_line.pk) variables = { "order": order_id, "input": { "orderLines": [{ "orderLineId": order_line_id, "quantity": 2 }], "fulfillmentLines": [{ "fulfillmentLineId": fulfillment_line_id, "quantity": 2 }], }, } staff_api_client.user.user_permissions.add(permission_manage_orders) response = staff_api_client.post_graphql(ORDER_FULFILL_REFUND_MUTATION, variables) content = get_graphql_content(response) data = content["data"]["orderFulfillmentRefundProducts"] refund_fulfillment = data["fulfillment"] errors = data["errors"] assert not errors assert refund_fulfillment["status"] == FulfillmentStatus.REFUNDED.upper() assert len(refund_fulfillment["lines"]) == 2 assert refund_fulfillment["lines"][0]["orderLine"]["id"] in [ order_line_id, order_line_from_fulfillment_line, ] assert refund_fulfillment["lines"][0]["quantity"] == 2 assert refund_fulfillment["lines"][1]["orderLine"]["id"] in [ order_line_id, order_line_from_fulfillment_line, ] assert refund_fulfillment["lines"][1]["quantity"] == 2 amount = fulfillment_line_to_refund.order_line.unit_price_gross_amount * 2 amount += order_line.unit_price_gross_amount * 2 amount = quantize_price(amount, fulfilled_order.currency) mocked_refund.assert_called_with( payment_dummy, ANY, amount=amount, channel_slug=fulfilled_order.channel.slug, refund_data=RefundData( order_lines_to_refund=[ OrderLineInfo(line=order_line, quantity=2, variant=order_line.variant) ], fulfillment_lines_to_refund=[ FulfillmentLineData( line=fulfillment_line_to_refund, quantity=2, ) ], ), )
tax_rate = PluginsManager(plugins=plugins).get_checkout_line_tax_rate( checkout_info, lines, checkout_line_info, checkout_with_item.shipping_address, [discount_info], unit_price, ) assert tax_rate == Decimal("0.08") @pytest.mark.parametrize( "unit_price, expected_tax_rate", [ (TaxedMoney(Money(12, "USD"), Money(15, "USD")), Decimal("0.25")), (Decimal("0.0"), Decimal("0.0")), ], ) def test_manager_get_checkout_line_tax_rate_no_plugins(checkout_with_item, discount_info, unit_price, expected_tax_rate): manager = get_plugins_manager() lines = fetch_checkout_lines(checkout_with_item) checkout_info = fetch_checkout_info(checkout_with_item, lines, [discount_info], manager) checkout_line_info = lines[0] tax_rate = PluginsManager(plugins=[]).get_checkout_line_tax_rate( checkout_info, lines,
def calculate_checkout_total(self, checkout_info, lines, address, discounts, previous_value): total = Money("1.0", currency=checkout_info.checkout.currency) return TaxedMoney(total, total)
def test_create_return_fulfillment_with_lines_already_refunded( mocked_refund, fulfilled_order, payment_dummy_fully_charged, staff_user, channel_USD, variant, warehouse, ): fulfilled_order.payments.add(payment_dummy_fully_charged) payment = fulfilled_order.get_last_payment() order_line_ids = fulfilled_order.lines.all().values_list("id", flat=True) fulfillment_lines = FulfillmentLine.objects.filter( order_line_id__in=order_line_ids) original_quantity = {line.id: line.quantity for line in fulfillment_lines} fulfillment_lines_to_return = fulfillment_lines stock = Stock.objects.create(warehouse=warehouse, product_variant=variant, quantity=5) channel_listing = variant.channel_listings.get() net = variant.get_price(variant.product, [], channel_USD, channel_listing) gross = Money(amount=net.amount * Decimal(1.23), currency=net.currency) unit_price = TaxedMoney(net=net, gross=gross) quantity = 5 order_line = fulfilled_order.lines.create( product_name=str(variant.product), variant_name=str(variant), product_sku=variant.sku, is_shipping_required=variant.is_shipping_required(), quantity=quantity, quantity_fulfilled=2, variant=variant, unit_price=unit_price, tax_rate=Decimal("0.23"), total_price=unit_price * quantity, ) Allocation.objects.create(order_line=order_line, stock=stock, quantity_allocated=order_line.quantity) refunded_fulfillment = Fulfillment.objects.create( order=fulfilled_order, status=FulfillmentStatus.REFUNDED) refunded_fulfillment_line = refunded_fulfillment.lines.create( order_line=order_line, quantity=2) fulfillment_lines_to_process = [ FulfillmentLineData(line=line, quantity=2) for line in fulfillment_lines_to_return ] fulfillment_lines_to_process.append( FulfillmentLineData(line=refunded_fulfillment_line, quantity=2)) create_fulfillments_for_returned_products( requester=staff_user, order=fulfilled_order, payment=payment, order_lines=[], fulfillment_lines=fulfillment_lines_to_process, manager=get_plugins_manager(), refund=True, ) returned_and_refunded_fulfillment = Fulfillment.objects.get( order=fulfilled_order, status=FulfillmentStatus.REFUNDED_AND_RETURNED) returned_and_refunded_lines = returned_and_refunded_fulfillment.lines.all() assert returned_and_refunded_lines.count() == len(order_line_ids) for fulfillment_line in returned_and_refunded_lines: assert fulfillment_line.quantity == 2 assert fulfillment_line.order_line_id in order_line_ids for line in fulfillment_lines: assert line.quantity == original_quantity.get(line.pk) - 2 # the already refunded line is not included in amount amount = sum([ line.order_line.unit_price_gross_amount * 2 for line in fulfillment_lines_to_return ]) mocked_refund.assert_called_once_with(payment_dummy_fully_charged, ANY, amount)
def get_total(self): if self.cost_price: return TaxedMoney(net=self.cost_price, gross=self.cost_price)
def test_delete_product_variants_in_draft_orders( staff_api_client, product_variant_list, permission_manage_products, order_line, order_list, ): # given query = PRODUCT_VARIANT_BULK_DELETE_MUTATION variants = product_variant_list draft_order = order_line.order draft_order.status = OrderStatus.DRAFT draft_order.save(update_fields=["status"]) second_variant_in_draft = variants[1] net = second_variant_in_draft.get_price() gross = Money(amount=net.amount, currency=net.currency) second_draft_order = OrderLine.objects.create( variant=second_variant_in_draft, order=draft_order, product_name=str(second_variant_in_draft.product), variant_name=str(second_variant_in_draft), product_sku=second_variant_in_draft.sku, is_shipping_required=second_variant_in_draft.is_shipping_required(), unit_price=TaxedMoney(net=net, gross=gross), quantity=3, ) variant = variants[0] net = variant.get_price() gross = Money(amount=net.amount, currency=net.currency) order_not_draft = order_list[-1] order_line_not_in_draft = OrderLine.objects.create( variant=variant, order=order_not_draft, product_name=str(variant.product), variant_name=str(variant), product_sku=variant.sku, is_shipping_required=variant.is_shipping_required(), unit_price=TaxedMoney(net=net, gross=gross), quantity=3, ) order_line_not_in_draft_pk = order_line_not_in_draft.pk variant_count = ProductVariant.objects.count() variables = { "ids": [ graphene.Node.to_global_id("ProductVariant", variant.id) for variant in ProductVariant.objects.all() ] } # when response = staff_api_client.post_graphql( query, variables, permissions=[permission_manage_products] ) # then content = get_graphql_content(response) assert content["data"]["productVariantBulkDelete"]["count"] == variant_count assert not ProductVariant.objects.filter( id__in=[variant.id for variant in product_variant_list] ).exists() with pytest.raises(order_line._meta.model.DoesNotExist): order_line.refresh_from_db() with pytest.raises(second_draft_order._meta.model.DoesNotExist): second_draft_order.refresh_from_db() assert OrderLine.objects.filter(pk=order_line_not_in_draft_pk).exists()
def calculate_checkout_subtotal(self, checkout, lines, discounts, previous_value): subtotal = Money("1.0", currency=checkout.currency) return TaxedMoney(subtotal, subtotal)
def order_with_lines(order, product_type, category, shipping_zone): product = Product.objects.create( name="Test product", price=Money("10.00", "USD"), product_type=product_type, category=category, ) variant = ProductVariant.objects.create( product=product, sku="SKU_A", cost_price=Money(1, "USD"), quantity=5, quantity_allocated=3, ) net = variant.get_price() gross = Money(amount=net.amount * Decimal(1.23), currency=net.currency) order.lines.create( product_name=variant.display_product(), product_sku=variant.sku, is_shipping_required=variant.is_shipping_required(), quantity=3, variant=variant, unit_price=TaxedMoney(net=net, gross=gross), tax_rate=23, ) product = Product.objects.create( name="Test product 2", price=Money("20.00", "USD"), product_type=product_type, category=category, ) variant = ProductVariant.objects.create( product=product, sku="SKU_B", cost_price=Money(2, "USD"), quantity=2, quantity_allocated=2, ) net = variant.get_price() gross = Money(amount=net.amount * Decimal(1.23), currency=net.currency) order.lines.create( product_name=variant.display_product(), product_sku=variant.sku, is_shipping_required=variant.is_shipping_required(), quantity=2, variant=variant, unit_price=TaxedMoney(net=net, gross=gross), tax_rate=23, ) order.shipping_address = order.billing_address.get_copy() method = shipping_zone.shipping_methods.get() order.shipping_method_name = method.name order.shipping_method = method net = method.get_total() gross = Money(amount=net.amount * Decimal(1.23), currency=net.currency) order.shipping_price = TaxedMoney(net=net, gross=gross) order.save() recalculate_order(order) order.refresh_from_db() return order
def calculate_checkout_shipping(self, checkout, lines, discounts, previous_value): price = Money("1.0", currency=checkout.currency) return TaxedMoney(price, price)
def test_update_order_line_discount_line_with_discount( status, draft_order_with_fixed_discount_order, staff_api_client, permission_manage_orders, ): order = draft_order_with_fixed_discount_order order.status = status order.save(update_fields=["status"]) line_to_discount = order.lines.first() unit_price = quantize_price(Money(Decimal(7.3), currency="USD"), currency="USD") line_to_discount.unit_price = TaxedMoney(unit_price, unit_price) line_to_discount.unit_discount_amount = Decimal("2.500") line_to_discount.unit_discount_type = DiscountValueType.FIXED line_to_discount.unit_discount_value = Decimal("2.500") line_to_discount.save() line_discount_amount_before_update = line_to_discount.unit_discount_amount line_discount_value_before_update = line_to_discount.unit_discount_value line_undiscounted_price = line_to_discount.undiscounted_unit_price value = Decimal("50") reason = "New reason for unit discount" variables = { "orderLineId": graphene.Node.to_global_id("OrderLine", line_to_discount.pk), "input": { "valueType": DiscountValueTypeEnum.PERCENTAGE.name, "value": value, "reason": reason, }, } staff_api_client.user.user_permissions.add(permission_manage_orders) response = staff_api_client.post_graphql(ORDER_LINE_DISCOUNT_UPDATE, variables) content = get_graphql_content(response) data = content["data"]["orderLineDiscountUpdate"] line_to_discount.refresh_from_db() errors = data["orderErrors"] assert not errors discount = partial( percentage_discount, percentage=value, ) expected_line_price = discount(line_undiscounted_price) assert line_to_discount.unit_price == expected_line_price unit_discount = line_to_discount.unit_discount assert unit_discount == (line_undiscounted_price - expected_line_price).gross event = order.events.get() assert event.type == OrderEvents.ORDER_LINE_DISCOUNT_UPDATED parameters = event.parameters lines = parameters.get("lines", {}) assert len(lines) == 1 line_data = lines[0] assert line_data.get("line_pk") == line_to_discount.pk discount_data = line_data.get("discount") assert discount_data["value"] == str(value) assert discount_data["value_type"] == DiscountValueTypeEnum.PERCENTAGE.value assert discount_data["amount_value"] == str(unit_discount.amount) assert discount_data["old_value"] == str(line_discount_value_before_update) assert discount_data["old_value_type"] == DiscountValueTypeEnum.FIXED.value assert discount_data["old_amount_value"] == str(line_discount_amount_before_update)
def calculate_order_shipping(self, order, previous_value): price = Money("1.0", currency=order.currency) return TaxedMoney(price, price)
def get_total_price(self): return TaxedMoney(net=Money(self.total - self.tax, self.currency), gross=Money(self.total, self.currency))
def calculate_checkout_line_total(self, checkout_line, discounts, previous_value): price = Money("1.0", currency=checkout_line.checkout.currency) return TaxedMoney(price, price)
def test_apply_tax_to_price_no_taxes_return_taxed_money(): money = Money(100, "USD") taxed_money = TaxedMoney(net=Money(100, "USD"), gross=Money(100, "USD")) assert apply_tax_to_price(None, "standard", money) == taxed_money assert apply_tax_to_price(None, "medical", taxed_money) == taxed_money
def calculate_order_line_unit(self, order_line, previous_value): currency = order_line.unit_price.currency price = Money("1.0", currency) return TaxedMoney(price, price)
def create_order_lines(order, discounts, how_many=10): channel = order.channel available_variant_ids = channel.variant_listings.values_list( "variant_id", flat=True ) variants = ( ProductVariant.objects.filter(pk__in=available_variant_ids) .order_by("?") .prefetch_related("product__product_type")[:how_many] ) variants_iter = itertools.cycle(variants) lines = [] for _ in range(how_many): variant = next(variants_iter) variant_channel_listing = variant.channel_listings.get(channel=channel) product = variant.product quantity = random.randrange(1, 5) unit_price = variant.get_price( product, product.collections.all(), channel, variant_channel_listing, discounts, ) unit_price = TaxedMoney(net=unit_price, gross=unit_price) total_price = unit_price * quantity lines.append( OrderLine( order=order, product_name=str(product), variant_name=str(variant), product_sku=variant.sku, is_shipping_required=variant.is_shipping_required(), quantity=quantity, variant=variant, unit_price=unit_price, total_price=total_price, tax_rate=0, ) ) lines = OrderLine.objects.bulk_create(lines) manager = get_plugins_manager() country = order.shipping_method.shipping_zone.countries[0] warehouses = Warehouse.objects.filter( shipping_zones__countries__contains=country ).order_by("?") warehouse_iter = itertools.cycle(warehouses) for line in lines: variant = line.variant unit_price = manager.calculate_order_line_unit( order, line, variant, variant.product ) line.unit_price = unit_price line.tax_rate = unit_price.tax / unit_price.net warehouse = next(warehouse_iter) increase_stock(line, warehouse, line.quantity, allocate=True) OrderLine.objects.bulk_update( lines, ["unit_price_net_amount", "unit_price_gross_amount", "currency", "tax_rate"], ) return lines
def apply_taxes_to_product(self, product, price, country, previous_value, **kwargs): price = Money("1.0", price.currency) return TaxedMoney(price, price)
def test_checkout_total_with_discount(request_checkout_with_item, sale, vatlayer): total = request_checkout_with_item.get_total(discounts=(sale, ), taxes=vatlayer) assert total == TaxedMoney(net=Money("4.07", "USD"), gross=Money("5.00", "USD"))
def test_delete_products( staff_api_client, product_list, permission_manage_products, order_list ): # given query = DELETE_PRODUCTS_MUTATION not_draft_order = order_list[0] draft_order = order_list[1] draft_order.status = OrderStatus.DRAFT draft_order.save(update_fields=["status"]) draft_order_lines_pks = [] not_draft_order_lines_pks = [] for variant in [product_list[0].variants.first(), product_list[1].variants.first()]: net = variant.get_price() gross = Money(amount=net.amount, currency=net.currency) order_line = OrderLine.objects.create( variant=variant, order=draft_order, product_name=str(variant.product), variant_name=str(variant), product_sku=variant.sku, is_shipping_required=variant.is_shipping_required(), unit_price=TaxedMoney(net=net, gross=gross), quantity=3, ) draft_order_lines_pks.append(order_line.pk) order_line_not_draft = OrderLine.objects.create( variant=variant, order=not_draft_order, product_name=str(variant.product), variant_name=str(variant), product_sku=variant.sku, is_shipping_required=variant.is_shipping_required(), unit_price=TaxedMoney(net=net, gross=gross), quantity=3, ) not_draft_order_lines_pks.append(order_line_not_draft.pk) variables = { "ids": [ graphene.Node.to_global_id("Product", product.id) for product in product_list ] } # when response = staff_api_client.post_graphql( query, variables, permissions=[permission_manage_products] ) # then content = get_graphql_content(response) assert content["data"]["productBulkDelete"]["count"] == 3 assert not Product.objects.filter( id__in=[product.id for product in product_list] ).exists() assert not OrderLine.objects.filter(pk__in=draft_order_lines_pks).exists() assert OrderLine.objects.filter(pk__in=not_draft_order_lines_pks).exists()
def test_currency_mismatch(): with pytest.raises(ValueError): fixed_discount(TaxedMoney(Money(10, 'BTC'), Money(10, 'BTC')), Money(10, 'USD'))
def test_templatetag_discount_amount_for(): price = TaxedMoney(Money(30, "BTC"), Money(30, "BTC")) discount = functools.partial(percentage_discount, percentage=50) discount_amount = prices.discount_amount_for(discount, price) assert discount_amount == TaxedMoney(Money(-15, "BTC"), Money(-15, "BTC"))