-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathauto-tag-customers-by-purchased-skus.json
27 lines (27 loc) · 19.7 KB
/
auto-tag-customers-by-purchased-skus.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"docs": "This task keeps customers tagged with the SKUs of the products they've purchased, optionally ignoring product purchases that have been refunded. Add a tag prefix to make SKU tags easy to distinguish from your other customer tags.\n\nThis task also records all purchased SKUs in a JSON metafield called \"mechanic.purchased_skus\", associated with the customer. For customers who have purchased more products than would fit inside Shopify's maximum tag limit of 250, this metafield provides a way for you to access _all_ purchased SKUs.",
"halt_action_run_sequence_on_error": false,
"name": "Auto-tag customers by purchased SKUs",
"online_store_javascript": null,
"options": {
"tag_prefix": "",
"ignore_refunded_purchases__boolean": true,
"enable_running_manually__boolean": null,
"test_mode__boolean": true
},
"order_status_javascript": null,
"perform_action_runs_in_sequence": false,
"script": "{% assign tag_prefix = options.tag_prefix %}\n{% assign ignore_refunded_purchases = options.ignore_refunded_purchases__boolean %}\n{% assign test_mode = options.test_mode__boolean %}\n\n{% assign metafield_namespace = \"mechanic\" %}\n{% assign metafield_key = \"purchased_skus\" %}\n\n{% assign jobs = array %}\n\n{% if event.topic contains \"mechanic/\" %}\n {% if event.topic == \"mechanic/user/trigger\" %}\n {% capture bulk_operation_query %}\n query {\n customers {\n edges {\n node {\n __typename\n id\n tags\n metafield(\n namespace: {{ metafield_namespace | json }}\n key: {{ metafield_key | json }}\n ) {\n id\n value\n }\n orders(sortKey: PROCESSED_AT, reverse: true) {\n edges {\n node {\n __typename\n id\n legacyResourceId\n displayFinancialStatus\n lineItems {\n edges {\n node {\n __typename\n id\n sku\n quantity\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% action \"shopify\" %}\n mutation {\n bulkOperationRunQuery(\n query: {{ bulk_operation_query | json }}\n ) {\n bulkOperation {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% elsif event.topic == \"mechanic/shopify/bulk_operation\" %}\n {% if event.preview %}\n {% comment %}\n Set up some test/preview data, enough to establish that we can both tag and untag\n {% endcomment %}\n\n {% assign customer = hash %}\n {% assign customer[\"id\"] = \"gid://shopify/Customer/1234567890\" %}\n {% assign customer[\"__typename\"] = \"Customer\" %}\n {% assign customer[\"tags\"] = array %}\n {% assign customer[\"tags\"][0] = \"REFUNDED2\" %}\n {% assign customer[\"metafield\"] = nil %}\n {% comment %}\n {% assign customer[\"metafield\"] = hash %}\n {% assign customer[\"metafield\"][\"id\"] = \"gid://shopify/Metafield/1234567890\" %}\n {% assign customer[\"metafield\"][\"value\"] = '[\"PURCHASED10\",\"PURCHASED5\"]' %}\n {% endcomment %}\n\n {% assign order1 = hash %}\n {% assign order1[\"id\"] = \"gid://shopify/Order/1234567890\" %}\n {% assign order1[\"__typename\"] = \"Order\" %}\n {% assign order1[\"__parentId\"] = customer.id %}\n {% assign order1[\"__parent\"] = customer %}\n {% assign order1[\"displayFinancialStatus\"] = \"PAID\" %}\n\n {% assign lineItem1 = hash %}\n {% assign lineItem1[\"id\"] = \"gid://shopify/LineItem/1234567890\" %}\n {% assign lineItem1[\"__typename\"] = \"LineItem\" %}\n {% assign lineItem1[\"__parentId\"] = order1.id %}\n {% assign lineItem1[\"__parent\"] = order1 %}\n {% assign lineItem1[\"sku\"] = \"PURCHASED5\" %}\n {% assign lineItem1[\"quantity\"] = 5 %}\n\n {% assign lineItem2 = hash %}\n {% assign lineItem2[\"__typename\"] = \"LineItem\" %}\n {% assign lineItem2[\"id\"] = \"gid://shopify/LineItem/2345678901\" %}\n {% assign lineItem2[\"__parentId\"] = order1.id %}\n {% assign lineItem2[\"__parent\"] = order1 %}\n {% assign lineItem2[\"sku\"] = \"PURCHASED10\" %}\n {% assign lineItem2[\"quantity\"] = 10 %}\n\n {% assign order2 = hash %}\n {% assign order2[\"id\"] = \"gid://shopify/Order/2345678901\" %}\n {% assign order2[\"__typename\"] = \"Order\" %}\n {% assign order2[\"__parentId\"] = customer.id %}\n {% assign order2[\"__parent\"] = customer %}\n {% assign order2[\"legacyResourceId\"] = \"2345678901\" %}\n {% assign order2[\"displayFinancialStatus\"] = \"REFUNDED\" %}\n\n {% assign lineItem3 = hash %}\n {% assign lineItem3[\"__typename\"] = \"LineItem\" %}\n {% assign lineItem3[\"id\"] = \"gid://shopify/LineItem/3456789012\" %}\n {% assign lineItem3[\"__parentId\"] = order2.id %}\n {% assign lineItem3[\"__parent\"] = order2 %}\n {% assign lineItem3[\"sku\"] = \"REFUNDED2\" %}\n {% assign lineItem3[\"quantity\"] = 2 %}\n\n {% assign shop = hash %}\n {% assign shop[\"orders\"] = hash %}\n {% assign shop[\"orders\"][\"2345678901\"] = hash %}\n {% assign shop[\"orders\"][\"2345678901\"][\"refunds\"] = array %}\n {% assign shop[\"orders\"][\"2345678901\"][\"refunds\"][0] = hash %}\n {% assign shop[\"orders\"][\"2345678901\"][\"refunds\"][0][\"refund_line_items\"] = array %}\n {% assign shop[\"orders\"][\"2345678901\"][\"refunds\"][0][\"refund_line_items\"][0] = hash %}\n {% assign shop[\"orders\"][\"2345678901\"][\"refunds\"][0][\"refund_line_items\"][0][\"line_item\"] = hash %}\n {% assign shop[\"orders\"][\"2345678901\"][\"refunds\"][0][\"refund_line_items\"][0][\"line_item\"][\"sku\"] = \"REFUNDED2\" %}\n {% assign shop[\"orders\"][\"2345678901\"][\"refunds\"][0][\"refund_line_items\"][0][\"line_item\"][\"quantity\"] = 2 %}\n\n {% assign objects = array %}\n {% assign objects[objects.size] = customer %}\n {% assign objects[objects.size] = order1 %}\n {% assign objects[objects.size] = order2 %}\n {% assign objects[objects.size] = lineItem1 %}\n {% assign objects[objects.size] = lineItem2 %}\n {% assign objects[objects.size] = lineItem3 %}\n\n {% assign bulkOperation = hash %}\n {% assign bulkOperation[\"objects\"] = objects %}\n {% endif %}\n\n {% assign quantities_by_sku_and_customer_id = hash %}\n\n {% for object in bulkOperation.objects %}\n {% case object.__typename %}\n {% when \"LineItem\" %}\n {% if object.sku == blank %}\n {% continue %}\n {% endif %}\n\n {% assign customer_id = object.__parent.__parent.id %}\n\n {% if customer_id == blank %}\n {% continue %}\n {% endif %}\n\n {% assign sku = object.sku %}\n {% assign quantity = object.quantity %}\n\n {% if quantities_by_sku_and_customer_id[customer_id] == nil %}\n {% assign quantities_by_sku_and_customer_id[customer_id] = hash %}\n {% endif %}\n\n {% assign quantities_by_sku_and_customer_id[customer_id][sku] = quantities_by_sku_and_customer_id[customer_id][sku] | default: 0 | plus: quantity %}\n\n {% when \"Order\" %}\n {% unless ignore_refunded_purchases %}\n {% continue %}\n {% endunless %}\n\n {% unless object.displayFinancialStatus == \"REFUNDED\" or object.displayFinancialStatus == \"PARTIALLY_REFUNDED\" %}\n {% continue %}\n {% endunless %}\n\n {% assign customer_id = object.__parent.id %}\n\n {% if customer_id == blank %}\n {% continue %}\n {% endif %}\n\n {% assign refunds = shop.orders[object.legacyResourceId].refunds %}\n\n {% for refund in refunds %}\n {% for refund_line_item in refund.refund_line_items %}\n {% assign sku = refund_line_item.line_item.sku %}\n {% assign quantity = refund_line_item.line_item.quantity %}\n\n {% if sku == blank %}\n {% continue %}\n {% endif %}\n\n {% if quantities_by_sku_and_customer_id[customer_id] == nil %}\n {% assign quantities_by_sku_and_customer_id[customer_id] = hash %}\n {% endif %}\n\n {% assign quantities_by_sku_and_customer_id[customer_id][sku] = quantities_by_sku_and_customer_id[customer_id][sku] | default: 0 | minus: quantity %}\n {% endfor %}\n {% endfor %}\n {% endcase %}\n {% endfor %}\n\n {% for object in bulkOperation.objects %}\n {% case object.__typename %}\n {% when \"Customer\" %}\n {% assign job = hash %}\n\n {% assign job[\"customer_id\"] = object.id %}\n {% assign job[\"customer_tags\"] = object.tags %}\n {% assign job[\"tags_to_remove\"] = array %}\n {% assign job[\"tags_to_add\"] = array %}\n\n {% assign job[\"metafield_id\"] = object.metafield.id %}\n {% assign job[\"skus_in_metafield\"] = object.metafield.value | default: \"[]\" | parse_json | sort %}\n {% assign job[\"skus_for_metafield\"] = array %}\n\n {% for quantities_by_sku in quantities_by_sku_and_customer_id[job.customer_id] %}\n {% assign sku = quantities_by_sku[0] %}\n {% assign sku_tag = tag_prefix | append: sku %}\n {% assign quantity = quantities_by_sku[1] %}\n\n {% if quantity > 0 %}\n {% unless object.tags contains sku_tag %}\n {% assign job[\"tags_to_add\"][job.tags_to_add.size] = sku_tag %}\n {% endunless %}\n {% assign job[\"skus_for_metafield\"][job.skus_for_metafield.size] = sku_tag %}\n\n {% else %}\n {% if object.tags contains sku_tag %}\n {% assign job[\"tags_to_remove\"][job.tags_to_remove.size] = sku_tag %}\n {% endif %}\n {% endif %}\n {% endfor %}\n\n {% assign job[\"skus_for_metafield\"] = job.skus_for_metafield | sort %}\n\n {% assign jobs[jobs.size] = job %}\n {% endcase %}\n {% endfor %}\n {% endif %}\n\n{% elsif event.topic contains \"shopify/\" %}\n {% if event.topic contains \"shopify/refunds/\" %}\n {% assign customer = refund.order.customer %}\n {% elsif event.topic contains \"shopify/orders/\" %}\n {% assign customer = order.customer %}\n {% endif %}\n\n {% assign cursor = nil %}\n {% assign sku_quantities = hash %}\n\n {% for n in (0..250) %}\n {% capture query %}\n query {\n orders(\n first: 5\n after: {{ cursor | json }}\n query: {{ \"customer_id:\" | append: customer.id | json }}\n sortKey: PROCESSED_AT\n reverse: true\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n lineItems(first: 50) {\n edges {\n node {\n sku\n quantity\n }\n }\n }\n refunds {\n refundLineItems(first: 50) {\n edges {\n node {\n quantity\n lineItem {\n sku\n }\n }\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% comment %}\n Set up some test/preview data, enough to establish that we can both tag and untag\n {% endcomment %}\n\n {% assign customer = hash %}\n {% assign customer[\"admin_graphql_api_id\"] = \"gid://shopify/Customer/1234567890\" %}\n {% assign customer[\"tags\"] = \"REFUNDED\" %}\n {% comment %}\n {% assign customer[\"metafields\"] = hash %}\n {% assign customer[\"metafields\"][\"mechanic\"] = array %}\n {% assign customer[\"metafields\"][metafield_namespace] = array %}\n {% assign customer[\"metafields\"][metafield_namespace][0] = hash %}\n {% assign customer[\"metafields\"][metafield_namespace][0][\"key\"] = metafield_key %}\n {% assign customer[\"metafields\"][metafield_namespace][0][\"admin_graphql_api_id\"] = \"gid://shopify/Metafield/1234567890\" %}\n {% assign customer[\"metafields\"][metafield_namespace][0][\"value\"] = '[\"PURTCHASED\"]' %}\n {% endcomment %}\n\n {% capture result_json %}\n {\n \"data\": {\n \"orders\": {\n \"pageInfo\": {\n \"hasNextPage\": false\n },\n \"edges\": [\n {\n \"node\": {\n \"lineItems\": {\n \"edges\": [\n {\n \"node\": {\n \"sku\": \"REFUNDED\",\n \"quantity\": 1\n }\n }\n ]\n },\n \"refunds\": [\n {\n \"refundLineItems\": {\n \"edges\": [\n {\n \"node\": {\n \"quantity\": 1,\n \"lineItem\": {\n \"sku\": \"REFUNDED\"\n }\n }\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"node\": {\n \"lineItems\": {\n \"edges\": [\n {\n \"node\": {\n \"sku\": \"PURCHASED\",\n \"quantity\": 1\n }\n }\n ]\n },\n \"refunds\": []\n }\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for order_edge in result.data.orders.edges %}\n {% for lineItem_edge in order_edge.node.lineItems.edges %}\n {% assign sku = lineItem_edge.node.sku %}\n\n {% if sku == blank %}\n {% continue %}\n {% endif %}\n\n {% assign sku_quantities[sku] = sku_quantities[sku] | default: 0 | plus: lineItem_edge.node.quantity %}\n {% endfor %}\n\n {% if ignore_refunded_purchases %}\n {% for refund in order_edge.node.refunds %}\n {% for refundLineItem_edge in refund.refundLineItems.edges %}\n {% assign sku = refundLineItem_edge.node.lineItem.sku %}\n\n {% if sku == blank %}\n {% continue %}\n {% endif %}\n\n {% assign sku_quantities[sku] = sku_quantities[sku] | default: 0 | minus: refundLineItem_edge.node.quantity %}\n {% endfor %}\n {% endfor %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.orders.pageInfo.hasNextPage %}\n {% assign cursor = result.data.orders.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% assign job = hash %}\n\n {% assign job[\"customer_id\"] = customer.admin_graphql_api_id %}\n {% assign job[\"tags_to_remove\"] = array %}\n {% assign job[\"tags_to_add\"] = array %}\n\n {% assign metafield = customer.metafields[metafield_namespace] | where: \"key\", metafield_key | first %}\n {% assign job[\"metafield_id\"] = metafield.admin_graphql_api_id %}\n {% assign job[\"skus_in_metafield\"] = metafield.value | default: \"[]\" | parse_json | sort %}\n {% assign job[\"skus_for_metafield\"] = array %}\n\n {% assign customer_tags = customer.tags | split: \", \" %}\n\n {% for sku_and_quantity in sku_quantities %}\n {% assign sku = sku_and_quantity[0] %}\n {% assign sku_tag = tag_prefix | append: sku %}\n {% assign quantity = sku_and_quantity[1] %}\n\n {% if quantity > 0 %}\n {% unless customer_tags contains sku_tag %}\n {% assign job[\"tags_to_add\"][job.tags_to_add.size] = sku_tag %}\n {% endunless %}\n {% assign job[\"skus_for_metafield\"][job.skus_for_metafield.size] = sku_tag %}\n\n {% else %}\n {% if customer_tags contains sku_tag %}\n {% assign job[\"tags_to_remove\"][job.tags_to_remove.size] = sku_tag %}\n {% endif %}\n {% endif %}\n {% endfor %}\n\n {% assign job[\"skus_for_metafield\"] = job.skus_for_metafield | sort %}\n\n {% assign jobs[jobs.size] = job %}\n{% endif %}\n\n{% for job in jobs %}\n {% if test_mode %}\n {% log job %}\n {% continue %}\n {% endif %}\n\n {% assign mutations = array %}\n\n {% if job.skus_in_metafield != job.skus_for_metafield %}\n {% capture mutation %}\n customerUpdate(\n input: {\n id: {{ job.customer_id | json }}\n metafields: [\n {\n {% if job.metafield_id %}\n id: {{ job.metafield_id | json }}\n {% endif %}\n namespace: {{ metafield_namespace | json }}\n key: {{ metafield_key | json }}\n value: {{ job.skus_for_metafield | json | json }}\n type: \"json\"\n }\n ]\n }\n ) {\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n\n {% assign mutations[mutations.size] = mutation %}\n {% endif %}\n\n {% assign tags_net_count = job.customer_tags.size | plus: job.tags_to_add.size | minus: job.tags_to_remove.size %}\n\n {% if tags_net_count > 250 %}\n {% assign tag_count_to_drop = tags_net_count | minus: 250 %}\n\n {% for i in (1..tag_count_to_drop) %}\n {% assign jobs[forloop.parentloop.index0][\"tags_to_add\"] = job.tags_to_add | remove_tag: job.tags_to_add.last %}\n {% endfor %}\n\n {% log %}\n \"Had to drop {{ tag_count_to_drop }} tag(s) in order to meet the 250-tag cap for customer {{ job.customer_id }}\"\n {% endlog %}\n {% endif %}\n\n {% if job.tags_to_remove != empty %}\n {% capture mutation %}\n tagsRemove(\n id: {{ job.customer_id | json }}\n tags: {{ job.tags_to_remove | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n\n {% assign mutations[mutations.size] = mutation %}\n {% endif %}\n\n {% if job.tags_to_add != empty %}\n {% capture mutation %}\n tagsAdd(\n id: {{ job.customer_id | json }}\n tags: {{ job.tags_to_add | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n\n {% assign mutations[mutations.size] = mutation %}\n {% endif %}\n\n {% if mutations != empty %}\n {% action \"shopify\" %}\n mutation {\n {{ mutations | join: newline }}\n }\n {% endaction %}\n {% endif %}\n{% endfor %}\n",
"subscriptions": [
"shopify/orders/paid",
"shopify/refunds/create",
"mechanic/user/trigger",
"mechanic/shopify/bulk_operation"
],
"subscriptions_template": "shopify/orders/paid\n{% if options.ignore_refunded_purchases__boolean %}\n shopify/refunds/create\n{% endif %}\nmechanic/user/trigger\nmechanic/shopify/bulk_operation",
"tags": [
"Auto-Tag",
"Customers",
"SKU"
]
}