Repository: saleor/saleor

Issue:
Remove quantity input from ProductInput and ProductVariantInput
Remove deprecated `quantity` input from `ProductInput` and `ProductVariantInput`.


Discussion:

Resolving patch:
diff --git a/saleor/graphql/product/mutations/products.py b/saleor/graphql/product/mutations/products.py
--- a/saleor/graphql/product/mutations/products.py
+++ b/saleor/graphql/product/mutations/products.py
@@ -28,7 +28,6 @@
     associate_attribute_values_to_instance,
     generate_name_for_variant,
 )
-from ....warehouse.management import set_stock_quantity
 from ...core.mutations import BaseMutation, ModelDeleteMutation, ModelMutation
 from ...core.scalars import Decimal, WeightScalar
 from ...core.types import SeoInput, Upload
@@ -526,13 +525,6 @@ class ProductInput(graphene.InputObjectType):
             "a product doesn't use variants."
         )
     )
-    quantity = graphene.Int(
-        description=(
-            "[Deprecated] Use stocks input field instead. This field will be removed "
-            "after 2020-07-31. The total quantity of a product available for sale. "
-            "Note: this field is only used if a product doesn't use variants."
-        ),
-    )
     track_inventory = graphene.Boolean(
         description=(
             "Determines if the inventory of this product should be tracked. If false, "
@@ -951,11 +943,8 @@ def save(cls, info, instance, cleaned_input):
                 product=instance, track_inventory=track_inventory, sku=sku
             )
             stocks = cleaned_input.get("stocks")
-            quantity = cleaned_input.get("quantity")
             if stocks:
                 cls.create_variant_stocks(variant, stocks)
-            elif quantity:  # DEPRECATED: Will be removed in 2.11 (issue #5325)
-                set_stock_quantity(variant, info.context.country, quantity)
 
         attributes = cleaned_input.get("attributes")
         if attributes:
@@ -1023,11 +1012,6 @@ def save(cls, info, instance, cleaned_input):
             if "track_inventory" in cleaned_input:
                 variant.track_inventory = cleaned_input["track_inventory"]
                 update_fields.append("track_inventory")
-            # DEPRECATED: Wil be removed in 2.11 (issue #5325).
-            # Use ProductVariantStocksUpdate insted.
-            if "quantity" in cleaned_input:
-                quantity = cleaned_input.get("quantity")
-                set_stock_quantity(variant, info.context.country, quantity)
             if "sku" in cleaned_input:
                 variant.sku = cleaned_input["sku"]
                 update_fields.append("sku")
@@ -1102,12 +1086,6 @@ class ProductVariantInput(graphene.InputObjectType):
     cost_price = Decimal(description="Cost price of the variant.")
     price_override = Decimal(description="Special price of the particular variant.")
     sku = graphene.String(description="Stock keeping unit.")
-    quantity = graphene.Int(
-        description=(
-            "[Deprecated] Use stocks input field instead. This field will be removed "
-            "after 2020-07-31. The total quantity of this variant available for sale."
-        ),
-    )
     track_inventory = graphene.Boolean(
         description=(
             "Determines if the inventory of this variant should be tracked. If false, "
@@ -1280,11 +1258,8 @@ def save(cls, info, instance, cleaned_input):
         # Recalculate the "minimal variant price" for the parent product
         update_product_minimal_variant_price_task.delay(instance.product_id)
         stocks = cleaned_input.get("stocks")
-        quantity = cleaned_input.get("quantity")
         if stocks:
             cls.create_variant_stocks(instance, stocks)
-        elif quantity:  # DEPRECATED: Will be removed in 2.11 (issue #5325)
-            set_stock_quantity(instance, info.context.country, quantity)
 
         attributes = cleaned_input.get("attributes")
         if attributes:
diff --git a/saleor/graphql/product/types/products.py b/saleor/graphql/product/types/products.py
--- a/saleor/graphql/product/types/products.py
+++ b/saleor/graphql/product/types/products.py
@@ -163,15 +163,14 @@ class Meta:
 class ProductVariant(CountableDjangoObjectType):
     quantity = graphene.Int(
         required=True,
-        description="Quantity of a product in the store's possession, "
-        "including the allocated stock that is waiting for shipment.",
+        description="Quantity of a product available for sale.",
         deprecation_reason=(
             "Use the stock field instead. This field will be removed after 2020-07-31."
         ),
     )
     quantity_allocated = graphene.Int(
         required=False,
-        description="Quantity allocated for orders",
+        description="Quantity allocated for orders.",
         deprecation_reason=(
             "Use the stock field instead. This field will be removed after 2020-07-31."
         ),
diff --git a/saleor/warehouse/management.py b/saleor/warehouse/management.py
--- a/saleor/warehouse/management.py
+++ b/saleor/warehouse/management.py
@@ -8,7 +8,6 @@
 
 if TYPE_CHECKING:
     from ..order.models import OrderLine
-    from ..product.models import ProductVariant
 
 
 @transaction.atomic
@@ -178,24 +177,3 @@ def decrease_stock(order_line: "OrderLine", quantity: int):
                 break
     if not quantity_decreased == quantity:
         raise AllocationError(order_line, quantity)
-
-
-@transaction.atomic
-def set_stock_quantity(
-    variant: "ProductVariant", country_code: str, quantity: int
-) -> Stock:
-    stock = (
-        Stock.objects.select_for_update(of=("self",))
-        .get_variant_stocks_for_country(country_code, variant)
-        .order_by("pk")
-        .first()
-    )
-    if stock:
-        stock.quantity = quantity
-        stock.save(update_fields=["quantity"])
-    else:
-        warehouse = Warehouse.objects.for_country(country_code).first()
-        stock = Stock.objects.create(
-            warehouse=warehouse, product_variant=variant, quantity=quantity
-        )
-    return stock


Test patch:
diff --git a/tests/api/test_product.py b/tests/api/test_product.py
--- a/tests/api/test_product.py
+++ b/tests/api/test_product.py
@@ -3780,7 +3780,6 @@ def test_product_filter_by_attribute_values(
         $name: String!,
         $sku: String,
         $stocks: [StockInput!],
-        $quantity: Int,
         $basePrice: Decimal!
         $trackInventory: Boolean)
     {
@@ -3792,7 +3791,6 @@ def test_product_filter_by_attribute_values(
                 sku: $sku,
                 stocks: $stocks,
                 trackInventory: $trackInventory,
-                quantity: $quantity,
                 basePrice: $basePrice,
             })
         {
@@ -3804,6 +3802,7 @@ def test_product_filter_by_attribute_values(
                     sku
                     trackInventory
                     quantity
+                    stockQuantity
                 }
             }
             productErrors {
@@ -3837,7 +3836,6 @@ def test_create_product_without_variant_creates_stocks(
         "category": category_id,
         "productType": product_type_id,
         "name": "Test",
-        "quantity": 8,
         "stocks": stocks,
         "sku": "23434",
         "trackInventory": True,
@@ -3849,7 +3847,8 @@ def test_create_product_without_variant_creates_stocks(
         permissions=[permission_manage_products],
     )
     content = get_graphql_content(response)
-    quantity = content["data"]["productCreate"]["product"]["variants"][0]["quantity"]
+    data = content["data"]["productCreate"]
+    quantity = data["product"]["variants"][0]["stockQuantity"]
     assert quantity == 20
 
 
@@ -3885,41 +3884,6 @@ def test_create_product_with_variants_does_not_create_stock(
     assert not Stock.objects.exists()
 
 
-MUTATION_UPDATE_PRODUCT_QUANTITY = """
-mutation updateProduct(
-    $productId: ID!,
-
-    $quantity: Int,
-    ) {
-        productUpdate(
-            id: $productId,
-            input: {
-                quantity: $quantity
-            }) {
-                product {
-                    variants {
-                        quantity
-                    }
-                }
-}}
-"""
-
-
-def test_update_product_without_variants_updates_stock(
-    staff_api_client, product_with_default_variant, permission_manage_products
-):
-    product_id = graphene.Node.to_global_id("Product", product_with_default_variant.pk)
-    stock = product_with_default_variant.variants.first().stocks.first()
-    variables = {"productId": product_id, "quantity": 17}
-    staff_api_client.post_graphql(
-        MUTATION_UPDATE_PRODUCT_QUANTITY,
-        variables,
-        permissions=[permission_manage_products],
-    )
-    stock.refresh_from_db()
-    assert stock.quantity == 17
-
-
 def test_create_stocks_failed(product_with_single_variant, warehouse):
     variant = product_with_single_variant.variants.first()
 
diff --git a/tests/api/test_variant.py b/tests/api/test_variant.py
--- a/tests/api/test_variant.py
+++ b/tests/api/test_variant.py
@@ -290,7 +290,6 @@ def test_create_product_variant_update_with_new_attributes(
           $costPrice: Decimal
           $priceOverride: Decimal
           $sku: String
-          $quantity: Int
           $trackInventory: Boolean!
         ) {
           productVariantUpdate(
@@ -300,7 +299,6 @@ def test_create_product_variant_update_with_new_attributes(
               costPrice: $costPrice
               priceOverride: $priceOverride
               sku: $sku
-              quantity: $quantity
               trackInventory: $trackInventory
             }
           ) {
@@ -862,7 +860,6 @@ def test_product_variant_bulk_create_by_attribute_id(
     variants = [
         {
             "sku": sku,
-            "quantity": 1000,
             "costPrice": None,
             "priceOverride": None,
             "weight": 2.5,
@@ -899,7 +896,6 @@ def test_product_variant_bulk_create_by_attribute_id_with_invalid_price(
     sku = str(uuid4())[:12]
     variant = {
         "sku": sku,
-        "quantity": 1000,
         "weight": 2.5,
         "trackInventory": True,
         "attributes": [{"id": attribut_id, "values": [attribute_value.name]}],
@@ -1203,87 +1199,6 @@ def test_product_variant_bulk_create_two_variants_duplicated_one_attribute_value
     assert product_variant_count + 1 == ProductVariant.objects.count()
 
 
-MUTATION_UPDATE_VARIANT_QUANTITY = """
-    mutation updateVariant(
-        $id: ID!,
-        $quantity: Int
-    ) {
-        productVariantUpdate(id: $id, input: {quantity: $quantity}) {
-            productVariant {
-                id
-                quantity
-            }
-        }
-    }
-"""
-
-
-def test_update_variant_and_set_quantity_creates_stock(
-    staff_api_client, variant, permission_manage_products, warehouse
-):
-    assert variant.stocks.count() == 0
-    variant_id = graphene.Node.to_global_id("ProductVariant", variant.pk)
-    variables = {"id": variant_id, "quantity": 70}
-    staff_api_client.post_graphql(
-        MUTATION_UPDATE_VARIANT_QUANTITY,
-        variables,
-        permissions=[permission_manage_products],
-    )
-    assert variant.stocks.count() == 1
-
-
-def test_update_variant_fails_when_there_is_no_warehouse_available(
-    staff_api_client, variant, permission_manage_products
-):
-    Stock.objects.all().delete()
-    Warehouse.objects.all().delete()
-    variant_id = graphene.Node.to_global_id("ProductVariant", variant.pk)
-    variables = {"id": variant_id, "quantity": 70}
-    response = staff_api_client.post_graphql(
-        MUTATION_UPDATE_VARIANT_QUANTITY,
-        variables,
-        permissions=[permission_manage_products],
-    )
-    content = get_graphql_content(response, ignore_errors=True)
-    errors = content["errors"]
-    assert len(errors) == 1
-    assert variant.stocks.count() == 0
-
-
-def test_update_variant_sets_quantity_in_stock(
-    staff_api_client, variant, stock, permission_manage_products
-):
-    variant_id = graphene.Node.to_global_id("ProductVariant", variant.pk)
-    variables = {
-        "id": variant_id,
-        "quantity": 8,
-    }
-    staff_api_client.post_graphql(
-        MUTATION_UPDATE_VARIANT_QUANTITY,
-        variables,
-        permissions=[permission_manage_products],
-    )
-    stock.refresh_from_db()
-    assert stock.quantity == 8
-
-
-def test_update_variant_changes_stock_only_when_quantity_is_passed(
-    staff_api_client, variant, stock, permission_manage_products, monkeypatch
-):
-    variant_id = graphene.Node.to_global_id("ProductVariant", variant.pk)
-    stock_quantity = stock.quantity
-    variables = {
-        "id": variant_id,
-    }
-    staff_api_client.post_graphql(
-        MUTATION_UPDATE_VARIANT_QUANTITY,
-        variables,
-        permissions=[permission_manage_products],
-    )
-    stock.refresh_from_db()
-    assert stock.quantity == stock_quantity
-
-
 VARIANT_STOCKS_CREATE_MUTATION = """
     mutation ProductVariantStocksCreate($variantId: ID!, $stocks: [StockInput!]!){
         productVariantStocksCreate(variantId: $variantId, stocks: $stocks){