Repository: saleor/saleor

Issue:
productCreate and productUpdate mutations improperly validate decimal places



Discussion:
I can't update price if a decimal is specified:

works 
```mutation {
  productUpdate(id: "UHJvZHVjdDoz", input: { price: 4}) {
    errors {
      field
      message
    }
    product {
     name
     description
     price {
      currency
      amount
      localized
    }
    }
  }
}
```

fails:

```mutation {
  productUpdate(id: "UHJvZHVjdDoz", input: { price: 4.1}) {
    errors {
      field
      message
    }
    product {
     name
     description
     price {
      currency
      amount
      localized
    }
    }
  }
}
```
error: 

```
{
  "data": {
    "productUpdate": {
      "errors": [
        {
          "field": "price",
          "message": "Ensure that there are no more than 2 decimal places."
        }
      ],
      "product": null
    }
  }
}
```

similar thing happens in product create, though it works with 1 digit after the decimal, but not two:

works:

```
mutation {
  productCreate(input: {description: "test", name: "gqltest", productType: "UHJvZHVjdFR5cGU6MQ==", price: 3.5, category: "Q2F0ZWdvcnk6MQ==", isPublished: true}) {
    product {
      name
      id
      description
      price {
        currency
        amount
      }
    }
    errors {
      field
      message
    }
  }
}
```

fails:

```
mutation {
  productCreate(input: {description: "test", name: "gqltest", productType: "UHJvZHVjdFR5cGU6MQ==", price: 3.55, category: "Q2F0ZWdvcnk6MQ==", isPublished: true}) {
    product {
      name
      id
      description
      price {
        currency
        amount
      }
    }
    errors {
      field
      message
    }
  }
}
```
error: 

```
{
  "data": {
    "productCreate": {
      "product": null,
      "errors": [
        {
          "field": "price",
          "message": "Ensure that there are no more than 2 decimal places."
        }
      ]
    }
  }
}
```
note, that it does appear to work if the price value is set using a variable whose type is Decimal! but the literal forms of the query don't handle the conversion
We're somehow using these mutations in the Dashboard 2.0. @dominik-zeglen how do we pass decimals from UI to mutations? Or it doesn't work in UI as well 🤔 ?
We're sending them as strings.
@cprinos It's interesting but it works when you pass price as a variable, e.g.

```
mutation ProductUpdate($price: Decimal) {
  productUpdate(id: "UHJvZHVjdDo2MQ==", input: { price: $price }) {
    product {
      price {
        amount
      }
    }
  }
}
```

and in "Query variables" field in the Playground:
```
{ "price": 4.29 }
```

Seems like we're handling `Decimal` differently when it's passed directly in the query and as a variable. @fowczarek we should investigate it whether it's a bug on our side.

Resolving patch:
diff --git a/saleor/graphql/core/types/common.py b/saleor/graphql/core/types/common.py
--- a/saleor/graphql/core/types/common.py
+++ b/saleor/graphql/core/types/common.py
@@ -23,6 +23,13 @@ class Decimal(graphene.Float):
     parses float to the Decimal on the way back.
     """
 
+    @staticmethod
+    def parse_literal(node):
+        try:
+            return decimal.Decimal(node.value)
+        except decimal.DecimalException:
+            return None
+
     @staticmethod
     def parse_value(value):
         try:


Test patch:
diff --git a/tests/api/test_core.py b/tests/api/test_core.py
--- a/tests/api/test_core.py
+++ b/tests/api/test_core.py
@@ -139,3 +139,59 @@ def test_total_count_query(api_client, product):
     response = api_client.post_graphql(query)
     content = get_graphql_content(response)
     assert content['data']['products']['totalCount'] == Product.objects.count()
+
+
+def test_mutation_decimal_input(
+        staff_api_client, variant, permission_manage_products):
+    query = """
+    mutation decimalInput($id: ID!, $cost: Decimal) {
+        productVariantUpdate(id: $id,
+        input: {costPrice: $cost}) {
+            errors {
+                field
+                message
+            }
+            productVariant {
+                costPrice{
+                    amount
+                }
+            }
+        }
+    }
+    """
+    variables = {
+        'id': graphene.Node.to_global_id('ProductVariant', variant.id),
+        'cost': 12.12}
+    response = staff_api_client.post_graphql(
+        query, variables, permissions=[permission_manage_products])
+    content = get_graphql_content(response)
+    data = content['data']['productVariantUpdate']
+    assert data['errors'] == []
+
+
+def test_mutation_decimal_input_without_arguments(
+        staff_api_client, variant, permission_manage_products):
+    query = """
+    mutation {
+        productVariantUpdate(id: "%(variant_id)s",
+        input: {costPrice: "%(cost)s"}) {
+            errors {
+                field
+                message
+            }
+            productVariant {
+                costPrice{
+                    amount
+                }
+            }
+        }
+    }
+    """ % {
+        'variant_id': graphene.Node.to_global_id('ProductVariant', variant.id),
+        'cost': 12.12
+    }
+    response = staff_api_client.post_graphql(
+        query, permissions=[permission_manage_products])
+    content = get_graphql_content(response)
+    data = content['data']['productVariantUpdate']
+    assert data['errors'] == []