def source_line_to_order_lines(self, order, source_line): """ Convert a source line into one or more order lines. Normally each source line will yield just one order line, but package products will yield a parent line and its child lines. :type order: wshop.core.models.Order :param order: The order :type source_line: wshop.core.order_creator.SourceLine :param source_line: The SourceLine :rtype: Iterable[OrderLine] """ order_line = OrderLine(order=order) product = source_line.product quantity = Decimal(source_line.quantity) if product: order_line.product = product if product.sales_unit: quantized_quantity = bankers_round(quantity, product.sales_unit.decimals) if quantized_quantity != quantity: raise ValueError( "Sales unit decimal conversion causes precision loss!") else: order_line.product = None def text(value): return force_text(value) if value is not None else "" order_line.quantity = quantity order_line.supplier = source_line.supplier order_line.sku = text(source_line.sku) order_line.text = (text(source_line.text))[:192] if source_line.base_unit_price: order_line.base_unit_price = source_line.base_unit_price if source_line.discount_amount: order_line.discount_amount = source_line.discount_amount order_line.type = (source_line.type if source_line.type is not None else OrderLineType.OTHER) order_line.accounting_identifier = text( source_line.accounting_identifier) order_line.require_verification = bool( source_line.require_verification) order_line.verified = (not order_line.require_verification) order_line.source_line = source_line order_line.parent_source_line = source_line.parent_line order_line.extra_data = {"source_line_id": source_line.line_id} self._check_orderability(order_line) yield order_line for child_order_line in self.create_package_children(order_line): yield child_order_line
def test_order_source_rounding(prices): shop = Shop.objects.create(name="test", identifier="test", status=ShopStatus.ENABLED, public_name="test", prices_include_tax=False) expected = 0 for p in prices: expected += bankers_round(p, 2) source = BasketishOrderSource(shop) for x, price in enumerate(prices): source.add_line( type=OrderLineType.OTHER, quantity=1, text=x, base_unit_price=source.create_price(price), ordering=x, ) for x, order_source in enumerate(source.get_lines()): price = Decimal(prices[x]).quantize(Decimal(".1")**9) # make sure prices are in database with original precision assert order_source.base_unit_price == source.shop.create_price(price) # make sure the line taxless price is rounded assert order_source.taxless_price == source.shop.create_price( bankers_round(price, 2)) # Check that total prices calculated from priceful parts still matches assert _get_taxless_price(order_source) == order_source.taxless_price assert _get_taxful_price(order_source) == order_source.taxful_price # make sure the line price is rounded assert order_source.price == source.shop.create_price(price) # make sure order total is rounded assert source.taxless_total_price == source.shop.create_price( bankers_round(expected, 2))
def _check_taxful_price(source): for line in source.get_lines(): from_components = bankers_round( line.taxful_base_unit_price * line.quantity - line.taxful_discount_amount, 2) assert from_components == line.taxful_price assert line.price == line.base_unit_price * line.quantity - line.discount_amount assert line.discount_amount == line.base_price - line.price if line.base_price: assert line.discount_rate == (1 - (line.price / line.base_price)) assert line.discount_percentage == 100 * ( 1 - (line.price / line.base_price)) if line.quantity: assert line.unit_discount_amount == line.discount_amount / line.quantity
def test_rounding(prices): expected = 0 for p in prices: expected += bankers_round(p, 2) order = create_empty_order(prices_include_tax=False) order.save() for x, price in enumerate(prices): ol = OrderLine(order=order, type=OrderLineType.OTHER, quantity=1, text="Thing", ordering=x, base_unit_price=order.shop.create_price(price)) ol.save() order.cache_prices() for x, order_line in enumerate(order.lines.all().order_by("ordering")): price = Decimal(prices[x]).quantize(Decimal(".1")**9) # make sure prices are in database with original precision assert order_line.base_unit_price == order.shop.create_price(price) # make sure the line taxless price is rounded assert order_line.taxless_price == order.shop.create_price( bankers_round(price, 2)) # Check that total prices calculated from priceful parts still matches assert _get_taxless_price(order_line) == order_line.taxless_price assert _get_taxful_price(order_line) == order_line.taxful_price # make sure the line price is rounded assert order_line.price == order.shop.create_price(price) # make sure order total is rounded assert order.taxless_total_price == order.shop.create_price( bankers_round(expected, 2))
def test_broken_order(admin_user): """ """ quantities = [44, 23, 65] expected = sum(quantities) * 50 expected_based_on = expected / 1.5 # Wshop is calculating taxes per line so there will be some "errors" expected_based_on = ensure_decimal_places( Decimal("%s" % (expected_based_on + 0.01))) shop = get_default_shop() supplier = get_default_supplier() product1 = create_product("simple-test-product1", shop, supplier, 50) product2 = create_product("simple-test-product2", shop, supplier, 50) product3 = create_product("simple-test-product3", shop, supplier, 50) tax = get_default_tax() source = BasketishOrderSource(get_default_shop()) billing_address = get_address(country="US") shipping_address = get_address(name="Test street", country="US") source.status = get_initial_order_status() source.billing_address = billing_address source.shipping_address = shipping_address source.customer = create_random_person() source.payment_method = get_default_payment_method() source.shipping_method = get_default_shipping_method() source.add_line( type=OrderLineType.PRODUCT, product=product1, supplier=get_default_supplier(), quantity=quantities[0], base_unit_price=source.create_price(50), ) source.add_line( type=OrderLineType.PRODUCT, product=product2, supplier=get_default_supplier(), quantity=quantities[1], base_unit_price=source.create_price(50), ) source.add_line( type=OrderLineType.PRODUCT, product=product3, supplier=get_default_supplier(), quantity=quantities[2], base_unit_price=source.create_price(50), ) currency = "EUR" summary = source.get_tax_summary() assert len(summary) == 1 summary = summary[0] assert summary.taxful == Money(expected, "EUR") assert summary.based_on == Money(expected_based_on, "EUR") # originally non-rounded value assert bankers_round(source.get_total_tax_amount()) == summary.tax_amount assert source.taxless_total_price.value == expected_based_on assert summary.taxful.value == source.taxful_total_price.value assert summary.tax_amount == Money( bankers_round(source.taxful_total_price.value - source.taxless_total_price.value), currency) assert summary.taxful == summary.raw_based_on + summary.tax_amount assert summary.tax_rate == tax.rate assert summary.taxful.value == ( summary.based_on + summary.tax_amount).value - Decimal("%s" % 0.01) # create order from basket creator = OrderCreator() order = creator.create_order(source) assert order.taxless_total_price.value == expected_based_on # originally non-rounded value assert bankers_round(order.get_total_tax_amount()) == summary.tax_amount
def _get_taxful_price(line): return bankers_round( line.taxful_base_unit_price * line.quantity - line.taxful_discount_amount, 2)
def round(self, value): return bankers_round(parse_decimal_string(value), self.decimals)
def test_basket_with_package_product(admin_user): with override_settings(**REQUIRED_SETTINGS): shop = factories.get_default_shop() factories.get_default_shipping_method() factories.get_default_payment_method() OrderStatusManager().ensure_default_statuses() client = get_client(admin_user) response = client.post("/api/wshop/basket/new/", format="json", data={"shop": shop.pk}) assert response.status_code == status.HTTP_201_CREATED basket_uuid = response.data["uuid"] supplier = factories.get_supplier(SimpleSupplierModule.identifier, shop=shop) # base product - 1kg of sand base_sand_product = factories.create_product( "Sand", shop=shop, supplier=supplier, default_price="15.2", net_weight=Decimal(1), stock_behavior=StockBehavior.STOCKED) # 10kg bag of sand - package made by 10kg of sand sand_bag_10kg_product = factories.create_product( "Sand-bag-10-kg", shop=shop, supplier=supplier, default_price="149.9", net_weight=Decimal(10000)) sand_bag_10kg_product.make_package({base_sand_product: 10}) sand_bag_10kg_product.save() # 18.45kg bag of sand - package made by 18.45kg of sand sand_bag_1845kg_product = factories.create_product( "Sand-bag-1845-kg", shop=shop, supplier=supplier, default_price="179.9", net_weight=Decimal(18450)) sand_bag_1845kg_product.make_package({base_sand_product: 18.45}) sand_bag_1845kg_product.save() # 25kg bag of sand - package made by 25kg of sand sand_bag_25kg_product = factories.create_product( "Sand-bag-25-kg", shop=shop, supplier=supplier, default_price="2450.25", net_weight=Decimal(25000)) sand_bag_25kg_product.make_package({base_sand_product: 25}) sand_bag_25kg_product.save() initial_stock = 55 # put 55 sands (55kg) in stock supplier.adjust_stock(base_sand_product.id, initial_stock) stock_status = supplier.get_stock_status(base_sand_product.id) assert stock_status.physical_count == initial_stock assert stock_status.logical_count == initial_stock # zero stock for packages assert supplier.get_stock_status( sand_bag_10kg_product.id).logical_count == 0 assert supplier.get_stock_status( sand_bag_1845kg_product.id).logical_count == 0 assert supplier.get_stock_status( sand_bag_25kg_product.id).logical_count == 0 # add all the 3 packages to the basket, this will require (10 + 18.45 + 25 = 53.45 Sands) for product in [ sand_bag_10kg_product, sand_bag_1845kg_product, sand_bag_25kg_product ]: response = client.post( "/api/wshop/basket/{}/add/".format(basket_uuid), format="json", data={ "shop": shop.pk, "product": product.id }) assert response.status_code == status.HTTP_200_OK # get the basket response = client.get("/api/wshop/basket/{}/".format(basket_uuid)) assert response.status_code == status.HTTP_200_OK assert response.data["validation_errors"] == [] # now add more 25kg and it shouldn't have enough stock response = client.post("/api/wshop/basket/{}/add/".format(basket_uuid), format="json", data={ "shop": shop.pk, "product": sand_bag_25kg_product.id }) assert response.status_code == status.HTTP_400_BAD_REQUEST assert "Insufficient stock" in response.data["error"] # create order anyway response = client.post( "/api/wshop/basket/{}/create_order/".format(basket_uuid), format="json") assert response.status_code == status.HTTP_201_CREATED order = Order.objects.get(id=response.data["id"]) line_counter = Counter() for line in order.lines.products(): line_counter[line.product.id] += line.quantity assert bankers_round( line_counter[base_sand_product.id]) == bankers_round( Decimal(10) + Decimal(18.45) + Decimal(25)) assert line_counter[sand_bag_10kg_product.id] == 1 assert line_counter[sand_bag_1845kg_product.id] == 1 assert line_counter[sand_bag_25kg_product.id] == 1