From c528823aef126c5d72f82605867bdff251558086 Mon Sep 17 00:00:00 2001 From: Tim Mackey Date: Sat, 29 Nov 2025 23:20:28 -0800 Subject: [PATCH] Auto-tag products marked "Continue selling when out of stock" This task will automatically tag any products which have variants set to "continue selling when out of stock." This allows you to set up a product collection that includes "available" products and not just products with inventory greater than zero. Configure the task with a tag to apply, and Mechanic will take care of applying and removing the tag as appropriate. --- docs/README.md | 5 + .../README.md | 47 +++++ .../script.liquid | 192 ++++++++++++++++++ ...ed-continue-selling-when-out-of-stock.json | 24 +++ 4 files changed, 268 insertions(+) create mode 100644 docs/auto-tag-products-marked-continue-selling-when-out-of-stock/README.md create mode 100644 docs/auto-tag-products-marked-continue-selling-when-out-of-stock/script.liquid create mode 100644 tasks/auto-tag-products-marked-continue-selling-when-out-of-stock.json diff --git a/docs/README.md b/docs/README.md index d8def18a..b25cb24a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -145,6 +145,7 @@ This directory is built automatically. Each task's documentation is generated fr * [Auto-tag products by their options](./auto-tag-products-by-their-options) * [Auto-tag products by their publish date](./auto-tag-products-by-their-publish-date) * [Auto-tag products in a manual collection](./auto-tag-products-in-a-manual-collection) +* [Auto-tag products marked "Continue selling when out of stock"](./auto-tag-products-marked-continue-selling-when-out-of-stock) * [Auto-tag products that are missing costs](./auto-tag-products-that-are-missing-costs) * [Auto-tag products that have a "compare at" price](./auto-tag-products-that-have-a-compare-at-price) * [Auto-tag products that meet a sales threshold](./auto-tag-products-that-meet-a-sales-threshold) @@ -509,6 +510,7 @@ This directory is built automatically. Each task's documentation is generated fr * [Auto-tag products by their options](./auto-tag-products-by-their-options) * [Auto-tag products by their publish date](./auto-tag-products-by-their-publish-date) * [Auto-tag products in a manual collection](./auto-tag-products-in-a-manual-collection) +* [Auto-tag products marked "Continue selling when out of stock"](./auto-tag-products-marked-continue-selling-when-out-of-stock) * [Auto-tag products that are missing costs](./auto-tag-products-that-are-missing-costs) * [Auto-tag products that have a "compare at" price](./auto-tag-products-that-have-a-compare-at-price) * [Auto-tag products that meet a sales threshold](./auto-tag-products-that-meet-a-sales-threshold) @@ -628,6 +630,7 @@ This directory is built automatically. Each task's documentation is generated fr * [Auto-sort collections by inventory levels](./auto-sort-collections-by-inventory-levels) * [Auto-tag orders by product collections](./auto-tag-orders-by-product-collections) * [Auto-tag products in a manual collection](./auto-tag-products-in-a-manual-collection) +* [Auto-tag products marked "Continue selling when out of stock"](./auto-tag-products-marked-continue-selling-when-out-of-stock) * [Delete the oldest x products from a specific collection](./delete-the-oldest-x-products-from-a-specific-collection) * [Maintain a collection of new products](./maintain-a-collection-of-new-products) * [Maintain a collection of recently purchased products](./maintain-a-collection-of-recently-purchased-products) @@ -1007,6 +1010,7 @@ This directory is built automatically. Each task's documentation is generated fr * [Auto-connect new products to all locations](./auto-connect-new-products-to-all-locations) * [Auto-sort collections by inventory levels](./auto-sort-collections-by-inventory-levels) * [Auto-tag out-of-stock products](./auto-tag-out-of-stock-products) +* [Auto-tag products marked "Continue selling when out of stock"](./auto-tag-products-marked-continue-selling-when-out-of-stock) * [Auto-tag products with incoming inventory](./auto-tag-products-with-incoming-inventory) * [Backup inventory to SFTP in Shopify CSV format](./backup-scheduled-inventory-exports-in-shopifys-csv-format) * [Create a product inventory CSV feed](./create-a-product-inventory-feed) @@ -1384,6 +1388,7 @@ This directory is built automatically. Each task's documentation is generated fr * [Auto-tag products by their options](./auto-tag-products-by-their-options) * [Auto-tag products by their publish date](./auto-tag-products-by-their-publish-date) * [Auto-tag products in a manual collection](./auto-tag-products-in-a-manual-collection) +* [Auto-tag products marked "Continue selling when out of stock"](./auto-tag-products-marked-continue-selling-when-out-of-stock) * [Auto-tag products that are missing costs](./auto-tag-products-that-are-missing-costs) * [Auto-tag products that have a "compare at" price](./auto-tag-products-that-have-a-compare-at-price) * [Auto-tag products that meet a sales threshold](./auto-tag-products-that-meet-a-sales-threshold) diff --git a/docs/auto-tag-products-marked-continue-selling-when-out-of-stock/README.md b/docs/auto-tag-products-marked-continue-selling-when-out-of-stock/README.md new file mode 100644 index 00000000..6c4a2034 --- /dev/null +++ b/docs/auto-tag-products-marked-continue-selling-when-out-of-stock/README.md @@ -0,0 +1,47 @@ +# Auto-tag products marked "Continue selling when out of stock" + +Tags: Auto-Tag, Collections, Inventory, Products + +This task will automatically tag any products which have variants set to "continue selling when out of stock." This allows you to set up a product collection that includes "available" products and not just products with inventory greater than zero. Configure the task with a tag to apply, and Mechanic will take care of applying and removing the tag as appropriate. + +* View in the task library: [tasks.mechanic.dev/auto-tag-products-marked-continue-selling-when-out-of-stock](https://tasks.mechanic.dev/auto-tag-products-marked-continue-selling-when-out-of-stock) +* Task JSON, for direct import: [task.json](../../tasks/auto-tag-products-marked-continue-selling-when-out-of-stock.json) +* Preview task code: [script.liquid](./script.liquid) + +## Default options + +```json +{ + "tag_for_continue_selling_products__required": "continue-selling" +} +``` + +[Learn about task options in Mechanic](https://learn.mechanic.dev/core/tasks/options) + +## Subscriptions + +```liquid +shopify/products/create +shopify/products/update +mechanic/user/trigger +``` + +[Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions) + +## Documentation + +This task will automatically tag any products which have variants set to "continue selling when out of stock." This allows you to set up a product collection that includes "available" products and not just products with inventory greater than zero. Configure the task with a tag to apply, and Mechanic will take care of applying and removing the tag as appropriate. + +Run this task manually to update your entire product catalog at once. + +## Installing this task + +Find this task [in the library at tasks.mechanic.dev](https://tasks.mechanic.dev/auto-tag-products-marked-continue-selling-when-out-of-stock), and use the "Try this task" button. Or, import [this task's JSON export](../../tasks/auto-tag-products-marked-continue-selling-when-out-of-stock.json) – see [Importing and exporting tasks](https://learn.mechanic.dev/core/tasks/import-and-export) to learn how imports work. + +## Contributions + +Found a bug? Got an improvement to add? Start here: [../../CONTRIBUTING.md](../../CONTRIBUTING.md). + +## Task requests + +Submit your [task requests](https://mechanic.canny.io/task-requests) for consideration by the Mechanic community, and they may be chosen for development and inclusion in the [task library](https://tasks.mechanic.dev/)! diff --git a/docs/auto-tag-products-marked-continue-selling-when-out-of-stock/script.liquid b/docs/auto-tag-products-marked-continue-selling-when-out-of-stock/script.liquid new file mode 100644 index 00000000..85bdeb1f --- /dev/null +++ b/docs/auto-tag-products-marked-continue-selling-when-out-of-stock/script.liquid @@ -0,0 +1,192 @@ +{% assign tag_for_continue_selling_products = options.tag_for_continue_selling_products__required %} + +{% comment %} + -- for product create/update, filter the products query with the product ID that caused the event +{% endcomment %} + +{% if event.topic contains "shopify/products/" %} + {% assign search_query = product.id | prepend: "id:" %} +{% endif %} + +{% comment %} + -- query product(s) in the shop; variants will be queried separately as needed to support up to 2K variants +{% endcomment %} + +{% assign cursor = nil %} +{% assign products = array %} + +{% for n in (1..100) %} + {% capture query %} + query { + products( + first: 250 + after: {{ cursor | json }} + query: {{ search_query | json }} + ) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + tags + } + } + } + {% endcapture %} + + {% assign result = query | shopify %} + + {% if event.preview %} + {% capture result_json %} + { + "data": { + "products": { + "nodes": [ + { + "id": "gid://shopify/Product/1234567890" + } + ] + } + } + } + {% endcapture %} + + {% assign result = result_json | parse_json %} + {% endif %} + + {% assign products = products | concat: result.data.products.nodes %} + + {% if result.data.products.pageInfo.hasNextPage %} + {% assign cursor = result.data.products.pageInfo.endCursor %} + {% else %} + {% break %} + {% endif %} +{% endfor %} + +{% comment %} + -- process each product by querying as many variants as needed to determine whether the product should be tagged +{% endcomment %} + +{% for product in products %} + {% assign product_set_to_continue_selling = nil %} + {% assign cursor = nil %} + + {% for n in (1..8) %} + {% capture query %} + query { + product(id: {{ product.id | json }}) { + variants( + first: 250 + after: {{ cursor | json }} + ) { + pageInfo { + hasNextPage + endCursor + } + nodes { + inventoryPolicy + } + } + } + } + {% endcapture %} + {% action "echo" query %} + + {% assign result = query | shopify %} + + {% if event.preview %} + {% capture result_json %} + { + "data": { + "product": { + "variants": { + "nodes": [ + { + "inventoryPolicy": "CONTINUE" + } + ] + } + } + } + } + {% endcapture %} + + {% assign result = result_json | parse_json %} + {% endif %} + + {% assign variants = result.data.product.variants.nodes %} + + {% comment %} + -- check batch of variants to see if any is marked "continue selling" + {% endcomment %} + + {% for variant in variants %} + {% if variant.inventoryPolicy == "CONTINUE" %} + {% assign product_set_to_continue_selling = true %} + {% break %} + {% endif %} + {% endfor %} + + {% comment %} + -- only query for more variants if the product has not yet qualified as "continue selling" AND if there are more variants + {% endcomment %} + + {% if product_set_to_continue_selling %} + {% break %} + {% elsif result.data.product.variants.pageInfo.hasNextPage %} + {% assign cursor = result.data.product.variants.pageInfo.endCursor %} + {% else %} + {% break %} + {% endif %} + {% endfor %} + + {% comment %} + -- adjust tags on product as needed + {% endcomment %} + + {% assign tag_to_add = nil %} + {% assign tag_to_remove = nil %} + + {% if product_set_to_continue_selling %} + {% unless product.tags contains tag_for_continue_selling_products %} + {% assign tag_to_add = tag_for_continue_selling_products %} + {% endunless %} + + {% else %} + {% if product.tags contains tag_for_continue_selling_products %} + {% assign tag_to_remove = tag_for_continue_selling_products %} + {% endif %} + + {% endif %} + + {% if tag_to_add or tag_to_remove %} + {% action "shopify" %} + mutation { + {% if tag_to_add %} + tagsAdd( + id: {{ product.id | json }} + tags: {{ tag_to_add | json }} + ) { + userErrors { + field + message + } + } + {% endif %} + + {% if tag_to_remove %} + tagsRemove( + id: {{ product.id | json }} + tags: {{ tag_to_remove | json }} + ) { + userErrors { + field + message + } + } + {% endif %} + } + {% endaction %} + {% endif %} +{% endfor %} diff --git a/tasks/auto-tag-products-marked-continue-selling-when-out-of-stock.json b/tasks/auto-tag-products-marked-continue-selling-when-out-of-stock.json new file mode 100644 index 00000000..da3af4be --- /dev/null +++ b/tasks/auto-tag-products-marked-continue-selling-when-out-of-stock.json @@ -0,0 +1,24 @@ +{ + "docs": "This task will automatically tag any products which have variants set to \"continue selling when out of stock.\" This allows you to set up a product collection that includes \"available\" products and not just products with inventory greater than zero. Configure the task with a tag to apply, and Mechanic will take care of applying and removing the tag as appropriate.\n\nRun this task manually to update your entire product catalog at once.", + "halt_action_run_sequence_on_error": false, + "name": "Auto-tag products marked \"Continue selling when out of stock\"", + "online_store_javascript": null, + "options": { + "tag_for_continue_selling_products__required": "continue-selling" + }, + "perform_action_runs_in_sequence": false, + "preview_event_definitions": [], + "script": "{% assign tag_for_continue_selling_products = options.tag_for_continue_selling_products__required %}\n\n{% comment %}\n -- for product create/update, filter the products query with the product ID that caused the event\n{% endcomment %}\n\n{% if event.topic contains \"shopify/products/\" %}\n {% assign search_query = product.id | prepend: \"id:\" %}\n{% endif %}\n\n{% comment %}\n -- query product(s) in the shop; variants will be queried separately as needed to support up to 2K variants\n{% endcomment %}\n\n{% assign cursor = nil %}\n{% assign products = array %}\n\n{% for n in (1..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 tags\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"products\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Product/1234567890\"\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign products = products | concat: result.data.products.nodes %}\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{% comment %}\n -- process each product by querying as many variants as needed to determine whether the product should be tagged\n{% endcomment %}\n\n{% for product in products %}\n {% assign product_set_to_continue_selling = nil %}\n {% assign cursor = nil %}\n\n {% for n in (1..8) %}\n {% capture query %}\n query {\n product(id: {{ product.id | json }}) {\n variants(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n inventoryPolicy\n }\n }\n }\n }\n {% endcapture %}\n {% action \"echo\" query %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"product\": {\n \"variants\": {\n \"nodes\": [\n {\n \"inventoryPolicy\": \"CONTINUE\"\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign variants = result.data.product.variants.nodes %}\n\n {% comment %}\n -- check batch of variants to see if any is marked \"continue selling\"\n {% endcomment %}\n\n {% for variant in variants %}\n {% if variant.inventoryPolicy == \"CONTINUE\" %}\n {% assign product_set_to_continue_selling = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- only query for more variants if the product has not yet qualified as \"continue selling\" AND if there are more variants\n {% endcomment %}\n\n {% if product_set_to_continue_selling %}\n {% break %}\n {% elsif result.data.product.variants.pageInfo.hasNextPage %}\n {% assign cursor = result.data.product.variants.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- adjust tags on product as needed\n {% endcomment %}\n\n {% assign tag_to_add = nil %}\n {% assign tag_to_remove = nil %}\n\n {% if product_set_to_continue_selling %}\n {% unless product.tags contains tag_for_continue_selling_products %}\n {% assign tag_to_add = tag_for_continue_selling_products %}\n {% endunless %}\n\n {% else %}\n {% if product.tags contains tag_for_continue_selling_products %}\n {% assign tag_to_remove = tag_for_continue_selling_products %}\n {% endif %}\n\n {% endif %}\n\n {% if tag_to_add or tag_to_remove %}\n {% action \"shopify\" %}\n mutation {\n {% if tag_to_add %}\n tagsAdd(\n id: {{ product.id | json }}\n tags: {{ tag_to_add | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n {% endif %}\n\n {% if tag_to_remove %}\n tagsRemove(\n id: {{ product.id | json }}\n tags: {{ tag_to_remove | json }}\n ) {\n userErrors {\n field\n message\n }\n }\n {% endif %}\n }\n {% endaction %}\n {% endif %}\n{% endfor %}\n", + "subscriptions": [ + "shopify/products/create", + "shopify/products/update", + "mechanic/user/trigger" + ], + "subscriptions_template": "shopify/products/create\nshopify/products/update\nmechanic/user/trigger", + "tags": [ + "Auto-Tag", + "Collections", + "Inventory", + "Products" + ] +}