Repository: saleor/saleor

Issue:
Weight based rates not working as expected
### What I'm trying to achieve
…
Hi there 😃 I think there is a bug when setting up shipping methods. 
### Steps to reproduce the problem
1. Setup a "Weight Based Rate" and check the "There are no value limits" checkbox.
2. Go to the storefront checkout and see that the shipping method is not shown.

### What I expected to happen
I would expect "DHL Express" to show up when the "no value limits" is enabled instead of giving me an empty "availableShippingMethods" list?

### Screenshots
--- WITH "no value limits" DISABLED ---

![image](https://user-images.githubusercontent.com/33222461/108908864-bc489500-761b-11eb-9693-a7f086266cc4.png)

![image](https://user-images.githubusercontent.com/33222461/108908700-86a3ac00-761b-11eb-97df-d22af6885530.png)

--- WITH "no value limits" ENABLED ---

![image](https://user-images.githubusercontent.com/33222461/108909031-ebf79d00-761b-11eb-915f-250f74699bcf.png)

![image](https://user-images.githubusercontent.com/33222461/108908978-dbdfbd80-761b-11eb-8fec-70dc6ef2e5d7.png)


**System information**
Operating system: Windows 10 (WSL2)
Saleor/Saleor-storefront/Saleor-Dashboard version: 2.11

Apologies if I'm misunderstanding something or if this has already been raised - couldn't find it as an issue in any of the repos.


Discussion:
Thanks for the reporting! We fixed the issue in the 3.0 branch and soon we will backport it to 2.11 :) 
#7021 
Awesome thank you very much for the response - closing the issue then :)

Resolving patch:
diff --git a/saleor/shipping/models.py b/saleor/shipping/models.py
--- a/saleor/shipping/models.py
+++ b/saleor/shipping/models.py
@@ -28,10 +28,13 @@
 def _applicable_weight_based_methods(weight, qs):
     """Return weight based shipping methods that are applicable for the total weight."""
     qs = qs.weight_based()
-    min_weight_matched = Q(minimum_order_weight__lte=weight)
-    no_weight_limit = Q(maximum_order_weight__isnull=True)
-    max_weight_matched = Q(maximum_order_weight__gte=weight)
-    return qs.filter(min_weight_matched & (no_weight_limit | max_weight_matched))
+    min_weight_matched = Q(minimum_order_weight__lte=weight) | Q(
+        minimum_order_weight__isnull=True
+    )
+    max_weight_matched = Q(maximum_order_weight__gte=weight) | Q(
+        maximum_order_weight__isnull=True
+    )
+    return qs.filter(min_weight_matched & max_weight_matched)
 
 
 def _applicable_price_based_methods(price: Money, qs):


Test patch:
diff --git a/saleor/graphql/checkout/tests/test_checkout.py b/saleor/graphql/checkout/tests/test_checkout.py
--- a/saleor/graphql/checkout/tests/test_checkout.py
+++ b/saleor/graphql/checkout/tests/test_checkout.py
@@ -7,6 +7,7 @@
 import pytest
 from django.core.exceptions import ValidationError
 from django.test import override_settings
+from measurement.measures import Weight
 from prices import Money, TaxedMoney
 
 from ....account.models import User
@@ -23,6 +24,7 @@
 from ....payment.interface import GatewayResponse
 from ....plugins.manager import PluginsManager
 from ....plugins.tests.sample_plugins import ActiveDummyPaymentGateway
+from ....product.models import ProductVariant
 from ....warehouse.models import Stock
 from ...tests.utils import assert_no_permission, get_graphql_content
 from ..mutations import (
@@ -744,28 +746,95 @@ def test_checkout_available_payment_gateways_currency_specified_PLN(
     )
 
 
+GET_CHECKOUT_AVAILABLE_SHIPPING_METHODS = """
+query getCheckout($token: UUID!) {
+    checkout(token: $token) {
+        availableShippingMethods {
+            name
+            price {
+                amount
+            }
+        }
+    }
+}
+"""
+
+
 def test_checkout_available_shipping_methods(
     api_client, checkout_with_item, address, shipping_zone
 ):
     checkout_with_item.shipping_address = address
     checkout_with_item.save()
 
-    query = """
-    query getCheckout($token: UUID!) {
-        checkout(token: $token) {
-            availableShippingMethods {
-                name
-            }
-        }
-    }
-    """
+    query = GET_CHECKOUT_AVAILABLE_SHIPPING_METHODS
     variables = {"token": checkout_with_item.token}
     response = api_client.post_graphql(query, variables)
     content = get_graphql_content(response)
     data = content["data"]["checkout"]
 
     shipping_method = shipping_zone.shipping_methods.first()
-    assert data["availableShippingMethods"] == [{"name": shipping_method.name}]
+    assert data["availableShippingMethods"][0]["name"] == shipping_method.name
+
+
+@pytest.mark.parametrize("minimum_order_weight_value", [0, 2, None])
+def test_checkout_available_shipping_methods_with_weight_based_shipping_method(
+    api_client,
+    checkout_with_item,
+    address,
+    shipping_method_weight_based,
+    minimum_order_weight_value,
+):
+    checkout_with_item.shipping_address = address
+    checkout_with_item.save()
+
+    shipping_method = shipping_method_weight_based
+    if minimum_order_weight_value is not None:
+        weight = Weight(kg=minimum_order_weight_value)
+        shipping_method.minimum_order_weight = weight
+        variant = checkout_with_item.lines.first().variant
+        variant.weight = weight
+        variant.save(update_fields=["weight"])
+    else:
+        shipping_method.minimum_order_weight = minimum_order_weight_value
+
+    shipping_method.save(update_fields=["minimum_order_weight"])
+
+    query = GET_CHECKOUT_AVAILABLE_SHIPPING_METHODS
+    variables = {"token": checkout_with_item.token}
+    response = api_client.post_graphql(query, variables)
+    content = get_graphql_content(response)
+    data = content["data"]["checkout"]
+
+    shipping_methods = [method["name"] for method in data["availableShippingMethods"]]
+    assert shipping_method.name in shipping_methods
+
+
+def test_checkout_available_shipping_methods_weight_method_with_higher_minimal_weigh(
+    api_client, checkout_with_item, address, shipping_method_weight_based
+):
+    checkout_with_item.shipping_address = address
+    checkout_with_item.save()
+
+    shipping_method = shipping_method_weight_based
+    weight_value = 5
+    shipping_method.minimum_order_weight = Weight(kg=weight_value)
+    shipping_method.save(update_fields=["minimum_order_weight"])
+
+    variants = []
+    for line in checkout_with_item.lines.all():
+        variant = line.variant
+        variant.weight = Weight(kg=1)
+        variants.append(variant)
+    ProductVariant.objects.bulk_update(variants, ["weight"])
+
+    query = GET_CHECKOUT_AVAILABLE_SHIPPING_METHODS
+    variables = {"token": checkout_with_item.token}
+    response = api_client.post_graphql(query, variables)
+    content = get_graphql_content(response)
+    data = content["data"]["checkout"]
+
+    shipping_methods = [method["name"] for method in data["availableShippingMethods"]]
+    assert shipping_method.name not in shipping_methods
 
 
 @pytest.mark.parametrize(
@@ -794,18 +863,7 @@ def test_checkout_available_shipping_methods_with_price_displayed(
     checkout_with_item.shipping_address = address
     checkout_with_item.save()
 
-    query = """
-    query getCheckout($token: UUID!) {
-        checkout(token: $token) {
-            availableShippingMethods {
-                name
-                price {
-                    amount
-                }
-            }
-        }
-    }
-    """
+    query = GET_CHECKOUT_AVAILABLE_SHIPPING_METHODS
     variables = {"token": checkout_with_item.token}
     response = api_client.post_graphql(query, variables)
     content = get_graphql_content(response)
@@ -822,15 +880,7 @@ def test_checkout_available_shipping_methods_with_price_displayed(
 def test_checkout_no_available_shipping_methods_without_address(
     api_client, checkout_with_item
 ):
-    query = """
-    query getCheckout($token: UUID!) {
-        checkout(token: $token) {
-            availableShippingMethods {
-                name
-            }
-        }
-    }
-    """
+    query = GET_CHECKOUT_AVAILABLE_SHIPPING_METHODS
     variables = {"token": checkout_with_item.token}
     response = api_client.post_graphql(query, variables)
     content = get_graphql_content(response)
@@ -840,15 +890,7 @@ def test_checkout_no_available_shipping_methods_without_address(
 
 
 def test_checkout_no_available_shipping_methods_without_lines(api_client, checkout):
-    query = """
-    query getCheckout($token: UUID!) {
-        checkout(token: $token) {
-            availableShippingMethods {
-                name
-            }
-        }
-    }
-    """
+    query = GET_CHECKOUT_AVAILABLE_SHIPPING_METHODS
     variables = {"token": checkout.token}
     response = api_client.post_graphql(query, variables)
     content = get_graphql_content(response)
diff --git a/saleor/graphql/order/tests/test_order.py b/saleor/graphql/order/tests/test_order.py
--- a/saleor/graphql/order/tests/test_order.py
+++ b/saleor/graphql/order/tests/test_order.py
@@ -7,6 +7,7 @@
 from django.contrib.auth.models import AnonymousUser
 from django.core.exceptions import ValidationError
 from freezegun import freeze_time
+from measurement.measures import Weight
 from prices import Money, TaxedMoney
 
 from ....account.models import CustomerEvent
@@ -307,6 +308,25 @@ def test_order_query(
     assert expected_method.type.upper() == method["type"]
 
 
+ORDERS_QUERY_SHIPPING_METHODS = """
+    query OrdersQuery {
+        orders(first: 1) {
+            edges {
+                node {
+                    availableShippingMethods {
+                        name
+                        price {
+                            amount
+                        }
+                        type
+                    }
+                }
+            }
+        }
+    }
+"""
+
+
 @pytest.mark.parametrize(
     "expected_price_type, expected_price, display_gross_prices",
     (("gross", 13, True), ("net", 10, False)),
@@ -322,23 +342,7 @@ def test_order_available_shipping_methods_query(
     shipping_zone,
     site_settings,
 ):
-    query = """
-    query OrdersQuery {
-        orders(first: 1) {
-            edges {
-                node {
-                    availableShippingMethods {
-                        id
-                        price {
-                            amount
-                        }
-                        type
-                    }
-                }
-            }
-        }
-    }
-    """
+    query = ORDERS_QUERY_SHIPPING_METHODS
     shipping_method = shipping_zone.shipping_methods.first()
     taxed_price = TaxedMoney(net=Money(10, "USD"), gross=Money(13, "USD"))
     apply_taxes_to_shipping_mock = Mock(return_value=taxed_price)
@@ -358,6 +362,62 @@ def test_order_available_shipping_methods_query(
     assert expected_price == method["price"]["amount"]
 
 
+@pytest.mark.parametrize("minimum_order_weight_value", [0, 2, None])
+def test_order_available_shipping_methods_with_weight_based_shipping_method(
+    staff_api_client,
+    order_line,
+    shipping_method_weight_based,
+    permission_manage_orders,
+    minimum_order_weight_value,
+):
+
+    shipping_method = shipping_method_weight_based
+    order = order_line.order
+    if minimum_order_weight_value is not None:
+        weight = Weight(kg=minimum_order_weight_value)
+        shipping_method.minimum_order_weight = weight
+        order.weight = weight
+        order.save(update_fields=["weight"])
+    else:
+        shipping_method.minimum_order_weight = minimum_order_weight_value
+
+    shipping_method.save(update_fields=["minimum_order_weight"])
+
+    staff_api_client.user.user_permissions.add(permission_manage_orders)
+    response = staff_api_client.post_graphql(ORDERS_QUERY_SHIPPING_METHODS)
+    content = get_graphql_content(response)
+    order_data = content["data"]["orders"]["edges"][0]["node"]
+
+    shipping_methods = [
+        method["name"] for method in order_data["availableShippingMethods"]
+    ]
+    assert shipping_method.name in shipping_methods
+
+
+def test_order_available_shipping_methods_weight_method_with_higher_minimal_weigh(
+    staff_api_client, order_line, shipping_method_weight_based, permission_manage_orders
+):
+    order = order_line.order
+
+    shipping_method = shipping_method_weight_based
+    weight_value = 5
+    shipping_method.minimum_order_weight = Weight(kg=weight_value)
+    shipping_method.save(update_fields=["minimum_order_weight"])
+
+    order.weight = Weight(kg=1)
+    order.save(update_fields=["weight"])
+
+    staff_api_client.user.user_permissions.add(permission_manage_orders)
+    response = staff_api_client.post_graphql(ORDERS_QUERY_SHIPPING_METHODS)
+    content = get_graphql_content(response)
+    order_data = content["data"]["orders"]["edges"][0]["node"]
+
+    shipping_methods = [
+        method["name"] for method in order_data["availableShippingMethods"]
+    ]
+    assert shipping_method.name not in shipping_methods
+
+
 def test_order_query_customer(api_client):
     query = """
         query OrdersQuery {
diff --git a/saleor/tests/fixtures.py b/saleor/tests/fixtures.py
--- a/saleor/tests/fixtures.py
+++ b/saleor/tests/fixtures.py
@@ -593,6 +593,16 @@ def shipping_method(shipping_zone):
     )
 
 
+@pytest.fixture
+def shipping_method_weight_based(shipping_zone):
+    method = ShippingMethod.objects.create(
+        name="weight based method",
+        type=ShippingMethodType.WEIGHT_BASED,
+        shipping_zone=shipping_zone,
+    )
+    return method
+
+
 @pytest.fixture
 def color_attribute(db):  # pylint: disable=W0613
     attribute = Attribute.objects.create(slug="color", name="Color")