Example #1
0
    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
Example #2
0
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))
Example #3
0
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
Example #4
0
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))
Example #5
0
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
Example #6
0
def _get_taxful_price(line):
    return bankers_round(
        line.taxful_base_unit_price * line.quantity -
        line.taxful_discount_amount, 2)
Example #7
0
 def round(self, value):
     return bankers_round(parse_decimal_string(value), self.decimals)
Example #8
0
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