Repository: saleor/saleor

Issue:
Send fulfillment update mail after updating the tracking number
### What I'm trying to achieve
Send an update email containing the new tracking number after invoking the `FulfillmentUpdateTracking` mutation.

### Describe a proposed solution
As done in dashboard 1.0, we would have a boolean field defaulted to `false` containing whether we should or not send an update mail to the customer.

### References
#### Dashboard 1.0
https://github.com/mirumee/saleor/blob/2ec7faf34c5e674e15a3b27326956effb5f979ee/saleor/dashboard/order/views.py#L687-L694

#### Mutation
https://github.com/mirumee/saleor/blob/9b3990b90699cb484fcc7621beb63bcbef182787/saleor/graphql/order/mutations/fulfillments.py#L160-L173



Discussion:
Same thing for placing a draft order.
We should send an email when a draft order is completed and turned into regular order. When draft order is created, no emails should be sent as this is a temporary state.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Resolving patch:
diff --git a/saleor/graphql/order/mutations/fulfillments.py b/saleor/graphql/order/mutations/fulfillments.py
--- a/saleor/graphql/order/mutations/fulfillments.py
+++ b/saleor/graphql/order/mutations/fulfillments.py
@@ -10,6 +10,7 @@
     fulfillment_tracking_updated,
     order_fulfilled,
 )
+from ....order.emails import send_fulfillment_update
 from ....order.error_codes import OrderErrorCode
 from ...core.mutations import (
     BaseMutation,
@@ -41,7 +42,8 @@ class FulfillmentCreateInput(graphene.InputObjectType):
 class FulfillmentUpdateTrackingInput(graphene.InputObjectType):
     tracking_number = graphene.String(description="Fulfillment tracking number.")
     notify_customer = graphene.Boolean(
-        description="If true, send an email notification to the customer."
+        default_value=False,
+        description="If true, send an email notification to the customer.",
     )
 
 
@@ -210,6 +212,10 @@ def perform_mutation(cls, _root, info, **data):
         fulfillment.save()
         order = fulfillment.order
         fulfillment_tracking_updated(fulfillment, info.context.user, tracking_number)
+        input_data = data.get("input", {})
+        notify_customer = input_data.get("notify_customer")
+        if notify_customer:
+            send_fulfillment_update.delay(order.pk, fulfillment.pk)
         return FulfillmentUpdateTracking(fulfillment=fulfillment, order=order)
 
 


Test patch:
diff --git a/tests/api/test_fulfillment.py b/tests/api/test_fulfillment.py
--- a/tests/api/test_fulfillment.py
+++ b/tests/api/test_fulfillment.py
@@ -162,8 +162,12 @@ def test_create_fulfillment_with_invalid_input(
     )
 
 
+@patch("saleor.order.emails.send_fulfillment_update.delay")
 def test_fulfillment_update_tracking(
-    staff_api_client, fulfillment, permission_manage_orders
+    send_fulfillment_update_mock,
+    staff_api_client,
+    fulfillment,
+    permission_manage_orders,
 ):
     query = """
     mutation updateFulfillment($id: ID!, $tracking: String) {
@@ -184,6 +188,64 @@ def test_fulfillment_update_tracking(
     content = get_graphql_content(response)
     data = content["data"]["orderFulfillmentUpdateTracking"]["fulfillment"]
     assert data["trackingNumber"] == tracking
+    send_fulfillment_update_mock.assert_not_called()
+
+
+FULFILLMENT_UPDATE_TRACKING_WITH_SEND_NOTIFICATION_QUERY = """
+    mutation updateFulfillment($id: ID!, $tracking: String, $notifyCustomer: Boolean) {
+            orderFulfillmentUpdateTracking(
+                id: $id
+                input: {trackingNumber: $tracking, notifyCustomer: $notifyCustomer}) {
+                    fulfillment {
+                        trackingNumber
+                    }
+                }
+        }
+    """
+
+
+@patch("saleor.order.emails.send_fulfillment_update.delay")
+def test_fulfillment_update_tracking_send_notification_true(
+    send_fulfillment_update_mock,
+    staff_api_client,
+    fulfillment,
+    permission_manage_orders,
+):
+    fulfillment_id = graphene.Node.to_global_id("Fulfillment", fulfillment.id)
+    tracking = "stationary tracking"
+    variables = {"id": fulfillment_id, "tracking": tracking, "notifyCustomer": True}
+    response = staff_api_client.post_graphql(
+        FULFILLMENT_UPDATE_TRACKING_WITH_SEND_NOTIFICATION_QUERY,
+        variables,
+        permissions=[permission_manage_orders],
+    )
+    content = get_graphql_content(response)
+    data = content["data"]["orderFulfillmentUpdateTracking"]["fulfillment"]
+    assert data["trackingNumber"] == tracking
+    send_fulfillment_update_mock.assert_called_once_with(
+        fulfillment.order.pk, fulfillment.pk
+    )
+
+
+@patch("saleor.order.emails.send_fulfillment_update.delay")
+def test_fulfillment_update_tracking_send_notification_false(
+    send_fulfillment_update_mock,
+    staff_api_client,
+    fulfillment,
+    permission_manage_orders,
+):
+    fulfillment_id = graphene.Node.to_global_id("Fulfillment", fulfillment.id)
+    tracking = "stationary tracking"
+    variables = {"id": fulfillment_id, "tracking": tracking, "notifyCustomer": False}
+    response = staff_api_client.post_graphql(
+        FULFILLMENT_UPDATE_TRACKING_WITH_SEND_NOTIFICATION_QUERY,
+        variables,
+        permissions=[permission_manage_orders],
+    )
+    content = get_graphql_content(response)
+    data = content["data"]["orderFulfillmentUpdateTracking"]["fulfillment"]
+    assert data["trackingNumber"] == tracking
+    send_fulfillment_update_mock.assert_not_called()
 
 
 def test_cancel_fulfillment_restock_items(