def test_stacked_tax_taxful_price(): shop = get_shop(prices_include_tax=True, currency='EUR') source = OrderSource(shop) assert source.prices_include_tax source.add_line( type=OrderLineType.OTHER, quantity=1, base_unit_price=source.create_price(20) ) with override_provides("tax_module", TAX_MODULE_SPEC): with override_settings(SHUUP_TAX_MODULE="irvine"): source.shipping_address = MutableAddress( street="16215 Alton Pkwy", postal_code="92602", ) line = source.get_final_lines(with_taxes=True)[0] assert isinstance(line, SourceLine) assert line.taxes assert line.taxful_price == TaxfulPrice(20, 'EUR') assert_almost_equal(line.taxless_price, TaxlessPrice("18.52", 'EUR')) source.uncache() # Let's move out to a taxless location. source.shipping_address.postal_code = "11111" line = source.get_final_lines(with_taxes=True)[0] assert isinstance(line, SourceLine) assert not line.taxes assert line.taxful_price == TaxfulPrice(20, source.currency) assert line.taxless_price.value == Decimal("20")
def test_processor_orderability(admin_user): source = OrderSource(Shop()) processor = OrderProcessor() line = source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, shop=get_default_shop(), base_unit_price=source.create_price(10), ) line.order = Order(shop=get_default_shop()) assert processor._check_orderability(line) is None unorderable_line = source.add_line( type=OrderLineType.PRODUCT, product=create_product("no-shop"), supplier=get_default_supplier(), quantity=1, shop=get_default_shop(), base_unit_price=source.create_price(20), ) unorderable_line.order = Order(shop=get_default_shop()) with pytest.raises(ValidationError) as exc: processor._check_orderability(unorderable_line) assert "Not available in" in exc.value.message
def create_random_order(customer=None, products=(), completion_probability=0, shop=None): if not customer: customer = Contact.objects.all().order_by("?").first() if not customer: raise ValueError("No valid contacts") if shop is None: shop = get_default_shop() pricing_context = _get_pricing_context(shop, customer) source = OrderSource(shop) source.customer = customer source.customer_comment = "Mock Order" if customer.default_billing_address and customer.default_shipping_address: source.billing_address = customer.default_billing_address source.shipping_address = customer.default_shipping_address else: source.billing_address = create_random_address() source.shipping_address = create_random_address() source.order_date = now() - datetime.timedelta(days=random.uniform(0, 400)) source.language = customer.language source.status = get_initial_order_status() if not products: products = list(Product.objects.listed(source.shop, customer).order_by("?")[:40]) for i in range(random.randint(3, 10)): product = random.choice(products) quantity = random.randint(1, 5) price_info = product.get_price_info(pricing_context, quantity=quantity) shop_product = product.get_shop_instance(source.shop) supplier = shop_product.suppliers.first() line = source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=supplier, quantity=quantity, base_unit_price=price_info.base_unit_price, discount_amount=price_info.discount_amount, sku=product.sku, text=product.safe_translation_getter("name", any_language=True) ) assert line.price == price_info.price with atomic(): oc = OrderCreator() order = oc.create_order(source) if random.random() < completion_probability: order.create_shipment_of_all_products() # also set complete order.status = OrderStatus.objects.get_default_complete() order.save(update_fields=("status",)) return order
def create_random_order(customer=None, products=(), completion_probability=0, shop=None): if not customer: customer = Contact.objects.all().order_by("?").first() if not customer: raise ValueError("No valid contacts") if shop is None: shop = get_default_shop() pricing_context = _get_pricing_context(shop, customer) source = OrderSource(shop) source.customer = customer source.customer_comment = "Mock Order" if customer.default_billing_address and customer.default_shipping_address: source.billing_address = customer.default_billing_address source.shipping_address = customer.default_shipping_address else: source.billing_address = create_random_address() source.shipping_address = create_random_address() source.order_date = now() - datetime.timedelta(days=random.uniform(0, 400)) source.status = get_initial_order_status() if not products: products = list(Product.objects.listed(source.shop, customer).order_by("?")[:40]) for i in range(random.randint(3, 10)): product = random.choice(products) quantity = random.randint(1, 5) price_info = product.get_price_info(pricing_context, quantity=quantity) shop_product = product.get_shop_instance(source.shop) supplier = shop_product.suppliers.first() line = source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=supplier, quantity=quantity, base_unit_price=price_info.base_unit_price, discount_amount=price_info.discount_amount, sku=product.sku, text=product.safe_translation_getter("name", any_language=True) ) assert line.price == price_info.price with atomic(): oc = OrderCreator() order = oc.create_order(source) if random.random() < completion_probability: order.create_shipment_of_all_products() # also set complete order.status = OrderStatus.objects.get_default_complete() order.save(update_fields=("status",)) return order
def test_stacked_tax_taxless_price(): source = OrderSource(get_shop(prices_include_tax=False)) assert source.prices_include_tax is False source.add_line(type=OrderLineType.OTHER, quantity=1, base_unit_price=source.create_price(10)) with override_provides("tax_module", TAX_MODULE_SPEC): with override_settings(SHUUP_TAX_MODULE="irvine"): source.shipping_address = MutableAddress( street="16215 Alton Pkwy", postal_code="92602", ) line = source.get_final_lines(with_taxes=True)[0] assert isinstance(line, SourceLine) assert line.taxes assert line.taxful_price.value == Decimal("10.800") source.uncache() # Let's move out to a taxless location. source.shipping_address.postal_code = "11111" line = source.get_final_lines(with_taxes=True)[0] assert isinstance(line, SourceLine) assert not line.taxes assert line.taxful_price.value == Decimal("10")
def test_order_creator_orderability(admin_user): source = OrderSource(get_default_shop()) product = get_default_product() line = source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=get_default_supplier(), quantity=1, shop=get_default_shop(), base_unit_price=source.create_price(10), ) assert len(list(source.get_validation_errors())) == 0 # delete the shop product product.get_shop_instance(get_default_shop()).delete() errors = list(source.get_validation_errors()) assert len(errors) == 1 assert "product_not_available_in_shop" in errors[0].code
def test_order_creator_orderability(admin_user): source = OrderSource(get_default_shop()) product = get_default_product() line = source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=get_default_supplier(), quantity=1, shop=get_default_shop(), base_unit_price=source.create_price(10), ) assert len(list(source.get_validation_errors())) == 0 # delete the shop product product.get_shop_instance(get_default_shop()).delete() errors = list(source.get_validation_errors()) assert len(errors) == 1 assert "product_not_available_in_shop" in errors[0].code
def test_order_full_refund_with_taxes(include_tax): tax_rate = Decimal(0.2) # 20% product_price = 100 discount_amount = 30 random_line_price = 5 shop = factories.get_shop(include_tax) source = OrderSource(shop) source.status = factories.get_initial_order_status() supplier = factories.get_default_supplier() create_default_order_statuses() tax = factories.get_tax("sales-tax", "Sales Tax", tax_rate) factories.create_default_tax_rule(tax) product = factories.create_product("sku", shop=shop, supplier=supplier, default_price=product_price) line = source.add_line( line_id="product-line", type=OrderLineType.PRODUCT, product=product, supplier=supplier, quantity=1, shop=shop, base_unit_price=source.create_price(product_price), ) discount_line = source.add_line( line_id="discount-line", type=OrderLineType.DISCOUNT, supplier=supplier, quantity=1, base_unit_price=source.create_price(0), discount_amount=source.create_price(discount_amount), parent_line_id=line.line_id) raw_total_price = Decimal(product_price - discount_amount) total_taxful = bround(source.taxful_total_price.value) total_taxless = bround(source.taxless_total_price.value) if include_tax: assert total_taxful == bround(raw_total_price) assert total_taxless == bround(raw_total_price / (1 + tax_rate)) else: assert total_taxful == bround(raw_total_price * (1 + tax_rate)) assert total_taxless == bround(raw_total_price) # Lines without quantity shouldn't affect refunds other_line = source.add_line( text="This random line for textual information", line_id="other-line", type=OrderLineType.OTHER, quantity=0) # Lines with quantity again should be able to be refunded normally. other_line_with_quantity = source.add_line( line_id="other_line_with_quantity", type=OrderLineType.OTHER, text="Special service $5/h", quantity=1, base_unit_price=source.create_price(random_line_price)) raw_total_price = Decimal(product_price - discount_amount + random_line_price) total_taxful = bround(source.taxful_total_price.value) total_taxless = bround(source.taxless_total_price.value) if include_tax: assert total_taxful == bround(raw_total_price) assert total_taxless == bround(raw_total_price / (1 + tax_rate)) else: assert total_taxful == bround(raw_total_price * (1 + tax_rate)) assert total_taxless == bround(raw_total_price) creator = OrderCreator() order = creator.create_order(source) assert order.taxful_total_price.value == total_taxful assert order.taxless_total_price.value == total_taxless order.create_payment(order.taxful_total_price) assert order.is_paid() order.create_full_refund() assert order.taxful_total_price_value == 0 for parent_order_line in order.lines.filter(parent_line__isnull=True): if parent_order_line.quantity == 0: assert not parent_order_line.child_lines.exists() else: refund_line = parent_order_line.child_lines.filter( type=OrderLineType.REFUND).first() assert refund_line assert parent_order_line.taxful_price.value == -refund_line.taxful_price.value assert parent_order_line.taxless_price.value == -refund_line.taxless_price.value assert parent_order_line.price.value == -refund_line.price.value
def test_order_partial_refund_with_taxes(include_tax): tax_rate = Decimal(0.2) # 20% product_price = 100 discount_amount = 30 random_line_price = 5 refunded_amount = 15 shop = factories.get_shop(include_tax) source = OrderSource(shop) source.status = factories.get_initial_order_status() supplier = factories.get_default_supplier() create_default_order_statuses() tax = factories.get_tax("sales-tax", "Sales Tax", tax_rate) factories.create_default_tax_rule(tax) product = factories.create_product("sku", shop=shop, supplier=supplier, default_price=product_price) line = source.add_line( line_id="product-line", type=OrderLineType.PRODUCT, product=product, supplier=supplier, quantity=1, shop=shop, base_unit_price=source.create_price(product_price), ) discount_line = source.add_line( line_id="discount-line", type=OrderLineType.DISCOUNT, supplier=supplier, quantity=1, base_unit_price=source.create_price(0), discount_amount=source.create_price(discount_amount), parent_line_id=line.line_id) raw_total_price = Decimal(product_price - discount_amount) total_taxful = bround(source.taxful_total_price.value) total_taxless = bround(source.taxless_total_price.value) if include_tax: assert total_taxful == bround(raw_total_price) assert total_taxless == bround(raw_total_price / (1 + tax_rate)) else: assert total_taxful == bround(raw_total_price * (1 + tax_rate)) assert total_taxless == bround(raw_total_price) creator = OrderCreator() order = creator.create_order(source) assert order.taxful_total_price.value == total_taxful assert order.taxless_total_price.value == total_taxless order.create_payment(order.taxful_total_price) assert order.is_paid() refund_data = [ dict( amount=Money(refunded_amount, shop.currency), quantity=1, line=order.lines.products().first(), ) ] order.create_refund(refund_data) total_taxful = bround(order.taxful_total_price.value) total_taxless = bround(order.taxless_total_price.value) taxless_refunded_amount = (refunded_amount / (1 + tax_rate)) if include_tax: raw_total_price = Decimal(product_price - discount_amount - refunded_amount) assert total_taxful == bround(raw_total_price) assert total_taxless == bround(raw_total_price / (1 + tax_rate)) else: # the refunded amount it considered a taxful price internally raw_total_price = Decimal(product_price - discount_amount) assert total_taxful == bround((raw_total_price * (1 + tax_rate)) - refunded_amount) assert total_taxless == bround(raw_total_price - taxless_refunded_amount) refund_line = order.lines.refunds().filter( type=OrderLineType.REFUND).first() if include_tax: assert refund_line.taxful_price.value == -bround(refunded_amount) assert refund_line.taxless_price.value == -bround( taxless_refunded_amount) else: assert refund_line.taxful_price.value == -bround(refunded_amount) assert refund_line.taxless_price.value == -bround( taxless_refunded_amount)
def create_random_order( # noqa customer=None, products=(), completion_probability=0, shop=None, random_products=True, create_payment_for_order_total=False, order_date=None, ): if not customer: customer = Contact.objects.all().order_by("?").first() if not customer: raise ValueError("Error! No valid contacts.") if shop is None: shop = get_default_shop() pricing_context = _get_pricing_context(shop, customer) source = OrderSource(shop) source.customer = customer source.customer_comment = "Mock Order" if customer.default_billing_address and customer.default_shipping_address: source.billing_address = customer.default_billing_address source.shipping_address = customer.default_shipping_address else: source.billing_address = create_random_address() source.shipping_address = create_random_address() source.order_date = order_date or (now() - datetime.timedelta(days=random.uniform(0, 400))) source.status = get_initial_order_status() if not products: products = list(Product.objects.listed(source.shop, customer).order_by("?")[:40]) if random_products: quantity = random.randint(3, 10) else: quantity = len(products) for i in range(quantity): if random_products: product = random.choice(products) else: product = products[i] quantity = random.randint(1, 5) price_info = product.get_price_info(pricing_context, quantity=quantity) shop_product = product.get_shop_instance(source.shop) supplier = shop_product.get_supplier(source.customer, quantity, source.shipping_address) line = source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=supplier, quantity=quantity, base_unit_price=price_info.base_unit_price, discount_amount=price_info.discount_amount, sku=product.sku, text=product.safe_translation_getter("name", any_language=True), ) assert line.price == price_info.price with atomic(): oc = OrderCreator() order = oc.create_order(source) if random.random() < completion_probability: suppliers = set([line.supplier for line in order.lines.filter(supplier__isnull=False, quantity__gt=0)]) for supplier in suppliers: order.create_shipment_of_all_products(supplier=supplier) if create_payment_for_order_total: order.create_payment(order.taxful_total_price) # also set complete order.save() order.change_status(next_status=get_completed_order_status(), user=customer.user) return order