From c65576c57cd57cc8130987a8feac6b7d199cff4b Mon Sep 17 00:00:00 2001 From: Brad Hover Date: Wed, 11 Dec 2024 10:11:38 -0800 Subject: [PATCH] remove extraneous json filters; switch to jsonValue for metafields --- .../script.liquid | 142 +++++++++++------- tasks/advanced-scheduled-price-changes.json | 2 +- 2 files changed, 88 insertions(+), 56 deletions(-) diff --git a/docs/advanced-scheduled-price-changes/script.liquid b/docs/advanced-scheduled-price-changes/script.liquid index a03a0d24..3c609220 100644 --- a/docs/advanced-scheduled-price-changes/script.liquid +++ b/docs/advanced-scheduled-price-changes/script.liquid @@ -22,7 +22,7 @@ key: {{ shop_metafield_key | json }} ) { id - value + jsonValue } } } @@ -38,7 +38,25 @@ "id": "gid://shopify/Shop/1234567890", "metafield": { "id": "gid://shopify/Metafield/9876543210", - "value": "\n{\n \"price_change_event__1234567890\": {\n \"status\": \"scheduled\",\n \"start\": \"2021-12-30 08:00\",\n \"end\": \"2021-12-31 20:00\",\n \"set_compare_at_prices\": true,\n \"collection_handles_and_discounts\": {\n \"collection-alpha\": \"20%\",\n \"collection-beta\": \"-10\",\n \"collection-gamma\": \"20\"\n },\n \"skus_to_include\": [],\n \"sku_discount\": \"\",\n \"skus_to_exclude\": [],\n \"exclude_products_tagged_with\": [\n \"clearance\"\n ]\n }\n}\n" + "jsonValue": { + "price_change_event__1234567890": { + "status": "scheduled", + "start": "2021-12-30 08:00", + "end": "2021-12-31 20:00", + "set_compare_at_prices": true, + "collection_handles_and_discounts": { + "collection-alpha": "20%", + "collection-beta": "-10", + "collection-gamma": "20" + }, + "skus_to_include": [], + "sku_discount": "", + "skus_to_exclude": [], + "exclude_products_tagged_with": [ + "clearance" + ] + } + } } } } @@ -49,7 +67,7 @@ {% endif %} {% assign shop = shop_result.data.shop %} -{% assign price_change_events = shop.metafield.value | default: "{}" | parse_json %} +{% assign price_change_events = shop.metafield.jsonValue | default: hash %} {% if event.topic == "mechanic/user/text" %} {% comment %} @@ -208,7 +226,7 @@ namespace key type - value + jsonValue owner { ... on Shop { id @@ -374,7 +392,7 @@ namespace key type - value + jsonValue owner { ... on Shop { id @@ -546,11 +564,11 @@ {% assign cursor = nil %} - {% for n in (1..10000) %} + {% for n in (1..200) %} {% capture products_query %} query { products( - first: 4 + first: 250 after: {{ cursor | json }} query: {{ query_filter | json }} ) { @@ -575,7 +593,7 @@ namespace: {{ variant_metafield_namespace | json }} key: {{ variant_metafield_key | json }} ) { - value + jsonValue } } } @@ -793,7 +811,7 @@ namespace: {{ variant_metafield_namespace | json }} key: {{ variant_metafield_key | json }} ) { - value + jsonValue } } userErrors { @@ -856,39 +874,39 @@ } {% endaction %} - {% capture email_subject %}A scheduled price change event has started ({{ price_change_event_id }}){% endcapture %} - - {% capture email_body %} - A scheduled price change event has started, using the {{ task_admin_link }} task within the Mechanic app. - - Price change event ID: {{ price_change_event_id }} - Status: {{ price_change_event["status"] }} - Event start: {{ price_change_event["start"] | date: "%F %H:%M %z" }} - Event end: {{ price_change_event["end"] | date: "%F %H:%M %z" }} - Set compare at price to original price during event: {{ price_change_event["set_compare_at_prices"] }} - Collection handles and discounts: - {% for keyval in price_change_event["collection_handles_and_discounts"] -%} - - {{ keyval[0] }}: {{ keyval[1] }} - {% else -%} - n/a - {%- endfor %} - SKUs to include: {{ price_change_event["skus_to_include"] | join: ", " | default: "n/a" }} - SKU discount: {{ price_change_event["sku_discount"] | default: "n/a" }}, - SKUs to exclude: {{ price_change_event["skus_to_exclude"] | join: ", " | default: "n/a" }} - Exclude products tagged with: {{ price_change_event["exclude_products_tagged_with"] | join: ", " | default: "n/a" }} - - Note: To cancel this event while it is ongoing, use the "cancel" keyword along with the price change event ID when running the task. - {% endcapture %} + {% capture email_subject %}A scheduled price change event has started ({{ price_change_event_id }}){% endcapture %} - {% action "email" %} - { - "to": {{ email_recipients | json }}, - "subject": {{ email_subject | json }}, - "body": {{ email_body | newline_to_br | json }}, - "reply_to": {{ shop.customer_email | json }}, - "from_display_name": {{ shop.name | json }} - } - {% endaction %} + {% capture email_body %} + A scheduled price change event has started, using the {{ task_admin_link }} task within the Mechanic app. + + Price change event ID: {{ price_change_event_id }} + Status: {{ price_change_event["status"] }} + Event start: {{ price_change_event["start"] | date: "%F %H:%M %z" }} + Event end: {{ price_change_event["end"] | date: "%F %H:%M %z" }} + Set compare at price to original price during event: {{ price_change_event["set_compare_at_prices"] }} + Collection handles and discounts: + {% for keyval in price_change_event["collection_handles_and_discounts"] -%} + - {{ keyval[0] }}: {{ keyval[1] }} + {% else -%} + n/a + {%- endfor %} + SKUs to include: {{ price_change_event["skus_to_include"] | join: ", " | default: "n/a" }} + SKU discount: {{ price_change_event["sku_discount"] | default: "n/a" }}, + SKUs to exclude: {{ price_change_event["skus_to_exclude"] | join: ", " | default: "n/a" }} + Exclude products tagged with: {{ price_change_event["exclude_products_tagged_with"] | join: ", " | default: "n/a" }} + + Note: To cancel this event while it is ongoing, use the "cancel" keyword along with the price change event ID when running the task. + {% endcapture %} + + {% action "email" %} + { + "to": {{ email_recipients | json }}, + "subject": {{ email_subject | json }}, + "body": {{ email_body | newline_to_br | json }}, + "reply_to": {{ shop.customer_email | json }}, + "from_display_name": {{ shop.name | json }} + } + {% endaction %} {% elsif event.topic == "user/price_changes/end" %} {% assign price_change_event_id = event.data.price_change_event_id %} @@ -932,11 +950,11 @@ {% assign cursor = nil %} - {% for n in (1..10000) %} + {% for n in (1..200) %} {% capture products_query %} query { products( - first: 4 + first: 250 after: {{ cursor | json }} ) { pageInfo { @@ -961,7 +979,7 @@ key: {{ variant_metafield_key | json }} ) { id - value + jsonValue } } } @@ -993,7 +1011,14 @@ "compareAtPrice": "10.00", "metafield": { "id": "gid://shopify/Metafield/9876543210", - "value": "\n{\n \"price_change_event_id\": \"01234567-89ab-cdef\",\n \"discount_to_apply\": \"25%\",\n \"original_compare_at_price\": \"15.00\",\n \"compare_at_price_to_set\": 7.50,\n \"original_price\": \"10.00\",\n \"price_to_set\": 7.50\n}\n" + "jsonValue": { + "price_change_event_id": "01234567-89ab-cdef", + "discount_to_apply": "25%", + "original_compare_at_price": "15.00", + "compare_at_price_to_set": 7.50, + "original_price": "10.00", + "price_to_set": 7.50 + } } } } @@ -1019,7 +1044,7 @@ {% assign variants_with_metafield = product.variants.edges | map: "node" | where: "metafield" %} {% for variant in variants_with_metafield %} - {% assign metafield = variant.metafield.value | parse_json %} + {% assign metafield = variant.metafield.jsonValue %} {% if metafield.price_change_event_id == price_change_event_id %} {% comment %} @@ -1036,8 +1061,8 @@ {% assign metafield_to_delete = hash %} {% assign metafield_to_delete["ownerId"] = variant.id %} - {% assign metafield_to_delete["namespace"] = variant_metafield_namespace | json %} - {% assign metafield_to_delete["key"] = variant_metafield_key | json %} + {% assign metafield_to_delete["namespace"] = variant_metafield_namespace %} + {% assign metafield_to_delete["key"] = variant_metafield_key %} {% assign metafields_to_delete = metafields_to_delete | push: metafield_to_delete %} {% endif %} {% endfor %} @@ -1185,11 +1210,11 @@ {% assign cursor = nil %} - {% for n in (1..10000) %} + {% for n in (1..200) %} {% capture products_query %} query { products( - first: 4 + first: 250 after: {{ cursor | json }} ) { pageInfo { @@ -1214,7 +1239,7 @@ key: {{ variant_metafield_key | json }} ) { id - value + jsonValue } } } @@ -1246,7 +1271,14 @@ "compareAtPrice": "10.00", "metafield": { "id": "gid://shopify/Metafield/9876543210", - "value": "\n{\n \"price_change_event_id\": \"01234567-89ab-cdef\",\n \"discount_to_apply\": \"25%\",\n \"original_compare_at_price\": \"15.00\",\n \"compare_at_price_to_set\": 7.50,\n \"original_price\": \"10.00\",\n \"price_to_set\": 7.50\n}\n" + "jsonValue": { + "price_change_event_id": "01234567-89ab-cdef", + "discount_to_apply": "25%", + "original_compare_at_price": "15.00", + "compare_at_price_to_set": 7.50, + "original_price": "10.00", + "price_to_set": 7.50 + } } } } @@ -1278,7 +1310,7 @@ %} {% for variant in variants_with_metafield %} - {% assign metafield = variant.metafield.value | parse_json %} + {% assign metafield = variant.metafield.jsonValue %} {% comment %} -- revert the prices on this variant and delete the metafield @@ -1294,8 +1326,8 @@ {% assign metafield_to_delete = hash %} {% assign metafield_to_delete["ownerId"] = variant.id %} - {% assign metafield_to_delete["namespace"] = variant_metafield_namespace | json %} - {% assign metafield_to_delete["key"] = variant_metafield_key | json %} + {% assign metafield_to_delete["namespace"] = variant_metafield_namespace %} + {% assign metafield_to_delete["key"] = variant_metafield_key %} {% assign metafields_to_delete = metafields_to_delete | push: metafield_to_delete %} {% endfor %} diff --git a/tasks/advanced-scheduled-price-changes.json b/tasks/advanced-scheduled-price-changes.json index 9b20652e..c5e78bf2 100644 --- a/tasks/advanced-scheduled-price-changes.json +++ b/tasks/advanced-scheduled-price-changes.json @@ -16,7 +16,7 @@ }, "order_status_javascript": null, "perform_action_runs_in_sequence": false, - "script": "{% assign shop_metafield_namespace = \"mechanic\" %}\n{% assign shop_metafield_key = \"price_change_events\" %}\n{% assign variant_metafield_namespace = \"mechanic\" %}\n{% assign variant_metafield_key = \"price_change_event\" %}\n\n{% assign email_recipients = options.notification_email_recipients__array_required %}\n\n{% capture task_admin_link -%}\n{{ task.name | remove: \"ADVANCED: \" }}\n{%- endcapture %}\n\n{% comment %}\n -- query for shop data on every task run, since it will be needed for each valid action keyword and custom event\n{% endcomment %}\n\n{% capture shop_query %}\n query {\n shop {\n id\n metafield(\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n ) {\n id\n value\n }\n }\n }\n{% endcapture %}\n\n{% assign shop_result = shop_query | shopify %}\n\n{% if event.preview %}\n {% capture shop_result_json %}\n {\n \"data\": {\n \"shop\": {\n \"id\": \"gid://shopify/Shop/1234567890\",\n \"metafield\": {\n \"id\": \"gid://shopify/Metafield/9876543210\",\n \"value\": \"\\n{\\n \\\"price_change_event__1234567890\\\": {\\n \\\"status\\\": \\\"scheduled\\\",\\n \\\"start\\\": \\\"2021-12-30 08:00\\\",\\n \\\"end\\\": \\\"2021-12-31 20:00\\\",\\n \\\"set_compare_at_prices\\\": true,\\n \\\"collection_handles_and_discounts\\\": {\\n \\\"collection-alpha\\\": \\\"20%\\\",\\n \\\"collection-beta\\\": \\\"-10\\\",\\n \\\"collection-gamma\\\": \\\"20\\\"\\n },\\n \\\"skus_to_include\\\": [],\\n \\\"sku_discount\\\": \\\"\\\",\\n \\\"skus_to_exclude\\\": [],\\n \\\"exclude_products_tagged_with\\\": [\\n \\\"clearance\\\"\\n ]\\n }\\n}\\n\"\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign shop_result = shop_result_json | parse_json %}\n{% endif %}\n\n{% assign shop = shop_result.data.shop %}\n{% assign price_change_events = shop.metafield.value | default: \"{}\" | parse_json %}\n\n{% if event.topic == \"mechanic/user/text\" %}\n {% comment %}\n -- check text entry to see what action to take\n {% endcomment %}\n\n {% assign action_keyword = event.data | downcase %}\n\n {% if event.preview %}\n {% assign action_keyword = \"schedule\" %}\n {% endif %}\n\n {% case action_keyword %}\n {% when \"schedule\" %}\n {% comment %}\n -- NOTE: run scheduling logic here instead of in custom event like most other keywords, so user gets immediate feedback on any configuration errors\n {% endcomment %}\n\n {% assign valid_start_datetime = options.event_start_datetime__required | parse_date: \"%Y-%m-%d %H:%M\" %}\n\n {% if valid_start_datetime == blank %}\n {% assign valid_start_datetime = options.event_start_datetime__required | parse_date: \"%Y-%m-%d\" %}\n\n {% if valid_start_datetime == blank %}\n {% unless event.preview %}\n {% error \"The event start date is not a valid date. Please re-enter it (per the task documentation) and try scheduling the event again.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n {% endif %}\n\n {% assign valid_end_datetime = options.event_end_datetime__required | parse_date: \"%Y-%m-%d %H:%M\" %}\n\n {% if valid_end_datetime == blank %}\n {% assign valid_end_datetime = options.event_end_datetime__required | parse_date: \"%Y-%m-%d\" %}\n\n {% if valid_end_datetime == blank %}\n {% unless event.preview %}\n {% error \"The event end date is not a valid date. Please re-enter it (per the task documentation) and try scheduling the event again.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n {% endif %}\n\n {% assign now = \"now\" | date: \"%s\" %}\n {% assign start_datetime_s = options.event_start_datetime__required | date: \"%s\" %}\n {% assign end_datetime_s = options.event_end_datetime__required | date: \"%s\" %}\n\n {% if start_datetime_s <= now or start_datetime_s >= end_datetime_s %}\n {% unless event.preview %}\n {% error \"The event start and end dates must be future dates and the start date must be before the end date. Please re-enter them (per the task documentation) and try scheduling the event again.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% assign set_compare_at_prices = options.set_compare_at_prices_to_original_price_during_event__boolean %}\n {% assign collection_handles_and_discounts = options.collection_handles_and_discounts__keyval %}\n {% assign skus_to_include = options.skus_to_include__array %}\n {% assign sku_discount = options.sku_discount %}\n {% assign exclude_products_tagged_with = options.exclude_products_tagged_with__array %}\n {% assign skus_to_exclude = options.skus_to_exclude__array %}\n\n {% comment %}\n -- validate discount entry formats: XX% (percentage discount), X.XX (fixed price), or -X.XX (fixed discount)\n {% endcomment %}\n\n {% assign discounts = collection_handles_and_discounts | values | default: array %}\n\n {% if skus_to_include != blank and sku_discount == blank %}\n {% unless event.preview %}\n {% error \"A SKU discount must be configured when specific SKUs are included.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% if sku_discount != blank %}\n {% assign discounts = discounts | push: sku_discount %}\n {% endif %}\n\n {% for discount in discounts %}\n {% if discount contains \"%\" %}\n {% assign discount_percentage = discount | times: 1 %}\n {% assign discount_string = discount_percentage | append: \"%\" %}\n\n {% if discount != discount_string %}\n {% unless event.preview %}\n {% error\n message: \"Percentage discount entry incorrect. Please update the task configuration (per the task documentation) and try scheduling the event again.\",\n discount: discount\n %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% if discount_percentage <= 0 or discount_percentage >= 100 %}\n {% unless event.preview %}\n {% error\n message: \"Percentage discount entries should contain an number between 0 and 100. Please update the task configuration (per the task documentation) and try scheduling the event again.\",\n discount: discount\n %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% else %}\n {% assign discount_absolute = discount | times: 1 | abs %}\n\n {% if discount_absolute == 0 %}\n {% unless event.preview %}\n {% error\n message: \"Fixed price and fixed discount entries must contain an absolute number > 0. Please update the task configuration (per the task documentation) and try scheduling the event again.\",\n discount: discount\n %}\n {% break %}\n {% endunless %}\n {% endif %}\n {% endif %}\n {% endfor %}\n\n {% assign price_change_event = hash %}\n\n {% assign price_change_event[\"status\"] = \"scheduled\" %}\n {% assign price_change_event[\"start\"] = start_datetime_s %}\n {% assign price_change_event[\"end\"] = end_datetime_s %}\n {% assign price_change_event[\"set_compare_at_prices\"] = set_compare_at_prices %}\n {% assign price_change_event[\"collection_handles_and_discounts\"] = collection_handles_and_discounts %}\n {% assign price_change_event[\"skus_to_include\"] = skus_to_include %}\n {% assign price_change_event[\"sku_discount\"] = sku_discount %}\n {% assign price_change_event[\"skus_to_exclude\"] = skus_to_exclude %}\n {% assign price_change_event[\"exclude_products_tagged_with\"] = exclude_products_tagged_with %}\n\n {% log\n task_options: task.options,\n price_change_event: price_change_event\n %}\n\n {% assign price_change_event_id = event.id | default: task.id %}\n\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n value\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% comment %}\n -- schedule start and end events\n {% endcomment %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/start\",\n \"task_id\": {{ task.id | json }},\n \"run_at\": {{ start_datetime_s | json }},\n \"data\": {\n \"price_change_event_id\": {{ price_change_event_id | json }}\n }\n }\n {% endaction %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/end\",\n \"task_id\": {{ task.id | json }},\n \"run_at\": {{ end_datetime_s | json }},\n \"data\": {\n \"price_change_event_id\": {{ price_change_event_id | json }}\n }\n }\n {% endaction %}\n\n {% capture email_subject %}New price change event scheduled ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A new price change event has been scheduled, from the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n\n Note: To cancel this event while it is still scheduled or ongoing, use the \"cancel\" keyword along with the price change event ID when running the task.\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% when \"list\" %}\n {% action \"echo\" price_change_events: price_change_events %}\n\n {% when \"email\" %}\n {% if price_change_events == blank %}\n {% assign email_body = \"There are currently no configured price change events\" %}\n\n {% else %}\n {% capture email_body %}\n Currently configured price change events, using the {{ task_admin_link }} task within the Mechanic app.\n\n {% for price_change_event in price_change_events -%}\n {%- assign price_change_event_id = price_change_event[0] -%}\n {%- assign price_change_event_data = price_change_event[1] -%}\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event_data[\"status\"] }}\n Event start: {{ price_change_event_data[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event_data[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event_data[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event_data[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event_data[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event_data[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event_data[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event_data[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n
\n {%- endfor %}\n {% endcapture %}\n {% endif %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": \"All price change events as of {{ \"now\" | date: \"%F\" }}\",\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% when \"reset\" %}\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/reset\",\n \"task_id\": {{ task.id | json }}\n }\n {% endaction %}\n\n {% else %}\n {% if action_keyword contains \"cancel\" %}\n {% assign price_change_event_id = action_keyword | remove: \"cancel \" %}\n {% assign price_change_event = price_change_events[price_change_event_id] %}\n\n {% if price_change_event == blank %}\n {% error \"Keyword of 'cancel' entered with an invalid price change event ID. Run task with 'list' keyword to see a list of all configured price change events.\" %}\n {% break %}\n {% endif %}\n\n {% if price_change_event.status == \"scheduled\" %}\n {% comment %}\n -- price change event has not started, so can just update it to cancelled and when the start/end events run they will ignore this event\n {% endcomment %}\n\n {% assign price_change_event[\"status\"] = \"cancelled\" %}\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n value\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% capture email_subject %}Scheduled price change event has been cancelled ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A scheduled price change event has been cancelled, from the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% elsif price_change_event.status == \"ongoing\" %}\n {% comment %}\n -- price change event in progress, so we have to revert the price changes to properly cancel it\n {% endcomment %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/end\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"price_change_event_id\": {{ price_change_event_id | json }},\n \"cancel\": true\n }\n }\n {% endaction %}\n\n {% else %}\n {% action \"echo\"\n message: \"Price change event cannot be cancelled because it does not have a status of 'scheduled' or 'ongoing'\",\n price_change_event: price_change_event\n %}\n {% endif %}\n\n {% else %}\n {% error\n message: \"Unrecognized action keyword. Action keyword must be one of 'schedule', 'list', 'email', or 'cancel'\",\n action_keyword: action_keyword\n %}\n {% endif %}\n {% endcase %}\n\n{% elsif event.topic == \"user/price_changes/start\" %}\n {% assign price_change_event_id = event.data.price_change_event_id %}\n {% assign price_change_event = price_change_events[price_change_event_id] %}\n\n {% if event.preview %}\n {% assign price_change_event_id = \"01234567-89ab-cdef\" %}\n\n {% capture price_change_event_json %}\n {\n \"status\": \"scheduled\",\n \"start\": {{ \"now + 1 day\" | date: \"%s\" }},\n \"end\": {{ \"now + 2 days\" | date: \"%s\" }},\n \"set_compare_at_prices\": false,\n \"collection_handles_and_discounts\": {\n \"alpha-beta-gamma\": \"10%\",\n \"sticks-and-stones\": \"-5.50\",\n \"lorem-ipsum\": \"3\"\n },\n \"skus_to_include\": [\n \"SKU-123\",\n \"SKU-456\"\n ],\n \"sku_discount\": \"15%\",\n \"skus_to_exclude\": [\n \"SKU-987\"\n ],\n \"exclude_products_tagged_with\": [\n \"do-not-discount\"\n ]\n }\n {% endcapture %}\n\n {% assign price_change_event = price_change_event_json | parse_json %}\n {% endif %}\n\n {% assign status = price_change_event.status %}\n {% assign set_compare_at_prices = price_change_event.set_compare_at_prices %}\n {% assign collection_handles_and_discounts = price_change_event.collection_handles_and_discounts %}\n {% assign skus_to_include = price_change_event.skus_to_include %}\n {% assign sku_discount = price_change_event.sku_discount %}\n {% assign exclude_products_tagged_with = price_change_event.exclude_products_tagged_with %}\n {% assign skus_to_exclude = price_change_event.skus_to_exclude %}\n\n {% if status != \"scheduled\" %}\n {% log\n message: \"This price change event does not have a status of scheduled, and thus will not start.\",\n price_change_event: price_change_event\n %}\n {% break %}\n {% endif %}\n\n {% comment %}\n -- get collection ids for products query\n {% endcomment %}\n\n {% assign in_collection_checks = array %}\n\n {% for keyval in collection_handles_and_discounts %}\n {% capture collection_query %}\n query {\n collectionByHandle(handle: {{ keyval[0] | json }}) {\n id\n handle\n }\n }\n {% endcapture %}\n\n {% assign collection_result = collection_query | shopify %}\n {% assign collection = collection_result.data.collectionByHandle %}\n\n {% if collection != blank %}\n {% capture in_collection_check -%}\n inCollection_{{ collection.handle | replace: \"-\", \"_\" }}: inCollection(id: {{ collection.id | json }})\n {%- endcapture %}\n\n {% assign in_collection_checks = in_collection_checks | push: in_collection_check %}\n {% endif %}\n {% endfor %}\n\n {% assign query_filter = \"gift_card:false\" %}\n\n {% for tag in exclude_products_tagged_with %}\n {% assign query_filter = tag | json | prepend: \" tag_not:\" | prepend: query_filter %}\n {% endfor %}\n\n {% log query_filter: query_filter %}\n\n {% assign products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (1..10000) %}\n {% capture products_query %}\n query {\n products(\n first: 4\n after: {{ cursor | json }}\n query: {{ query_filter | json }}\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n id\n title\n tags\n variants(first: 100) {\n edges {\n node {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n value\n }\n }\n }\n }\n {{ in_collection_checks | join: newline }}\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_query | shopify %}\n\n {% if event.preview %}\n {% capture products_result_json %}\n {\n \"data\": {\n \"products\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"variants\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"sku\": \"ACME-BRICK-RED\",\n \"price\": \"10.00\",\n \"compareAtPrice\": \"15.00\",\n \"metafield\": null\n }\n }\n ]\n },\n \"inCollection_{{ collection_handles_and_discounts.first.first | replace: \"-\", \"_\" | default: \"sample\" }}\": true\n }\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_result_json | parse_json %}\n {% endif %}\n\n {% assign products = products_result.data.products.edges | map: \"node\" %}\n\n {% for product in products %}\n {% comment %}\n -- For each product, match to a collection discount if applicable, then, if needed, loop through variants to see if there are any overrides\n {% endcomment %}\n\n {% assign product_level_discount = nil %}\n\n {% for keyval in collection_handles_and_discounts %}\n {% assign in_collection_label\n = \"inCollection_\"\n | append: keyval[0]\n | replace: \"-\", \"_\"\n %}\n\n {% if product[in_collection_label] %}\n {% assign product_level_discount = keyval[1] %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if product_level_discount == blank\n and skus_to_include == blank\n and skus_to_exclude == blank\n %}\n {% log\n message: \"This product is not within any configured collections, nor does the price change event have any sku inclusion or exclusion settings; skipping. \",\n product: product\n %}\n {% continue %}\n {% endif %}\n\n {% assign variants = product.variants.edges | map: \"node\" %}\n\n {% assign variant_updates = array %}\n\n {% for variant in variants %}\n {% if skus_to_exclude != blank and skus_to_exclude contains variant.sku %}\n {% log\n message: \"This variant was excluded by SKU\",\n variant: variant\n %}\n {% continue %}\n {% endif %}\n\n {% if variant.metafield != blank %}\n {% log\n message: \"This variant is already part of a price change event; skipping.\",\n variant: variant\n %}\n {% continue %}\n {% endif %}\n\n {% assign discount_to_apply = product_level_discount %}\n\n {% if skus_to_include != blank and skus_to_include contains variant.sku %}\n {% assign discount_to_apply = sku_discount %}\n {% endif %}\n\n {% log\n product_title: product.title,\n sku: variant.sku,\n skus_to_include: skus_to_include,\n sku_discount: sku_discount,\n discount_to_apply: discount_to_apply\n %}\n\n {% if discount_to_apply == blank %}\n {% log\n message: \"This variant's product is not within any configured collections, nor has this variant been specifically included by sku; skipping.\",\n variant: variant\n %}\n {% continue %}\n {% endif %}\n\n {% assign price_to_set = nil %}\n {% assign compare_at_price_to_set = nil %}\n\n {% if discount_to_apply contains \"%\" %}\n {% assign price_to_set\n = discount_to_apply\n | remove: \"%\"\n | minus: 100\n | abs\n | times: variant.price\n | divided_by: 100\n | round: 2\n %}\n\n {% elsif discount_to_apply contains \"-\" %}\n {% assign price_to_set\n = variant.price\n | plus: discount_to_apply\n | at_least: 0.0\n %}\n\n {% else %}\n {% assign price_to_set = discount_to_apply %}\n {% endif %}\n\n {% if set_compare_at_prices %}\n {% assign compare_at_price_to_set = variant.price %}\n {% endif %}\n\n {% log\n product: product,\n product_level_discount: product_level_discount,\n variant: variant,\n discount_to_apply: discount_to_apply,\n price_to_set: price_to_set,\n compare_at_price_to_set: compare_at_price_to_set\n %}\n\n {% capture variant_metafield_value %}\n {\n \"price_change_event_id\": {{ price_change_event_id | json }},\n \"discount_to_apply\": {{ discount_to_apply | json }},\n {% if set_compare_at_prices %}\n \"original_compare_at_price\": {{ variant.compareAtPrice | json }},\n \"compare_at_price_to_set\": {{ compare_at_price_to_set | json }},\n {% endif %}\n \"original_price\": {{ variant.price | json }},\n \"price_to_set\": {{ price_to_set | json }}\n }\n {% endcapture %}\n\n {% capture variant_update %}\n {\n id: {{ variant.id | json}}\n price: {{ price_to_set | json }}\n {% if set_compare_at_prices %}compareAtPrice: {{ variant.price | json }}{% endif %}\n metafields: [\n {\n key: {{ variant_metafield_key | json }}\n namespace: {{ variant_metafield_namespace | json }}\n type: \"json\"\n value: {{ variant_metafield_value | json }}\n }\n ]\n }\n {% endcapture %}\n\n {% assign variant_updates = variant_updates | push: variant_update %}\n {% endfor %}\n\n {% if variant_updates != blank %}\n {% action \"shopify\" %}\n mutation {\n productVariantsBulkUpdate(\n productId: {{ product.id | json }}\n variants: [\n {{ variant_updates | join: newline }}\n ]\n ) {\n product {\n id\n title\n tags\n }\n productVariants {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n value\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if products_result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = products_result.data.products.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- update the price change event\n {% endcomment %}\n\n {% assign price_change_event[\"status\"] = \"ongoing\" %}\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n value\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% capture email_subject %}A scheduled price change event has started ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A scheduled price change event has started, using the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n\n Note: To cancel this event while it is ongoing, use the \"cancel\" keyword along with the price change event ID when running the task.\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n{% elsif event.topic == \"user/price_changes/end\" %}\n {% assign price_change_event_id = event.data.price_change_event_id %}\n {% assign price_change_event = price_change_events[price_change_event_id] %}\n\n {% if event.preview %}\n {% assign price_change_event_id = \"01234567-89ab-cdef\" %}\n {% assign price_change_event = hash %}\n {% assign price_change_event[\"status\"] = \"ongoing\" %}\n {% endif %}\n\n {% if event.data.cancel %}\n {% comment %}\n -- go ahead and cancel, as status was already checked to be \"scheduled\" prior to custom event call\n {% endcomment %}\n\n {% assign price_change_event[\"status\"] = \"cancelled\" %}\n\n {% else %}\n {% comment %}\n -- since this was a scheduled run, need to make sure the event status is \"ongoing\" before reverting changes\n {% endcomment %}\n\n {% if price_change_event.status != \"ongoing\" %}\n {% log\n message: \"This price change event does not have a status of 'ongoing', and thus will not be reverted.\",\n price_change_event: price_change_event\n %}\n {% break %}\n {% endif %}\n\n {% assign price_change_event[\"status\"] = \"completed\" %}\n {% endif %}\n\n {% comment %}\n -- To revert the price change event, check every variant in the shop to see if the metafield exists and contains this price change event ID\n -- Note: Query from the product level so that productVariantsBulkUpdate can be used, causing only one product update event to fire\n {% endcomment %}\n\n {% assign products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (1..10000) %}\n {% capture products_query %}\n query {\n products(\n first: 4\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n id\n title\n tags\n variants(first: 100) {\n edges {\n node {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n id\n value\n }\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_query | shopify %}\n\n {% if event.preview %}\n {% capture products_result_json %}\n {\n \"data\": {\n \"products\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"variants\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"sku\": \"ACME-BRICK-RED\",\n \"price\": \"7.50\",\n \"compareAtPrice\": \"10.00\",\n \"metafield\": {\n \"id\": \"gid://shopify/Metafield/9876543210\",\n \"value\": \"\\n{\\n \\\"price_change_event_id\\\": \\\"01234567-89ab-cdef\\\",\\n \\\"discount_to_apply\\\": \\\"25%\\\",\\n \\\"original_compare_at_price\\\": \\\"15.00\\\",\\n \\\"compare_at_price_to_set\\\": 7.50,\\n \\\"original_price\\\": \\\"10.00\\\",\\n \\\"price_to_set\\\": 7.50\\n}\\n\"\n }\n }\n }\n ]\n }\n }\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_result_json | parse_json %}\n {% endif %}\n\n {% assign products = products_result.data.products.edges | map: \"node\" %}\n\n {% for product in products %}\n {% assign variant_updates = array %}\n {% assign metafields_to_delete = array %}\n\n {% assign variants_with_metafield = product.variants.edges | map: \"node\" | where: \"metafield\" %}\n\n {% for variant in variants_with_metafield %}\n {% assign metafield = variant.metafield.value | parse_json %}\n\n {% if metafield.price_change_event_id == price_change_event_id %}\n {% comment %}\n -- revert the prices on this variant and delete the metafield\n {% endcomment %}\n\n {% assign variant_update = hash %}\n {% assign variant_update[\"id\"] = variant.id %}\n {% assign variant_update[\"price\"] = metafield.original_price %}\n {% if metafield.original_compare_at_price %}\n {% assign variant_update[\"compareAtPrice\"] = metafield.original_compare_at_price %}\n {% endif %}\n {% assign variant_updates = variant_updates | push: variant_update %}\n\n {% assign metafield_to_delete = hash %}\n {% assign metafield_to_delete[\"ownerId\"] = variant.id %}\n {% assign metafield_to_delete[\"namespace\"] = variant_metafield_namespace | json %}\n {% assign metafield_to_delete[\"key\"] = variant_metafield_key | json %}\n {% assign metafields_to_delete = metafields_to_delete | push: metafield_to_delete %}\n {% endif %}\n {% endfor %}\n\n {% if metafields_to_delete != blank %}\n {% action \"shopify\" %}\n mutation {\n metafieldsDelete(\n metafields: {{ metafields_to_delete | graphql_arguments }}\n ) {\n deletedMetafields {\n ownerId\n namespace\n key\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n {% if variant_updates != blank %}\n {% action \"shopify\" %}\n mutation {\n productVariantsBulkUpdate(\n productId: {{ product.id | json }}\n variants: {{ variant_updates | graphql_arguments }}\n ) {\n product {\n id\n title\n }\n productVariants {\n id\n displayName\n sku\n price\n compareAtPrice\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if products_result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = products_result.data.products.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- update the price change event\n {% endcomment %}\n\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n value\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% comment %}\n -- send email notification about the price change event status change\n {% endcomment %}\n\n {% capture email_subject %}A price change event has been {{ price_change_event[\"status\"] }} ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A price change event has been {{ price_change_event[\"status\"] }}, using the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n{% elsif event.topic == \"user/price_changes/reset\" %}\n {% comment %}\n -- To reset price change events, check every variant in the shop to see if the price change event metafield exists\n -- Note: Query from the product level so that productVariantsBulkUpdate can be used, causing only one product update event to fire\n {% endcomment %}\n\n {% assign products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (1..10000) %}\n {% capture products_query %}\n query {\n products(\n first: 4\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n id\n title\n tags\n variants(first: 100) {\n edges {\n node {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n id\n value\n }\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_query | shopify %}\n\n {% if event.preview %}\n {% capture products_result_json %}\n {\n \"data\": {\n \"products\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"variants\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"sku\": \"ACME-BRICK-RED\",\n \"price\": \"7.50\",\n \"compareAtPrice\": \"10.00\",\n \"metafield\": {\n \"id\": \"gid://shopify/Metafield/9876543210\",\n \"value\": \"\\n{\\n \\\"price_change_event_id\\\": \\\"01234567-89ab-cdef\\\",\\n \\\"discount_to_apply\\\": \\\"25%\\\",\\n \\\"original_compare_at_price\\\": \\\"15.00\\\",\\n \\\"compare_at_price_to_set\\\": 7.50,\\n \\\"original_price\\\": \\\"10.00\\\",\\n \\\"price_to_set\\\": 7.50\\n}\\n\"\n }\n }\n }\n ]\n }\n }\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_result_json | parse_json %}\n {% endif %}\n\n {% assign products = products_result.data.products.edges | map: \"node\" %}\n\n {% for product in products %}\n {% assign variant_updates = array %}\n {% assign metafields_to_delete = array %}\n\n {% assign variants_with_metafield = product.variants.edges | map: \"node\" | where: \"metafield\" %}\n\n {% log\n product_id: product.id,\n variants_count: product.variants.edges.size,\n variants_with_metafield: variants_with_metafield.size\n %}\n\n {% for variant in variants_with_metafield %}\n {% assign metafield = variant.metafield.value | parse_json %}\n\n {% comment %}\n -- revert the prices on this variant and delete the metafield\n {% endcomment %}\n\n {% assign variant_update = hash %}\n {% assign variant_update[\"id\"] = variant.id %}\n {% assign variant_update[\"price\"] = metafield.original_price %}\n {% if metafield.original_compare_at_price %}\n {% assign variant_update[\"compareAtPrice\"] = metafield.original_compare_at_price %}\n {% endif %}\n {% assign variant_updates = variant_updates | push: variant_update %}\n\n {% assign metafield_to_delete = hash %}\n {% assign metafield_to_delete[\"ownerId\"] = variant.id %}\n {% assign metafield_to_delete[\"namespace\"] = variant_metafield_namespace | json %}\n {% assign metafield_to_delete[\"key\"] = variant_metafield_key | json %}\n {% assign metafields_to_delete = metafields_to_delete | push: metafield_to_delete %}\n {% endfor %}\n\n {% if metafields_to_delete != blank %}\n {% action \"shopify\" %}\n mutation {\n metafieldsDelete(\n metafields: {{ metafields_to_delete | graphql_arguments }}\n ) {\n deletedMetafields {\n ownerId\n namespace\n key\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n {% if variant_updates != blank %}\n {% action \"shopify\" %}\n mutation {\n productVariantsBulkUpdate(\n productId: {{ product.id | json }}\n variants: {{ variant_updates | graphql_arguments }}\n ) {\n product {\n id\n title\n }\n productVariants {\n id\n displayName\n sku\n price\n compareAtPrice\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if products_result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = products_result.data.products.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- delete the price change events shop metafield\n {% endcomment %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsDelete(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n }\n ]\n ) {\n deletedMetafields {\n ownerId\n namespace\n key\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% comment %}\n -- send email notification about the price change events reset\n {% endcomment %}\n\n {% capture email_subject %}All price change events have been cleared{% endcapture %}\n\n {% capture email_body %}\n All price change events have been reverted and cleared, using the {{ task_admin_link }} task within the Mechanic app.\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n{% endif %}\n", + "script": "{% assign shop_metafield_namespace = \"mechanic\" %}\n{% assign shop_metafield_key = \"price_change_events\" %}\n{% assign variant_metafield_namespace = \"mechanic\" %}\n{% assign variant_metafield_key = \"price_change_event\" %}\n\n{% assign email_recipients = options.notification_email_recipients__array_required %}\n\n{% capture task_admin_link -%}\n{{ task.name | remove: \"ADVANCED: \" }}\n{%- endcapture %}\n\n{% comment %}\n -- query for shop data on every task run, since it will be needed for each valid action keyword and custom event\n{% endcomment %}\n\n{% capture shop_query %}\n query {\n shop {\n id\n metafield(\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n ) {\n id\n jsonValue\n }\n }\n }\n{% endcapture %}\n\n{% assign shop_result = shop_query | shopify %}\n\n{% if event.preview %}\n {% capture shop_result_json %}\n {\n \"data\": {\n \"shop\": {\n \"id\": \"gid://shopify/Shop/1234567890\",\n \"metafield\": {\n \"id\": \"gid://shopify/Metafield/9876543210\",\n \"jsonValue\": {\n \"price_change_event__1234567890\": {\n \"status\": \"scheduled\",\n \"start\": \"2021-12-30 08:00\",\n \"end\": \"2021-12-31 20:00\",\n \"set_compare_at_prices\": true,\n \"collection_handles_and_discounts\": {\n \"collection-alpha\": \"20%\",\n \"collection-beta\": \"-10\",\n \"collection-gamma\": \"20\"\n },\n \"skus_to_include\": [],\n \"sku_discount\": \"\",\n \"skus_to_exclude\": [],\n \"exclude_products_tagged_with\": [\n \"clearance\"\n ]\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign shop_result = shop_result_json | parse_json %}\n{% endif %}\n\n{% assign shop = shop_result.data.shop %}\n{% assign price_change_events = shop.metafield.jsonValue | default: hash %}\n\n{% if event.topic == \"mechanic/user/text\" %}\n {% comment %}\n -- check text entry to see what action to take\n {% endcomment %}\n\n {% assign action_keyword = event.data | downcase %}\n\n {% if event.preview %}\n {% assign action_keyword = \"schedule\" %}\n {% endif %}\n\n {% case action_keyword %}\n {% when \"schedule\" %}\n {% comment %}\n -- NOTE: run scheduling logic here instead of in custom event like most other keywords, so user gets immediate feedback on any configuration errors\n {% endcomment %}\n\n {% assign valid_start_datetime = options.event_start_datetime__required | parse_date: \"%Y-%m-%d %H:%M\" %}\n\n {% if valid_start_datetime == blank %}\n {% assign valid_start_datetime = options.event_start_datetime__required | parse_date: \"%Y-%m-%d\" %}\n\n {% if valid_start_datetime == blank %}\n {% unless event.preview %}\n {% error \"The event start date is not a valid date. Please re-enter it (per the task documentation) and try scheduling the event again.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n {% endif %}\n\n {% assign valid_end_datetime = options.event_end_datetime__required | parse_date: \"%Y-%m-%d %H:%M\" %}\n\n {% if valid_end_datetime == blank %}\n {% assign valid_end_datetime = options.event_end_datetime__required | parse_date: \"%Y-%m-%d\" %}\n\n {% if valid_end_datetime == blank %}\n {% unless event.preview %}\n {% error \"The event end date is not a valid date. Please re-enter it (per the task documentation) and try scheduling the event again.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n {% endif %}\n\n {% assign now = \"now\" | date: \"%s\" %}\n {% assign start_datetime_s = options.event_start_datetime__required | date: \"%s\" %}\n {% assign end_datetime_s = options.event_end_datetime__required | date: \"%s\" %}\n\n {% if start_datetime_s <= now or start_datetime_s >= end_datetime_s %}\n {% unless event.preview %}\n {% error \"The event start and end dates must be future dates and the start date must be before the end date. Please re-enter them (per the task documentation) and try scheduling the event again.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% assign set_compare_at_prices = options.set_compare_at_prices_to_original_price_during_event__boolean %}\n {% assign collection_handles_and_discounts = options.collection_handles_and_discounts__keyval %}\n {% assign skus_to_include = options.skus_to_include__array %}\n {% assign sku_discount = options.sku_discount %}\n {% assign exclude_products_tagged_with = options.exclude_products_tagged_with__array %}\n {% assign skus_to_exclude = options.skus_to_exclude__array %}\n\n {% comment %}\n -- validate discount entry formats: XX% (percentage discount), X.XX (fixed price), or -X.XX (fixed discount)\n {% endcomment %}\n\n {% assign discounts = collection_handles_and_discounts | values | default: array %}\n\n {% if skus_to_include != blank and sku_discount == blank %}\n {% unless event.preview %}\n {% error \"A SKU discount must be configured when specific SKUs are included.\" %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% if sku_discount != blank %}\n {% assign discounts = discounts | push: sku_discount %}\n {% endif %}\n\n {% for discount in discounts %}\n {% if discount contains \"%\" %}\n {% assign discount_percentage = discount | times: 1 %}\n {% assign discount_string = discount_percentage | append: \"%\" %}\n\n {% if discount != discount_string %}\n {% unless event.preview %}\n {% error\n message: \"Percentage discount entry incorrect. Please update the task configuration (per the task documentation) and try scheduling the event again.\",\n discount: discount\n %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% if discount_percentage <= 0 or discount_percentage >= 100 %}\n {% unless event.preview %}\n {% error\n message: \"Percentage discount entries should contain an number between 0 and 100. Please update the task configuration (per the task documentation) and try scheduling the event again.\",\n discount: discount\n %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% else %}\n {% assign discount_absolute = discount | times: 1 | abs %}\n\n {% if discount_absolute == 0 %}\n {% unless event.preview %}\n {% error\n message: \"Fixed price and fixed discount entries must contain an absolute number > 0. Please update the task configuration (per the task documentation) and try scheduling the event again.\",\n discount: discount\n %}\n {% break %}\n {% endunless %}\n {% endif %}\n {% endif %}\n {% endfor %}\n\n {% assign price_change_event = hash %}\n\n {% assign price_change_event[\"status\"] = \"scheduled\" %}\n {% assign price_change_event[\"start\"] = start_datetime_s %}\n {% assign price_change_event[\"end\"] = end_datetime_s %}\n {% assign price_change_event[\"set_compare_at_prices\"] = set_compare_at_prices %}\n {% assign price_change_event[\"collection_handles_and_discounts\"] = collection_handles_and_discounts %}\n {% assign price_change_event[\"skus_to_include\"] = skus_to_include %}\n {% assign price_change_event[\"sku_discount\"] = sku_discount %}\n {% assign price_change_event[\"skus_to_exclude\"] = skus_to_exclude %}\n {% assign price_change_event[\"exclude_products_tagged_with\"] = exclude_products_tagged_with %}\n\n {% log\n task_options: task.options,\n price_change_event: price_change_event\n %}\n\n {% assign price_change_event_id = event.id | default: task.id %}\n\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n jsonValue\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% comment %}\n -- schedule start and end events\n {% endcomment %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/start\",\n \"task_id\": {{ task.id | json }},\n \"run_at\": {{ start_datetime_s | json }},\n \"data\": {\n \"price_change_event_id\": {{ price_change_event_id | json }}\n }\n }\n {% endaction %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/end\",\n \"task_id\": {{ task.id | json }},\n \"run_at\": {{ end_datetime_s | json }},\n \"data\": {\n \"price_change_event_id\": {{ price_change_event_id | json }}\n }\n }\n {% endaction %}\n\n {% capture email_subject %}New price change event scheduled ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A new price change event has been scheduled, from the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n\n Note: To cancel this event while it is still scheduled or ongoing, use the \"cancel\" keyword along with the price change event ID when running the task.\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% when \"list\" %}\n {% action \"echo\" price_change_events: price_change_events %}\n\n {% when \"email\" %}\n {% if price_change_events == blank %}\n {% assign email_body = \"There are currently no configured price change events\" %}\n\n {% else %}\n {% capture email_body %}\n Currently configured price change events, using the {{ task_admin_link }} task within the Mechanic app.\n\n {% for price_change_event in price_change_events -%}\n {%- assign price_change_event_id = price_change_event[0] -%}\n {%- assign price_change_event_data = price_change_event[1] -%}\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event_data[\"status\"] }}\n Event start: {{ price_change_event_data[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event_data[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event_data[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event_data[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event_data[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event_data[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event_data[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event_data[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n
\n {%- endfor %}\n {% endcapture %}\n {% endif %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": \"All price change events as of {{ \"now\" | date: \"%F\" }}\",\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% when \"reset\" %}\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/reset\",\n \"task_id\": {{ task.id | json }}\n }\n {% endaction %}\n\n {% else %}\n {% if action_keyword contains \"cancel\" %}\n {% assign price_change_event_id = action_keyword | remove: \"cancel \" %}\n {% assign price_change_event = price_change_events[price_change_event_id] %}\n\n {% if price_change_event == blank %}\n {% error \"Keyword of 'cancel' entered with an invalid price change event ID. Run task with 'list' keyword to see a list of all configured price change events.\" %}\n {% break %}\n {% endif %}\n\n {% if price_change_event.status == \"scheduled\" %}\n {% comment %}\n -- price change event has not started, so can just update it to cancelled and when the start/end events run they will ignore this event\n {% endcomment %}\n\n {% assign price_change_event[\"status\"] = \"cancelled\" %}\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n jsonValue\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% capture email_subject %}Scheduled price change event has been cancelled ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A scheduled price change event has been cancelled, from the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n {% elsif price_change_event.status == \"ongoing\" %}\n {% comment %}\n -- price change event in progress, so we have to revert the price changes to properly cancel it\n {% endcomment %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/price_changes/end\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"price_change_event_id\": {{ price_change_event_id | json }},\n \"cancel\": true\n }\n }\n {% endaction %}\n\n {% else %}\n {% action \"echo\"\n message: \"Price change event cannot be cancelled because it does not have a status of 'scheduled' or 'ongoing'\",\n price_change_event: price_change_event\n %}\n {% endif %}\n\n {% else %}\n {% error\n message: \"Unrecognized action keyword. Action keyword must be one of 'schedule', 'list', 'email', or 'cancel'\",\n action_keyword: action_keyword\n %}\n {% endif %}\n {% endcase %}\n\n{% elsif event.topic == \"user/price_changes/start\" %}\n {% assign price_change_event_id = event.data.price_change_event_id %}\n {% assign price_change_event = price_change_events[price_change_event_id] %}\n\n {% if event.preview %}\n {% assign price_change_event_id = \"01234567-89ab-cdef\" %}\n\n {% capture price_change_event_json %}\n {\n \"status\": \"scheduled\",\n \"start\": {{ \"now + 1 day\" | date: \"%s\" }},\n \"end\": {{ \"now + 2 days\" | date: \"%s\" }},\n \"set_compare_at_prices\": false,\n \"collection_handles_and_discounts\": {\n \"alpha-beta-gamma\": \"10%\",\n \"sticks-and-stones\": \"-5.50\",\n \"lorem-ipsum\": \"3\"\n },\n \"skus_to_include\": [\n \"SKU-123\",\n \"SKU-456\"\n ],\n \"sku_discount\": \"15%\",\n \"skus_to_exclude\": [\n \"SKU-987\"\n ],\n \"exclude_products_tagged_with\": [\n \"do-not-discount\"\n ]\n }\n {% endcapture %}\n\n {% assign price_change_event = price_change_event_json | parse_json %}\n {% endif %}\n\n {% assign status = price_change_event.status %}\n {% assign set_compare_at_prices = price_change_event.set_compare_at_prices %}\n {% assign collection_handles_and_discounts = price_change_event.collection_handles_and_discounts %}\n {% assign skus_to_include = price_change_event.skus_to_include %}\n {% assign sku_discount = price_change_event.sku_discount %}\n {% assign exclude_products_tagged_with = price_change_event.exclude_products_tagged_with %}\n {% assign skus_to_exclude = price_change_event.skus_to_exclude %}\n\n {% if status != \"scheduled\" %}\n {% log\n message: \"This price change event does not have a status of scheduled, and thus will not start.\",\n price_change_event: price_change_event\n %}\n {% break %}\n {% endif %}\n\n {% comment %}\n -- get collection ids for products query\n {% endcomment %}\n\n {% assign in_collection_checks = array %}\n\n {% for keyval in collection_handles_and_discounts %}\n {% capture collection_query %}\n query {\n collectionByHandle(handle: {{ keyval[0] | json }}) {\n id\n handle\n }\n }\n {% endcapture %}\n\n {% assign collection_result = collection_query | shopify %}\n {% assign collection = collection_result.data.collectionByHandle %}\n\n {% if collection != blank %}\n {% capture in_collection_check -%}\n inCollection_{{ collection.handle | replace: \"-\", \"_\" }}: inCollection(id: {{ collection.id | json }})\n {%- endcapture %}\n\n {% assign in_collection_checks = in_collection_checks | push: in_collection_check %}\n {% endif %}\n {% endfor %}\n\n {% assign query_filter = \"gift_card:false\" %}\n\n {% for tag in exclude_products_tagged_with %}\n {% assign query_filter = tag | json | prepend: \" tag_not:\" | prepend: query_filter %}\n {% endfor %}\n\n {% log query_filter: query_filter %}\n\n {% assign products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (1..200) %}\n {% capture products_query %}\n query {\n products(\n first: 250\n after: {{ cursor | json }}\n query: {{ query_filter | json }}\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n id\n title\n tags\n variants(first: 100) {\n edges {\n node {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n jsonValue\n }\n }\n }\n }\n {{ in_collection_checks | join: newline }}\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_query | shopify %}\n\n {% if event.preview %}\n {% capture products_result_json %}\n {\n \"data\": {\n \"products\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"variants\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"sku\": \"ACME-BRICK-RED\",\n \"price\": \"10.00\",\n \"compareAtPrice\": \"15.00\",\n \"metafield\": null\n }\n }\n ]\n },\n \"inCollection_{{ collection_handles_and_discounts.first.first | replace: \"-\", \"_\" | default: \"sample\" }}\": true\n }\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_result_json | parse_json %}\n {% endif %}\n\n {% assign products = products_result.data.products.edges | map: \"node\" %}\n\n {% for product in products %}\n {% comment %}\n -- For each product, match to a collection discount if applicable, then, if needed, loop through variants to see if there are any overrides\n {% endcomment %}\n\n {% assign product_level_discount = nil %}\n\n {% for keyval in collection_handles_and_discounts %}\n {% assign in_collection_label\n = \"inCollection_\"\n | append: keyval[0]\n | replace: \"-\", \"_\"\n %}\n\n {% if product[in_collection_label] %}\n {% assign product_level_discount = keyval[1] %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if product_level_discount == blank\n and skus_to_include == blank\n and skus_to_exclude == blank\n %}\n {% log\n message: \"This product is not within any configured collections, nor does the price change event have any sku inclusion or exclusion settings; skipping. \",\n product: product\n %}\n {% continue %}\n {% endif %}\n\n {% assign variants = product.variants.edges | map: \"node\" %}\n\n {% assign variant_updates = array %}\n\n {% for variant in variants %}\n {% if skus_to_exclude != blank and skus_to_exclude contains variant.sku %}\n {% log\n message: \"This variant was excluded by SKU\",\n variant: variant\n %}\n {% continue %}\n {% endif %}\n\n {% if variant.metafield != blank %}\n {% log\n message: \"This variant is already part of a price change event; skipping.\",\n variant: variant\n %}\n {% continue %}\n {% endif %}\n\n {% assign discount_to_apply = product_level_discount %}\n\n {% if skus_to_include != blank and skus_to_include contains variant.sku %}\n {% assign discount_to_apply = sku_discount %}\n {% endif %}\n\n {% log\n product_title: product.title,\n sku: variant.sku,\n skus_to_include: skus_to_include,\n sku_discount: sku_discount,\n discount_to_apply: discount_to_apply\n %}\n\n {% if discount_to_apply == blank %}\n {% log\n message: \"This variant's product is not within any configured collections, nor has this variant been specifically included by sku; skipping.\",\n variant: variant\n %}\n {% continue %}\n {% endif %}\n\n {% assign price_to_set = nil %}\n {% assign compare_at_price_to_set = nil %}\n\n {% if discount_to_apply contains \"%\" %}\n {% assign price_to_set\n = discount_to_apply\n | remove: \"%\"\n | minus: 100\n | abs\n | times: variant.price\n | divided_by: 100\n | round: 2\n %}\n\n {% elsif discount_to_apply contains \"-\" %}\n {% assign price_to_set\n = variant.price\n | plus: discount_to_apply\n | at_least: 0.0\n %}\n\n {% else %}\n {% assign price_to_set = discount_to_apply %}\n {% endif %}\n\n {% if set_compare_at_prices %}\n {% assign compare_at_price_to_set = variant.price %}\n {% endif %}\n\n {% log\n product: product,\n product_level_discount: product_level_discount,\n variant: variant,\n discount_to_apply: discount_to_apply,\n price_to_set: price_to_set,\n compare_at_price_to_set: compare_at_price_to_set\n %}\n\n {% capture variant_metafield_value %}\n {\n \"price_change_event_id\": {{ price_change_event_id | json }},\n \"discount_to_apply\": {{ discount_to_apply | json }},\n {% if set_compare_at_prices %}\n \"original_compare_at_price\": {{ variant.compareAtPrice | json }},\n \"compare_at_price_to_set\": {{ compare_at_price_to_set | json }},\n {% endif %}\n \"original_price\": {{ variant.price | json }},\n \"price_to_set\": {{ price_to_set | json }}\n }\n {% endcapture %}\n\n {% capture variant_update %}\n {\n id: {{ variant.id | json}}\n price: {{ price_to_set | json }}\n {% if set_compare_at_prices %}compareAtPrice: {{ variant.price | json }}{% endif %}\n metafields: [\n {\n key: {{ variant_metafield_key | json }}\n namespace: {{ variant_metafield_namespace | json }}\n type: \"json\"\n value: {{ variant_metafield_value | json }}\n }\n ]\n }\n {% endcapture %}\n\n {% assign variant_updates = variant_updates | push: variant_update %}\n {% endfor %}\n\n {% if variant_updates != blank %}\n {% action \"shopify\" %}\n mutation {\n productVariantsBulkUpdate(\n productId: {{ product.id | json }}\n variants: [\n {{ variant_updates | join: newline }}\n ]\n ) {\n product {\n id\n title\n tags\n }\n productVariants {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n jsonValue\n }\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if products_result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = products_result.data.products.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- update the price change event\n {% endcomment %}\n\n {% assign price_change_event[\"status\"] = \"ongoing\" %}\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n value\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% capture email_subject %}A scheduled price change event has started ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A scheduled price change event has started, using the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n\n Note: To cancel this event while it is ongoing, use the \"cancel\" keyword along with the price change event ID when running the task.\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n{% elsif event.topic == \"user/price_changes/end\" %}\n {% assign price_change_event_id = event.data.price_change_event_id %}\n {% assign price_change_event = price_change_events[price_change_event_id] %}\n\n {% if event.preview %}\n {% assign price_change_event_id = \"01234567-89ab-cdef\" %}\n {% assign price_change_event = hash %}\n {% assign price_change_event[\"status\"] = \"ongoing\" %}\n {% endif %}\n\n {% if event.data.cancel %}\n {% comment %}\n -- go ahead and cancel, as status was already checked to be \"scheduled\" prior to custom event call\n {% endcomment %}\n\n {% assign price_change_event[\"status\"] = \"cancelled\" %}\n\n {% else %}\n {% comment %}\n -- since this was a scheduled run, need to make sure the event status is \"ongoing\" before reverting changes\n {% endcomment %}\n\n {% if price_change_event.status != \"ongoing\" %}\n {% log\n message: \"This price change event does not have a status of 'ongoing', and thus will not be reverted.\",\n price_change_event: price_change_event\n %}\n {% break %}\n {% endif %}\n\n {% assign price_change_event[\"status\"] = \"completed\" %}\n {% endif %}\n\n {% comment %}\n -- To revert the price change event, check every variant in the shop to see if the metafield exists and contains this price change event ID\n -- Note: Query from the product level so that productVariantsBulkUpdate can be used, causing only one product update event to fire\n {% endcomment %}\n\n {% assign products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (1..200) %}\n {% capture products_query %}\n query {\n products(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n id\n title\n tags\n variants(first: 100) {\n edges {\n node {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n id\n jsonValue\n }\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_query | shopify %}\n\n {% if event.preview %}\n {% capture products_result_json %}\n {\n \"data\": {\n \"products\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"variants\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"sku\": \"ACME-BRICK-RED\",\n \"price\": \"7.50\",\n \"compareAtPrice\": \"10.00\",\n \"metafield\": {\n \"id\": \"gid://shopify/Metafield/9876543210\",\n \"jsonValue\": {\n \"price_change_event_id\": \"01234567-89ab-cdef\",\n \"discount_to_apply\": \"25%\",\n \"original_compare_at_price\": \"15.00\",\n \"compare_at_price_to_set\": 7.50,\n \"original_price\": \"10.00\",\n \"price_to_set\": 7.50\n }\n }\n }\n }\n ]\n }\n }\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_result_json | parse_json %}\n {% endif %}\n\n {% assign products = products_result.data.products.edges | map: \"node\" %}\n\n {% for product in products %}\n {% assign variant_updates = array %}\n {% assign metafields_to_delete = array %}\n\n {% assign variants_with_metafield = product.variants.edges | map: \"node\" | where: \"metafield\" %}\n\n {% for variant in variants_with_metafield %}\n {% assign metafield = variant.metafield.jsonValue %}\n\n {% if metafield.price_change_event_id == price_change_event_id %}\n {% comment %}\n -- revert the prices on this variant and delete the metafield\n {% endcomment %}\n\n {% assign variant_update = hash %}\n {% assign variant_update[\"id\"] = variant.id %}\n {% assign variant_update[\"price\"] = metafield.original_price %}\n {% if metafield.original_compare_at_price %}\n {% assign variant_update[\"compareAtPrice\"] = metafield.original_compare_at_price %}\n {% endif %}\n {% assign variant_updates = variant_updates | push: variant_update %}\n\n {% assign metafield_to_delete = hash %}\n {% assign metafield_to_delete[\"ownerId\"] = variant.id %}\n {% assign metafield_to_delete[\"namespace\"] = variant_metafield_namespace %}\n {% assign metafield_to_delete[\"key\"] = variant_metafield_key %}\n {% assign metafields_to_delete = metafields_to_delete | push: metafield_to_delete %}\n {% endif %}\n {% endfor %}\n\n {% if metafields_to_delete != blank %}\n {% action \"shopify\" %}\n mutation {\n metafieldsDelete(\n metafields: {{ metafields_to_delete | graphql_arguments }}\n ) {\n deletedMetafields {\n ownerId\n namespace\n key\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n {% if variant_updates != blank %}\n {% action \"shopify\" %}\n mutation {\n productVariantsBulkUpdate(\n productId: {{ product.id | json }}\n variants: {{ variant_updates | graphql_arguments }}\n ) {\n product {\n id\n title\n }\n productVariants {\n id\n displayName\n sku\n price\n compareAtPrice\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if products_result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = products_result.data.products.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- update the price change event\n {% endcomment %}\n\n {% assign price_change_events[price_change_event_id] = price_change_event %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsSet(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n value: {{ price_change_events | json | json }}\n type: \"json\"\n }\n ]\n ) {\n metafields {\n id\n namespace\n key\n type\n value\n owner {\n ... on Shop {\n id\n name\n myshopifyDomain\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% comment %}\n -- send email notification about the price change event status change\n {% endcomment %}\n\n {% capture email_subject %}A price change event has been {{ price_change_event[\"status\"] }} ({{ price_change_event_id }}){% endcapture %}\n\n {% capture email_body %}\n A price change event has been {{ price_change_event[\"status\"] }}, using the {{ task_admin_link }} task within the Mechanic app.\n\n Price change event ID: {{ price_change_event_id }}\n Status: {{ price_change_event[\"status\"] }}\n Event start: {{ price_change_event[\"start\"] | date: \"%F %H:%M %z\" }}\n Event end: {{ price_change_event[\"end\"] | date: \"%F %H:%M %z\" }}\n Set compare at price to original price during event: {{ price_change_event[\"set_compare_at_prices\"] }}\n Collection handles and discounts:\n {% for keyval in price_change_event[\"collection_handles_and_discounts\"] -%}\n - {{ keyval[0] }}: {{ keyval[1] }}\n {% else -%}\n n/a\n {%- endfor %}\n SKUs to include: {{ price_change_event[\"skus_to_include\"] | join: \", \" | default: \"n/a\" }}\n SKU discount: {{ price_change_event[\"sku_discount\"] | default: \"n/a\" }},\n SKUs to exclude: {{ price_change_event[\"skus_to_exclude\"] | join: \", \" | default: \"n/a\" }}\n Exclude products tagged with: {{ price_change_event[\"exclude_products_tagged_with\"] | join: \", \" | default: \"n/a\" }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n\n{% elsif event.topic == \"user/price_changes/reset\" %}\n {% comment %}\n -- To reset price change events, check every variant in the shop to see if the price change event metafield exists\n -- Note: Query from the product level so that productVariantsBulkUpdate can be used, causing only one product update event to fire\n {% endcomment %}\n\n {% assign products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (1..200) %}\n {% capture products_query %}\n query {\n products(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n id\n title\n tags\n variants(first: 100) {\n edges {\n node {\n id\n title\n sku\n price\n compareAtPrice\n metafield(\n namespace: {{ variant_metafield_namespace | json }}\n key: {{ variant_metafield_key | json }}\n ) {\n id\n jsonValue\n }\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_query | shopify %}\n\n {% if event.preview %}\n {% capture products_result_json %}\n {\n \"data\": {\n \"products\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"variants\": {\n \"edges\": [\n {\n \"node\": {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"sku\": \"ACME-BRICK-RED\",\n \"price\": \"7.50\",\n \"compareAtPrice\": \"10.00\",\n \"metafield\": {\n \"id\": \"gid://shopify/Metafield/9876543210\",\n \"jsonValue\": {\n \"price_change_event_id\": \"01234567-89ab-cdef\",\n \"discount_to_apply\": \"25%\",\n \"original_compare_at_price\": \"15.00\",\n \"compare_at_price_to_set\": 7.50,\n \"original_price\": \"10.00\",\n \"price_to_set\": 7.50\n }\n }\n }\n }\n ]\n }\n }\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign products_result = products_result_json | parse_json %}\n {% endif %}\n\n {% assign products = products_result.data.products.edges | map: \"node\" %}\n\n {% for product in products %}\n {% assign variant_updates = array %}\n {% assign metafields_to_delete = array %}\n\n {% assign variants_with_metafield = product.variants.edges | map: \"node\" | where: \"metafield\" %}\n\n {% log\n product_id: product.id,\n variants_count: product.variants.edges.size,\n variants_with_metafield: variants_with_metafield.size\n %}\n\n {% for variant in variants_with_metafield %}\n {% assign metafield = variant.metafield.jsonValue %}\n\n {% comment %}\n -- revert the prices on this variant and delete the metafield\n {% endcomment %}\n\n {% assign variant_update = hash %}\n {% assign variant_update[\"id\"] = variant.id %}\n {% assign variant_update[\"price\"] = metafield.original_price %}\n {% if metafield.original_compare_at_price %}\n {% assign variant_update[\"compareAtPrice\"] = metafield.original_compare_at_price %}\n {% endif %}\n {% assign variant_updates = variant_updates | push: variant_update %}\n\n {% assign metafield_to_delete = hash %}\n {% assign metafield_to_delete[\"ownerId\"] = variant.id %}\n {% assign metafield_to_delete[\"namespace\"] = variant_metafield_namespace %}\n {% assign metafield_to_delete[\"key\"] = variant_metafield_key %}\n {% assign metafields_to_delete = metafields_to_delete | push: metafield_to_delete %}\n {% endfor %}\n\n {% if metafields_to_delete != blank %}\n {% action \"shopify\" %}\n mutation {\n metafieldsDelete(\n metafields: {{ metafields_to_delete | graphql_arguments }}\n ) {\n deletedMetafields {\n ownerId\n namespace\n key\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n {% if variant_updates != blank %}\n {% action \"shopify\" %}\n mutation {\n productVariantsBulkUpdate(\n productId: {{ product.id | json }}\n variants: {{ variant_updates | graphql_arguments }}\n ) {\n product {\n id\n title\n }\n productVariants {\n id\n displayName\n sku\n price\n compareAtPrice\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n {% endfor %}\n\n {% if products_result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = products_result.data.products.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- delete the price change events shop metafield\n {% endcomment %}\n\n {% action \"shopify\" %}\n mutation {\n metafieldsDelete(\n metafields: [\n {\n ownerId: {{ shop.id | json }}\n namespace: {{ shop_metafield_namespace | json }}\n key: {{ shop_metafield_key | json }}\n }\n ]\n ) {\n deletedMetafields {\n ownerId\n namespace\n key\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% comment %}\n -- send email notification about the price change events reset\n {% endcomment %}\n\n {% capture email_subject %}All price change events have been cleared{% endcapture %}\n\n {% capture email_body %}\n All price change events have been reverted and cleared, using the {{ task_admin_link }} task within the Mechanic app.\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n{% endif %}\n", "subscriptions": [ "mechanic/user/text", "user/price_changes/start",