Example #1
0
def test_admin_form(rf, admin_user):
    supplier = get_simple_supplier()
    shop = get_default_shop()
    product = create_product("simple-test-product", shop, supplier)
    request = rf.get("/")
    request.user = admin_user
    frm = SimpleSupplierForm(product=product, request=request)
    # Form contains 1 product even if the product is not stocked
    assert len(frm.products) == 1
    assert not frm.products[0].is_stocked()

    product.stock_behavior = StockBehavior.STOCKED  # Make product stocked
    product.save()

    # Now since product is stocked it should be in the form
    frm = SimpleSupplierForm(product=product, request=request)
    assert len(frm.products) == 1

    # Add stocked children for product
    child_product = create_product("child-test-product", shop, supplier)
    child_product.stock_behavior = StockBehavior.STOCKED
    child_product.save()
    child_product.link_to_parent(product)

    # Admin form should now contain only child products for product
    frm = SimpleSupplierForm(product=product, request=request)
    assert len(frm.products) == 1
    assert frm.products[0] == child_product
Example #2
0
def test_product_cross_sells(admin_user):
    shop1 = get_default_shop()
    supplier1 = create_simple_supplier("supplier1")
    product1 = create_product("product1", shop=shop1, supplier=supplier1)
    product2 = create_product("product2", shop=shop1, supplier=supplier1)

    # assign cross sell of product1 and product2
    ProductCrossSell.objects.create(product1=product1, product2=product2, type=ProductCrossSellType.RECOMMENDED)
    ProductCrossSell.objects.create(product1=product1, product2=product2, type=ProductCrossSellType.RELATED)
    ProductCrossSell.objects.create(product1=product1, product2=product2, type=ProductCrossSellType.COMPUTED)
    ProductCrossSell.objects.create(product1=product1, product2=product2, type=ProductCrossSellType.BOUGHT_WITH)

    # product1 must have cross shell of product2
    request = get_request("/api/shuup/front/shop_products/", admin_user)
    response = FrontShopProductViewSet.as_view({"get": "list"})(request)
    response.render()
    products_data = json.loads(response.content.decode("utf-8"))
    products = sorted(products_data, key=lambda p: p["id"])

    assert products[0]["product_id"] == product1.id
    assert products[0]["cross_sell"]["recommended"][0]["product_id"] == product2.id
    assert products[0]["cross_sell"]["related"][0]["product_id"] == product2.id
    assert products[0]["cross_sell"]["computed"][0]["product_id"] == product2.id
    assert products[0]["cross_sell"]["bought_with"][0]["product_id"] == product2.id

    assert products[1]["product_id"] == product2.id
    assert products[1]["cross_sell"]["recommended"] == []
    assert products[1]["cross_sell"]["related"] == []
    assert products[1]["cross_sell"]["computed"] == []
    assert products[1]["cross_sell"]["bought_with"] == []
def test_get_listed_products_filter():
    context = get_jinja_context()
    shop = get_default_shop()
    supplier = get_default_supplier()

    product_1 = create_product(
        "test-sku-1",
        supplier=supplier,
        shop=shop,
    )
    product_2 = create_product(
        "test-sku-2",
        supplier=supplier,
        shop=shop,
    )

    cache.clear()
    from shuup.front.template_helpers import general
    filter_dict = {"id": product_1.id}
    for cache_test in range(2):
        product_list = general.get_listed_products(context, n_products=2, filter_dict=filter_dict)
        assert product_1 in product_list
        assert product_2 not in product_list

    for cache_test in range(2):
        product_list = general.get_listed_products(context, n_products=2, filter_dict=filter_dict, orderable_only=False)
        assert product_1 in product_list
        assert product_2 not in product_list
Example #4
0
def test_variation_product_price_simple(client):
    shop = get_default_shop()
    supplier = get_default_supplier()
    product = create_product("Parent", supplier=supplier, shop=shop, default_price="10")
    child = create_product("SimpleVarChild", supplier=supplier, shop=shop, default_price="5")
    child.link_to_parent(product, variables={"size": "S"})
    response = client.get(
        reverse('shuup:xtheme_extra_view', kwargs={
                'view': 'product_price'
            }
        ) + "?id=%s&quantity=%s&var_1=1" % (product.pk, 1)
    )
    assert response.context_data["product"] == child
    assert b"form" in response.content

    sp = child.get_shop_instance(shop)
    sp.suppliers.remove(supplier)
    response = client.get(
        reverse('shuup:xtheme_extra_view', kwargs={
                'view': 'product_price'
            }
        ) + "?id=%s&quantity=%s&var_1=1" % (product.pk, 1)
    )
    assert response.context_data["product"] == child
    # product isn't orderable since no supplier
    assert b"no-price" in response.content
def test_get_newest_products():
    from shuup.front.template_helpers import general

    supplier = get_default_supplier()
    shop = get_default_shop()
    products = [create_product("sku-%d" % x, supplier=supplier, shop=shop) for x in range(2)]
    children = [create_product("SimpleVarChild-%d" % x, supplier=supplier, shop=shop) for x in range(2)]

    for child in children:
        child.link_to_parent(products[0])

    context = get_jinja_context()

    for cache_test in range(2):
        newest_products = list(general.get_newest_products(context, n_products=10))
    # only 2 products exist
    assert len(newest_products) == 2
    assert products[0] in newest_products
    assert products[1] in newest_products

    # Delete one product
    products[0].soft_delete()

    for cache_test in range(2):
        newest_products = list(general.get_newest_products(context, n_products=10))
    # only 2 products exist
    assert len(newest_products) == 1
    assert products[0] not in newest_products
    assert products[1] in newest_products
def test_get_random_products_cache_bump():
    from shuup.front.template_helpers import general

    supplier = get_default_supplier()
    shop = get_default_shop()

    products = [create_product("sku-%d" % x, supplier=supplier, shop=shop) for x in range(2)]
    children = [create_product("SimpleVarChild-%d" % x, supplier=supplier, shop=shop) for x in range(2)]

    for child in children:
        child.link_to_parent(products[0])

    context = get_jinja_context()
    cache.clear()
    set_cached_value_mock = mock.Mock(wraps=context_cache.set_cached_value)
    def set_cache_value(key, value, timeout=None):
        if "random_products" in key:
            return set_cached_value_mock(key, value, timeout)

    with mock.patch.object(context_cache, "set_cached_value", new=set_cache_value):
        assert set_cached_value_mock.call_count == 0

        assert general.get_random_products(context, n_products=10)
        assert set_cached_value_mock.call_count == 1

        # call again, the cache should be returned instead and the set_cached_value shouldn't be called again
        assert general.get_random_products(context, n_products=10)
        assert set_cached_value_mock.call_count == 1

        # change a shop product, the cache should be bumped
        ShopProduct.objects.filter(shop=shop).first().save()
        assert general.get_random_products(context, n_products=10)
        assert set_cached_value_mock.call_count == 2
Example #7
0
def test_extract_products_shortdescription():
    activate("en")
    out = StringIO()

    html_description1 = "<b>a HTML description</b>"
    product1 = create_product("p1", description=html_description1)

    html_description2 = '<p class="what">another HTML description</p>'
    product2 = create_product("p2", description=html_description2)

    faker = Faker()
    long_description = faker.sentence(nb_words=150, variable_nb_words=True)
    product3 = create_product("p3", description=long_description)

    for lang, _ in settings.LANGUAGES:
        product2.set_current_language(lang)
        product2.description = html_description2
        product2.save()

    call_command("shuup_extract_products_shortdescription", stdout=out)

    product1 = Product.objects.get(pk=product1.pk)
    product2 = Product.objects.get(pk=product2.pk)
    product3 = Product.objects.get(pk=product3.pk)

    assert product1.short_description == do_striptags(html_description1)

    for lang, _ in settings.LANGUAGES:
        product2.set_current_language(lang)
        assert product2.short_description == do_striptags(html_description2)

    assert product3.short_description == long_description[:150]

    assert "Done." in out.getvalue()
Example #8
0
def test_add_invalid_product():
    shop = get_default_shop()
    supplier = get_default_supplier()
    request = get_request_with_basket()
    basket = request.basket

    # cannot add simple/variable variation parent to the basket
    parent = create_product("parent", shop=shop, supplier=supplier)
    child = create_product("child", shop=shop, supplier=supplier)
    child.link_to_parent(parent)
    parent.refresh_from_db()
    assert parent.mode == ProductMode.SIMPLE_VARIATION_PARENT

    with pytest.raises(ValidationError) as excinfo:
        basket_commands.handle_add(request, basket, product_id=parent.pk, quantity=2)
    assert excinfo.value.code == 'invalid_product'

    child.unlink_from_parent()
    child.link_to_parent(parent, variables={"size": "XXL"})
    parent.refresh_from_db()
    assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT

    with pytest.raises(ValidationError) as excinfo:
        basket_commands.handle_add(request, basket, product_id=parent.pk, quantity=3)
    assert excinfo.value.code == 'invalid_product'
Example #9
0
def test_make_product_package(admin_user):
    shop = get_default_shop()
    client = _get_client(admin_user)
    product1 = create_product("product1")
    product2 = create_product("product2")
    product3 = create_product("product3")
    product4 = create_product("product4")
    assert product1.mode == product2.mode == product3.mode == product4.mode == ProductMode.NORMAL

    package_data = [
        {"product": product2.pk, "quantity":1},
        {"product": product3.pk, "quantity":2},
        {"product": product4.pk, "quantity":3.5}
    ]
    response = client.post("/api/shuup/product/%d/make_package/" % product1.pk,
                           content_type="application/json",
                           data=json.dumps(package_data))
    assert response.status_code == status.HTTP_201_CREATED
    product1.refresh_from_db()
    product2.refresh_from_db()
    product3.refresh_from_db()
    product4.refresh_from_db()
    assert product1.mode == ProductMode.PACKAGE_PARENT

    child1 = ProductPackageLink.objects.get(parent=product1, child=product2)
    assert child1.quantity == 1
    child2 = ProductPackageLink.objects.get(parent=product1, child=product3)
    assert child2.quantity == 2
    child3 = ProductPackageLink.objects.get(parent=product1, child=product4)
    assert child3.quantity == 3.5
Example #10
0
def test_mass_edit_products2(rf, admin_user):
    shop = get_default_shop()
    supplier = get_default_supplier()
    product1 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="50")
    product2 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="501")

    shop_product1 = product1.get_shop_instance(shop)
    shop_product2 = product2.get_shop_instance(shop)

    # ensure no categories set
    assert shop_product1.primary_category is None
    assert shop_product2.primary_category is None
    payload = {
        "action": InvisibleMassAction().identifier,
        "values": [product1.pk, product2.pk]
    }
    request = apply_request_middleware(rf.post(
        "/",
        user=admin_user,
    ))
    request._body = json.dumps(payload).encode("UTF-8")
    view = ProductListView.as_view()
    response = view(request=request)
    assert response.status_code == 200
    for product in Product.objects.all():
        assert product.get_shop_instance(shop).visibility == ShopProductVisibility.NOT_VISIBLE
def test_best_selling_products_with_multiple_orders():
    context = get_jinja_context()
    supplier = get_default_supplier()
    shop = get_default_shop()
    n_products = 2
    price = 10

    product_1 = create_product("test-sku-1", supplier=supplier, shop=shop)
    product_2 = create_product("test-sku-2", supplier=supplier, shop=shop)
    create_order_with_product(product_1, supplier, quantity=1, taxless_base_unit_price=price, shop=shop)
    create_order_with_product(product_2, supplier, quantity=1, taxless_base_unit_price=price, shop=shop)
    cache.clear()
    # Two initial products sold
    assert product_1 in general.get_best_selling_products(context, n_products=n_products)
    assert product_2 in general.get_best_selling_products(context, n_products=n_products)

    product_3 = create_product("test-sku-3", supplier=supplier, shop=shop)
    create_order_with_product(product_3, supplier, quantity=2, taxless_base_unit_price=price, shop=shop)
    cache.clear()
    # Third product sold in greater quantity
    assert product_3 in general.get_best_selling_products(context, n_products=n_products)

    create_order_with_product(product_1, supplier, quantity=4, taxless_base_unit_price=price, shop=shop)
    create_order_with_product(product_2, supplier, quantity=4, taxless_base_unit_price=price, shop=shop)
    cache.clear()
    # Third product outsold by first two products
    assert product_3 not in general.get_best_selling_products(context, n_products=n_products)
Example #12
0
def test_simple_variation():
    shop = get_default_shop()
    parent = create_product("SimpleVarParent")
    children = [create_product("SimpleVarChild-%d" % x) for x in range(10)]
    for child in children:
        child.link_to_parent(parent)
        sp = ShopProduct.objects.create(shop=shop, product=child, listed=True)
        assert child.is_variation_child()
        assert not sp.is_list_visible()  # Variation children are not list visible

    assert parent.mode == ProductMode.SIMPLE_VARIATION_PARENT
    assert not list(parent.get_all_available_combinations())  # Simple variations can't have these.

    # Validation tests

    dummy = create_product("InvalidSimpleVarChild")

    with pytest.raises(ValueError):
        dummy.link_to_parent(parent, variables={"size": "XL"})

    with pytest.raises(ValueError):
        parent.link_to_parent(dummy)

    with pytest.raises(ValueError):
        dummy.link_to_parent(children[0])

    # Unlinkage

    for child in children:
        child.unlink_from_parent()
        assert not child.is_variation_child()
        assert child.mode == ProductMode.NORMAL

    assert not parent.is_variation_parent()
    assert parent.variation_children.count() == 0
def get_frontend_order_state(contact, valid_lines=True):
    """
    Get a dict structure mirroring what the frontend JavaScript would submit.
    :type contact: Contact|None
    """
    translation.activate("en")
    shop = get_default_shop()
    tax = Tax.objects.create(code="test_code", rate=decimal.Decimal("0.20"), name="Default")
    tax_class = TaxClass.objects.create(identifier="test_tax_class", name="Default")
    rule = TaxRule.objects.create(tax=tax)
    rule.tax_classes.add(tax_class)
    rule.save()
    product = create_product(
        sku=printable_gibberish(),
        supplier=get_default_supplier(),
        shop=shop
    )
    product.tax_class = tax_class
    product.save()
    if valid_lines:
        lines = [
            {"id": "x", "type": "product", "product": {"id": product.id}, "quantity": "32", "baseUnitPrice": 50},
            {"id": "y", "type": "other", "sku": "hello", "text": "A greeting", "quantity": 1, "unitPrice": "5.5"},
            {"id": "z", "type": "text", "text": "This was an order!", "quantity": 0},
        ]
    else:
        unshopped_product = create_product(sku=printable_gibberish(), supplier=get_default_supplier())
        not_visible_product = create_product(
            sku=printable_gibberish(),
            supplier=get_default_supplier(),
            shop=shop
        )
        not_visible_shop_product = not_visible_product.get_shop_instance(shop)
        not_visible_shop_product.visible = False
        not_visible_shop_product.save()
        lines = [
            {"id": "x", "type": "product"},  # no product?
            {"id": "x", "type": "product", "product": {"id": unshopped_product.id}},  # not in this shop?
            {"id": "y", "type": "product", "product": {"id": -product.id}},  # invalid product?
            {"id": "z", "type": "other", "quantity": 1, "unitPrice": "q"},  # what's that price?
            {"id": "rr", "type": "product", "quantity": 1, "product": {"id": not_visible_product.id}}  # not visible
        ]

    state = {
        "customer": {"id": contact.id if contact else None},
        "lines": lines,
        "methods": {
            "shippingMethod": {"id": get_default_shipping_method().id},
            "paymentMethod": {"id": get_default_payment_method().id},
        },
        "shop": {
            "selected": {
                "id": shop.id,
                "name": shop.name,
                "currency": shop.currency,
                "priceIncludeTaxes": shop.prices_include_tax
            }
        }
    }
    return state
Example #14
0
def test_get_front_products(admin_user):
    shop1 = get_default_shop()
    shop2 = Shop.objects.create(status=ShopStatus.ENABLED)
    supplier = get_default_supplier()

    p1 = create_product("product1", shop=shop1, supplier=supplier)
    p2 = create_product("product2", shop=shop1, supplier=supplier)

    sp1 = ShopProduct.objects.get(shop=shop1, product=p1)
    sp2 = ShopProduct.objects.get(shop=shop1, product=p2)

    # add products to other shop
    sp3 = ShopProduct.objects.create(shop=shop2, product=p1)
    sp4 = ShopProduct.objects.create(shop=shop2, product=p2)

    request = get_request("/api/shuup/front/products/", admin_user)
    response = FrontProductViewSet.as_view({"get": "list"})(request)
    response.render()
    assert response.status_code == status.HTTP_200_OK
    products_data = json.loads(response.content.decode("utf-8"))
    # should return only 2 procuts with 2 shops each
    assert len(products_data) == 2
    products = sorted(products_data, key=lambda p: p["id"])
    assert products[0]["id"] == p1.id
    assert products[0]["shop_products"] == [sp1.id, sp3.id]
    assert products[1]["id"] == p2.id
    assert products[1]["shop_products"] == [sp2.id, sp4.id]
def test_price_info_cache_bump(rf):
    request, shop, group = initialize_test(rf, True)

    product_one = create_product("Product_1", shop, default_price=150)
    product_two = create_product("Product_2", shop, default_price=250)

    contact = create_customer()
    group2 = ContactGroup.objects.create(name="Group 2", shop=shop)

    cgp_price = CgpPrice.objects.create(product=product_one, shop=shop, group=group, price_value=100)
    cgp_discount = CgpDiscount.objects.create(product=product_two, shop=shop, group=group, discount_amount_value=200)

    spm = get_pricing_module()
    assert isinstance(spm, CustomerGroupPricingModule)
    pricing_context = spm.get_context_from_request(request)

    for function in [
        lambda: cgp_price.save(),
        lambda: cgp_discount.save(),
        lambda: group2.members.add(contact),
        lambda: cgp_price.delete(),
        lambda: cgp_discount.delete()
    ]:
        cache_price_info(pricing_context, product_one, 1, product_one.get_price_info(pricing_context))
        cache_price_info(pricing_context, product_two, 1, product_two.get_price_info(pricing_context))

        # prices are cached
        assert get_cached_price_info(pricing_context, product_one)
        assert get_cached_price_info(pricing_context, product_two)

        # caches should be bumped
        function()
        assert get_cached_price_info(pricing_context, product_one) is None
        assert get_cached_price_info(pricing_context, product_two) is None
Example #16
0
def test_complex_variation():
    request = get_request_with_basket()
    basket = request.basket
    shop = get_default_shop()
    supplier = get_default_supplier()

    parent = create_product("SuperComplexVarParent", shop=shop, supplier=supplier)
    color_var = ProductVariationVariable.objects.create(product=parent, identifier="color")
    size_var = ProductVariationVariable.objects.create(product=parent, identifier="size")

    ProductVariationVariableValue.objects.create(variable=color_var, identifier="yellow")
    ProductVariationVariableValue.objects.create(variable=size_var, identifier="small")

    combinations = list(parent.get_all_available_combinations())
    for combo in combinations:
        child = create_product("xyz-%s" % combo["sku_part"], shop=shop, supplier=supplier)
        child.link_to_parent(parent, combo["variable_to_value"])

    # Elided product should not yield a result
    yellow_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="yellow")
    small_size_value = ProductVariationVariableValue.objects.get(variable=size_var, identifier="small")
    # add to basket yellow + small
    kwargs = {"var_%d" % color_var.pk: yellow_color_value.pk, "var_%d" % size_var.pk: small_size_value.pk}
    basket_commands.handle_add_var(request, basket, parent.id, **kwargs)
    assert basket.get_product_ids_and_quantities()[child.pk] == 1

    with pytest.raises(ValidationError):
        kwargs = {"var_%d" % color_var.pk: yellow_color_value.pk, "var_%d" % size_var.pk: small_size_value.pk + 1}
        basket_commands.handle_add_var(request, basket, parent.id, **kwargs)
Example #17
0
def test_basket_shipping_error(rf):
    StoredBasket.objects.all().delete()
    shop = get_default_shop()
    supplier = get_default_supplier()
    shipped_product = create_product(
        printable_gibberish(), shop=shop, supplier=supplier, default_price=50,
        shipping_mode=ShippingMode.SHIPPED
    )
    unshipped_product = create_product(
        printable_gibberish(), shop=shop, supplier=supplier, default_price=50,
        shipping_mode=ShippingMode.NOT_SHIPPED
    )

    request = rf.get("/")
    request.session = {}
    request.shop = shop
    apply_request_middleware(request)
    basket = get_basket(request)

    # With a shipped product but no shipping methods, we oughta get an error
    basket.add_product(supplier=supplier, shop=shop, product=shipped_product, quantity=1)
    assert any(ve.code == "no_common_shipping" for ve in basket.get_validation_errors())
    basket.clear_all()

    # But with an unshipped product, we should not
    basket.add_product(supplier=supplier, shop=shop, product=unshipped_product, quantity=1)
    assert not any(ve.code == "no_common_shipping" for ve in basket.get_validation_errors())
def test_product_in_basket_condition_with_variation_parent(rf):
    request, shop, group = initialize_test(rf, False)

    basket = get_basket(request)
    supplier = get_default_supplier()

    product = create_product(
        "test-product", shop, default_price="200", supplier=supplier, mode=ProductMode.SIMPLE_VARIATION_PARENT)

    child_products = []
    for x in range(0, 3):
        child_product = create_product("test-product-%s" % x, shop, default_price="10", supplier=supplier)
        child_product.link_to_parent(product)
        child_products.append(child_product)

    condition = ProductsInBasketCondition.objects.create()
    condition.values = [product]
    condition.operator = ComparisonOperator.EQUALS
    condition.quantity = 3
    condition.save()

    assert not condition.matches(basket, [])

    for child_product in child_products:
        basket.add_product(supplier=supplier, shop=shop, product=child_product, quantity=1)

    assert condition.matches(basket, [])
Example #19
0
def test_mass_edit_orders3(rf, admin_user):
    shop = get_default_shop()
    supplier = get_default_supplier()
    contact1 = create_random_person()
    product1 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="50")
    product2 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="501")

    order1 = create_random_order(customer=contact1,
                                products=[product1, product2],
                                completion_probability=0)

    order2 = create_random_order(customer=contact1,
                                products=[product1, product2],
                                completion_probability=0)
    assert order1.status.role != OrderStatusRole.CANCELED
    assert order2.status.role != OrderStatusRole.CANCELED

    payload = {
        "action": OrderConfirmationPdfAction().identifier,
        "values": [order1.pk, order2.pk]
    }
    request = apply_request_middleware(rf.post(
        "/",
        user=admin_user,
    ))
    request._body = json.dumps(payload).encode("UTF-8")
    view = OrderListView.as_view()
    response = view(request=request)
    assert response.status_code == 200
    if weasyprint:
        assert response['Content-Disposition'] == 'attachment; filename=order_confirmation_pdf.zip'
    else:
        assert response["content-type"] == "application/json"
def init_test(request, shop, prices):
    apply_request_middleware(request)
    parent = create_product("parent_product", shop=shop)
    children = [create_product("child-%d" % price, shop=shop, default_price=price) for price in prices]
    for child in children:
        child.link_to_parent(parent)
    return parent
def test_best_selling_products_with_multiple_orders():
    context = get_jinja_context()
    supplier = get_default_supplier()
    shop = get_default_shop()
    n_products = 2
    price = 10

    product_1 = create_product("test-sku-1", supplier=supplier, shop=shop)
    product_2 = create_product("test-sku-2", supplier=supplier, shop=shop)
    create_order_with_product(product_1, supplier, quantity=1, taxless_base_unit_price=price, shop=shop)
    create_order_with_product(product_2, supplier, quantity=1, taxless_base_unit_price=price, shop=shop)
    cache.clear()
    # Two initial products sold
    assert product_1 in general.get_best_selling_products(context, n_products=n_products)
    assert product_2 in general.get_best_selling_products(context, n_products=n_products)

    product_3 = create_product("test-sku-3", supplier=supplier, shop=shop)
    create_order_with_product(product_3, supplier, quantity=2, taxless_base_unit_price=price, shop=shop)
    cache.clear()
    # Third product sold in greater quantity
    assert product_3 in general.get_best_selling_products(context, n_products=n_products)

    create_order_with_product(product_1, supplier, quantity=4, taxless_base_unit_price=price, shop=shop)
    create_order_with_product(product_2, supplier, quantity=4, taxless_base_unit_price=price, shop=shop)
    cache.clear()
    # Third product outsold by first two products
    assert product_3 not in general.get_best_selling_products(context, n_products=n_products)

    children = [create_product("SimpleVarChild-%d" % x, supplier=supplier, shop=shop) for x in range(5)]
    for child in children:
        child.link_to_parent(product_3)
        create_order_with_product(child, supplier, quantity=1, taxless_base_unit_price=price, shop=shop)
    cache.clear()
    # Third product now sold in greatest quantity
    assert product_3 == general.get_best_selling_products(context, n_products=n_products)[0]
Example #22
0
def test_products_form_add_multiple_products():
    shop = get_default_shop()
    category = get_default_category()
    category.shops.add(shop)
    product_ids = []
    for x in range(0, 15):
        product = create_product("%s" % x, shop=shop)
        product_ids.append(product.id)

    for x in range(0, 5):
        product = create_product("parent_%s" % x, shop=shop, mode=ProductMode.SIMPLE_VARIATION_PARENT)
        for y in range(0, 3):
            child_product = create_product("child_%s_%s" % (x, y), shop=shop)
            child_product.link_to_parent(product)
        product_ids.append(product.id)

    assert (category.shop_products.count() == 0)
    data = {
        "additional_products": ["%s" % product_id for product_id in product_ids]
    }
    form = CategoryProductForm(shop=shop, category=category, data=data)
    form.full_clean()
    form.save()

    category.refresh_from_db()
    assert (category.shop_products.count() == 35)  # 15 normal products and 5 parents with 3 children each
Example #23
0
def test_mass_edit_orders(rf, admin_user):
    shop = get_default_shop()
    supplier = get_default_supplier()
    contact1 = create_random_person()
    product1 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="50")
    product2 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="501")

    order = create_random_order(customer=contact1,
                                products=[product1, product2],
                                completion_probability=0)

    assert order.status.role != OrderStatusRole.CANCELED
    payload = {
        "action": CancelOrderAction().identifier,
        "values": [order.pk]
    }
    request = apply_request_middleware(rf.post(
        "/",
        user=admin_user,
    ))
    request._body = json.dumps(payload).encode("UTF-8")
    view = OrderListView.as_view()
    response = view(request=request)
    assert response.status_code == 200
    for order in Order.objects.all():
        assert order.status.role == OrderStatusRole.CANCELED
Example #24
0
def test_multivariable_variation():
    parent = create_product("SuperComplexVarParent")
    color_var = ProductVariationVariable.objects.create(product=parent, identifier="color")
    size_var = ProductVariationVariable.objects.create(product=parent, identifier="size")

    for color in ("yellow", "blue", "brown"):
        ProductVariationVariableValue.objects.create(variable=color_var, identifier=color)

    for size in ("small", "medium", "large", "huge"):
        ProductVariationVariableValue.objects.create(variable=size_var, identifier=size)

    combinations = list(parent.get_all_available_combinations())
    assert len(combinations) == (3 * 4)
    for combo in combinations:
        assert not combo["result_product_pk"]
        # Elide a combination (yellow/small) for testing:
        if combo["variable_to_value"][color_var].identifier == "yellow" and combo["variable_to_value"][size_var].identifier == "small":
            continue
        child = create_product("xyz-%s" % combo["sku_part"])
        child.link_to_parent(parent, combo["variable_to_value"])
    assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT

    # Elided product should not yield a result
    yellow_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="yellow")
    small_size_value = ProductVariationVariableValue.objects.get(variable=size_var, identifier="small")
    assert not ProductVariationResult.resolve(parent, {color_var: yellow_color_value, size_var: small_size_value})
    # Anything else should
    brown_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="brown")
    result1 = ProductVariationResult.resolve(parent, {color_var: brown_color_value, size_var: small_size_value})
    result2 = ProductVariationResult.resolve(parent, {color_var.pk: brown_color_value.pk, size_var.pk: small_size_value.pk})
    assert result1 and result2
    assert result1.pk == result2.pk

    assert len(parent.get_available_variation_results()) == (3 * 4 - 1)
def test_price_infos(rf):
    request, shop, group = initialize_test(rf, True)
    price = shop.create_price

    product_one = create_product("Product_1", shop, default_price=150)
    product_two = create_product("Product_2", shop, default_price=250)

    spp = CgpPrice(product=product_one, shop=shop, group=group, price_value=100)
    spp.save()

    spp = CgpPrice(product=product_two, shop=shop, group=group, price_value=200)
    spp.save()

    product_ids = [product_one.pk, product_two.pk]

    spm = get_pricing_module()
    assert isinstance(spm, CustomerGroupPricingModule)
    pricing_context = spm.get_context_from_request(request)
    price_infos = spm.get_price_infos(pricing_context, product_ids)

    assert len(price_infos) == 2
    assert product_one.pk in price_infos
    assert product_two.pk in price_infos

    assert price_infos[product_one.pk].price == price(100)
    assert price_infos[product_two.pk].price == price(200)

    assert price_infos[product_one.pk].base_price == price(100)
    assert price_infos[product_two.pk].base_price == price(200)
Example #26
0
def test_product_hightlight_plugin(rf, highlight_type, orderable):
    shop = get_default_shop()
    p1 = create_product("p1", shop, get_default_supplier(), "10")
    p2 = create_product("p2", shop, get_default_supplier(), "20")
    p3 = create_product("p3", shop, get_default_supplier(), "30")
    p4 = create_product("p4", shop, get_default_supplier(), "40")

    sp4 = p4.get_shop_instance(shop)
    sp4.purchasable = False
    sp4.save()

    context = get_context(rf)
    plugin = ProductHighlightPlugin({
        "type": highlight_type,
        "count": 4,
        "orderable_only": orderable
    })
    context_products = plugin.get_context_data(context)["products"]

    assert p1 in context_products
    assert p2 in context_products
    assert p3 in context_products
    if orderable:
        assert p4 not in context_products
    else:
        assert p4 in context_products
Example #27
0
def test_limited_methods():
    """
    Test that products can declare that they limit available shipping methods.
    """
    unique_shipping_method = get_shipping_method(name="unique", price=0)
    shop = get_default_shop()
    common_product = create_product(sku="SH_COMMON", shop=shop)  # A product that does not limit shipping methods
    unique_product = create_product(sku="SH_UNIQUE", shop=shop)  # A product that only supports unique_shipping_method
    unique_shop_product = unique_product.get_shop_instance(shop)
    unique_shop_product.limit_shipping_methods = True
    unique_shop_product.shipping_methods.add(unique_shipping_method)
    unique_shop_product.save()
    impossible_product = create_product(sku="SH_IMP", shop=shop)  # A product that can't be shipped at all
    imp_shop_product = impossible_product.get_shop_instance(shop)
    imp_shop_product.limit_shipping_methods = True
    imp_shop_product.save()
    for product_ids, method_ids in [
        ((common_product.pk, unique_product.pk), (unique_shipping_method.pk,)),
        ((common_product.pk,), ShippingMethod.objects.values_list("pk", flat=True)),
        ((unique_product.pk,), (unique_shipping_method.pk,)),
        ((unique_product.pk, impossible_product.pk,), ()),
        ((common_product.pk, impossible_product.pk,), ()),
    ]:
        product_ids = set(product_ids)
        assert ShippingMethod.objects.available_ids(shop=shop, products=product_ids) == set(method_ids)
Example #28
0
def test_variable_variation():
    parent = create_product("ComplexVarParent")
    sizes_and_children = [("%sL" % ("X" * x), create_product("ComplexVarChild-%d" % x)) for x in range(4)]
    for size, child in sizes_and_children:
        child.link_to_parent(parent, variables={"size": size})
    assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT
    assert all(child.is_variation_child() for (size, child) in sizes_and_children)

    # Validation tests

    dummy = create_product("InvalidComplexVarChild")

    with pytest.raises(ValueError):
        dummy.link_to_parent(parent)

    with pytest.raises(ValueError):
        parent.link_to_parent(dummy)

    with pytest.raises(ValueError):
        dummy.link_to_parent(sizes_and_children[0][1])

    # Variable tests

    size_attr = parent.variation_variables.get(identifier="size")

    for size, child in sizes_and_children:
        size_val = size_attr.values.get(identifier=size)
        result_product = ProductVariationResult.resolve(parent, {size_attr: size_val})
        assert result_product == child
Example #29
0
def test_protected_fields():
    activate("en")
    shop = Shop.objects.create(
        name="testshop",
        identifier="testshop",
        status=ShopStatus.ENABLED,
        public_name="test shop",
        domain="derp",
        currency="EUR"
    )
    get_currency("EUR")
    get_currency("USD")
    assert shop.name == "testshop"
    assert shop.currency == "EUR"
    assert not ConfigurationItem.objects.filter(shop=shop, key="languages").exists()
    shop_form = ShopBaseForm(instance=shop, languages=settings.LANGUAGES)
    assert not shop_form._get_protected_fields()  # No protected fields just yet, right?
    data = get_form_data(shop_form, prepared=True)
    shop_form = ShopBaseForm(data=data, instance=shop, languages=settings.LANGUAGES)
    _test_cleanliness(shop_form)
    shop_form.save()

    # Now let's make it protected!
    create_product(printable_gibberish(), shop=shop, supplier=get_default_supplier())
    order = create_random_order(customer=create_random_person(), shop=shop)
    assert order.shop == shop

    # And try again...
    data["currency"] = "USD"
    shop_form = ShopBaseForm(data=data, instance=shop, languages=settings.LANGUAGES)
    assert shop_form._get_protected_fields()  # So protected!
    _test_cleanliness(shop_form)
    shop = shop_form.save()
    assert shop.currency == "EUR"  # But the shop form ignored the change . . .
Example #30
0
def test_get_orderable_variation_children(rf):
    supplier = get_default_supplier()
    shop = get_default_shop()

    variable_name = "Color"
    parent = create_product("test-sku-1", shop=shop)
    variation_variable = ProductVariationVariable.objects.create(product=parent, identifier="color", name=variable_name)
    red_value = ProductVariationVariableValue.objects.create(variable=variation_variable, identifier="red", value="Red")
    blue_value =ProductVariationVariableValue.objects.create(variable=variation_variable, identifier="blue", value="Blue")
    combinations = list(parent.get_all_available_combinations())
    assert len(combinations) == 2
    for combo in combinations:
        assert not combo["result_product_pk"]
        child = create_product("xyz-%s" % combo["sku_part"], shop=shop, supplier=get_default_supplier(), default_price=20)
        child.link_to_parent(parent, combination_hash=combo["hash"])

    combinations = list(parent.get_all_available_combinations())
    assert len(combinations) == 2
    parent.refresh_from_db()
    assert parent.is_variation_parent()
    request = apply_request_middleware(rf.get("/"))

    cache.clear()
    for time in range(2):
        orderable_children, is_orderable = get_orderable_variation_children(parent, request, None)
        assert len(orderable_children)
        for var_variable, var_values in dict(orderable_children).items():
            assert var_variable == variation_variable
            assert red_value in var_values
            assert blue_value in var_values
Example #31
0
def test_bump_caches_signal(rf):
    """
    Test that prices are bumped when discount objects changes
    """
    initial_price = 10
    discounted_price = 5

    shop1 = factories.get_default_shop()
    shop2 = factories.get_shop(identifier="shop2", domain="shop2")

    product1 = factories.create_product(
        "product",
        shop=shop1,
        supplier=factories.get_default_supplier(),
        default_price=initial_price)

    product2 = factories.create_product(
        "product2",
        shop=shop2,
        supplier=factories.get_default_supplier(),
        default_price=20)

    now = datetime(2018, 1, 1, 9, 0, tzinfo=pytz.UTC)  # 01/01/2018 09:00 AM

    with patch("django.utils.timezone.now", new=lambda: now):
        discount = Discount.objects.create(
            name="discount",
            active=True,
            start_datetime=now - timedelta(days=10),
            end_datetime=now + timedelta(days=10),
            discounted_price_value=discounted_price,
            shop=shop1,
        )

        request = apply_request_middleware(rf.get("/"))
        request_shop2 = apply_request_middleware(
            rf.get("/", HTTP_HOST=shop2.domain))

        def assert_cache_product1(discounted=False):
            cache_price_info(request, product1, 1,
                             product1.get_price_info(request))
            if discounted:
                assert get_cached_price_info(
                    request, product1,
                    1).price == shop1.create_price(discounted_price)
            else:
                assert get_cached_price_info(
                    request, product1,
                    1).price == shop1.create_price(initial_price)

        def assert_product1_is_not_cached():
            assert get_cached_price_info(request, product1) is None

        def assert_product2_is_cached():
            assert get_cached_price_info(request_shop2, product2) is not None

        assert_product1_is_not_cached()
        assert_cache_product1(True)

        # cache bumped - the cache should be dropped - then, cache again
        discount.save()
        assert_product1_is_not_cached()
        assert_cache_product1(True)

        # cache product 2.. from now on, shop2 cache should never be bumped
        cache_price_info(request_shop2, product2, 1,
                         product2.get_price_info(request_shop2))
        assert_product2_is_cached()

        discount.product = product1
        discount.save()

        assert_product1_is_not_cached()
        assert_cache_product1(True)
        assert_product2_is_cached()

        happy_hour = HappyHour.objects.create(name="hh 1", shop=shop1)
        happy_hour.discounts.add(discount)
        assert_product1_is_not_cached()
        assert_cache_product1(True)
        assert_product2_is_cached()

        happy_hour.save()
        assert_product1_is_not_cached()
        assert_cache_product1(True)
        assert_product2_is_cached()

        time_range = TimeRange.objects.create(
            happy_hour=happy_hour,
            from_hour=(now - timedelta(hours=1)).time(),
            to_hour=(now + timedelta(hours=1)).time(),
            weekday=now.weekday(),
        )
        assert_product1_is_not_cached()
        assert_cache_product1(True)
        assert_product2_is_cached()

        time_range.save()
        assert_product1_is_not_cached()
        assert_cache_product1(True)
        assert_product2_is_cached()

        time_range.delete()
        assert_product1_is_not_cached()
        assert_cache_product1(True)
        assert_product2_is_cached()

        with pytest.raises(DiscountM2MChangeError):
            handle_generic_m2m_changed("test", time_range)
Example #32
0
def test_many_price_info_cache_bump(rf):
    initial_price = 10
    shop = factories.get_default_shop()
    tax = factories.get_default_tax()
    tax_class = factories.get_default_tax_class()
    product = factories.create_product(
        "product",
        shop=shop,
        supplier=factories.get_default_supplier(),
        default_price=initial_price)
    child1 = factories.create_product(
        "child1",
        shop=shop,
        supplier=factories.get_default_supplier(),
        default_price=5)
    child2 = factories.create_product(
        "child2",
        shop=shop,
        supplier=factories.get_default_supplier(),
        default_price=9)
    child1.link_to_parent(product, variables={"color": "red"})
    child2.link_to_parent(product, variables={"color": "blue"})

    request = apply_request_middleware(rf.get("/"))
    child1_pi = child1.get_price_info(request)
    child2_pi = child1.get_price_info(request)

    def assert_cache_products():
        cache_many_price_info(request, product, 1, [child1_pi, child2_pi])
        assert get_many_cached_price_info(request, product,
                                          1)[0].price == child1_pi.price
        assert get_many_cached_price_info(request, product,
                                          1)[1].price == child2_pi.price

    def assert_nothing_is_cached():
        # nothing is cached
        assert get_many_cached_price_info(request, product, 1) is None

    # cache the item
    assert_nothing_is_cached()
    assert_cache_products()

    # cache bumped - the cache should be dropped - then, cache again
    tax.save()
    assert_nothing_is_cached()
    assert_cache_products()

    # cache bumped - the cache should be dropped - then, cache again
    tax_class.save()
    assert_nothing_is_cached()
    assert_cache_products()

    # cache bumped - the cache should be dropped - then, cache again
    product.save()
    assert_nothing_is_cached()
    assert_cache_products()

    shop_product = product.get_shop_instance(shop)

    # cache bumped - the cache should be dropped - then, cache again
    shop_product.save()
    assert_nothing_is_cached()
    assert_cache_products()

    category = factories.get_default_category()

    # cache bumped - the cache should be dropped - then, cache again
    shop_product.categories.add(category)
    assert_nothing_is_cached()
    assert_cache_products()

    # cache bumped - the cache should be dropped - then, cache again
    supplier = shop_product.suppliers.first()
    supplier.enabled = False
    supplier.save()
    assert_nothing_is_cached()
    assert_cache_products()
def test_best_selling_products_with_multiple_orders():
    context = get_jinja_context()
    supplier = get_default_supplier()
    shop = get_default_shop()
    n_products = 2
    price = 10

    product_1 = create_product("test-sku-1", supplier=supplier, shop=shop)
    product_2 = create_product("test-sku-2", supplier=supplier, shop=shop)
    create_order_with_product(product_1,
                              supplier,
                              quantity=1,
                              taxless_base_unit_price=price,
                              shop=shop)
    create_order_with_product(product_2,
                              supplier,
                              quantity=1,
                              taxless_base_unit_price=price,
                              shop=shop)
    cache.clear()
    # Two initial products sold
    assert product_1 in general.get_best_selling_products(
        context, n_products=n_products)
    assert product_2 in general.get_best_selling_products(
        context, n_products=n_products)

    product_3 = create_product("test-sku-3", supplier=supplier, shop=shop)
    create_order_with_product(product_3,
                              supplier,
                              quantity=2,
                              taxless_base_unit_price=price,
                              shop=shop)
    cache.clear()
    # Third product sold in greater quantity
    assert product_3 in general.get_best_selling_products(
        context, n_products=n_products)

    create_order_with_product(product_1,
                              supplier,
                              quantity=4,
                              taxless_base_unit_price=price,
                              shop=shop)
    create_order_with_product(product_2,
                              supplier,
                              quantity=4,
                              taxless_base_unit_price=price,
                              shop=shop)
    cache.clear()
    # Third product outsold by first two products
    assert product_3 not in general.get_best_selling_products(
        context, n_products=n_products)

    children = [
        create_product("SimpleVarChild-%d" % x, supplier=supplier, shop=shop)
        for x in range(5)
    ]
    for child in children:
        child.link_to_parent(product_3)
        create_order_with_product(child,
                                  supplier,
                                  quantity=1,
                                  taxless_base_unit_price=price,
                                  shop=shop)
    cache.clear()
    # Third product now sold in greatest quantity
    assert product_3 == general.get_best_selling_products(
        context, n_products=n_products)[0]
Example #34
0
def test_basket_partial_quantity_update_all_product_counts():
    shop = get_default_shop()
    supplier = get_default_supplier()
    request = get_request_with_basket()
    basket = request.basket

    pieces = SalesUnit.objects.create(identifier="pieces",
                                      decimals=0,
                                      name="Pieces",
                                      symbol='pc.')
    kilograms = SalesUnit.objects.create(identifier="kilograms",
                                         decimals=3,
                                         name="Kilograms",
                                         symbol='kg')
    cup = create_product(sku="COFFEE-CUP-123",
                         sales_unit=pieces,
                         shop=shop,
                         supplier=supplier)
    beans = create_product(sku="COFFEEBEANS3",
                           sales_unit=kilograms,
                           shop=shop,
                           supplier=supplier)
    beans_shop_product = beans.get_shop_instance(shop)
    beans_shop_product.minimum_purchase_quantity = Decimal('0.1')
    beans_shop_product.save()
    pears = create_product(sku="PEARS-27",
                           sales_unit=kilograms,
                           shop=shop,
                           supplier=supplier)

    add = basket_commands.handle_add
    update = basket_commands.handle_update

    # Empty basket
    assert basket.product_count == 0
    assert basket.smart_product_count == 0
    assert basket.product_line_count == 0

    # 1 cup
    add(request, basket, product_id=cup.pk, quantity=1)
    assert basket.product_count == 1
    assert basket.smart_product_count == 1
    assert basket.product_line_count == 1

    # Basket update operations work by prefixing line id with operation
    qty_update_cup = 'q_' + basket.get_lines()[0].line_id
    delete_cup = 'delete_' + basket.get_lines()[0].line_id

    # 3 cups
    update(request, basket, **{qty_update_cup: "3"})
    assert basket.product_count == 3
    assert basket.smart_product_count == 3
    assert basket.product_line_count == 1

    # 3 cups + 0.5 kg beans
    add(request, basket, product_id=beans.pk, quantity='0.5')
    assert basket.product_count == Decimal('3.5')
    assert basket.smart_product_count == 4
    assert basket.product_line_count == 2

    qty_update_beans = 'q_' + basket.get_lines()[1].line_id
    delete_beans1 = 'delete_' + basket.get_lines()[1].line_id

    # 1 cup + 2.520 kg beans
    update(request, basket, **{qty_update_cup: "1.0"})
    update(request, basket, **{qty_update_beans: "2.520"})
    assert basket.product_count == Decimal('3.520')
    assert basket.smart_product_count == 2
    assert basket.product_line_count == 2

    # 42 cups + 2.520 kg beans
    update(request, basket, **{qty_update_cup: "42"})
    assert basket.product_count == Decimal('44.520')
    assert basket.smart_product_count == 43
    assert basket.product_line_count == 2

    # 42 cups + 2.520 kg beans + 3.5 kg pears
    add(request, basket, product_id=pears.pk, quantity='3.5')
    assert basket.product_count == Decimal('48.020')
    assert basket.smart_product_count == 44
    assert basket.product_line_count == 3

    # 42 cups + 3.5 kg pears
    update(request, basket, **{delete_beans1: "1"})
    assert basket.product_count == Decimal('45.5')
    assert basket.smart_product_count == 43
    assert basket.product_line_count == 2

    # 3.5 kg pears
    update(request, basket, **{delete_cup: "1"})
    assert basket.product_count == Decimal('3.5')
    assert basket.smart_product_count == 1
    assert basket.product_line_count == 1
Example #35
0
def test_refunds_for_discounted_order_lines():
    shop = get_default_shop()
    supplier = get_default_supplier()
    product = create_product("test-sku",
                             shop=get_default_shop(),
                             default_price=10,
                             stock_behavior=StockBehavior.STOCKED)

    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    discount_line = OrderLine(order_id=order.id,
                              type=OrderLineType.DISCOUNT,
                              quantity=1,
                              discount_amount_value=Decimal("0.54321"))
    discount_line.save()
    order.lines.add(discount_line)

    product_line = order.lines.filter(type=OrderLineType.PRODUCT).first()
    product_line.discount_amount = TaxfulPrice(100, order.currency)
    product_line.save()
    taxful_price_with_discount = product_line.taxful_price
    order.cache_prices()
    order.save()

    assert product_line.base_price == TaxfulPrice(400, order.currency)
    assert taxful_price_with_discount == TaxfulPrice(300, order.currency)

    # try to refund only the product line - should fail since this would result in a negative total
    with pytest.raises(RefundExceedsAmountException):
        order.create_refund([{
            "line": product_line,
            "quantity": 2,
            "amount": taxful_price_with_discount.amount
        }])

    # try to refund the product line with a negative amount
    with pytest.raises(InvalidRefundAmountException):
        order.create_refund([{
            "line": product_line,
            "quantity": 1,
            "amount": -taxful_price_with_discount.amount
        }])
    # try to refund the discount line with a positive amount
    with pytest.raises(InvalidRefundAmountException):
        order.create_refund([{
            "line": discount_line,
            "quantity": 1,
            "amount": -discount_line.taxful_price.amount
        }])

    order.create_refund([{
        "line": discount_line,
        "quantity": 1,
        "amount": discount_line.taxful_price.amount
    }, {
        "line": product_line,
        "quantity": 2,
        "amount": taxful_price_with_discount.amount
    }])
    assert product_line.max_refundable_amount.value == 0
    assert discount_line.max_refundable_amount.value == 0
    assert order.taxful_total_price.value == 0

    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    discount_line = OrderLine(order_id=order.id,
                              type=OrderLineType.DISCOUNT,
                              quantity=1,
                              discount_amount_value=Decimal("0.54321"))
    discount_line.save()
    order.lines.add(discount_line)
    product_line = order.lines.filter(type=OrderLineType.PRODUCT).first()
    product_line.discount_amount = TaxfulPrice(100, order.currency)
    product_line.save()
    order.cache_prices()
    order.save()

    order.create_full_refund(restock_products=False)
    assert order.taxful_total_price.value == 0
Example #36
0
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_get_best_selling_products(admin_user):
    shop1 = get_default_shop()
    shop2 = get_shop(True)
    person1 = create_random_person()
    person1.user = admin_user
    person1.save()

    supplier = create_simple_supplier("supplier1")
    client = _get_client(admin_user)

    # list best selling products
    response = client.get("/api/shuup/front/shop_products/best_selling/", {
        "shop": shop2.pk,
        "limit": 20
    })
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products["results"]) == 0

    # THIS IS IMPORTANT!
    cache.clear()

    products = [
        create_product("Standard-%d" % x, supplier=supplier, shop=shop2)
        for x in range(10)
    ]

    # create 1 product with 4 variations
    parent_product = create_product("ParentProduct1",
                                    supplier=supplier,
                                    shop=shop2)
    children = [
        create_product("SimpleVarChild-%d" % x, supplier=supplier, shop=shop2)
        for x in range(4)
    ]
    for child in children:
        child.link_to_parent(parent_product)

    best_selling = defaultdict(int)

    # create orders with standard products
    for p_index in range(len(products)):
        order = create_empty_order(shop=shop2)
        order.save()
        qty = (len(products) - p_index)
        add_product_to_order(order, supplier, products[p_index], qty,
                             Decimal(1.0))
        order.create_shipment_of_all_products()
        order.status = OrderStatus.objects.get_default_complete()
        order.save(update_fields=("status", ))

        best_selling[products[p_index].id] = qty

    # create orders with variation products - the parent product is counted instead of its children
    for p_index in range(2):
        variation = random.choice(children)
        qty = 5
        order = create_empty_order(shop=shop2)
        order.save()
        add_product_to_order(order, supplier, variation, qty, Decimal(1.0))
        order.create_shipment_of_all_products()
        order.status = OrderStatus.objects.get_default_complete()
        order.save(update_fields=("status", ))
        best_selling[parent_product.id] = best_selling[parent_product.id] + qty

    # get the top 100 best selling products
    response = client.get("/api/shuup/front/shop_products/best_selling/", {
        "shop": shop2.pk,
        "limit": 100
    })
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products["results"]) == len(
        best_selling)  # as we added less then 100, this must be true
    assert products["next"] is None

    # check the if all IDS are part of best selling
    for ix in range(len(products)):
        assert products["results"][ix]["product_id"] in best_selling.keys()

    # get the top 5 best selling products (we should get paginated results)
    response = client.get("/api/shuup/front/shop_products/best_selling/", {
        "shop": shop2.pk,
        "limit": 5
    })
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products["results"]) == 5
    assert products["count"] == len(best_selling)
    assert products["next"] is not None
    sorted_best_selling_ids = [
        prod[0]
        for prod in sorted(best_selling.items(), key=lambda prod: -prod[1])
    ][:5]

    # check the if all the 5 best sellers are part of best selling
    for ix in range(len(products)):
        assert products["results"][ix]["product_id"] in sorted_best_selling_ids
Example #38
0
def test_basket_campaign_case2(rf):
    request, shop, group = initialize_test(rf, False)
    price = shop.create_price

    basket = get_basket(request)
    supplier = get_default_supplier()
    # create a basket rule that requires at least value of 200
    rule = BasketTotalAmountCondition.objects.create(value="200")

    single_product_price = "50"
    discount_amount_value = "10"

    unique_shipping_method = get_shipping_method(shop, price=50)

    for x in range(3):
        product = create_product(printable_gibberish(),
                                 shop=shop,
                                 supplier=supplier,
                                 default_price=single_product_price)
        basket.add_product(supplier=supplier,
                           shop=shop,
                           product=product,
                           quantity=1)

    assert basket.product_count == 3

    campaign = BasketCampaign.objects.create(shop=shop,
                                             public_name="test",
                                             name="test",
                                             active=True)
    campaign.conditions.add(rule)
    campaign.save()

    BasketDiscountAmount.objects.create(discount_amount=discount_amount_value,
                                        campaign=campaign)

    assert len(basket.get_final_lines()) == 3
    assert basket.total_price == price(
        single_product_price) * basket.product_count

    # check that shipping method affects campaign
    basket.shipping_method = unique_shipping_method
    basket.save()
    basket.uncache()
    assert len(basket.get_final_lines()
               ) == 4  # Shipping should not affect the rule being triggered

    line_types = [l.type for l in basket.get_final_lines()]
    assert OrderLineType.DISCOUNT not in line_types

    product = create_product(printable_gibberish(),
                             shop=shop,
                             supplier=supplier,
                             default_price=single_product_price)
    basket.add_product(supplier=supplier,
                       shop=shop,
                       product=product,
                       quantity=1)

    assert len(basket.get_final_lines()) == 6  # Discount included
    assert OrderLineType.DISCOUNT in [l.type for l in basket.get_final_lines()]
Example #39
0
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 test_product_media_api(admin_user):
    shop = get_default_shop()
    client = _get_client(admin_user)
    product = create_product("product", shop=shop)

    # create 2 images for product, 1 with contents, other with external url
    image = get_random_filer_image()
    media = ProductMedia.objects.create(
        product=product, kind=ProductMediaKind.IMAGE, file=image, enabled=True, public=True
    )
    media_external = ProductMedia.objects.create(
        product=product,
        kind=ProductMediaKind.IMAGE,
        external_url="http://www.myimage.com/img.gif",
        enabled=True,
        public=True,
    )
    product.primary_image = media
    product.save()

    # get product media
    response = client.get("/api/shuup/product_media/%d/" % media.pk)
    media_data = json.loads(response.content.decode("utf-8"))
    assert media_data["kind"] == media.kind.value
    assert media_data["product"] == product.pk
    assert media_data["public"] == media.public
    assert media_data["id"] == media.pk

    # get external media
    response = client.get("/api/shuup/product_media/%d/" % media_external.pk)
    media_data = json.loads(response.content.decode("utf-8"))
    assert media_data["kind"] == media_external.kind.value
    assert media_data["product"] == product.pk
    assert media_data["public"] == media_external.public
    assert media_data["id"] == media_external.pk
    assert media_data["external_url"] == media_external.external_url

    # update product media
    image = get_random_filer_image()
    with open(image.path, "rb") as f:
        img_base64 = base64.b64encode(os.urandom(50)).decode()
        uri = "data:application/octet-stream;base64,{}".format(img_base64)

    media_data = {
        "translations": {"en": {"title": "title 2", "description": "desc2"}},
        "shops": [shop.pk],
        "kind": ProductMediaKind.IMAGE.value,
        "file": uri,
        "path": "/what/the/zzzz",
    }
    response = client.put(
        "/api/shuup/product_media/%d/" % media.pk, content_type="application/json", data=json.dumps(media_data)
    )
    assert response.status_code == status.HTTP_200_OK
    media.refresh_from_db()
    assert media.title == media_data["translations"]["en"]["title"]
    assert media.description == media_data["translations"]["en"]["description"]
    assert media.shops.count() == len(media_data["shops"])
    assert set(media.shops.all().values_list("pk", flat=True)) >= set(media_data["shops"])
    assert media.kind.value == media_data["kind"]
    assert media.file.folder.pretty_logical_path == media_data["path"]
    with open(media.file.path, "rb") as f:
        assert img_base64 == base64.b64encode(f.read()).decode()

    media_count = product.media.count()
    # deletes an image
    response = client.delete("/api/shuup/product_media/%d/" % media.pk)
    assert response.status_code == status.HTTP_204_NO_CONTENT
    assert media_count - 1 == product.media.count()
Example #41
0
def test_multiple_campaigns_match_with_coupon(rf):
    request, shop, group = initialize_test(rf, False)
    price = shop.create_price

    basket = get_basket(request)
    supplier = get_default_supplier()
    # create a basket rule that requires atleast value of 200
    rule = BasketTotalAmountCondition.objects.create(value="200")

    product_price = "200"

    discount1 = "10"
    discount2 = "20"
    product = create_product(printable_gibberish(),
                             shop=shop,
                             supplier=supplier,
                             default_price=product_price)
    basket.add_product(supplier=supplier,
                       shop=shop,
                       product=product,
                       quantity=1)
    basket.shipping_method = get_shipping_method(shop=shop)

    campaign = BasketCampaign.objects.create(shop=shop,
                                             public_name="test",
                                             name="test",
                                             active=True)
    campaign.conditions.add(rule)
    campaign.save()

    BasketDiscountAmount.objects.create(discount_amount=discount1,
                                        campaign=campaign)

    dc = Coupon.objects.create(code="TEST", active=True)
    campaign2 = BasketCampaign.objects.create(shop=shop,
                                              public_name="test",
                                              name="test",
                                              coupon=dc,
                                              active=True)

    BasketDiscountAmount.objects.create(discount_amount=discount2,
                                        campaign=campaign2)
    basket.add_product(supplier=supplier,
                       shop=shop,
                       product=product,
                       quantity=1)

    resp = handle_add_campaign_code(request, basket, dc.code)
    assert resp.get("ok")

    discount_lines_values = [
        line.discount_amount for line in basket.get_final_lines()
        if line.type == OrderLineType.DISCOUNT
    ]
    assert price(discount1) in discount_lines_values
    assert price(discount2) in discount_lines_values
    assert basket.total_price == (price(product_price) * basket.product_count -
                                  price(discount1) - price(discount2))

    assert basket.codes == [dc.code]

    # test code removal
    resp = handle_remove_campaign_code(request, basket, dc.code)
    assert resp.get("ok")

    assert basket.codes == []
    discount_lines_values = [
        line.discount_amount for line in basket.get_final_lines()
        if line.type == OrderLineType.DISCOUNT
    ]
    assert price(discount1) in discount_lines_values
    assert not price(discount2) in discount_lines_values
Example #42
0
def create_orderable_product(name, sku, price):
    supplier = get_default_supplier()
    shop = get_default_shop()
    product = create_product(sku=sku, shop=shop, supplier=supplier, default_price=price, name=name)
    return product
Example #43
0
def _create_product_for_day(shop, day):
    product = create_product("test_product")
    product.created_on = day
    product.save()
Example #44
0
def get_frontend_order_state(contact,
                             payment_method,
                             product_price,
                             valid_lines=True):
    """
    Get a dict structure mirroring what the frontend JavaScript would submit.
    :type contact: Contact|None
    """
    activate("en")
    shop = get_default_shop()
    tax = Tax.objects.create(code="test_code",
                             rate=decimal.Decimal("0.20"),
                             name="Default")
    tax_class = TaxClass.objects.create(identifier="test_tax_class",
                                        name="Default")
    rule = TaxRule.objects.create(tax=tax)
    rule.tax_classes.add(tax_class)
    rule.save()
    supplier = get_default_supplier()
    product = create_product(sku=printable_gibberish(),
                             supplier=supplier,
                             shop=shop)
    product.tax_class = tax_class
    product.save()
    if valid_lines:
        lines = [
            {
                "id": "x",
                "type": "product",
                "product": {
                    "id": product.id
                },
                "quantity": "1",
                "baseUnitPrice": product_price,
                'supplier': {
                    'name': supplier.name,
                    'id': supplier.id
                }
            },
            {
                "id": "z",
                "type": "text",
                "text": "This was an order!",
                "quantity": 0
            },
        ]
    else:
        unshopped_product = create_product(sku=printable_gibberish(),
                                           supplier=supplier)
        not_visible_product = create_product(sku=printable_gibberish(),
                                             supplier=supplier,
                                             shop=shop)
        not_visible_shop_product = not_visible_product.get_shop_instance(shop)
        not_visible_shop_product.visibility = ShopProductVisibility.NOT_VISIBLE
        not_visible_shop_product.save()
        lines = [
            {
                "id": "x",
                "type": "product",
                'supplier': {
                    'name': supplier.name,
                    'id': supplier.id
                }
            },  # no product?
            {
                "id": "x",
                "type": "product",
                "product": {
                    "id": unshopped_product.id
                },
                'supplier': {
                    'name': supplier.name,
                    'id': supplier.id
                }
            },  # not in this shop?
            {
                "id": "y",
                "type": "product",
                "product": {
                    "id": -product.id
                },
                'supplier': {
                    'name': supplier.name,
                    'id': supplier.id
                }
            },  # invalid product?
            {
                "id": "z",
                "type": "other",
                "quantity": 1,
                "unitPrice": "q",
                'supplier': {
                    'name': supplier.name,
                    'id': supplier.id
                }
            },  # what's that price?
            {
                "id": "rr",
                "type": "product",
                "quantity": 1,
                "product": {
                    "id": not_visible_product.id
                },
                'supplier': {
                    'name': supplier.name,
                    'id': supplier.id
                }
            },  # not visible
            {
                "id": "y",
                "type": "product",
                "product": {
                    "id": product.id
                }
            }  # no supplier
        ]

    state = {
        "customer": {
            "id": contact.id if contact else None,
            "billingAddress":
            encode_address(contact.default_billing_address) if contact else {},
            "shippingAddress": encode_address(contact.default_shipping_address)
            if contact else {},
        },
        "lines": lines,
        "methods": {
            "shippingMethod": {
                "id": get_default_shipping_method().id
            },
            "paymentMethod": {
                "id": payment_method.id
            },
        },
        "shop": {
            "selected": {
                "id": shop.id,
                "name": shop.name,
                "currency": shop.currency,
                "priceIncludeTaxes": shop.prices_include_tax
            }
        }
    }
    return state
Example #45
0
def _create_total_sales(shop, day):
    product = create_product("test", shop=shop)
    supplier = get_default_supplier()
    order = create_order_with_product(product, supplier, 1, 10, shop=shop)
    order.order_date = day
    order.save()
Example #46
0
def test_catalog_campaign_sync():
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    default_price = 100
    product1 = factories.create_product("test1",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=default_price)
    product2 = factories.create_product("test2",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=default_price)
    product3 = factories.create_product("test3",
                                        shop=shop,
                                        supplier=supplier,
                                        default_price=default_price)
    category = factories.get_default_category()
    shop_product = product1.get_shop_instance(shop)
    shop_product.primary_category = category
    shop_product.save()
    shop_product.categories.add(category)

    contact1 = factories.create_random_person()

    contact2 = factories.create_random_person()
    contact_group = factories.get_default_customer_group()
    contact2.groups.add(contact_group)

    happy_hour1_weekdays = "0,1"  # Mon, Tue
    happy_hour1_start = datetime.time(21)
    happy_hour1_end = datetime.time(3)
    happy_hour1_condition = HourCondition.objects.create(
        days=happy_hour1_weekdays,
        hour_start=happy_hour1_start,
        hour_end=happy_hour1_end)

    happy_hour2_weekdays = "2,6"  # Wed, Sun
    happy_hour2_start = datetime.time(14)
    happy_hour2_end = datetime.time(16)
    happy_hour2_condition = HourCondition.objects.create(
        days=happy_hour2_weekdays,
        hour_start=happy_hour2_start,
        hour_end=happy_hour2_end)

    discount_amount_value = 50
    discount_percentage = decimal.Decimal("0.35")
    _create_catalog_campaign_for_products(shop, [product1],
                                          discount_amount_value,
                                          happy_hour1_condition)
    _create_catalog_campaign_for_products(shop, [product2, product3],
                                          discount_amount_value)
    _create_catalog_campaign_for_category(shop, category, discount_percentage)
    _create_catalog_campaign_for_contact(shop, product1, contact1,
                                         discount_amount_value)
    _create_catalog_campaign_for_contact_group(shop,
                                               [product1, product2, product3],
                                               contact_group,
                                               discount_percentage,
                                               happy_hour2_condition)

    call_command("import_catalog_campaigns", *[], **{})

    # From first campaign we should get 1 discount with happy hour
    # From second campaign we should get 2 discounts
    # From third campaign we should get 1 discount
    # From fourth campaign we should get also 1 discount
    # From last campaign we should get 3 discounts with happy hour
    assert Discount.objects.count() == 8

    # There should be 2 happy hours in total
    assert HappyHour.objects.count() == 2

    # From first happy hour there should be 4 ranges
    # Mon 21-23, Tue 0-3, Tue 21-23, Wed 0-3
    # From second happy hour there should be 2 ranges
    # Wed 14-16 and Sun 14-16
    assert TimeRange.objects.count() == 6

    # Let's go through all our 8 discounts to make sure all is good
    first_discount = Discount.objects.filter(
        product=product1,
        category__isnull=True,
        contact__isnull=True,
        contact_group__isnull=True).first()
    assert first_discount.happy_hours.count() == 1
    assert first_discount.discount_amount_value == discount_amount_value

    second_discount = Discount.objects.filter(
        product=product2,
        category__isnull=True,
        contact__isnull=True,
        contact_group__isnull=True).first()
    assert second_discount.happy_hours.count() == 0
    assert second_discount.discount_amount_value == discount_amount_value

    third_discount = Discount.objects.filter(
        product=product3,
        category__isnull=True,
        contact__isnull=True,
        contact_group__isnull=True).first()
    assert third_discount.happy_hours.count() == 0
    assert third_discount.discount_amount_value == discount_amount_value

    category_discount = Discount.objects.filter(
        product__isnull=True,
        category=category,
        contact__isnull=True,
        contact_group__isnull=True).first()
    assert category_discount.happy_hours.count() == 0
    assert category_discount.discount_percentage == discount_percentage

    contact_discount = Discount.objects.filter(
        product=product1,
        category__isnull=True,
        contact=contact1,
        contact_group__isnull=True).first()
    assert contact_discount.discount_amount_value == discount_amount_value

    product1_contact_group_discount = Discount.objects.filter(
        product=product1,
        category__isnull=True,
        contact__isnull=True,
        contact_group=contact_group).first()
    assert product1_contact_group_discount.happy_hours.count() == 1
    assert product1_contact_group_discount.discount_percentage == discount_percentage

    product2_contact_group_discount = Discount.objects.filter(
        product=product2,
        category__isnull=True,
        contact__isnull=True,
        contact_group=contact_group).first()
    assert product2_contact_group_discount.happy_hours.count() == 1
    assert product2_contact_group_discount.discount_percentage == discount_percentage

    product3_contact_group_discount = Discount.objects.filter(
        product=product3,
        category__isnull=True,
        contact__isnull=True,
        contact_group=contact_group).first()
    assert product3_contact_group_discount.happy_hours.count() == 1
    assert product3_contact_group_discount.discount_percentage == discount_percentage
def test_nearby_products(admin_user):
    get_default_shop()
    supplier = create_simple_supplier("supplier1")

    # create Apple and its products
    shop1 = Shop.objects.create(status=ShopStatus.ENABLED)
    shop1.contact_address = MutableAddress.objects.create(
        name="Apple Infinite Loop",
        street="1 Infinite Loop",
        country="US",
        city="Cupertino",
        latitude=37.331667,
        longitude=-122.030146)
    shop1.save()
    product1 = create_product("macbook", shop1, supplier=supplier)
    product2 = create_product("imac", shop1, supplier=supplier)

    # create Google and its products
    shop2 = Shop.objects.create(status=ShopStatus.ENABLED)
    shop2.contact_address = MutableAddress.objects.create(
        name="Google",
        street="1600 Amphitheatre Pkwy",
        country="US",
        city="Mountain View",
        latitude=37.422000,
        longitude=-122.084024)
    shop2.save()
    product3 = create_product("nexus 1", shop2, supplier=supplier)
    product4 = create_product("nexux 7", shop2, supplier=supplier)

    my_position_to_apple = 2.982
    my_position_to_google = 10.57
    # YMCA
    my_position = (37.328330, -122.063612)

    # fetch products and their closest shops
    params = {
        "lat": my_position[0],
        "lng": my_position[1],
        "ordering": "-distance"
    }
    request = get_request("/api/shuup/front/products/",
                          admin_user,
                          data=params)
    response = FrontProductViewSet.as_view({"get": "list"})(request)
    response.render()
    assert response.status_code == status.HTTP_200_OK
    products = json.loads(response.content.decode("utf-8"))
    assert len(products) == 4

    assert products[0]["id"] == product3.id
    assert products[0]["name"] == product3.name
    assert products[0][
        "closest_shop_distance"] - my_position_to_google < 0.05  # 5 meters of error margin

    assert products[1]["id"] == product4.id
    assert products[1]["name"] == product4.name
    assert products[1][
        "closest_shop_distance"] - my_position_to_google < 0.05  # 5 meters of error margin

    assert products[2]["id"] == product1.id
    assert products[2]["name"] == product1.name
    assert products[2][
        "closest_shop_distance"] - my_position_to_apple < 0.05  # 5 meters of error margin

    assert products[3]["id"] == product2.id
    assert products[3]["name"] == product2.name
    assert products[3][
        "closest_shop_distance"] - my_position_to_apple < 0.05  # 5 meters of error margin
Example #48
0
def test_discount_no_limits(rf, include_tax):
    # check whether is it possible to earn money buying from us
    # adding lots of effects
    request, shop, _ = initialize_test(rf, include_tax)

    basket = get_basket(request)
    supplier = get_default_supplier()

    single_product_price = Decimal(50)
    quantity = 4
    discount_amount = (single_product_price * quantity * 2)
    discount_percentage = Decimal(1.9) # 190%

    second_product = create_product(printable_gibberish(),
                                    shop=shop,
                                    supplier=supplier,
                                    default_price=single_product_price)

    # the expected discount amount should not be greater than the products
    expected_discount_amount = basket.create_price(single_product_price) * quantity

    category = CategoryFactory()

     # create basket rule that requires 2 products in basket
    product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price)
    ShopProduct.objects.get(shop=shop, product=product).categories.add(category)
    basket.add_product(supplier=supplier, shop=shop, product=product, quantity=quantity)
    basket.shipping_method = get_shipping_method(shop=shop)
    basket.save()

    rule = ProductsInBasketCondition.objects.create(quantity=2)
    rule.products.add(product)
    rule.save()

    # BasketCampaign
    campaign = BasketCampaign.objects.create(active=True, shop=shop, name="test", public_name="test")
    campaign.conditions.add(rule)

    # effect 1 - categories from products
    DiscountFromCategoryProducts.objects.create(campaign=campaign,
                                                discount_percentage=discount_percentage,
                                                category=category)

    # effect 2 - discount from products
    effect2 = DiscountFromProduct.objects.create(campaign=campaign, discount_amount=discount_amount)
    effect2.products.add(product)

    # effect 3 - basket discount
    BasketDiscountAmount.objects.create(campaign=campaign, discount_amount=discount_amount)

    # effect 4 - basket discount percetage
    BasketDiscountPercentage.objects.create(campaign=campaign, discount_percentage=discount_percentage)

    basket.uncache()

    final_lines = basket.get_final_lines()
    assert len(final_lines) == 3

    original_price = basket.create_price(single_product_price) * quantity
    line = final_lines[0]
    assert line.discount_amount == expected_discount_amount
    assert basket.total_price == original_price - expected_discount_amount

    # effect free - aaaww yes, it's free
    effect_free = FreeProductLine.objects.create(campaign=campaign, quantity=1)
    effect_free.products.add(second_product)

    basket.uncache()

    final_lines = basket.get_final_lines()
    assert len(final_lines) == 4
    line = final_lines[0]
    assert line.discount_amount == expected_discount_amount
    assert basket.total_price == original_price - expected_discount_amount

    # CatalogCampaign
    catalog_campaign = CatalogCampaign.objects.create(active=True, shop=shop, name="test2", public_name="test")
    product_filter = ProductFilter.objects.create()
    product_filter.products.add(product)
    catalog_campaign.filters.add(product_filter)

    # effect 5 - ProductDiscountAmount
    ProductDiscountAmount.objects.create(campaign=catalog_campaign, discount_amount=discount_amount)

    # effct 6 - ProductDiscountPercentage
    ProductDiscountPercentage.objects.create(campaign=catalog_campaign, discount_percentage=discount_percentage)

    basket.uncache()

    final_lines = basket.get_final_lines()
    assert len(final_lines) == 4
    line = final_lines[0]
    assert line.discount_amount == expected_discount_amount
    assert basket.total_price == original_price - expected_discount_amount
Example #49
0
def get_product():
    shop = get_default_shop()
    product = create_product("tmp", shop, default_price=200)
    return product
def test_complex_orderability(admin_user):
    shop = get_default_shop()

    fake_supplier = Supplier.objects.create(identifier="fake")
    admin_contact = get_person_contact(admin_user)

    parent = create_product("SuperComplexVarParent")

    shop_product = ShopProduct.objects.create(product=parent, shop=shop, visibility=ShopProductVisibility.ALWAYS_VISIBLE)
    shop_product.suppliers.add(fake_supplier)
    shop_product.visibility = ShopProductVisibility.ALWAYS_VISIBLE
    shop_product.save()

    color_var = ProductVariationVariable.objects.create(product=parent, identifier="color")
    size_var = ProductVariationVariable.objects.create(product=parent, identifier="size")

    for color in ("yellow", "blue", "brown"):
        ProductVariationVariableValue.objects.create(variable=color_var, identifier=color)

    for size in ("small", "medium", "large", "huge"):
        ProductVariationVariableValue.objects.create(variable=size_var, identifier=size)

    combinations = list(parent.get_all_available_combinations())
    assert len(combinations) == (3 * 4)
    for combo in combinations:
        assert not combo["result_product_pk"]
        child = create_product("xyz-%s" % combo["sku_part"], shop=shop, supplier=fake_supplier)
        child.link_to_parent(parent, combo["variable_to_value"])
        result_product = ProductVariationResult.resolve(parent, combo["variable_to_value"])
        assert result_product == child

    assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT

    small_size_value = ProductVariationVariableValue.objects.get(variable=size_var, identifier="small")
    brown_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="brown")

    result1 = ProductVariationResult.resolve(parent, {color_var: brown_color_value, size_var: small_size_value})
    result2 = ProductVariationResult.resolve(parent,
                                             {color_var.pk: brown_color_value.pk, size_var.pk: small_size_value.pk})
    assert result1 and result2
    assert result1.pk == result2.pk

    assert error_does_not_exist(
        shop_product.get_orderability_errors(supplier=fake_supplier, customer=admin_contact, quantity=1),
        code="no_sellable_children")

    # result 1 is no longer sellable
    sp = result1.get_shop_instance(shop)
    sp.visibility = ShopProductVisibility.NOT_VISIBLE
    sp.save()

    assert error_does_not_exist(
        shop_product.get_orderability_errors(supplier=fake_supplier, customer=admin_contact, quantity=1),
        code="no_sellable_children")

    # no sellable children
    for combo in combinations:
        result_product = ProductVariationResult.resolve(parent, combo["variable_to_value"])
        sp = result_product.get_shop_instance(shop)
        sp.visibility = ShopProductVisibility.NOT_VISIBLE
        sp.save()

    assert error_exists(
        shop_product.get_orderability_errors(supplier=fake_supplier, customer=admin_contact, quantity=1),
        code="no_sellable_children")
Example #51
0
def test_product_catalog_campaigns():
    shop = get_default_shop()

    product = create_product("test", shop, default_price=20)
    parent_product = create_product("parent", shop, default_price=40)
    no_shop_child = create_product("child-no-shop")
    shop_child = create_product("child-shop", shop, default_price=60)

    shop_child.link_to_parent(parent_product)
    no_shop_child.link_to_parent(parent_product)

    shop_product = product.get_shop_instance(shop)
    parent_shop_product = parent_product.get_shop_instance(shop)
    child_shop_product = shop_child.get_shop_instance(shop)

    cat = Category.objects.create(name="test")
    campaign = CatalogCampaign.objects.create(shop=shop,
                                              name="test",
                                              active=True)

    # no rules
    assert CatalogCampaign.get_for_product(shop_product).count() == 0
    assert CatalogCampaign.get_for_product(parent_shop_product).count() == 0
    assert CatalogCampaign.get_for_product(child_shop_product).count() == 0

    # category filter that doesn't match
    cat_filter = CategoryFilter.objects.create()
    cat_filter.categories.add(cat)
    campaign.filters.add(cat_filter)
    assert CatalogCampaign.get_for_product(shop_product).count() == 0
    assert CatalogCampaign.get_for_product(parent_shop_product).count() == 0
    assert CatalogCampaign.get_for_product(child_shop_product).count() == 0

    for sp in [shop_product, parent_shop_product, child_shop_product]:
        sp.primary_category = cat
        sp.save()
        assert CatalogCampaign.get_for_product(sp).count() == 1
        sp.categories.remove(cat)
        sp.primary_category = None
        sp.save()
        assert CatalogCampaign.get_for_product(sp).count() == 0
        # category filter that matches
        sp.categories.add(cat)
        assert CatalogCampaign.get_for_product(sp).count() == 1

    # create other shop
    shop1 = Shop.objects.create(name="testshop",
                                identifier="testshop",
                                status=ShopStatus.ENABLED,
                                public_name="testshop")
    sp = ShopProduct.objects.create(product=product,
                                    shop=shop1,
                                    default_price=shop1.create_price(200))

    assert product.get_shop_instance(shop1) == sp

    campaign2 = CatalogCampaign.objects.create(shop=shop1,
                                               name="test1",
                                               active=True)
    cat_filter2 = CategoryFilter.objects.create()
    cat_filter2.categories.add(cat)
    campaign2.filters.add(cat_filter2)
    assert CatalogCampaign.get_for_product(sp).count() == 0

    # add product to this category
    sp.primary_category = cat
    sp.save()

    assert CatalogCampaign.get_for_product(sp).count() == 1  # matches now
    sp.categories.remove(cat)
    sp.primary_category = None
    sp.save()
    assert CatalogCampaign.get_for_product(sp).count() == 0  # no match
    sp.categories.add(cat)

    assert CatalogCampaign.get_for_product(sp).count() == 1  # matches now

    campaign3 = CatalogCampaign.objects.create(shop=shop1,
                                               name="test1",
                                               active=True)
    cat_filter3 = CategoryFilter.objects.create()
    cat_filter3.categories.add(cat)
    campaign3.filters.add(cat_filter3)

    assert CatalogCampaign.get_for_product(
        sp).count() == 2  # there are now two matching campaigns in same shop
    assert CatalogCampaign.get_for_product(
        shop_product).count() == 1  # another campaign matches only once
def new_product(i, shop, category):
    product = create_product(sku="test%s" % i, shop=shop, name="test%s" % i)
    sp = product.get_shop_instance(shop)
    sp.primary_category = category
    sp.save()
    return product
Example #53
0
def test_get_suppliers(admin_user):
    client = _get_client(admin_user)
    shop1 = Shop.objects.create()
    shop2 = Shop.objects.create()

    supplier1 = create_simple_supplier("supplier1")
    supplier2 = create_simple_supplier("supplier2")

    product1 = create_product("product 1")
    product1.stock_behavior = StockBehavior.STOCKED
    sp = ShopProduct.objects.create(product=product1, shop=shop1)
    sp = ShopProduct.objects.create(product=product1, shop=shop2)
    sp.suppliers.add(supplier1)
    sp.suppliers.add(supplier2)

    product2 = create_product("product 2")
    product2.stock_behavior = StockBehavior.STOCKED
    sp = ShopProduct.objects.create(product=product2, shop=shop1)
    sp = ShopProduct.objects.create(product=product2, shop=shop2)
    sp.suppliers.add(supplier1)

    product3 = create_product("product 3")
    product3.stock_behavior = StockBehavior.STOCKED
    sp = ShopProduct.objects.create(product=product3, shop=shop1)
    sp.suppliers.add(supplier2)

    # put some stock
    supplier1.adjust_stock(product1.pk, 100)
    supplier1.adjust_stock(product2.pk, 300)
    supplier2.adjust_stock(product1.pk, 110)
    supplier2.adjust_stock(product3.pk, 300)

    # list suppliers
    response = client.get("/api/shuup/supplier/")
    assert response.status_code == status.HTTP_200_OK
    supplier_data = json.loads(response.content.decode("utf-8"))
    assert len(supplier_data) == 2
    assert supplier_data[0]["id"] == supplier1.pk
    assert supplier_data[1]["id"] == supplier2.pk

    # get supplier by id
    response = client.get("/api/shuup/supplier/%s/" % supplier2.pk)
    assert response.status_code == status.HTTP_200_OK
    supplier_data = json.loads(response.content.decode("utf-8"))
    assert supplier_data["id"] == supplier2.pk
    assert supplier_data["type"] == supplier2.type.value

    # get stocks by supplier
    response = client.get("/api/shuup/supplier/%s/stock_statuses/" %
                          supplier1.pk)
    assert response.status_code == status.HTTP_200_OK
    supplier_data = sorted(json.loads(response.content.decode("utf-8")),
                           key=lambda sup: sup["product"])

    assert supplier_data[0]["sku"] == product1.sku
    assert supplier_data[0]["product"] == product1.pk
    assert supplier_data[0]["physical_count"] == supplier1.get_stock_status(
        product1.pk).physical_count
    assert supplier_data[0]["logical_count"] == supplier1.get_stock_status(
        product1.pk).logical_count

    assert supplier_data[1]["sku"] == product2.sku
    assert supplier_data[1]["product"] == product2.pk
    assert supplier_data[1]["physical_count"] == supplier1.get_stock_status(
        product2.pk).physical_count
    assert supplier_data[1]["logical_count"] == supplier1.get_stock_status(
        product2.pk).logical_count

    # get stocks by supplier - filter by product id
    response = client.get("/api/shuup/supplier/%s/stock_statuses/" %
                          supplier1.pk,
                          format="json",
                          data={"products": [product1.id]})
    assert response.status_code == status.HTTP_200_OK
    supplier_data = json.loads(response.content.decode("utf-8"))
    assert len(supplier_data) == 1
    assert supplier_data[0]["sku"] == product1.sku
    assert supplier_data[0]["product"] == product1.pk
    assert supplier_data[0]["physical_count"] == supplier1.get_stock_status(
        product1.pk).physical_count
    assert supplier_data[0]["logical_count"] == supplier1.get_stock_status(
        product1.pk).logical_count

    # get stocks by supplier - filter by sku
    response = client.get("/api/shuup/supplier/%s/stock_statuses/" %
                          supplier1.pk,
                          format="json",
                          data={"skus": [product2.sku]})
    assert response.status_code == status.HTTP_200_OK
    supplier_data = json.loads(response.content.decode("utf-8"))
    assert len(supplier_data) == 1
    assert supplier_data[0]["sku"] == product2.sku
    assert supplier_data[0]["product"] == product2.pk
    assert supplier_data[0]["physical_count"] == supplier1.get_stock_status(
        product2.pk).physical_count
    assert supplier_data[0]["logical_count"] == supplier1.get_stock_status(
        product2.pk).logical_count
Example #54
0
def test_start_end_dates():
    rf = RequestFactory()
    activate("en")
    original_price = "180"
    discounted_price = "160"
    request, shop, group = initialize_test(rf, False)
    cat = Category.objects.create(name="test")
    rule1, rule2 = create_condition_and_filter(cat, request)

    discount_amount = 20

    campaign = CatalogCampaign.objects.create(shop=shop,
                                              name="test",
                                              active=True)
    campaign.conditions.add(rule1)
    campaign.save()

    ProductDiscountAmount.objects.create(discount_amount=discount_amount,
                                         campaign=campaign)

    price = shop.create_price

    product = create_product("Just-A-Product-Too",
                             shop,
                             default_price=original_price)

    today = now()

    # starts in future
    campaign.start_datetime = (today + datetime.timedelta(days=2))
    campaign.save()
    assert not campaign.is_available()
    assert product.get_price_info(request,
                                  quantity=1).price == price(original_price)

    # has already started
    campaign.start_datetime = (today - datetime.timedelta(days=2))
    campaign.save()
    assert product.get_price_info(request,
                                  quantity=1).price == price(discounted_price)

    # already ended
    campaign.end_datetime = (today - datetime.timedelta(days=1))
    campaign.save()
    assert not campaign.is_available()
    assert product.get_price_info(request,
                                  quantity=1).price == price(original_price)

    # not ended yet
    campaign.end_datetime = (today + datetime.timedelta(days=1))
    campaign.save()
    assert product.get_price_info(request,
                                  quantity=1).price == price(discounted_price)

    # no start datetime
    campaign.start_datetime = None
    campaign.save()
    assert product.get_price_info(request,
                                  quantity=1).price == price(discounted_price)

    # no start datetime but ended
    campaign.end_datetime = (today - datetime.timedelta(days=1))
    campaign.save()
    assert not campaign.is_available()
    assert product.get_price_info(request,
                                  quantity=1).price == price(original_price)
Example #55
0
def test_xtheme_edit_product(admin_user, browser, live_server, settings):
    shop = factories.get_default_shop()
    supplier = factories.get_default_supplier()
    products = []
    for x in range(3):
        products.append(
            factories.create_product("test%s" % x,
                                     shop=shop,
                                     supplier=supplier,
                                     default_price=10))

    browser = initialize_admin_browser_test(
        browser, live_server, settings)  # Login to admin as admin user

    browser.visit(live_server + "/")
    wait_until_condition(browser,
                         lambda x: x.is_text_present("Welcome to Default!"))

    # Start edit
    click_element(browser, ".xt-edit-toggle button[type='submit']")

    # Visit first product and edit the layout with custom text
    first_product = products.pop()
    first_product_url = "%s%s" % (live_server,
                                  reverse("shuup:product",
                                          kwargs={
                                              "pk": first_product.pk,
                                              "slug": first_product.slug
                                          }))
    browser.visit(first_product_url)

    first_product_text_content = "This text is only visible for product %s." % first_product.name
    _edit_layout(
        browser, "product_extra_1",
        "#xt-ph-product_extra_1-xtheme-product-layout-%s" % first_product.pk,
        first_product_text_content)

    # Visit second product and edit the layout with custom text
    second_product = products.pop()
    second_product_url = "%s%s" % (live_server,
                                   reverse("shuup:product",
                                           kwargs={
                                               "pk": second_product.pk,
                                               "slug": second_product.slug
                                           }))
    browser.visit(second_product_url)

    second_product_text_content = "This text is only visible for product %s." % second_product.name
    _edit_layout(
        browser, "product_extra_1",
        "#xt-ph-product_extra_1-xtheme-product-layout-%s" % second_product.pk,
        second_product_text_content)

    # Visit third product and edit common layout with text
    third_product = products.pop()
    third_product_url = "%s%s" % (live_server,
                                  reverse("shuup:product",
                                          kwargs={
                                              "pk": third_product.pk,
                                              "slug": third_product.slug
                                          }))
    browser.visit(third_product_url)

    common_text_content = "This text is visible for all products."
    _edit_layout(browser, "product_extra_1", "#xt-ph-product_extra_1",
                 common_text_content)

    # Close edit
    click_element(browser, ".xt-edit-toggle button[type='submit']")

    # Logout
    click_element(browser, "div.top-nav i.menu-icon.fa.fa-user")
    click_element(browser, "a[href='/logout/']")

    # Let's revisit the product details as anonymous and check the placeholder content
    browser.visit(first_product_url)
    wait_until_condition(browser,
                         lambda x: x.is_text_present(common_text_content))
    wait_until_condition(
        browser, lambda x: x.is_text_present(first_product_text_content))
    wait_until_condition(
        browser, lambda x: not x.is_text_present(second_product_text_content))

    browser.visit(second_product_url)
    wait_until_condition(browser,
                         lambda x: x.is_text_present(common_text_content))
    wait_until_condition(
        browser, lambda x: not x.is_text_present(first_product_text_content))
    wait_until_condition(
        browser, lambda x: x.is_text_present(second_product_text_content))

    browser.visit(third_product_url)
    wait_until_condition(browser,
                         lambda x: x.is_text_present(common_text_content))
    wait_until_condition(
        browser, lambda x: not x.is_text_present(first_product_text_content))
    wait_until_condition(
        browser, lambda x: not x.is_text_present(second_product_text_content))
Example #56
0
def test_product_summary():
    shop = get_default_shop()
    supplier = get_simple_supplier()
    product = create_product("test-sku",
                             shop=get_default_shop(),
                             default_price=10,
                             stock_behavior=StockBehavior.STOCKED)
    supplier.adjust_stock(product.id, 5)

    # Order with 2 unshipped, non-refunded items and a shipping cost
    order = create_order_with_product(product, supplier, 2, 200, shop=shop)
    order.cache_prices()
    product_line = order.lines.first()
    shipping_line = order.lines.create(type=OrderLineType.SHIPPING,
                                       base_unit_price_value=5,
                                       quantity=1)

    # Make sure no invalid entries and check product quantities
    product_summary = order.get_product_summary()
    assert all(product_summary.keys())
    summary = product_summary[product.id]
    assert_defaultdict_values(summary,
                              ordered=2,
                              shipped=0,
                              refunded=0,
                              unshipped=2)

    # Create a shipment for the other item, make sure status changes
    assert order.shipping_status == ShippingStatus.NOT_SHIPPED
    assert order.can_create_shipment()
    order.create_shipment(supplier=supplier, product_quantities={product: 1})
    assert order.shipping_status == ShippingStatus.PARTIALLY_SHIPPED

    order.create_refund([{
        "line": shipping_line,
        "quantity": 1,
        "amount": Money(5, order.currency),
        "restock": False
    }])

    product_summary = order.get_product_summary()
    assert all(product_summary.keys())
    summary = product_summary[product.id]
    assert_defaultdict_values(summary,
                              ordered=2,
                              shipped=1,
                              refunded=0,
                              unshipped=1)

    # Create a refund for 2 items, we should get no negative values
    order.create_refund([{
        "line": product_line,
        "quantity": 2,
        "amount": Money(200, order.currency),
        "restock": False
    }])

    product_summary = order.get_product_summary()
    assert all(product_summary.keys())
    summary = product_summary[product.id]
    assert_defaultdict_values(summary,
                              ordered=2,
                              shipped=1,
                              refunded=2,
                              unshipped=0)
Example #57
0
def test_product_selection_plugin(rf, reindex_catalog):
    shop = factories.get_default_shop()
    shop2 = factories.get_shop(identifier="shop2")
    category1 = factories.CategoryFactory(status=CategoryStatus.VISIBLE)
    category2 = factories.CategoryFactory(status=CategoryStatus.VISIBLE)

    p1 = factories.create_product("p1", shop, factories.get_default_supplier(),
                                  "10")
    p2 = factories.create_product("p2", shop, factories.get_default_supplier(),
                                  "20")
    p3 = factories.create_product("p3", shop, factories.get_default_supplier(),
                                  "30")
    p4 = factories.create_product("p4", shop, factories.get_default_supplier(),
                                  "40")
    p5 = factories.create_product("p5", shop, factories.get_default_supplier(),
                                  "50")

    sp1 = p1.get_shop_instance(shop)
    sp2 = p2.get_shop_instance(shop)
    sp3 = p3.get_shop_instance(shop)
    sp4 = p4.get_shop_instance(shop)

    sp1.categories.add(category1, category2)
    sp2.categories.add(category1)
    sp3.categories.add(category2)
    sp4.categories.add(category2)

    # this discount should show products: p1, p2 and p5
    discount1 = Discount.objects.create(
        shop=shop,
        name="discount1",
        active=True,
        start_datetime=now() - timedelta(days=10),
        end_datetime=now() + timedelta(days=1),
        product=p5,
        category=category1,
    )

    # this discount should show products: p1, p3 and p4
    discount2 = Discount.objects.create(
        shop=shop,
        name="discount2",
        active=True,
        start_datetime=now() - timedelta(days=10),
        end_datetime=now() + timedelta(days=1),
        category=category2,
    )

    # this discount shouldn't be available for this shop
    discount3 = Discount.objects.create(
        shop=shop2,
        name="discount3",
        active=True,
        start_datetime=now() - timedelta(days=10),
        end_datetime=now() + timedelta(days=1),
        category=category2,
    )

    reindex_catalog()
    context = get_context(rf)

    # test only discount1
    plugin = DiscountedProductsPlugin({
        "discounts": [discount1.pk],
        "count": 10
    })
    context_products = plugin.get_context_data(context)["products"]
    assert p1 in context_products
    assert p2 in context_products
    assert p3 not in context_products
    assert p4 not in context_products
    assert p5 in context_products

    for status in range(2):
        if status == 1:
            discount2.active = False
            discount2.save()
            reindex_catalog()

        # test only discount2
        plugin = DiscountedProductsPlugin({
            "discounts": [discount2.pk],
            "count": 10
        })
        context_products = plugin.get_context_data(context)["products"]

        if status == 1:
            assert list(context_products) == []
        else:
            assert p1 in context_products
            assert p2 not in context_products
            assert p3 in context_products
            assert p4 in context_products
            assert p5 not in context_products

    # test discount3
    plugin = DiscountedProductsPlugin({
        "discounts": [discount3.pk],
        "count": 10
    })
    assert list(plugin.get_context_data(context)["products"]) == []

    discount2.active = True
    discount2.save()
    reindex_catalog()

    # test both discount1 and discount2
    plugin = DiscountedProductsPlugin({
        "discounts": [discount1.pk, discount2.pk],
        "count": 10
    })
    context_products = plugin.get_context_data(context)["products"]
    assert p1 in context_products
    assert p2 in context_products
    assert p3 in context_products
    assert p4 in context_products
    assert p5 in context_products

    # test the plugin form
    with override_current_theme_class(None):
        theme = get_current_theme(shop)
        cell = LayoutCell(theme,
                          DiscountedProductsPlugin.identifier,
                          sizes={"md": 8})
        lcfg = LayoutCellFormGroup(layout_cell=cell,
                                   theme=theme,
                                   request=apply_request_middleware(
                                       rf.get("/")))
        # not valid, products are required
        assert not lcfg.is_valid()

        lcfg = LayoutCellFormGroup(
            data={
                "general-cell_width": "8",
                "general-cell_align": "pull-right",
                "plugin-discounts": [discount1.pk, discount2.pk],
                "plugin-count": 6,
            },
            layout_cell=cell,
            theme=theme,
            request=apply_request_middleware(rf.get("/")),
        )
        assert lcfg.is_valid()
        lcfg.save()
        assert cell.config["discounts"] == [discount1.pk, discount2.pk]
Example #58
0
def test_broken_order(admin_user):
    """
    """
    quantities = [44, 23, 65]
    expected = sum(quantities) * 50
    expected_based_on = expected / 1.5

    # Shuup 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 create_products(shop):
    supplier = get_default_supplier()
    for i in range(0, 200):
        sku = "sku-%d" % i
        create_product(sku, shop, supplier, default_price=i)
Example #60
0
def test_refunds():
    shop = get_default_shop()
    supplier = get_default_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
    )
    tax_rate = Decimal("0.1")
    taxless_base_unit_price = shop.create_price(200)
    order = create_order_with_product(product,
                                      supplier,
                                      3,
                                      taxless_base_unit_price,
                                      tax_rate,
                                      shop=shop)
    order.payment_status = PaymentStatus.DEFERRED
    order.cache_prices()
    order.save()

    assert order.get_total_refunded_amount().value == 0
    assert order.get_total_unrefunded_amount(
    ).value == order.taxful_total_price.value
    assert not order.can_edit()

    assert len(order.lines.all()) == 1

    product_line = order.lines.first()
    assert product_line.ordering == 0
    assert order.can_create_refund()
    assert not order.has_refunds()

    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": (product_line.taxful_price.amount / 3)
    }])
    assert len(order.lines.all()) == 2
    assert order.lines.last().ordering == 1
    assert order.has_refunds()

    # Confirm the value of the refund
    assert order.lines.last().taxful_price == -product_line.base_unit_price
    assert order.lines.last().tax_amount == -(
        product_line.taxless_base_unit_price * tax_rate).amount

    # Create a refund with a parent line and amount
    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": product_line.taxful_price.amount / 3
    }])
    assert len(order.lines.all()) == 3
    assert order.lines.last().ordering == 2

    assert order.lines.last(
    ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate)
    assert order.lines.last(
    ).tax_amount == -taxless_base_unit_price.amount * tax_rate

    assert order.taxless_total_price.amount == taxless_base_unit_price.amount
    assert order.taxful_total_price.amount == taxless_base_unit_price.amount * (
        1 + tax_rate)
    assert order.can_create_refund()
    assert order.get_total_tax_amount() == Money(
        (order.taxful_total_price_value - order.taxless_total_price_value),
        order.currency)

    # Try to refunding remaining amount without a parent line
    with pytest.raises(AssertionError):
        order.create_refund([{"amount": taxless_base_unit_price}])

    # refund remaining amount
    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": product_line.taxful_price.amount / 3
    }])
    assert len(order.lines.all()) == 4
    assert order.lines.last().ordering == 3
    assert order.lines.last(
    ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate)

    assert not order.taxful_total_price.amount
    assert not order.can_create_refund()
    assert order.get_total_tax_amount() == Money(
        (order.taxful_total_price_value - order.taxless_total_price_value),
        order.currency)

    with pytest.raises(RefundExceedsAmountException):
        order.create_refund([{
            "line": product_line,
            "quantity": 1,
            "amount": taxless_base_unit_price.amount
        }])