def test_quantize(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price_range = TaxedMoneyRange(price1, price2) result = price_range.quantize() assert str(result.start.net.amount) == '10.00' assert str(result.stop.net.amount) == '30.00'
def test_construction(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price_range = TaxedMoneyRange(price1, price2) assert price_range.start == price1 assert price_range.stop == price2 with pytest.raises(ValueError): TaxedMoneyRange(price1, TaxedMoney(Money(20, 'PLN'), Money(20, 'PLN'))) with pytest.raises(ValueError): TaxedMoneyRange(price2, price1)
def get_price_range(self, discounts=None): if self.variants.exists(): prices = [ self.get_price_per_item(variant, discounts=discounts) for variant in self ] return TaxedMoneyRange(min(prices), max(prices)) price = TaxedMoney(net=self.price, gross=self.price) discounted_price = calculate_discounted_price(self, price, discounts) return TaxedMoneyRange(start=discounted_price, stop=discounted_price)
def get_product_availability( *, product: Product, variants: Iterable[ProductVariant], collections: Iterable[Collection], discounts: Iterable[DiscountInfo], country: Optional[str] = None, local_currency: Optional[str] = None, plugins: Optional["PluginsManager"] = None, ) -> ProductAvailability: with opentracing.global_tracer().start_active_span( "get_product_availability"): if not plugins: plugins = get_plugins_manager() discounted_net_range = get_product_price_range( product=product, variants=variants, collections=collections, discounts=discounts, ) undiscounted_net_range = get_product_price_range( product=product, variants=variants, collections=collections, discounts=[]) discounted = TaxedMoneyRange( start=plugins.apply_taxes_to_product(product, discounted_net_range.start, country), stop=plugins.apply_taxes_to_product(product, discounted_net_range.stop, country), ) undiscounted = TaxedMoneyRange( start=plugins.apply_taxes_to_product(product, undiscounted_net_range.start, country), stop=plugins.apply_taxes_to_product(product, undiscounted_net_range.stop, country), ) discount = _get_total_discount_from_range(undiscounted, discounted) price_range_local, discount_local_currency = _get_product_price_range( discounted, undiscounted, local_currency) is_on_sale = product.is_visible and discount is not None return ProductAvailability( on_sale=is_on_sale, price_range=discounted, price_range_undiscounted=undiscounted, discount=discount, price_range_local_currency=price_range_local, discount_local_currency=discount_local_currency, )
def test_replace(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price3 = TaxedMoney(Money(20, 'EUR'), Money(30, 'EUR')) price_range = TaxedMoneyRange(price1, price2) result = price_range.replace(stop=price3) assert result.start == price1 assert result.stop == price3 result = price_range.replace(start=price3) assert result.start == price3 assert result.stop == price2
def get_price_range(self, discounts=None, taxes=None): if self.variants.all(): prices = [ variant.get_price(discounts=discounts, taxes=taxes) for variant in self ] return TaxedMoneyRange(min(prices), max(prices)) price = calculate_discounted_price(self, self.price, discounts) if not self.charge_taxes: taxes = None tax_rate = self.tax_rate or self.product_type.tax_rate price = apply_tax_to_price(taxes, tax_rate, price) return TaxedMoneyRange(start=price, stop=price)
def test_addition_with_taxed_money_range(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price_range1 = TaxedMoneyRange(price1, price2) price3 = TaxedMoney(Money(40, 'EUR'), Money(60, 'EUR')) price4 = TaxedMoney(Money(80, 'EUR'), Money(120, 'EUR')) price_range2 = TaxedMoneyRange(price3, price4) result = price_range1 + price_range2 assert result.start == price1 + price3 assert result.stop == price2 + price4 with pytest.raises(ValueError): price_range1 + TaxedMoneyRange( TaxedMoney(Money(1, 'BTC'), Money(1, 'BTC')), TaxedMoney(Money(2, 'BTC'), Money(2, 'BTC')))
def test_comparison(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price_range1 = TaxedMoneyRange(price1, price2) price3 = TaxedMoney(Money(40, 'EUR'), Money(60, 'EUR')) price4 = TaxedMoney(Money(80, 'EUR'), Money(120, 'EUR')) price_range2 = TaxedMoneyRange(price3, price4) assert price_range1 == TaxedMoneyRange(price1, price2) assert price_range1 != price_range2 assert price_range1 != TaxedMoneyRange(price1, price1) assert price_range1 != TaxedMoneyRange( TaxedMoney(Money(10, 'USD'), Money(15, 'USD')), TaxedMoney(Money(30, 'USD'), Money(45, 'USD'))) assert price_range1 != price1
def test_repr(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price_range = TaxedMoneyRange(price1, price2) assert repr(price_range) == ( "TaxedMoneyRange(TaxedMoney(net=Money('10', 'EUR'), gross=Money('15', 'EUR')), TaxedMoney(net=Money('30', 'EUR'), gross=Money('45', 'EUR')))" )
def get_cart_data(cart, shipping_range, currency, discounts, taxes): """Return a JSON-serializable representation of the cart.""" cart_total = None local_cart_total = None shipping_required = False total_with_shipping = None local_total_with_shipping = None if cart: cart_total = cart.get_subtotal(discounts, taxes) local_cart_total = to_local_currency(cart_total, currency) shipping_required = cart.is_shipping_required() total_with_shipping = TaxedMoneyRange(start=cart_total, stop=cart_total) if shipping_required and shipping_range: total_with_shipping = shipping_range + cart_total local_total_with_shipping = to_local_currency(total_with_shipping, currency) return { 'cart_total': cart_total, 'local_cart_total': local_cart_total, 'shipping_required': shipping_required, 'total_with_shipping': total_with_shipping, 'local_total_with_shipping': local_total_with_shipping }
def get_product_costs_data(product): purchase_costs_range = TaxedMoneyRange(start=ZERO_TAXED_MONEY, stop=ZERO_TAXED_MONEY) gross_margin = (0, 0) if not product.variants.exists(): return purchase_costs_range, gross_margin variants = product.variants.all() costs_data = get_cost_data_from_variants(variants) if costs_data.costs: purchase_costs_range = TaxedMoneyRange(min(costs_data.costs), max(costs_data.costs)) if costs_data.margins: gross_margin = (costs_data.margins[0], costs_data.margins[-1]) return purchase_costs_range, gross_margin
def get_checkout_context(checkout, discounts, taxes, currency=None, shipping_range=None): """Data shared between views in checkout process.""" checkout_total = checkout.get_total(discounts, taxes) checkout_subtotal = checkout.get_subtotal(discounts, taxes) shipping_required = checkout.is_shipping_required() checkout_subtotal = checkout.get_subtotal(discounts, taxes) total_with_shipping = TaxedMoneyRange( start=checkout_subtotal, stop=checkout_subtotal) if shipping_required and shipping_range: total_with_shipping = shipping_range + checkout_subtotal context = { 'checkout': checkout, 'checkout_are_taxes_handled': bool(taxes), 'checkout_lines': [ (line, line.get_total(discounts, taxes)) for line in checkout], 'checkout_shipping_price': checkout.get_shipping_price(taxes), 'checkout_subtotal': checkout_subtotal, 'checkout_total': checkout_total, 'shipping_required': checkout.is_shipping_required(), 'total_with_shipping': total_with_shipping} if currency: context.update( local_checkout_total=to_local_currency( checkout_total, currency), local_checkout_subtotal=to_local_currency( checkout_subtotal, currency), local_total_with_shipping=to_local_currency( total_with_shipping, currency)) return context
def test_availability(product, monkeypatch, settings): taxed_price = TaxedMoney(Money("10.0", "USD"), Money("12.30", "USD")) monkeypatch.setattr(ExtensionsManager, "apply_taxes_to_product", Mock(return_value=taxed_price)) availability = get_product_availability(product) taxed_price_range = TaxedMoneyRange(start=taxed_price, stop=taxed_price) assert availability.price_range == taxed_price_range assert availability.price_range_local_currency is None monkeypatch.setattr( "django_prices_openexchangerates.models.get_rates", lambda c: {"PLN": Mock(rate=2)}, ) settings.DEFAULT_COUNTRY = "PL" settings.OPENEXCHANGERATES_API_KEY = "fake-key" availability = get_product_availability(product, local_currency="PLN") assert availability.price_range_local_currency.start.currency == "PLN" assert availability.available availability = get_product_availability(product) assert availability.price_range.start.tax.amount assert availability.price_range.stop.tax.amount assert availability.price_range_undiscounted.start.tax.amount assert availability.price_range_undiscounted.stop.tax.amount assert availability.available
def get_cart_data(cart, delivery_range, currency, discounts, taxes): """Return a JSON-serializable representation of the cart.""" cart_total = None local_cart_total = None delivery_required = False total_with_delivery = None local_total_with_delivery = None if cart: cart_total = cart.get_subtotal(discounts, taxes) local_cart_total = to_local_currency(cart_total, currency) delivery_required = cart.is_delivery_required() total_with_delivery = TaxedMoneyRange(start=cart_total, stop=cart_total) if delivery_required and delivery_range: total_with_delivery = delivery_range + cart_total local_total_with_delivery = to_local_currency(total_with_delivery, currency) return { 'cart_total': cart_total, 'local_cart_total': local_cart_total, 'delivery_required': delivery_required, 'total_with_delivery': total_with_delivery, 'local_total_with_delivery': local_total_with_delivery }
def test_availability(stock, monkeypatch, settings, channel_USD): product = stock.product_variant.product product_channel_listing = product.channel_listings.first() variants = product.variants.all() variants_channel_listing = models.ProductVariantChannelListing.objects.filter( variant__in=variants) taxed_price = TaxedMoney(Money("10.0", "USD"), Money("12.30", "USD")) monkeypatch.setattr(PluginsManager, "apply_taxes_to_product", Mock(return_value=taxed_price)) manager = get_plugins_manager() availability = get_product_availability( product=product, product_channel_listing=product_channel_listing, variants=product.variants.all(), variants_channel_listing=variants_channel_listing, channel=channel_USD, manager=manager, collections=[], discounts=[], country=Country("PL"), ) taxed_price_range = TaxedMoneyRange(start=taxed_price, stop=taxed_price) assert availability.price_range == taxed_price_range assert availability.price_range_local_currency is None monkeypatch.setattr( "django_prices_openexchangerates.models.get_rates", lambda c: {"PLN": Mock(rate=2)}, ) settings.DEFAULT_COUNTRY = "PL" settings.OPENEXCHANGERATES_API_KEY = "fake-key" availability = get_product_availability( product=product, product_channel_listing=product_channel_listing, variants=variants, variants_channel_listing=variants_channel_listing, collections=[], discounts=[], channel=channel_USD, manager=manager, local_currency="PLN", country=Country("PL"), ) assert availability.price_range_local_currency.start.currency == "PLN" availability = get_product_availability( product=product, product_channel_listing=product_channel_listing, variants=variants, variants_channel_listing=variants_channel_listing, collections=[], discounts=[], channel=channel_USD, manager=manager, country=Country("PL"), ) assert availability.price_range.start.tax.amount assert availability.price_range.stop.tax.amount assert availability.price_range_undiscounted.start.tax.amount assert availability.price_range_undiscounted.stop.tax.amount
def price_range(self): prices = [ country.get_total_price() for country in self.price_per_country.all() ] if prices: return TaxedMoneyRange(min(prices), max(prices)) return None
def get_gross_price_range(self, discounts=None): grosses = [ self.get_price_per_item(variant, discounts=discounts) for variant in self] if not grosses: return None grosses = sorted(grosses, key=lambda x: x.tax) return TaxedMoneyRange(min(grosses), max(grosses))
def get_product_availability( product: Product, discounts: Iterable[DiscountInfo] = None, country=None, local_currency=None, extensions=None, ) -> ProductAvailability: if not extensions: extensions = get_extensions_manager() discounted_net_range = product.get_price_range(discounts=discounts) undiscounted_net_range = product.get_price_range() discounted = TaxedMoneyRange( start=extensions.apply_taxes_to_product( product, discounted_net_range.start, country ), stop=extensions.apply_taxes_to_product( product, discounted_net_range.stop, country ), ) undiscounted = TaxedMoneyRange( start=extensions.apply_taxes_to_product( product, undiscounted_net_range.start, country ), stop=extensions.apply_taxes_to_product( product, undiscounted_net_range.stop, country ), ) discount = _get_total_discount(undiscounted, discounted) price_range_local, discount_local_currency = _get_product_price_range( discounted, undiscounted, local_currency ) is_on_sale = product.is_visible and discount is not None return ProductAvailability( available=product.is_available, on_sale=is_on_sale, price_range=discounted, price_range_undiscounted=undiscounted, discount=discount, price_range_local_currency=price_range_local, discount_local_currency=discount_local_currency, )
def test_apply_tax_to_price_no_taxes_return_taxed_money_range(): money_range = MoneyRange(Money(100, "USD"), Money(200, "USD")) taxed_money_range = TaxedMoneyRange( TaxedMoney(net=Money(100, "USD"), gross=Money(100, "USD")), TaxedMoney(net=Money(200, "USD"), gross=Money(200, "USD")), ) assert apply_tax_to_price(None, "standard", money_range) == taxed_money_range assert apply_tax_to_price(None, "standard", taxed_money_range) == taxed_money_range
def apply_taxes_to_shipping_price_range(self, prices: MoneyRange, country: Country): start = TaxedMoney(net=prices.start, gross=prices.start) stop = TaxedMoney(net=prices.stop, gross=prices.stop) default_value = quantize_price(TaxedMoneyRange(start=start, stop=stop), start.currency) return self.__run_method_on_plugins( "apply_taxes_to_shipping_price_range", default_value, prices, country)
def get_product_availability( product: Product, discounts: Iterable[DiscountInfo] = None, country: Optional[str] = None, local_currency: Optional[str] = None, extensions: Optional["ExtensionsManager"] = None, ) -> ProductAvailability: if not extensions: extensions = get_extensions_manager() discounted_net_range = product.get_price_range(discounts=discounts) undiscounted_net_range = product.get_price_range() discounted = TaxedMoneyRange( start=extensions.apply_taxes_to_product(product, discounted_net_range.start, country), stop=extensions.apply_taxes_to_product(product, discounted_net_range.stop, country), ) undiscounted = TaxedMoneyRange( start=extensions.apply_taxes_to_product(product, undiscounted_net_range.start, country), stop=extensions.apply_taxes_to_product(product, undiscounted_net_range.stop, country), ) discount = _get_total_discount_from_range(undiscounted, discounted) price_range_local, discount_local_currency = _get_product_price_range( discounted, undiscounted, local_currency) is_on_sale = product.is_visible and discount is not None country = country if country is not None else settings.DEFAULT_COUNTRY is_available = product.is_visible and is_product_in_stock(product, country) return ProductAvailability( on_sale=is_on_sale, price_range=discounted, price_range_undiscounted=undiscounted, discount=discount, price_range_local_currency=price_range_local, discount_local_currency=discount_local_currency, )
def get_checkout_context( checkout: Checkout, discounts: "DiscountsListType", currency: Optional[str] = None, shipping_range=None, ) -> dict: """Retrieve the data shared between views in checkout process.""" cards_balance = checkout.get_total_gift_cards_balance() # FIXME: We are having here parts of checkout total calculation logic checkout_total = calculations.checkout_total(checkout=checkout, discounts=discounts) checkout_total.gross -= cards_balance checkout_total.net -= cards_balance checkout_total = max(checkout_total, zero_taxed_money(checkout_total.currency)) checkout_subtotal = calculations.checkout_subtotal(checkout, discounts) shipping_price = calculations.checkout_shipping_price(checkout, discounts) shipping_required = checkout.is_shipping_required() total_with_shipping = TaxedMoneyRange(start=checkout_subtotal, stop=checkout_subtotal) if shipping_required and shipping_range: total_with_shipping = shipping_range + checkout_subtotal manager = get_extensions_manager() context = { "checkout": checkout, "checkout_are_taxes_handled": manager.taxes_are_enabled(), "checkout_lines": [(line, calculations.checkout_line_total(line, discounts)) for line in checkout], "checkout_shipping_price": shipping_price, "checkout_subtotal": checkout_subtotal, "checkout_total": checkout_total, "shipping_required": checkout.is_shipping_required(), "total_with_shipping": total_with_shipping, } if currency: context.update( local_checkout_total=to_local_currency(checkout_total, currency), local_checkout_subtotal=to_local_currency(checkout_subtotal, currency), local_total_with_shipping=to_local_currency( total_with_shipping, currency), ) return context
def test_apply_tax_to_price_no_taxes_return_taxed_money_range(): money_range = MoneyRange(Money(100, 'USD'), Money(200, 'USD')) taxed_money_range = TaxedMoneyRange( TaxedMoney(net=Money(100, 'USD'), gross=Money(100, 'USD')), TaxedMoney(net=Money(200, 'USD'), gross=Money(200, 'USD'))) assert (apply_tax_to_price(None, 'standard', money_range) == taxed_money_range) assert (apply_tax_to_price(None, 'standard', taxed_money_range) == taxed_money_range)
def test_membership(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price_range = TaxedMoneyRange(price1, price2) assert price1 in price_range assert price2 in price_range assert (price1 + price2) / 2 in price_range assert price1 + price2 not in price_range with pytest.raises(TypeError): 15 in price_range
def test_subtraction_with_money(): price1 = TaxedMoney(Money(40, 'EUR'), Money(60, 'EUR')) price2 = TaxedMoney(Money(80, 'EUR'), Money(120, 'EUR')) price_range = TaxedMoneyRange(price1, price2) price3 = Money(10, 'EUR') result = price_range - price3 assert result.start == price1 - price3 assert result.stop == price2 - price3 with pytest.raises(ValueError): price_range - Money(1, 'BTC')
def test_discount(): price = TaxedMoney(Money(100, 'BTC'), Money(100, 'BTC')) discount = partial(percentage_discount, percentage=10) result = discount(price) assert result.net == Money(90, 'BTC') assert result.gross == Money(90, 'BTC') price_range = TaxedMoneyRange(price, price) result = discount(price_range) assert result.start == TaxedMoney(Money(90, 'BTC'), Money(90, 'BTC')) assert result.stop == TaxedMoney(Money(90, 'BTC'), Money(90, 'BTC'))
def test_addition_with_money(): price1 = TaxedMoney(Money(10, 'EUR'), Money(15, 'EUR')) price2 = TaxedMoney(Money(30, 'EUR'), Money(45, 'EUR')) price_range = TaxedMoneyRange(price1, price2) price3 = Money(40, 'EUR') result = price_range + price3 assert result.start == price1 + price3 assert result.stop == price2 + price3 with pytest.raises(ValueError): price_range + Money(1, 'BTC')
def test_range(): price_range = MoneyRange(Money(10, 'BTC'), Money(20, 'BTC')) result = flat_tax(price_range, 1) assert result.start == TaxedMoney(Money(10, 'BTC'), Money(20, 'BTC')) assert result.stop == TaxedMoney(Money(20, 'BTC'), Money(40, 'BTC')) price_range = TaxedMoneyRange( TaxedMoney(Money(10, 'BTC'), Money(10, 'BTC')), TaxedMoney(Money(20, 'BTC'), Money(20, 'BTC'))) result = flat_tax(price_range, 1) assert result.start == TaxedMoney(Money(10, 'BTC'), Money(20, 'BTC')) assert result.stop == TaxedMoney(Money(20, 'BTC'), Money(40, 'BTC'))
def get_product_availability( product: Product, discounts: Iterable[DiscountInfo] = None, country: Optional[str] = None, local_currency: Optional[str] = None, plugins: Optional["PluginsManager"] = None, ) -> ProductAvailability: if not plugins: plugins = get_plugins_manager() discounted_net_range = product.get_price_range(discounts=discounts) undiscounted_net_range = product.get_price_range() discounted = TaxedMoneyRange( start=plugins.apply_taxes_to_product(product, discounted_net_range.start, country), stop=plugins.apply_taxes_to_product(product, discounted_net_range.stop, country), ) undiscounted = TaxedMoneyRange( start=plugins.apply_taxes_to_product(product, undiscounted_net_range.start, country), stop=plugins.apply_taxes_to_product(product, undiscounted_net_range.stop, country), ) discount = _get_total_discount_from_range(undiscounted, discounted) price_range_local, discount_local_currency = _get_product_price_range( discounted, undiscounted, local_currency) is_on_sale = product.is_visible and discount is not None return ProductAvailability( on_sale=is_on_sale, price_range=discounted, price_range_undiscounted=undiscounted, discount=discount, price_range_local_currency=price_range_local, discount_local_currency=discount_local_currency, )
def test_subtraction_with_money_range(): price1 = Money(10, 'EUR') price2 = Money(30, 'EUR') price_range1 = MoneyRange(price1, price2) price3 = TaxedMoney(Money(40, 'EUR'), Money(60, 'EUR')) price4 = TaxedMoney(Money(80, 'EUR'), Money(120, 'EUR')) price_range2 = TaxedMoneyRange(price3, price4) result = price_range2 - price_range1 assert result.start == price3 - price1 assert result.stop == price4 - price2 with pytest.raises(ValueError): price_range2 - MoneyRange(Money(1, 'BTC'), Money(2, 'BTC'))