diff --git a/docs/hide-out-of-stock-products/README.md b/docs/hide-out-of-stock-products/README.md index 5fd36da4..985366b6 100644 --- a/docs/hide-out-of-stock-products/README.md +++ b/docs/hide-out-of-stock-products/README.md @@ -2,7 +2,7 @@ Tags: Out of Stock, Products, Unpublish -This task monitors inventory updates, and pulls the product from the selected sales channels whenever a product's total inventory meets your "out of stock" threshold. Optionally, it'll send you an email when it does so. This task can also be run manually, to scan all products at once. +This task monitors inventory updates, and pulls the product from the configured sales channels whenever a product's total inventory meets your "out of stock" threshold. Optionally, it'll send you an email when it does so. You may also choose whether to further refine the products being considered by this task by configuring inclusion or exclusion tags (Note: exclusion tags will always take precedence over inclusion tags). * View in the task library: [tasks.mechanic.dev/hide-out-of-stock-products](https://tasks.mechanic.dev/hide-out-of-stock-products) * Task JSON, for direct import: [task.json](../../tasks/hide-out-of-stock-products.json) @@ -12,11 +12,13 @@ This task monitors inventory updates, and pulls the product from the selected sa ```json { + "out_of_stock_inventory_quantity__number_required": "0", "sales_channel_names__required_array": [ "Online Store" ], - "email_notification_recipient__email": "", - "out_of_stock_inventory_quantity__number_required": "0" + "only_include_products_with_any_of_these_tags__array": null, + "always_exclude_products_with_any_of_these_tags__array": null, + "email_notification_recipient__email": null } ``` @@ -33,13 +35,11 @@ shopify/inventory_levels/update ## Documentation -This task monitors inventory updates, and pulls the product from the selected sales channels whenever a product's total inventory meets your "out of stock" threshold. Optionally, it'll send you an email when it does so. This task can also be run manually, to scan all products at once. +This task monitors inventory updates, and pulls the product from the configured sales channels whenever a product's total inventory meets your "out of stock" threshold. Optionally, it'll send you an email when it does so. You may also choose whether to further refine the products being considered by this task by configuring inclusion or exclusion tags (Note: exclusion tags will always take precedence over inclusion tags). -This task monitors inventory updates, and pulls the product from the selected sales channels whenever a product's total inventory meets your "out of stock" threshold. - -To scan your entire catalog for out of stock products, use the "Run task" button. Otherwise, this task will run whenever an inventory level is updated. - -If you'd like to wait until the product has been out of stock for several days, use this task instead: [Unpublish products that have been out of stock for x days](https://usemechanic.com/task/unpublish-products-that-have-been-out-of-stock-for-x-days). +This task can also be run manually, to scan all products in the shop. + +If you'd like to wait until the product has been out of stock for several days, use this task instead: [Unpublish products that have been out of stock for x days](https://tasks.mechanic.dev/unpublish-products-that-have-been-out-of-stock-for-x-days). ## Installing this task diff --git a/docs/hide-out-of-stock-products/script.liquid b/docs/hide-out-of-stock-products/script.liquid index 3909e88d..44921d76 100644 --- a/docs/hide-out-of-stock-products/script.liquid +++ b/docs/hide-out-of-stock-products/script.liquid @@ -1,11 +1,19 @@ +{% assign out_of_stock_inventory_quantity = options.out_of_stock_inventory_quantity__number_required %} +{% assign sales_channel_names = options.sales_channel_names__required_array %} +{% assign inclusion_tags = options.only_include_products_with_any_of_these_tags__array %} +{% assign exclusion_tags = options.always_exclude_products_with_any_of_these_tags__array %} +{% assign email_notification_recipient = options.email_notification_recipient__email %} + +{% comment %} + -- get all of the sales channels in the shop +{% endcomment %} + {% capture query %} query { publications(first: 250) { - edges { - node { - id - name - } + nodes { + id + name } } } @@ -16,17 +24,26 @@ {% assign publication_ids = array %} {% assign publication_names_by_id = hash %} -{% for publication_edge in result.data.publications.edges %} - {% if options.sales_channel_names__required_array contains publication_edge.node.name %} - {% assign publication_ids[publication_ids.size] = publication_edge.node.id %} - {% assign publication_names_by_id[publication_edge.node.id] = publication_edge.node.name %} +{% for publication in result.data.publications.nodes %} + {% if sales_channel_names contains publication.name %} + {% assign publication_ids[publication_ids.size] = publication.id %} + {% assign publication_names_by_id[publication.id] = publication.name %} {% endif %} {% endfor %} +{% comment %} + -- make sure the configured sales channel names match what is in the shop; send alert email otherwise +{% endcomment %} + {% unless event.preview %} - {% assign available_channels = result.data.publications.edges | map: "node" | map: "name" %} - {% if publication_ids.size != options.sales_channel_names__required_array.size %} - {% error message: "Didn't find all configured sales channels. Please check the list of available channels, and compare it with the list of configured channels.", available_channels: available_channels, configured_channels: options.sales_channel_names__required_array %} + {% assign available_channels = result.data.publications.nodes | map: "name" %} + + {% if publication_ids.size != sales_channel_names.size %} + {% error + message: "Each sales channel configured in this task must exist in the shop. Check the list of available channels and verify each configured channel exists.", + available_sales_channel_names: available_channels, + configured_sales_channel_names: sales_channel_names + %} {% endif %} {% endunless %} @@ -47,7 +64,7 @@ } {% endaction %} - {% if options.email_notification_recipient__email != blank %} + {% if email_notification_recipient != blank %} {% capture email_subject %} Out of stock: Short Sleeve T-shirt {% endcapture %} @@ -65,15 +82,59 @@ {% action "email" %} { - "to": {{ options.email_notification_recipient__email | json }}, + "to": {{ email_notification_recipient | json }}, "subject": {{ email_subject | unindent | strip | json }}, "body": {{ email_body | unindent | strip | newline_to_br | json }} } {% endaction %} {% endif %} + {% elsif event.topic == "mechanic/user/trigger" %} {% assign unpublished_product_links = array %} {% assign cursor = nil %} + {% assign search_query = out_of_stock_inventory_quantity | prepend: "inventory_total:<=" %} + + {% comment %} + -- if inclusion or exclusion tags are configured, use them to refine the search query in order to reduce the number of products to be processed + {% endcomment %} + + {% if inclusion_tags != blank %} + {% assign inclusion_tag_filters = array %} + + {% for inclusion_tag in inclusion_tags %} + {% assign inclusion_tag_filter + = inclusion_tag + | json + | prepend: "tag:" + %} + {% assign inclusion_tag_filters = inclusion_tag_filters | push: inclusion_tag_filter %} + {% endfor %} + + {% capture search_query %}{{ search_query }} AND ({{ inclusion_tag_filters | join: " OR " }}){% endcapture %} + {% endif %} + + {% if exclusion_tags != blank %} + {% assign exclusion_tag_filters = array %} + + {% for exclusion_tag in exclusion_tags %} + {% assign exclusion_tag_filter + = exclusion_tag + | json + | prepend: "tag_not:" + %} + {% assign exclusion_tag_filters = exclusion_tag_filters | push: exclusion_tag_filter %} + {% endfor %} + + {% capture search_query -%} + {{ search_query }} AND {{ exclusion_tag_filters | join: " AND " }} + {%- endcapture %} + {% endif %} + + {% log search_query: search_query %} + + {% comment %} + -- use paginated query to get all products in the shop at or below the inventory threshold, optionaly filtered by inclusion or exclusion tags + {% endcomment %} {% for n in (0..100) %} {% capture query %} @@ -81,23 +142,21 @@ products( first: 250 after: {{ cursor | json }} - query: "inventory_total:<={{ options.out_of_stock_inventory_quantity__number_required }}" + query: {{ search_query | json }} ) { pageInfo { hasNextPage + endCursor } - edges { - cursor - node { - id - legacyResourceId - title - totalInventory - - {% for publication_id in publication_ids %} - published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }}) - {% endfor %} - } + nodes { + id + legacyResourceId + title + totalInventory + tags + {% for publication_id in publication_ids %} + published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }}) + {% endfor %} } } } @@ -105,14 +164,18 @@ {% assign result = query | shopify %} - {% for edge in result.data.products.edges %} - {% assign product_node = edge.node %} + {% comment %} + -- process this batch of returned products, unpublishing as needed and saving links for optional email output + {% endcomment %} + + {% for product in result.data.products.nodes %} {% assign mutations = array %} {% assign publication_names = array %} {% for publication_id in publication_ids %} {% assign key = "published" | append: forloop.index %} - {% if product_node[key] == false %} + + {% if product[key] == false %} {% continue %} {% endif %} @@ -120,7 +183,7 @@ {% capture mutation %} publishableUnpublish{{ forloop.index}}: publishableUnpublish( - id: {{ product_node.id | json }} + id: {{ product.id | json }} input: { publicationId: {{ publication_id | json }} } @@ -131,6 +194,7 @@ } } {% endcapture %} + {% assign mutations[mutations.size] = mutation %} {% endfor %} @@ -141,19 +205,25 @@ } {% endaction %} - {% capture link %}
  • {{ product_node.title }} ({{ product_node.totalInventory }} - unpublished from {{ publication_names | join: ", " }})
  • {% endcapture %} + {% capture link %}
  • {{ product.title }} ({{ product.totalInventory }} - unpublished from {{ publication_names | join: ", " }})
  • {% endcapture %} + {% assign unpublished_product_links[unpublished_product_links.size] = link %} {% endif %} {% endfor %} {% if result.data.products.pageInfo.hasNextPage %} - {% assign cursor = result.data.products.edges.last.cursor %} + {% assign cursor = result.data.products.pageInfo.endCursor %} {% else %} {% break %} {% endif %} {% endfor %} - {% if unpublished_product_links != empty and options.email_notification_recipient__email != blank %} + {% if unpublished_product_links == blank %} + {% log "No products qualified to be unpublished during this task run." %} + {% break %} + {% endif %} + + {% if email_notification_recipient != blank %} {% capture email_subject %} Found {{ unpublished_product_links.size }} {{ unpublished_product_links.size | pluralize: "product", "products" }} out of stock {% endcapture %} @@ -161,7 +231,7 @@ {% capture email_body %} Hi there,

    - These products were found to be under your out of stock minimum quantity ({{ options.out_of_stock_inventory_quantity__number_required }}), when adding up the inventory for each product. + These products were found to be under your out of stock minimum quantity ({{ out_of_stock_inventory_quantity }}), when adding up the inventory for each product.

    @@ -172,13 +242,18 @@ {% action "email" %} { - "to": {{ options.email_notification_recipient__email | json }}, + "to": {{ email_notification_recipient | json }}, "subject": {{ email_subject | unindent | strip | json }}, "body": {{ email_body | unindent | strip | json }} } {% endaction %} {% endif %} + {% elsif event.topic contains "shopify/inventory_levels/" %} + {% comment %} + -- query the inventory level to get the product data needed for this task + {% endcomment %} + {% capture query %} query { inventoryLevel( @@ -191,7 +266,7 @@ legacyResourceId title totalInventory - + tags {% for publication_id in publication_ids %} published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }}) {% endfor %} @@ -203,15 +278,67 @@ {% endcapture %} {% assign result = query | shopify %} - {% assign product_node = result.data.inventoryLevel.item.variant.product %} - {% if product_node.totalInventory <= options.out_of_stock_inventory_quantity__number_required %} + {% assign product = result.data.inventoryLevel.item.variant.product %} + + {% comment %} + -- check to see if product is included or excluded (i.e. whether to process) by any configured tags + {% endcomment %} + + {% assign product_included_by_tag = nil %} + {% assign product_excluded_by_tag = nil %} + + {% if inclusion_tags != blank %} + {% for inclusion_tag in inclusion_tags %} + {% if product.tags contains inclusion_tag %} + {% assign product_included_by_tag = true %} + {% endif %} + {% endfor %} + {% endif %} + + {% if exclusion_tags != blank %} + {% for exclusion_tag in exclusion_tags %} + {% if product.tags contains exclusion_tag %} + {% assign product_excluded_by_tag = true %} + {% endif %} + {% endfor %} + {% endif %} + + {% comment %} + -- check if a product has been excluded first, then only if there are inclusion tags configured check if it has been included + {% endcomment %} + + {% if product_excluded_by_tag %} + {% log + message: "Product was excluded by a configured tag and will not be processed by this task.", + exclusion_tags: exclusion_tags, + product: product + %} + {% break %} + + {% elsif inclusion_tags != blank %} + {% unless product_included_by_tag %} + {% log + message: "Product was not included by any configured tag and will not be processed by this task.", + inclusion_tags: inclusion_tags, + product: product + %} + {% break %} + {% endunless %} + {% endif %} + + {% comment %} + -- unpublish the product from the configured sales channels if it is at or below the configured out of stock threshold and optioanlly send email notification + {% endcomment %} + + {% if product.totalInventory <= out_of_stock_inventory_quantity %} {% assign mutations = array %} {% assign publication_names = array %} {% for publication_id in publication_ids %} {% assign key = "published" | append: forloop.index %} - {% if product_node[key] == false %} + + {% if product[key] == false %} {% continue %} {% endif %} @@ -219,7 +346,7 @@ {% capture mutation %} publishableUnpublish{{ forloop.index}}: publishableUnpublish( - id: {{ product_node.id | json }} + id: {{ product.id | json }} input: { publicationId: {{ publication_id | json }} } @@ -230,6 +357,7 @@ } } {% endcapture %} + {% assign mutations[mutations.size] = mutation %} {% endfor %} @@ -240,9 +368,9 @@ } {% endaction %} - {% if options.email_notification_recipient__email != blank %} + {% if email_notification_recipient != blank %} {% capture email_subject %} - Out of stock: {{ product_node.title }} + Out of stock: {{ product.title }} {% endcapture %} {% capture email_body %} @@ -250,7 +378,7 @@ Your product is out of stock! This product has been unpublished from: {{ publication_names | join: ", " }}. - Manage this product + Manage this product Thanks, - Mechanic, for {{ shop.name }} @@ -258,7 +386,7 @@ {% action "email" %} { - "to": {{ options.email_notification_recipient__email | json }}, + "to": {{ email_notification_recipient | json }}, "subject": {{ email_subject | unindent | strip | json }}, "body": {{ email_body | unindent | strip | newline_to_br | json }} } diff --git a/tasks/hide-out-of-stock-products.json b/tasks/hide-out-of-stock-products.json index 8192381a..4b29302c 100644 --- a/tasks/hide-out-of-stock-products.json +++ b/tasks/hide-out-of-stock-products.json @@ -1,18 +1,20 @@ { - "docs": "This task monitors inventory updates, and pulls the product from the selected sales channels whenever a product's total inventory meets your \"out of stock\" threshold. Optionally, it'll send you an email when it does so. This task can also be run manually, to scan all products at once.\n\nThis task monitors inventory updates, and pulls the product from the selected sales channels whenever a product's total inventory meets your \"out of stock\" threshold.\r\n\r\nTo scan your entire catalog for out of stock products, use the \"Run task\" button. Otherwise, this task will run whenever an inventory level is updated.\r\n\r\nIf you'd like to wait until the product has been out of stock for several days, use this task instead: [Unpublish products that have been out of stock for x days](https://usemechanic.com/task/unpublish-products-that-have-been-out-of-stock-for-x-days).", + "docs": "This task monitors inventory updates, and pulls the product from the configured sales channels whenever a product's total inventory meets your \"out of stock\" threshold. Optionally, it'll send you an email when it does so. You may also choose whether to further refine the products being considered by this task by configuring inclusion or exclusion tags (Note: exclusion tags will always take precedence over inclusion tags).\n\nThis task can also be run manually, to scan all products in the shop.\n\nIf you'd like to wait until the product has been out of stock for several days, use this task instead: [Unpublish products that have been out of stock for x days](https://tasks.mechanic.dev/unpublish-products-that-have-been-out-of-stock-for-x-days).", "halt_action_run_sequence_on_error": false, "name": "Hide out-of-stock products", "online_store_javascript": null, "options": { + "out_of_stock_inventory_quantity__number_required": "0", "sales_channel_names__required_array": [ "Online Store" ], - "email_notification_recipient__email": "", - "out_of_stock_inventory_quantity__number_required": "0" + "only_include_products_with_any_of_these_tags__array": null, + "always_exclude_products_with_any_of_these_tags__array": null, + "email_notification_recipient__email": null }, "order_status_javascript": null, "perform_action_runs_in_sequence": false, - "script": "{% capture query %}\n query {\n publications(first: 250) {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n{% endcapture %}\n\n{% assign result = query | shopify %}\n\n{% assign publication_ids = array %}\n{% assign publication_names_by_id = hash %}\n\n{% for publication_edge in result.data.publications.edges %}\n {% if options.sales_channel_names__required_array contains publication_edge.node.name %}\n {% assign publication_ids[publication_ids.size] = publication_edge.node.id %}\n {% assign publication_names_by_id[publication_edge.node.id] = publication_edge.node.name %}\n {% endif %}\n{% endfor %}\n\n{% unless event.preview %}\n {% assign available_channels = result.data.publications.edges | map: \"node\" | map: \"name\" %}\n {% if publication_ids.size != options.sales_channel_names__required_array.size %}\n {% error message: \"Didn't find all configured sales channels. Please check the list of available channels, and compare it with the list of configured channels.\", available_channels: available_channels, configured_channels: options.sales_channel_names__required_array %}\n {% endif %}\n{% endunless %}\n\n{% if event.preview %}\n {% action \"shopify\" %}\n mutation {\n publishableUnpublish1: publishableUnpublish(\n id: \"gid://shopify/Product/1234567890\"\n input: {\n publicationId: \"gid://shopify/Publication/1234567809\"\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% if options.email_notification_recipient__email != blank %}\n {% capture email_subject %}\n Out of stock: Short Sleeve T-shirt\n {% endcapture %}\n\n {% capture email_body %}\n Hi there,\n\n Your product is out of stock!\n\n Manage this product\n\n Thanks,\n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ options.email_notification_recipient__email | json }},\n \"subject\": {{ email_subject | unindent | strip | json }},\n \"body\": {{ email_body | unindent | strip | newline_to_br | json }}\n }\n {% endaction %}\n {% endif %}\n{% elsif event.topic == \"mechanic/user/trigger\" %}\n {% assign unpublished_product_links = array %}\n {% assign cursor = nil %}\n\n {% for n in (0..100) %}\n {% capture query %}\n query {\n products(\n first: 250\n after: {{ cursor | json }}\n query: \"inventory_total:<={{ options.out_of_stock_inventory_quantity__number_required }}\"\n ) {\n pageInfo {\n hasNextPage\n }\n edges {\n cursor\n node {\n id\n legacyResourceId\n title\n totalInventory\n\n {% for publication_id in publication_ids %}\n published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }})\n {% endfor %}\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% for edge in result.data.products.edges %}\n {% assign product_node = edge.node %}\n {% assign mutations = array %}\n {% assign publication_names = array %}\n\n {% for publication_id in publication_ids %}\n {% assign key = \"published\" | append: forloop.index %}\n {% if product_node[key] == false %}\n {% continue %}\n {% endif %}\n\n {% assign publication_names[publication_names.size] = publication_names_by_id[publication_id] %}\n\n {% capture mutation %}\n publishableUnpublish{{ forloop.index}}: publishableUnpublish(\n id: {{ product_node.id | json }}\n input: {\n publicationId: {{ publication_id | json }}\n }\n ) {\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n {% assign mutations[mutations.size] = mutation %}\n {% endfor %}\n\n {% if mutations != empty %}\n {% action \"shopify\" %}\n mutation {\n {{ mutations | join: newline }}\n }\n {% endaction %}\n\n {% capture link %}
  • {{ product_node.title }} ({{ product_node.totalInventory }} - unpublished from {{ publication_names | join: \", \" }})
  • {% endcapture %}\n {% assign unpublished_product_links[unpublished_product_links.size] = link %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.products.edges.last.cursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if unpublished_product_links != empty and options.email_notification_recipient__email != blank %}\n {% capture email_subject %}\n Found {{ unpublished_product_links.size }} {{ unpublished_product_links.size | pluralize: \"product\", \"products\" }} out of stock\n {% endcapture %}\n\n {% capture email_body %}\n Hi there,\n

    \n These products were found to be under your out of stock minimum quantity ({{ options.out_of_stock_inventory_quantity__number_required }}), when adding up the inventory for each product.\n
    \n \n
    \n Thanks,\n
    \n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ options.email_notification_recipient__email | json }},\n \"subject\": {{ email_subject | unindent | strip | json }},\n \"body\": {{ email_body | unindent | strip | json }}\n }\n {% endaction %}\n {% endif %}\n{% elsif event.topic contains \"shopify/inventory_levels/\" %}\n {% capture query %}\n query {\n inventoryLevel(\n id: {{ inventory_level.admin_graphql_api_id | json }}\n ) {\n item {\n variant {\n product {\n id\n legacyResourceId\n title\n totalInventory\n\n {% for publication_id in publication_ids %}\n published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }})\n {% endfor %}\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n {% assign product_node = result.data.inventoryLevel.item.variant.product %}\n\n {% if product_node.totalInventory <= options.out_of_stock_inventory_quantity__number_required %}\n {% assign mutations = array %}\n {% assign publication_names = array %}\n\n {% for publication_id in publication_ids %}\n {% assign key = \"published\" | append: forloop.index %}\n {% if product_node[key] == false %}\n {% continue %}\n {% endif %}\n\n {% assign publication_names[publication_names.size] = publication_names_by_id[publication_id] %}\n\n {% capture mutation %}\n publishableUnpublish{{ forloop.index}}: publishableUnpublish(\n id: {{ product_node.id | json }}\n input: {\n publicationId: {{ publication_id | json }}\n }\n ) {\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n {% assign mutations[mutations.size] = mutation %}\n {% endfor %}\n\n {% if mutations != empty %}\n {% action \"shopify\" %}\n mutation {\n {{ mutations | join: newline }}\n }\n {% endaction %}\n\n {% if options.email_notification_recipient__email != blank %}\n {% capture email_subject %}\n Out of stock: {{ product_node.title }}\n {% endcapture %}\n\n {% capture email_body %}\n Hi there,\n\n Your product is out of stock! This product has been unpublished from: {{ publication_names | join: \", \" }}.\n\n Manage this product\n\n Thanks,\n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ options.email_notification_recipient__email | json }},\n \"subject\": {{ email_subject | unindent | strip | json }},\n \"body\": {{ email_body | unindent | strip | newline_to_br | json }}\n }\n {% endaction %}\n {% endif %}\n {% endif %}\n {% endif %}\n{% endif %}\n", + "script": "{% assign out_of_stock_inventory_quantity = options.out_of_stock_inventory_quantity__number_required %}\n{% assign sales_channel_names = options.sales_channel_names__required_array %}\n{% assign inclusion_tags = options.only_include_products_with_any_of_these_tags__array %}\n{% assign exclusion_tags = options.always_exclude_products_with_any_of_these_tags__array %}\n{% assign email_notification_recipient = options.email_notification_recipient__email %}\n\n{% comment %}\n -- get all of the sales channels in the shop\n{% endcomment %}\n\n{% capture query %}\n query {\n publications(first: 250) {\n nodes {\n id\n name\n }\n }\n }\n{% endcapture %}\n\n{% assign result = query | shopify %}\n\n{% assign publication_ids = array %}\n{% assign publication_names_by_id = hash %}\n\n{% for publication in result.data.publications.nodes %}\n {% if sales_channel_names contains publication.name %}\n {% assign publication_ids[publication_ids.size] = publication.id %}\n {% assign publication_names_by_id[publication.id] = publication.name %}\n {% endif %}\n{% endfor %}\n\n{% comment %}\n -- make sure the configured sales channel names match what is in the shop; send alert email otherwise\n{% endcomment %}\n\n{% unless event.preview %}\n {% assign available_channels = result.data.publications.nodes | map: \"name\" %}\n\n {% if publication_ids.size != sales_channel_names.size %}\n {% error\n message: \"Each sales channel configured in this task must exist in the shop. Check the list of available channels and verify each configured channel exists.\",\n available_sales_channel_names: available_channels,\n configured_sales_channel_names: sales_channel_names\n %}\n {% endif %}\n{% endunless %}\n\n{% if event.preview %}\n {% action \"shopify\" %}\n mutation {\n publishableUnpublish1: publishableUnpublish(\n id: \"gid://shopify/Product/1234567890\"\n input: {\n publicationId: \"gid://shopify/Publication/1234567809\"\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% if email_notification_recipient != blank %}\n {% capture email_subject %}\n Out of stock: Short Sleeve T-shirt\n {% endcapture %}\n\n {% capture email_body %}\n Hi there,\n\n Your product is out of stock!\n\n Manage this product\n\n Thanks,\n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_notification_recipient | json }},\n \"subject\": {{ email_subject | unindent | strip | json }},\n \"body\": {{ email_body | unindent | strip | newline_to_br | json }}\n }\n {% endaction %}\n {% endif %}\n\n{% elsif event.topic == \"mechanic/user/trigger\" %}\n {% assign unpublished_product_links = array %}\n {% assign cursor = nil %}\n {% assign search_query = out_of_stock_inventory_quantity | prepend: \"inventory_total:<=\" %}\n\n {% comment %}\n -- if inclusion or exclusion tags are configured, use them to refine the search query in order to reduce the number of products to be processed\n {% endcomment %}\n\n {% if inclusion_tags != blank %}\n {% assign inclusion_tag_filters = array %}\n\n {% for inclusion_tag in inclusion_tags %}\n {% assign inclusion_tag_filter\n = inclusion_tag\n | json\n | prepend: \"tag:\"\n %}\n {% assign inclusion_tag_filters = inclusion_tag_filters | push: inclusion_tag_filter %}\n {% endfor %}\n\n {% capture search_query %}{{ search_query }} AND ({{ inclusion_tag_filters | join: \" OR \" }}){% endcapture %}\n {% endif %}\n\n {% if exclusion_tags != blank %}\n {% assign exclusion_tag_filters = array %}\n\n {% for exclusion_tag in exclusion_tags %}\n {% assign exclusion_tag_filter\n = exclusion_tag\n | json\n | prepend: \"tag_not:\"\n %}\n {% assign exclusion_tag_filters = exclusion_tag_filters | push: exclusion_tag_filter %}\n {% endfor %}\n\n {% capture search_query -%}\n {{ search_query }} AND {{ exclusion_tag_filters | join: \" AND \" }}\n {%- endcapture %}\n {% endif %}\n\n {% log search_query: search_query %}\n\n {% comment %}\n -- use paginated query to get all products in the shop at or below the inventory threshold, optionaly filtered by inclusion or exclusion tags\n {% endcomment %}\n\n {% for n in (0..100) %}\n {% capture query %}\n query {\n products(\n first: 250\n after: {{ cursor | json }}\n query: {{ search_query | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n legacyResourceId\n title\n totalInventory\n tags\n {% for publication_id in publication_ids %}\n published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }})\n {% endfor %}\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% comment %}\n -- process this batch of returned products, unpublishing as needed and saving links for optional email output\n {% endcomment %}\n\n {% for product in result.data.products.nodes %}\n {% assign mutations = array %}\n {% assign publication_names = array %}\n\n {% for publication_id in publication_ids %}\n {% assign key = \"published\" | append: forloop.index %}\n\n {% if product[key] == false %}\n {% continue %}\n {% endif %}\n\n {% assign publication_names[publication_names.size] = publication_names_by_id[publication_id] %}\n\n {% capture mutation %}\n publishableUnpublish{{ forloop.index}}: publishableUnpublish(\n id: {{ product.id | json }}\n input: {\n publicationId: {{ publication_id | json }}\n }\n ) {\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n\n {% assign mutations[mutations.size] = mutation %}\n {% endfor %}\n\n {% if mutations != empty %}\n {% action \"shopify\" %}\n mutation {\n {{ mutations | join: newline }}\n }\n {% endaction %}\n\n {% capture link %}
  • {{ product.title }} ({{ product.totalInventory }} - unpublished from {{ publication_names | join: \", \" }})
  • {% endcapture %}\n\n {% assign unpublished_product_links[unpublished_product_links.size] = link %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if unpublished_product_links == blank %}\n {% log \"No products qualified to be unpublished during this task run.\" %}\n {% break %}\n {% endif %}\n\n {% if email_notification_recipient != blank %}\n {% capture email_subject %}\n Found {{ unpublished_product_links.size }} {{ unpublished_product_links.size | pluralize: \"product\", \"products\" }} out of stock\n {% endcapture %}\n\n {% capture email_body %}\n Hi there,\n

    \n These products were found to be under your out of stock minimum quantity ({{ out_of_stock_inventory_quantity }}), when adding up the inventory for each product.\n
    \n \n
    \n Thanks,\n
    \n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_notification_recipient | json }},\n \"subject\": {{ email_subject | unindent | strip | json }},\n \"body\": {{ email_body | unindent | strip | json }}\n }\n {% endaction %}\n {% endif %}\n\n{% elsif event.topic contains \"shopify/inventory_levels/\" %}\n {% comment %}\n -- query the inventory level to get the product data needed for this task\n {% endcomment %}\n\n {% capture query %}\n query {\n inventoryLevel(\n id: {{ inventory_level.admin_graphql_api_id | json }}\n ) {\n item {\n variant {\n product {\n id\n legacyResourceId\n title\n totalInventory\n tags\n {% for publication_id in publication_ids %}\n published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }})\n {% endfor %}\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign product = result.data.inventoryLevel.item.variant.product %}\n\n {% comment %}\n -- check to see if product is included or excluded (i.e. whether to process) by any configured tags\n {% endcomment %}\n\n {% assign product_included_by_tag = nil %}\n {% assign product_excluded_by_tag = nil %}\n\n {% if inclusion_tags != blank %}\n {% for inclusion_tag in inclusion_tags %}\n {% if product.tags contains inclusion_tag %}\n {% assign product_included_by_tag = true %}\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% if exclusion_tags != blank %}\n {% for exclusion_tag in exclusion_tags %}\n {% if product.tags contains exclusion_tag %}\n {% assign product_excluded_by_tag = true %}\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% comment %}\n -- check if a product has been excluded first, then only if there are inclusion tags configured check if it has been included\n {% endcomment %}\n\n {% if product_excluded_by_tag %}\n {% log\n message: \"Product was excluded by a configured tag and will not be processed by this task.\",\n exclusion_tags: exclusion_tags,\n product: product\n %}\n {% break %}\n\n {% elsif inclusion_tags != blank %}\n {% unless product_included_by_tag %}\n {% log\n message: \"Product was not included by any configured tag and will not be processed by this task.\",\n inclusion_tags: inclusion_tags,\n product: product\n %}\n {% break %}\n {% endunless %}\n {% endif %}\n\n {% comment %}\n -- unpublish the product from the configured sales channels if it is at or below the configured out of stock threshold and optioanlly send email notification\n {% endcomment %}\n\n {% if product.totalInventory <= out_of_stock_inventory_quantity %}\n {% assign mutations = array %}\n {% assign publication_names = array %}\n\n {% for publication_id in publication_ids %}\n {% assign key = \"published\" | append: forloop.index %}\n\n {% if product[key] == false %}\n {% continue %}\n {% endif %}\n\n {% assign publication_names[publication_names.size] = publication_names_by_id[publication_id] %}\n\n {% capture mutation %}\n publishableUnpublish{{ forloop.index}}: publishableUnpublish(\n id: {{ product.id | json }}\n input: {\n publicationId: {{ publication_id | json }}\n }\n ) {\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n\n {% assign mutations[mutations.size] = mutation %}\n {% endfor %}\n\n {% if mutations != empty %}\n {% action \"shopify\" %}\n mutation {\n {{ mutations | join: newline }}\n }\n {% endaction %}\n\n {% if email_notification_recipient != blank %}\n {% capture email_subject %}\n Out of stock: {{ product.title }}\n {% endcapture %}\n\n {% capture email_body %}\n Hi there,\n\n Your product is out of stock! This product has been unpublished from: {{ publication_names | join: \", \" }}.\n\n Manage this product\n\n Thanks,\n - Mechanic, for {{ shop.name }}\n {% endcapture %}\n\n {% action \"email\" %}\n {\n \"to\": {{ email_notification_recipient | json }},\n \"subject\": {{ email_subject | unindent | strip | json }},\n \"body\": {{ email_body | unindent | strip | newline_to_br | json }}\n }\n {% endaction %}\n {% endif %}\n {% endif %}\n {% endif %}\n{% endif %}\n", "subscriptions": [ "mechanic/user/trigger", "shopify/inventory_levels/update"