diff --git a/docs/auto-sort-collections-by-inventory-levels/script.liquid b/docs/auto-sort-collections-by-inventory-levels/script.liquid index 462571c2..193461f0 100644 --- a/docs/auto-sort-collections-by-inventory-levels/script.liquid +++ b/docs/auto-sort-collections-by-inventory-levels/script.liquid @@ -305,7 +305,12 @@ {% endif %} {% endfor %} - {% assign move_groups = moves | in_groups_of: 250, fill_with: false %} + {% comment %} + -- using reverse filter below due to a bug in the collectionReorderProducts mutation + -- this filter will NOT affect the sort order determined above + {% endcomment %} + + {% assign move_groups = moves | reverse | in_groups_of: 250, fill_with: false %} {% for move_group in move_groups %} {% action "shopify" %} diff --git a/docs/auto-sort-collections-by-product-properties/script.liquid b/docs/auto-sort-collections-by-product-properties/script.liquid index 4440c511..6a6f25fe 100644 --- a/docs/auto-sort-collections-by-product-properties/script.liquid +++ b/docs/auto-sort-collections-by-product-properties/script.liquid @@ -228,7 +228,12 @@ collection: collection.title %} - {% assign moves_in_groups = moves | in_groups_of: 250, fill_with: false %} + {% comment %} + -- using reverse filter below due to a bug in the collectionReorderProducts mutation + -- this filter will NOT affect the sort order determined above + {% endcomment %} + + {% assign moves_in_groups = moves | reverse | in_groups_of: 250, fill_with: false %} {% for move_group in moves_in_groups %} {% action "shopify" %} diff --git a/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid b/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid index 909078b0..3ff9a786 100644 --- a/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid +++ b/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid @@ -269,7 +269,12 @@ {% endif %} {% endfor %} - {% assign move_groups = moves | in_groups_of: 250, fill_with: false %} + {% comment %} + -- using reverse filter below due to a bug in the collectionReorderProducts mutation + -- this filter will NOT affect the sort order determined above + {% endcomment %} + + {% assign move_groups = moves | reverse | in_groups_of: 250, fill_with: false %} {% for move_group in move_groups %} {% action "shopify" %} diff --git a/tasks/auto-sort-collections-by-inventory-levels.json b/tasks/auto-sort-collections-by-inventory-levels.json index e4392243..63e6b8a6 100644 --- a/tasks/auto-sort-collections-by-inventory-levels.json +++ b/tasks/auto-sort-collections-by-inventory-levels.json @@ -15,7 +15,7 @@ "order_status_javascript": null, "perform_action_runs_in_sequence": true, "preview_event_definitions": [], - "script": "{% assign allowed_base_sort_orders = \"MANUAL,BEST_SELLING,ALPHA_ASC,ALPHA_DESC,PRICE_DESC,PRICE_ASC,CREATED_DESC,CREATED\" | split: \",\" %}\n\n{% unless allowed_base_sort_orders contains options.base_sort_order__required %}\n {% error %}\n {{ allowed_base_sort_orders | join: \", \" | prepend: \"Base sort order must be one of: \" | json }}\n {% enderror %}\n{% endunless %}\n\n{% log %}\n {{ options.base_sort_order__required | prepend: \"Base sort order for this task run: \" | json }}\n{% endlog %}\n\n{% assign product_sort_order = options.base_sort_order__required %}\n{% assign reverse_sort = nil %}\n\n{% case product_sort_order %}\n {% when \"ALPHA_ASC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n\n {% when \"ALPHA_DESC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n {% assign reverse_sort = true %}\n\n {% when \"CREATED_DESC\" %}\n {% assign product_sort_order = \"CREATED\" %}\n {% assign reverse_sort = true %}\n\n {% when \"PRICE_ASC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n\n {% when \"PRICE_DESC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n {% assign reverse_sort = true %}\n{% endcase %}\n\n{% assign collection_handles_or_ids_to_include = options.collection_handles_or_ids_to_include__array %}\n{% assign collection_handles_or_ids_to_exclude = options.collection_handles_or_ids_to_exclude__array %}\n{% assign force_manual_sorting_on_collections = options.force_manual_sorting_on_collections__boolean %}\n{% assign use_sellable_online_quantity = options.use_sellable_online_quantity_instead_of_total_inventory__boolean %}\n\n{% assign collections = shop.collections %}\n\n{% if event.preview %}\n {% capture collections_json %}\n [\n {\n \"id\": {{ collection_handles_or_ids_to_include.first | default: \"1234567890\" | json }},\n \"admin_graphql_api_id\": \"gid://shopify/Collection/1234567890\"\n }\n ]\n {% endcapture %}\n\n {% assign collections = collections_json | parse_json %}\n{% endif %}\n\n{% for collection in collections %}\n {% assign collection_id_string = \"\" | append: collection.id %}\n\n {% if collection_handles_or_ids_to_include != blank %}\n {% unless collection_handles_or_ids_to_include contains collection_id_string\n or collection_handles_or_ids_to_include contains collection.handle%}\n {% continue %}\n {% endunless %}\n\n {% elsif collection_handles_or_ids_to_exclude != blank %}\n {% if collection_handles_or_ids_to_exclude contains collection_id_string\n or collection_handles_or_ids_to_exclude contains collection.handle %}\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% if collection.sort_order != \"manual\" %}\n {% if force_manual_sorting_on_collections or event.preview %}\n {% action \"shopify\" %}\n mutation {\n collectionUpdate(\n input: {\n id: {{ collection.admin_graphql_api_id | json }}\n sortOrder: MANUAL\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log\n message: \"Collection is not configured for manual sorting; skipping.\",\n collection_title: collection.title,\n collection_handle: collection.handle,\n collection_id: collection.id\n %}\n\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% assign all_product_ids_current_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (0..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: COLLECTION_DEFAULT\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign product_ids_batch = result.data.collection.products.nodes | map: \"id\" %}\n {% assign all_product_ids_current_sort = all_product_ids_current_sort | concat: product_ids_batch %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% assign in_stock_products_and_quantities = array %}\n {% assign out_of_stock_products = array %}\n {% assign untracked_products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (0..3000) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: {{ product_sort_order }}\n reverse: {{ reverse_sort | json }}\n first: 9\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n tracksInventory\n totalInventory\n {% if use_sellable_online_quantity %}\n variants(first: 100) {\n nodes {\n sellableOnlineQuantity\n }\n }\n {% endif %}\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collection\": {\n \"products\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"tracksInventory\": true,\n \"totalInventory\": 1\n },\n {\n \"id\": \"gid://shopify/Product/2345678901\",\n \"tracksInventory\": true,\n \"totalInventory\": 0\n },\n {\n \"id\": \"gid://shopify/Product/3456789012\",\n \"tracksInventory\": false,\n \"totalInventory\": null\n },\n {\n \"id\": \"gid://shopify/Product/4567890123\",\n \"tracksInventory\": true,\n \"totalInventory\": 2,\n \"variants\": {\n \"nodes\": [\n {\n \"sellableOnlineQuantity\": 1\n },\n {\n \"sellableOnlineQuantity\": 0\n }\n ]\n }\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% comment %}\n NOTE: first sort products into 3 groups\n - untracked (to preserve base collection sort, and push to top, i.e. unlimited stock)\n - in stock (to be sorted by quantity descending)\n - out of stock (to preserve base collection sort, and append to bottom)\n {% endcomment %}\n\n {% for product in result.data.collection.products.nodes %}\n {% unless product.tracksInventory %}\n {% assign untracked_products = untracked_products | push: product.id %}\n {% continue %}\n {% endunless %}\n\n {% assign product_and_quantity = hash %}\n {% assign product_and_quantity[\"id\"] = product.id %}\n\n {% if use_sellable_online_quantity %}\n {% assign sellable_online_quantity\n = product.variants.nodes\n | map: \"sellableOnlineQuantity\"\n | sum\n %}\n\n {% if sellable_online_quantity > 0 %}\n {% assign product_and_quantity[\"quantity\"] = sellable_online_quantity %}\n {% assign in_stock_products_and_quantities = in_stock_products_and_quantities | push: product_and_quantity %}\n\n {% else %}\n {% assign out_of_stock_products = out_of_stock_products | push: product.id %}\n {% endif %}\n\n {% elsif product.totalInventory > 0 %}\n {% assign product_and_quantity[\"quantity\"] = product.totalInventory %}\n {% assign in_stock_products_and_quantities = in_stock_products_and_quantities | push: product_and_quantity %}\n\n {% else %}\n {% assign out_of_stock_products = out_of_stock_products | push: product.id %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n NOTE: reverse before and after sort by quantity to preserve base collection sort since Liquid sorts numbers ascending\n {% endcomment %}\n\n {% assign sorted_in_stock_products\n = in_stock_products_and_quantities\n | reverse\n | sort: \"quantity\"\n | reverse\n | map: \"id\"\n %}\n\n {% log\n collection_title: collection.title,\n all_product_ids_current_sort: all_product_ids_current_sort,\n untracked_products: untracked_products,\n sorted_in_stock_products: sorted_in_stock_products,\n out_of_stock_products: out_of_stock_products\n %}\n\n {% assign all_product_ids_new_sort\n = untracked_products\n | concat: sorted_in_stock_products\n | concat: out_of_stock_products\n %}\n\n {% assign moves = array %}\n\n {% for product_id in all_product_ids_new_sort %}\n {% if all_product_ids_current_sort[forloop.index0] != product_id %}\n {% assign move = hash %}\n {% assign move[\"id\"] = product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves = moves | push: move %}\n {% endif %}\n {% endfor %}\n\n {% assign move_groups = moves | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in move_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.admin_graphql_api_id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log\n message: \"No position moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n {% endfor %}\n{% endfor %}\n", + "script": "{% assign allowed_base_sort_orders = \"MANUAL,BEST_SELLING,ALPHA_ASC,ALPHA_DESC,PRICE_DESC,PRICE_ASC,CREATED_DESC,CREATED\" | split: \",\" %}\n\n{% unless allowed_base_sort_orders contains options.base_sort_order__required %}\n {% error %}\n {{ allowed_base_sort_orders | join: \", \" | prepend: \"Base sort order must be one of: \" | json }}\n {% enderror %}\n{% endunless %}\n\n{% log %}\n {{ options.base_sort_order__required | prepend: \"Base sort order for this task run: \" | json }}\n{% endlog %}\n\n{% assign product_sort_order = options.base_sort_order__required %}\n{% assign reverse_sort = nil %}\n\n{% case product_sort_order %}\n {% when \"ALPHA_ASC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n\n {% when \"ALPHA_DESC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n {% assign reverse_sort = true %}\n\n {% when \"CREATED_DESC\" %}\n {% assign product_sort_order = \"CREATED\" %}\n {% assign reverse_sort = true %}\n\n {% when \"PRICE_ASC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n\n {% when \"PRICE_DESC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n {% assign reverse_sort = true %}\n{% endcase %}\n\n{% assign collection_handles_or_ids_to_include = options.collection_handles_or_ids_to_include__array %}\n{% assign collection_handles_or_ids_to_exclude = options.collection_handles_or_ids_to_exclude__array %}\n{% assign force_manual_sorting_on_collections = options.force_manual_sorting_on_collections__boolean %}\n{% assign use_sellable_online_quantity = options.use_sellable_online_quantity_instead_of_total_inventory__boolean %}\n\n{% assign collections = shop.collections %}\n\n{% if event.preview %}\n {% capture collections_json %}\n [\n {\n \"id\": {{ collection_handles_or_ids_to_include.first | default: \"1234567890\" | json }},\n \"admin_graphql_api_id\": \"gid://shopify/Collection/1234567890\"\n }\n ]\n {% endcapture %}\n\n {% assign collections = collections_json | parse_json %}\n{% endif %}\n\n{% for collection in collections %}\n {% assign collection_id_string = \"\" | append: collection.id %}\n\n {% if collection_handles_or_ids_to_include != blank %}\n {% unless collection_handles_or_ids_to_include contains collection_id_string\n or collection_handles_or_ids_to_include contains collection.handle%}\n {% continue %}\n {% endunless %}\n\n {% elsif collection_handles_or_ids_to_exclude != blank %}\n {% if collection_handles_or_ids_to_exclude contains collection_id_string\n or collection_handles_or_ids_to_exclude contains collection.handle %}\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% if collection.sort_order != \"manual\" %}\n {% if force_manual_sorting_on_collections or event.preview %}\n {% action \"shopify\" %}\n mutation {\n collectionUpdate(\n input: {\n id: {{ collection.admin_graphql_api_id | json }}\n sortOrder: MANUAL\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log\n message: \"Collection is not configured for manual sorting; skipping.\",\n collection_title: collection.title,\n collection_handle: collection.handle,\n collection_id: collection.id\n %}\n\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% assign all_product_ids_current_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (0..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: COLLECTION_DEFAULT\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign product_ids_batch = result.data.collection.products.nodes | map: \"id\" %}\n {% assign all_product_ids_current_sort = all_product_ids_current_sort | concat: product_ids_batch %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% assign in_stock_products_and_quantities = array %}\n {% assign out_of_stock_products = array %}\n {% assign untracked_products = array %}\n\n {% assign cursor = nil %}\n\n {% for n in (0..3000) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: {{ product_sort_order }}\n reverse: {{ reverse_sort | json }}\n first: 9\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n tracksInventory\n totalInventory\n {% if use_sellable_online_quantity %}\n variants(first: 100) {\n nodes {\n sellableOnlineQuantity\n }\n }\n {% endif %}\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collection\": {\n \"products\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"tracksInventory\": true,\n \"totalInventory\": 1\n },\n {\n \"id\": \"gid://shopify/Product/2345678901\",\n \"tracksInventory\": true,\n \"totalInventory\": 0\n },\n {\n \"id\": \"gid://shopify/Product/3456789012\",\n \"tracksInventory\": false,\n \"totalInventory\": null\n },\n {\n \"id\": \"gid://shopify/Product/4567890123\",\n \"tracksInventory\": true,\n \"totalInventory\": 2,\n \"variants\": {\n \"nodes\": [\n {\n \"sellableOnlineQuantity\": 1\n },\n {\n \"sellableOnlineQuantity\": 0\n }\n ]\n }\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% comment %}\n NOTE: first sort products into 3 groups\n - untracked (to preserve base collection sort, and push to top, i.e. unlimited stock)\n - in stock (to be sorted by quantity descending)\n - out of stock (to preserve base collection sort, and append to bottom)\n {% endcomment %}\n\n {% for product in result.data.collection.products.nodes %}\n {% unless product.tracksInventory %}\n {% assign untracked_products = untracked_products | push: product.id %}\n {% continue %}\n {% endunless %}\n\n {% assign product_and_quantity = hash %}\n {% assign product_and_quantity[\"id\"] = product.id %}\n\n {% if use_sellable_online_quantity %}\n {% assign sellable_online_quantity\n = product.variants.nodes\n | map: \"sellableOnlineQuantity\"\n | sum\n %}\n\n {% if sellable_online_quantity > 0 %}\n {% assign product_and_quantity[\"quantity\"] = sellable_online_quantity %}\n {% assign in_stock_products_and_quantities = in_stock_products_and_quantities | push: product_and_quantity %}\n\n {% else %}\n {% assign out_of_stock_products = out_of_stock_products | push: product.id %}\n {% endif %}\n\n {% elsif product.totalInventory > 0 %}\n {% assign product_and_quantity[\"quantity\"] = product.totalInventory %}\n {% assign in_stock_products_and_quantities = in_stock_products_and_quantities | push: product_and_quantity %}\n\n {% else %}\n {% assign out_of_stock_products = out_of_stock_products | push: product.id %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n NOTE: reverse before and after sort by quantity to preserve base collection sort since Liquid sorts numbers ascending\n {% endcomment %}\n\n {% assign sorted_in_stock_products\n = in_stock_products_and_quantities\n | reverse\n | sort: \"quantity\"\n | reverse\n | map: \"id\"\n %}\n\n {% log\n collection_title: collection.title,\n all_product_ids_current_sort: all_product_ids_current_sort,\n untracked_products: untracked_products,\n sorted_in_stock_products: sorted_in_stock_products,\n out_of_stock_products: out_of_stock_products\n %}\n\n {% assign all_product_ids_new_sort\n = untracked_products\n | concat: sorted_in_stock_products\n | concat: out_of_stock_products\n %}\n\n {% assign moves = array %}\n\n {% for product_id in all_product_ids_new_sort %}\n {% if all_product_ids_current_sort[forloop.index0] != product_id %}\n {% assign move = hash %}\n {% assign move[\"id\"] = product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves = moves | push: move %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- using reverse filter below due to a bug in the collectionReorderProducts mutation\n -- this filter will NOT affect the sort order determined above\n {% endcomment %}\n\n {% assign move_groups = moves | reverse | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in move_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.admin_graphql_api_id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log\n message: \"No position moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n {% endfor %}\n{% endfor %}\n", "subscriptions": [ "mechanic/user/trigger" ], diff --git a/tasks/auto-sort-collections-by-product-properties.json b/tasks/auto-sort-collections-by-product-properties.json index 374d4691..d6173a97 100644 --- a/tasks/auto-sort-collections-by-product-properties.json +++ b/tasks/auto-sort-collections-by-product-properties.json @@ -14,7 +14,7 @@ }, "order_status_javascript": null, "perform_action_runs_in_sequence": false, - "script": "{% assign product_property_lookups = options.product_property_lookups__array_required %}\n{% assign only_sort_these_collections = options.only_sort_these_collections__array %}\n{% assign reverse_sort = options.reverse_sort__boolean %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% comment %}\n -- get IDs for all manually sorted collections, optionally restricted to specific collections by ID or title\n {% endcomment %}\n\n {% assign collection_ids_to_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (1..50) %}\n {% capture query %}\n query {\n collections(\n first: 250\n after: {{ cursor | json }}\n query: {{ search_query | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n legacyResourceId\n sortOrder\n title\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collections\": {\n \"nodes\": [\n {\n \"legacyResourceId\": \"1234567890\",\n \"sortOrder\": \"MANUAL\"\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for collection in result.data.collections.nodes %}\n {% if only_sort_these_collections != blank %}\n {% unless only_sort_these_collections contains collection.legacyResourceId\n or only_sort_these_collections contains collection.title\n or event.preview\n %}\n {% continue %}\n {% endunless %}\n {% endif %}\n\n {% if collection.sortOrder != \"MANUAL\" %}\n {% log\n message: \"Collection is not configured for manual sorting; skipping.\",\n collection: collection\n %}\n {% continue %}\n {% endif %}\n\n {% assign collection_ids_to_sort = collection_ids_to_sort | push: collection.legacyResourceId %}\n {% endfor %}\n\n {% if result.data.collections.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collections.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if collection_ids_to_sort == blank %}\n {% log \"No collections qualified to be sorted on this task run.\" %}\n {% break %}\n {% endif %}\n\n {% comment %}\n -- save the qualified collection IDs to the cache for lookup in later child events\n -- NOTE: using event ID instead of task ID in case one instance of this task runs concurrent with another\n {% endcomment %}\n\n {% assign cache_key = event.id | prepend: \"collection_ids_to_sort_\" %}\n {% action \"cache\", \"set\", cache_key, collection_ids_to_sort %}\n\n {% log\n message: \"Begin processing collections loop using sequential child events.\",\n total_collections_to_sort: collection_ids_to_sort.size\n %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/process\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": 0\n }\n }\n {% endaction %}\n\n{% elsif event.topic == \"user/collection_sort/process\" %}\n {% assign cache_key = event.data.cache_key %}\n {% assign cache_index = event.data.cache_index %}\n {% assign collection_ids_to_sort = cache[cache_key] %}\n {% assign collection_id = collection_ids_to_sort[cache_index] %}\n {% assign collection = shop.collections[collection_id] %}\n\n {% if event.preview %}\n {% comment %}\n -- use stub data instead of a preview event to incorporate the configured product properties\n {% endcomment %}\n\n {% assign product_ids = array | push: \"1234567890\", \"3456789012\", \"2345678901\" %}\n {% assign products = array %}\n\n {% for product_id in product_ids %}\n {% assign product = hash %}\n\n {% for lookup in product_property_lookups reversed %}\n {% if forloop.first %}\n {% assign product[lookup] = product_id %}\n\n {% else %}\n {% assign temp = product %}\n {% assign product = hash %}\n {% assign product[lookup] = temp %}\n {% endif %}\n {% endfor %}\n\n {% assign product[\"id\"] = product_id %}\n {% assign product[\"admin_graphql_api_id\"] = \"gid://shopify/Product/\" | append: product_id %}\n {% assign products[products.size] = product %}\n {% endfor %}\n\n {% capture collection_json %}\n {\n \"title\": {{ only_sort_these_collections[0] | default: \"Some collection\" | json }},\n \"admin_graphql_api_id\": \"gid://shopify/Collection/1234567890\",\n \"products\": {{ products | json }}\n }\n {% endcapture %}\n\n {% assign collection = collection_json | parse_json %}\n {% endif %}\n\n {% comment %}\n -- process products using REST resources to align with configured product properties\n {% endcomment %}\n\n {% assign moves = array %}\n {% assign product_ids_and_positions = hash %}\n {% assign product_ids_and_values = array %}\n\n {% for product in collection.products %}\n {% assign product_ids_and_positions[product.admin_graphql_api_id] = forloop.index0 %}\n\n {% assign product_id_and_value = hash %}\n {% assign product_id_and_value[\"id\"] = product.admin_graphql_api_id %}\n\n {% assign value = product %}\n\n {% for lookup in options.product_property_lookups__array_required %}\n {% comment %}\n -- NOTE: command methods (size, first, last) can only be invoked via dot notation\n {% endcomment %}\n\n {% case lookup %}\n {% when \"size\" %}\n {% assign value = value.size %}\n {% when \"first\" %}\n {% assign value = value.first %}\n {% when \"last\" %}\n {% assign value = value.last %}\n {% else %}\n {% assign value = value[lookup] %}\n {% endcase %}\n {% endfor %}\n\n {% comment %}\n -- make sure this is always a serializable/sortable object, defaulting to nil in the case of an empty string so that SKU values which are *either* nil or an empty string always end up at the end of the list.\n {% endcomment %}\n\n {% assign product_id_and_value[\"value\"] = value | json | parse_json | default: nil %}\n\n {% assign product_ids_and_values[product_ids_and_values.size] = product_id_and_value %}\n {% endfor %}\n\n {% assign sorted_product_values = product_ids_and_values | sort: \"value\" %}\n {% assign sorted_product_ids = product_ids_and_values | sort: \"value\" | map: \"id\" %}\n\n {% if reverse_sort %}\n {% assign sorted_product_ids = sorted_product_ids | reverse %}\n {% endif %}\n\n {% comment %}\n -- determine the moves necessary to place products in their sorted positions\n {% endcomment %}\n\n {% for sorted_product_id in sorted_product_ids %}\n {% if forloop.index0 != product_ids_and_positions[sorted_product_id] %}\n {% assign move = hash %}\n {% assign move[\"id\"] = sorted_product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves[moves.size] = move %}\n {% endif %}\n {% endfor %}\n\n {% if moves == blank %}\n {% log\n message: \"No moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n\n {% else %}\n {% log\n message: \"Collection requires sorting.\",\n collection: collection.title\n %}\n\n {% assign moves_in_groups = moves | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in moves_in_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.admin_graphql_api_id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n {% endif %}\n\n {% assign new_cache_index = cache_index | plus: 1 %}\n\n {% log\n collections_seen: new_cache_index,\n total_collections_to_sort: collection_ids_to_sort.size\n %}\n\n {% comment %}\n -- process next collection in the cache if there is one\n {% endcomment %}\n\n {% if new_cache_index < collection_ids_to_sort.size %}\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/process\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": {{ new_cache_index }}\n }\n }\n {% endaction %}\n\n {% else %}\n {% comment %}\n -- use a distinct user event to indicate the entire task run is complete, so it can be filtered in the event log\n {% endcomment %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/complete\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": {{ new_cache_index }}\n }\n }\n {% endaction %}\n {% endif %}\n\n{% elsif event.topic == \"user/collection_sort/complete\" %}\n {% assign cache_key = event.data.cache_key %}\n {% assign cache_index = event.data.cache_index %}\n\n {% log\n message: \"Collection sorting complete. Deleting cached collection IDs.\",\n collections_seen: cache_index\n %}\n\n {% action \"cache\", \"del\", cache_key %}\n{% endif %}\n", + "script": "{% assign product_property_lookups = options.product_property_lookups__array_required %}\n{% assign only_sort_these_collections = options.only_sort_these_collections__array %}\n{% assign reverse_sort = options.reverse_sort__boolean %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% comment %}\n -- get IDs for all manually sorted collections, optionally restricted to specific collections by ID or title\n {% endcomment %}\n\n {% assign collection_ids_to_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (1..50) %}\n {% capture query %}\n query {\n collections(\n first: 250\n after: {{ cursor | json }}\n query: {{ search_query | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n legacyResourceId\n sortOrder\n title\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collections\": {\n \"nodes\": [\n {\n \"legacyResourceId\": \"1234567890\",\n \"sortOrder\": \"MANUAL\"\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for collection in result.data.collections.nodes %}\n {% if only_sort_these_collections != blank %}\n {% unless only_sort_these_collections contains collection.legacyResourceId\n or only_sort_these_collections contains collection.title\n or event.preview\n %}\n {% continue %}\n {% endunless %}\n {% endif %}\n\n {% if collection.sortOrder != \"MANUAL\" %}\n {% log\n message: \"Collection is not configured for manual sorting; skipping.\",\n collection: collection\n %}\n {% continue %}\n {% endif %}\n\n {% assign collection_ids_to_sort = collection_ids_to_sort | push: collection.legacyResourceId %}\n {% endfor %}\n\n {% if result.data.collections.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collections.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if collection_ids_to_sort == blank %}\n {% log \"No collections qualified to be sorted on this task run.\" %}\n {% break %}\n {% endif %}\n\n {% comment %}\n -- save the qualified collection IDs to the cache for lookup in later child events\n -- NOTE: using event ID instead of task ID in case one instance of this task runs concurrent with another\n {% endcomment %}\n\n {% assign cache_key = event.id | prepend: \"collection_ids_to_sort_\" %}\n {% action \"cache\", \"set\", cache_key, collection_ids_to_sort %}\n\n {% log\n message: \"Begin processing collections loop using sequential child events.\",\n total_collections_to_sort: collection_ids_to_sort.size\n %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/process\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": 0\n }\n }\n {% endaction %}\n\n{% elsif event.topic == \"user/collection_sort/process\" %}\n {% assign cache_key = event.data.cache_key %}\n {% assign cache_index = event.data.cache_index %}\n {% assign collection_ids_to_sort = cache[cache_key] %}\n {% assign collection_id = collection_ids_to_sort[cache_index] %}\n {% assign collection = shop.collections[collection_id] %}\n\n {% if event.preview %}\n {% comment %}\n -- use stub data instead of a preview event to incorporate the configured product properties\n {% endcomment %}\n\n {% assign product_ids = array | push: \"1234567890\", \"3456789012\", \"2345678901\" %}\n {% assign products = array %}\n\n {% for product_id in product_ids %}\n {% assign product = hash %}\n\n {% for lookup in product_property_lookups reversed %}\n {% if forloop.first %}\n {% assign product[lookup] = product_id %}\n\n {% else %}\n {% assign temp = product %}\n {% assign product = hash %}\n {% assign product[lookup] = temp %}\n {% endif %}\n {% endfor %}\n\n {% assign product[\"id\"] = product_id %}\n {% assign product[\"admin_graphql_api_id\"] = \"gid://shopify/Product/\" | append: product_id %}\n {% assign products[products.size] = product %}\n {% endfor %}\n\n {% capture collection_json %}\n {\n \"title\": {{ only_sort_these_collections[0] | default: \"Some collection\" | json }},\n \"admin_graphql_api_id\": \"gid://shopify/Collection/1234567890\",\n \"products\": {{ products | json }}\n }\n {% endcapture %}\n\n {% assign collection = collection_json | parse_json %}\n {% endif %}\n\n {% comment %}\n -- process products using REST resources to align with configured product properties\n {% endcomment %}\n\n {% assign moves = array %}\n {% assign product_ids_and_positions = hash %}\n {% assign product_ids_and_values = array %}\n\n {% for product in collection.products %}\n {% assign product_ids_and_positions[product.admin_graphql_api_id] = forloop.index0 %}\n\n {% assign product_id_and_value = hash %}\n {% assign product_id_and_value[\"id\"] = product.admin_graphql_api_id %}\n\n {% assign value = product %}\n\n {% for lookup in options.product_property_lookups__array_required %}\n {% comment %}\n -- NOTE: command methods (size, first, last) can only be invoked via dot notation\n {% endcomment %}\n\n {% case lookup %}\n {% when \"size\" %}\n {% assign value = value.size %}\n {% when \"first\" %}\n {% assign value = value.first %}\n {% when \"last\" %}\n {% assign value = value.last %}\n {% else %}\n {% assign value = value[lookup] %}\n {% endcase %}\n {% endfor %}\n\n {% comment %}\n -- make sure this is always a serializable/sortable object, defaulting to nil in the case of an empty string so that SKU values which are *either* nil or an empty string always end up at the end of the list.\n {% endcomment %}\n\n {% assign product_id_and_value[\"value\"] = value | json | parse_json | default: nil %}\n\n {% assign product_ids_and_values[product_ids_and_values.size] = product_id_and_value %}\n {% endfor %}\n\n {% assign sorted_product_values = product_ids_and_values | sort: \"value\" %}\n {% assign sorted_product_ids = product_ids_and_values | sort: \"value\" | map: \"id\" %}\n\n {% if reverse_sort %}\n {% assign sorted_product_ids = sorted_product_ids | reverse %}\n {% endif %}\n\n {% comment %}\n -- determine the moves necessary to place products in their sorted positions\n {% endcomment %}\n\n {% for sorted_product_id in sorted_product_ids %}\n {% if forloop.index0 != product_ids_and_positions[sorted_product_id] %}\n {% assign move = hash %}\n {% assign move[\"id\"] = sorted_product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves[moves.size] = move %}\n {% endif %}\n {% endfor %}\n\n {% if moves == blank %}\n {% log\n message: \"No moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n\n {% else %}\n {% log\n message: \"Collection requires sorting.\",\n collection: collection.title\n %}\n\n {% comment %}\n -- using reverse filter below due to a bug in the collectionReorderProducts mutation\n -- this filter will NOT affect the sort order determined above\n {% endcomment %}\n\n {% assign moves_in_groups = moves | reverse | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in moves_in_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.admin_graphql_api_id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n {% endif %}\n\n {% assign new_cache_index = cache_index | plus: 1 %}\n\n {% log\n collections_seen: new_cache_index,\n total_collections_to_sort: collection_ids_to_sort.size\n %}\n\n {% comment %}\n -- process next collection in the cache if there is one\n {% endcomment %}\n\n {% if new_cache_index < collection_ids_to_sort.size %}\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/process\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": {{ new_cache_index }}\n }\n }\n {% endaction %}\n\n {% else %}\n {% comment %}\n -- use a distinct user event to indicate the entire task run is complete, so it can be filtered in the event log\n {% endcomment %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/complete\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": {{ new_cache_index }}\n }\n }\n {% endaction %}\n {% endif %}\n\n{% elsif event.topic == \"user/collection_sort/complete\" %}\n {% assign cache_key = event.data.cache_key %}\n {% assign cache_index = event.data.cache_index %}\n\n {% log\n message: \"Collection sorting complete. Deleting cached collection IDs.\",\n collections_seen: cache_index\n %}\n\n {% action \"cache\", \"del\", cache_key %}\n{% endif %}\n", "subscriptions": [ "mechanic/user/trigger", "user/collection_sort/process", diff --git a/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json b/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json index 61f98d26..de05666c 100644 --- a/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json +++ b/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json @@ -13,7 +13,7 @@ }, "order_status_javascript": null, "perform_action_runs_in_sequence": true, - "script": "{% assign allowed_base_sort_orders = \"MANUAL,BEST_SELLING,ALPHA_ASC,ALPHA_DESC,PRICE_DESC,PRICE_ASC,CREATED_DESC,CREATED\" | split: \",\" %}\n\n{% unless allowed_base_sort_orders contains options.base_sort_order__required %}\n {% error %}\n {{ allowed_base_sort_orders | join: \", \" | prepend: \"Base sort order must be one of: \" | json }}\n {% enderror %}\n{% endunless %}\n\n{% log %}\n {{ options.base_sort_order__required | prepend: \"Base sort order for this task run: \" | json }}\n{% endlog %}\n\n{% assign product_sort_order = options.base_sort_order__required %}\n{% assign reverse_sort = nil %}\n\n{% case product_sort_order %}\n {% when \"ALPHA_ASC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n\n {% when \"ALPHA_DESC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n {% assign reverse_sort = true %}\n\n {% when \"CREATED_DESC\" %}\n {% assign product_sort_order = \"CREATED\" %}\n {% assign reverse_sort = true %}\n\n {% when \"PRICE_ASC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n\n {% when \"PRICE_DESC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n {% assign reverse_sort = true %}\n{% endcase %}\n\n{% assign collection_handles_or_ids_to_include = options.collection_handles_or_ids_to_include__array %}\n{% assign collection_handles_or_ids_to_exclude = options.collection_handles_or_ids_to_exclude__array %}\n{% assign force_manual_sorting_on_collections = options.force_manual_sorting_on_collections__boolean %}\n\n{% assign collections = shop.collections %}\n\n{% if event.preview %}\n {% capture collections_json %}\n [\n {\n \"id\": {{ collection_handles_or_ids_to_include.first | default: \"1234567890\" | json }},\n \"admin_graphql_api_id\": \"gid://shopify/Collection/1234567890\"\n }\n ]\n {% endcapture %}\n\n {% assign collections = collections_json | parse_json %}\n{% endif %}\n\n{% for collection in collections %}\n {% assign collection_id_string = \"\" | append: collection.id %}\n\n {% if collection_handles_or_ids_to_include != blank %}\n {% unless collection_handles_or_ids_to_include contains collection_id_string\n or collection_handles_or_ids_to_include contains collection.handle %}\n {% continue %}\n {% endunless %}\n\n {% elsif collection_handles_or_ids_to_exclude != blank %}\n {% if collection_handles_or_ids_to_exclude contains collection_id_string\n or collection_handles_or_ids_to_exclude contains collection.handle %}\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% if collection.sort_order != \"manual\" %}\n {% if force_manual_sorting_on_collections or event.preview %}\n {% action \"shopify\" %}\n mutation {\n collectionUpdate(\n input: {\n id: {{ collection.admin_graphql_api_id | json }}\n sortOrder: MANUAL\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log %}\n {{ collection.title | json | append: \" is not configured for manual sorting; skipping.\" | json }}\n {% endlog %}\n\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% assign all_product_ids_current_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (0..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: COLLECTION_DEFAULT\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign product_ids_batch = result.data.collection.products.nodes | map: \"id\" %}\n {% assign all_product_ids_current_sort = all_product_ids_current_sort | concat: product_ids_batch %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% assign in_stock_product_ids = array %}\n {% assign out_of_stock_product_ids = array %}\n {% assign cursor = nil %}\n\n {% for n in (0..3000) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: {{ product_sort_order }}\n reverse: {{ reverse_sort | json }}\n first: 9\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n tracksInventory\n variants(first: 100) {\n nodes {\n inventoryPolicy\n inventoryQuantity\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collection\": {\n \"products\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"tracksInventory\": true,\n \"variants\": {\n \"nodes\": [\n {\n \"inventoryPolicy\": \"DENY\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n },\n {\n \"id\": \"gid://shopify/Product/2345678901\",\n \"tracksInventory\": true,\n \"variants\": {\n \"nodes\": [\n {\n \"inventoryPolicy\": \"CONTINUE\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n },\n {\n \"id\": \"gid://shopify/Product/3456789012\",\n \"tracksInventory\": false\n },\n {\n \"id\": \"gid://shopify/Product/4567890123\",\n \"tracksInventory\": true,\n \"variants\": {\n \"nodes\": [\n {\n \"inventoryPolicy\": \"DENY\",\n \"inventoryQuantity\": 0\n },\n {\n \"inventoryPolicy\": \"CONTINUE\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for product in result.data.collection.products.nodes %}\n {% assign has_in_stock_variant = nil %}\n\n {% unless product.tracksInventory %}\n {% assign has_in_stock_variant = true %}\n\n {% else %}\n {% for variant in product.variants.nodes %}\n {% if variant.inventoryPolicy == \"CONTINUE\" or variant.inventoryQuantity > 0 %}\n {% assign has_in_stock_variant = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n {% endunless %}\n\n {% if has_in_stock_variant %}\n {% assign in_stock_product_ids[in_stock_product_ids.size] = product.id %}\n {% else %}\n {% assign out_of_stock_product_ids[out_of_stock_product_ids.size] = product.id %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% assign all_product_ids = in_stock_product_ids | concat: out_of_stock_product_ids %}\n\n {% assign moves = array %}\n\n {% for product_id in all_product_ids %}\n {% if all_product_ids_current_sort[forloop.index0] != product_id %}\n {% assign move = hash %}\n {% assign move[\"id\"] = product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves[moves.size] = move %}\n {% endif %}\n {% endfor %}\n\n {% assign move_groups = moves | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in move_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.admin_graphql_api_id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log\n message: \"No position moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n {% endfor %}\n{% endfor %}\n", + "script": "{% assign allowed_base_sort_orders = \"MANUAL,BEST_SELLING,ALPHA_ASC,ALPHA_DESC,PRICE_DESC,PRICE_ASC,CREATED_DESC,CREATED\" | split: \",\" %}\n\n{% unless allowed_base_sort_orders contains options.base_sort_order__required %}\n {% error %}\n {{ allowed_base_sort_orders | join: \", \" | prepend: \"Base sort order must be one of: \" | json }}\n {% enderror %}\n{% endunless %}\n\n{% log %}\n {{ options.base_sort_order__required | prepend: \"Base sort order for this task run: \" | json }}\n{% endlog %}\n\n{% assign product_sort_order = options.base_sort_order__required %}\n{% assign reverse_sort = nil %}\n\n{% case product_sort_order %}\n {% when \"ALPHA_ASC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n\n {% when \"ALPHA_DESC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n {% assign reverse_sort = true %}\n\n {% when \"CREATED_DESC\" %}\n {% assign product_sort_order = \"CREATED\" %}\n {% assign reverse_sort = true %}\n\n {% when \"PRICE_ASC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n\n {% when \"PRICE_DESC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n {% assign reverse_sort = true %}\n{% endcase %}\n\n{% assign collection_handles_or_ids_to_include = options.collection_handles_or_ids_to_include__array %}\n{% assign collection_handles_or_ids_to_exclude = options.collection_handles_or_ids_to_exclude__array %}\n{% assign force_manual_sorting_on_collections = options.force_manual_sorting_on_collections__boolean %}\n\n{% assign collections = shop.collections %}\n\n{% if event.preview %}\n {% capture collections_json %}\n [\n {\n \"id\": {{ collection_handles_or_ids_to_include.first | default: \"1234567890\" | json }},\n \"admin_graphql_api_id\": \"gid://shopify/Collection/1234567890\"\n }\n ]\n {% endcapture %}\n\n {% assign collections = collections_json | parse_json %}\n{% endif %}\n\n{% for collection in collections %}\n {% assign collection_id_string = \"\" | append: collection.id %}\n\n {% if collection_handles_or_ids_to_include != blank %}\n {% unless collection_handles_or_ids_to_include contains collection_id_string\n or collection_handles_or_ids_to_include contains collection.handle %}\n {% continue %}\n {% endunless %}\n\n {% elsif collection_handles_or_ids_to_exclude != blank %}\n {% if collection_handles_or_ids_to_exclude contains collection_id_string\n or collection_handles_or_ids_to_exclude contains collection.handle %}\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% if collection.sort_order != \"manual\" %}\n {% if force_manual_sorting_on_collections or event.preview %}\n {% action \"shopify\" %}\n mutation {\n collectionUpdate(\n input: {\n id: {{ collection.admin_graphql_api_id | json }}\n sortOrder: MANUAL\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log %}\n {{ collection.title | json | append: \" is not configured for manual sorting; skipping.\" | json }}\n {% endlog %}\n\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% assign all_product_ids_current_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (0..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: COLLECTION_DEFAULT\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign product_ids_batch = result.data.collection.products.nodes | map: \"id\" %}\n {% assign all_product_ids_current_sort = all_product_ids_current_sort | concat: product_ids_batch %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% assign in_stock_product_ids = array %}\n {% assign out_of_stock_product_ids = array %}\n {% assign cursor = nil %}\n\n {% for n in (0..3000) %}\n {% capture query %}\n query {\n collection(id: {{ collection.admin_graphql_api_id | json }}) {\n products(\n sortKey: {{ product_sort_order }}\n reverse: {{ reverse_sort | json }}\n first: 9\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n tracksInventory\n variants(first: 100) {\n nodes {\n inventoryPolicy\n inventoryQuantity\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collection\": {\n \"products\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"tracksInventory\": true,\n \"variants\": {\n \"nodes\": [\n {\n \"inventoryPolicy\": \"DENY\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n },\n {\n \"id\": \"gid://shopify/Product/2345678901\",\n \"tracksInventory\": true,\n \"variants\": {\n \"nodes\": [\n {\n \"inventoryPolicy\": \"CONTINUE\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n },\n {\n \"id\": \"gid://shopify/Product/3456789012\",\n \"tracksInventory\": false\n },\n {\n \"id\": \"gid://shopify/Product/4567890123\",\n \"tracksInventory\": true,\n \"variants\": {\n \"nodes\": [\n {\n \"inventoryPolicy\": \"DENY\",\n \"inventoryQuantity\": 0\n },\n {\n \"inventoryPolicy\": \"CONTINUE\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for product in result.data.collection.products.nodes %}\n {% assign has_in_stock_variant = nil %}\n\n {% unless product.tracksInventory %}\n {% assign has_in_stock_variant = true %}\n\n {% else %}\n {% for variant in product.variants.nodes %}\n {% if variant.inventoryPolicy == \"CONTINUE\" or variant.inventoryQuantity > 0 %}\n {% assign has_in_stock_variant = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n {% endunless %}\n\n {% if has_in_stock_variant %}\n {% assign in_stock_product_ids[in_stock_product_ids.size] = product.id %}\n {% else %}\n {% assign out_of_stock_product_ids[out_of_stock_product_ids.size] = product.id %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% assign all_product_ids = in_stock_product_ids | concat: out_of_stock_product_ids %}\n\n {% assign moves = array %}\n\n {% for product_id in all_product_ids %}\n {% if all_product_ids_current_sort[forloop.index0] != product_id %}\n {% assign move = hash %}\n {% assign move[\"id\"] = product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves[moves.size] = move %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- using reverse filter below due to a bug in the collectionReorderProducts mutation\n -- this filter will NOT affect the sort order determined above\n {% endcomment %}\n\n {% assign move_groups = moves | reverse | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in move_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.admin_graphql_api_id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log\n message: \"No position moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n {% endfor %}\n{% endfor %}\n", "subscriptions": [ "mechanic/user/trigger" ],