Repository: saleor/saleor

Issue:
`productVariantBulkUpdate` error policy does not properly reject failed rows
Using the `productVariantBulkUpdate` mutation to create stocks does not allow for the error policy to gracefully handle failed errors. Instead, Saleor throws a 500 server error. This happens if you try to call create on the same variant warehouse stock more than once.
```
mutation updateChannel($id: ID!, $variants: [ProductVariantBulkUpdateInput!]!) {
        productVariantBulkUpdate(errorPolicy: REJECT_FAILED_ROWS, product: $id, variants: $variants) {
            errors {
                field
                message
            }
            results {
                errors {
                    message
                }
                productVariant {
                    id
                }
            }
        }
    }
```
This mutation with the following input
```
{
  "id": "UHJvZHVjdDo2MQ==",
  "variants": [
    {
      "id": "UHJvZHVjdFZhcmlhbnQ6MTcwNzk=",
      "stocks": {
        "create": [
          {
            "warehouse": "V2FyZWhvdXNlOjE1YzRmM2E1LTQwNGEtNDg0Zi1iMjY0LThjYjdhOTE1N2U3OA==",
            "quantity": 100
          },
          {
            "warehouse": "V2FyZWhvdXNlOjIyZTJlODQ3LTgzNWMtNDcwMi1hMDJjLTI1YjhiNWZlNWEzMQ==",
            "quantity": 100
          },
          {
            "warehouse": "V2FyZWhvdXNlOjdlZTE5Nzc0LWJiZTQtNDA5YS1iMjBiLTg4YWNlOTA2Zjg1NA==",
            "quantity": 100
          },
          {
            "warehouse": "V2FyZWhvdXNlOmQ0ZGNhZDc5LWI0MmEtNGRhZC04M2VhLTdkMDEyYzRjZjkwYQ==",
            "quantity": 100
          }
        ]
      }
    }
  ]
}
```
Will succeed if the stock has never been created, but when running it again it throws a server error.  There are instances where we do not know if the stock has been created, so we attempt it again as there is no 'upsert' function provided in the schema and would like to simply ignore the errors.


Discussion:

Resolving patch:
diff --git a/saleor/graphql/product/bulk_mutations/product_variant_bulk_update.py b/saleor/graphql/product/bulk_mutations/product_variant_bulk_update.py
--- a/saleor/graphql/product/bulk_mutations/product_variant_bulk_update.py
+++ b/saleor/graphql/product/bulk_mutations/product_variant_bulk_update.py
@@ -297,6 +297,7 @@ def clean_stocks(
             used_warehouses[warehouse_global_id].append(stock.product_variant_id)
 
         if stocks_data := cleaned_input["stocks"].get("create"):
+            stocks_to_create = []
             variant = cleaned_input["id"]
             for stock_index, stock in enumerate(stocks_data):
                 if variant.id in used_warehouses.get(stock["warehouse"], {}):
@@ -310,9 +311,10 @@ def clean_stocks(
                         )
                     )
                     continue
+                stocks_to_create.append(stock)
 
             cleaned_input["stocks"]["create"] = ProductVariantBulkCreate.clean_stocks(
-                stocks_data,
+                stocks_to_create,
                 warehouse_global_id_to_instance_map,
                 None,
                 variant_index,


Test patch:
diff --git a/saleor/graphql/product/tests/mutations/test_product_variant_bulk_update.py b/saleor/graphql/product/tests/mutations/test_product_variant_bulk_update.py
--- a/saleor/graphql/product/tests/mutations/test_product_variant_bulk_update.py
+++ b/saleor/graphql/product/tests/mutations/test_product_variant_bulk_update.py
@@ -5,6 +5,7 @@
 from .....product.error_codes import ProductVariantBulkErrorCode
 from .....product.models import ProductChannelListing
 from .....tests.utils import flush_post_commit_hooks
+from ....core.enums import ErrorPolicyEnum
 from ....tests.utils import get_graphql_content
 
 PRODUCT_VARIANT_BULK_UPDATE_MUTATION = """
@@ -239,7 +240,11 @@ def test_product_variant_bulk_update_create_already_existing_stock(
         }
     ]
 
-    variables = {"productId": product_id, "variants": variants}
+    variables = {
+        "productId": product_id,
+        "variants": variants,
+        "errorPolicy": ErrorPolicyEnum.IGNORE_FAILED.name,
+    }
 
     # when
     staff_api_client.user.user_permissions.add(permission_manage_products)