Sync variant inventory within a product by pack size, with Mechanic.

Mechanic is a development and ecommerce automation platform for Shopify. :)

Sync variant inventory within a product by pack size

Use this task to sell a single product in different pack sizes, keeping available inventory levels synchronized appropriately, storing the "main" inventory level in a product tag (e.g. "inventory:50").

Runs Occurs whenever an order is created, Occurs whenever a product is created, and Occurs whenever a product is updated, or whenever a product is ordered, or whenever a variant is added, removed, or updated. Configuration includes product inventory tag prefix, variant pack size option name, variant pack size metafield namespace dot key, and run when orders are paid instead of when they are created.

15-day free trial – unlimited tasks

Documentation

Use this task to sell a single product in different pack sizes, keeping available inventory levels synchronized appropriately, storing the "main" inventory level in a product tag (e.g. "inventory:50").

New orders for a pack-sized variant will result in the product's inventory tag being updated, and all pack-sized variants having their available inventory levels re-synced. And, manually updating a product's inventory tag will automatically re-sync variant available inventory levels as well.

The pack size for each variant can be determined by a numeric product option (e.g. "Pack size: 50"), or by metafield.

This task will skip any products that do not have an inventory tag, and will skip any variants ordered that have no pack size information.

Developer details

Mechanic is designed to benefit everybody: merchants, customers, developers, agencies, Shopifolks, everybody.

That’s why we make it easy to configure automation without code, why we make it easy to tweak the underlying code once tasks are installed, and why we publish it all here for everyone to learn from.

(By the way, have you seen our documentation? Have you joined the Slack community?)

Open source
View on GitHub to contribute to this task
Subscriptions
{% if options.run_when_orders_are_paid_instead_of_when_they_are_created__boolean %}
  shopify/orders/paid
{% else %}
  shopify/orders/create
{% endif %}
shopify/products/create
shopify/products/update
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
product inventory tag prefix (required), variant pack size option name, variant pack size metafield namespace dot key, run when orders are paid instead of when they are created (boolean)
Code
{% assign product_inventory_tag_prefix = options.product_inventory_tag_prefix__required %}
{% assign product_inventory_tag_prefix_size = product_inventory_tag_prefix.size %}
{% assign pack_size_option_name = options.variant_pack_size_option_name %}
{% assign metafield_namespace_dot_key = options.variant_pack_size_metafield_namespace_dot_key %}

{% if pack_size_option_name == blank and metafield_namespace_dot_key == blank %}
  {% error "Choose a method for determining variant pack size: either option name, or metafield." %}
{% elsif pack_size_option_name != blank and metafield_namespace_dot_key != blank %}
  {% error "Choose to determine the variant pack size by *either* option name or by metafield - not both." %}
{% endif %}

{% if pack_size_option_name != blank %}
  {% assign pack_size_mode = "option" %}
{% else %}
  {% assign pack_size_mode = "metafield" %}
{% endif %}

{% assign primary_location = shop.locations[shop.primary_location_id] %}

{% assign product_considerations = array %}

{% if event.topic contains "shopify/orders/" %}
  {% capture query %}
    query {
      order(id: {{ order.admin_graphql_api_id | json }}) {
        lineItems(first: 200) {
          nodes {
            quantity
            variant {
              product {
                id
                tags
              }
              {% if pack_size_mode == "option" %}
                selectedOptions {
                  name
                  value
                }
              {% else %}
                metafield(key: {{ metafield_namespace_dot_key | json }}) {
                  value
                }
              {% endif %}
            }
          }
        }
      }
    }
  {% endcapture %}

  {% assign result = query | shopify %}

  {% if event.preview %}
    {% capture result_json %}
      {
        "data": {
          "order": {
            "lineItems": {
              "nodes": [
                {
                  "quantity": 2,
                  "variant": {
                    "product": {
                      "id": "gid://shopify/Product/1234567890",
                      "tags": {{ product_inventory_tag_prefix | append: 80 | json }}
                    },
                    {% if pack_size_mode == "option" %}
                      "selectedOptions": [{
                        "name": {{ pack_size_option_name | json }},
                        "value": "25"
                      }]
                    {% else %}
                      "metafield": {
                        "value": "25"
                      }
                    {% endif %}
                  }
                }
              ]
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = result_json | parse_json %}
  {% endif %}

  {% for line_item in result.data.order.lineItems.nodes %}
    {% assign pack_size = nil %}

    {% if pack_size_mode == "option" %}
      {% assign pack_size_option = line_item.variant.selectedOptions | where: "name", pack_size_option_name | first %}
      {% assign pack_size = pack_size_option.value %}
    {% else %}
      {% assign pack_size = line_item.variant.metafield.value %}
    {% endif %}

    {% if pack_size == blank %}
      {% continue %}
    {% endif %}

    {% assign quantity_times_pack_size = line_item.quantity | times: pack_size %}
    {% assign product_inventory_new = product_inventory_old | minus: quantity_times_pack_size %}

    {% assign product_consideration = hash %}
    {% assign product_consideration["id"] = line_item.variant.product.id %}
    {% assign product_consideration["tags"] = line_item.variant.product.tags %}
    {% assign product_consideration["inventory_difference"] = quantity_times_pack_size %}
    {% assign product_considerations[product_considerations.size] = product_consideration %}
  {% endfor %}

{% elsif event.topic contains "shopify/products/" %}
  {% assign product_id = product.admin_graphql_api_id %}
  {% assign product_tags = product.tags | split: ", " %}

  {% if event.preview %}
    {% assign product_id = "gid://shopify/Product/1234576890" %}
    {% assign product_tags = product_inventory_tag_prefix | append: 80 %}
  {% endif %}

  {% assign product_consideration = hash %}
  {% assign product_consideration["id"] = product_id %}
  {% assign product_consideration["tags"] = product_tags %}
  {% assign product_considerations[product_considerations.size] = product_consideration %}
{% endif %}

{% for product_consideration in product_considerations %}
  {% assign product_inventory_old = nil %}

  {% for product_tag in product_consideration.tags %}
    {% assign product_tag_possible_prefix = product_tag | slice: 0, product_inventory_tag_prefix_size %}

    {% if product_tag_possible_prefix == product_inventory_tag_prefix %}
      {% assign product_inventory_old = product_tag | slice: product_inventory_tag_prefix_size, 100 | times: 1 %}
      {% break %}
    {% endif %}
  {% endfor %}

  {% if product_inventory_old == blank %}
    {% continue %}
  {% endif %}

  {% assign product_inventory_new = product_inventory_old | minus: product_consideration.inventory_difference %}

  {% capture query %}
    query {
      product(id: {{ product_consideration.id | json }}) {
        variants(first: 100) {
          nodes {
            inventoryItem {
              id
              inventoryLevel(locationId: {{ primary_location.admin_graphql_api_id | json }}) {
                quantities(names: "available") {
                  name
                  quantity
                }
              }
            }
            {% if pack_size_mode == "option" %}
              selectedOptions {
                name
                value
              }
            {% else %}
              metafield(key: {{ metafield_namespace_dot_key | json }}) {
                value
              }
            {% endif %}
          }
        }
      }
    }
  {% endcapture %}

  {% assign result = query | shopify %}

  {% if event.preview %}
    {% capture result_json %}
      {
        "data": {
          "product": {
            "variants": {
              "nodes":
              [
                {
                  "inventoryItem": {
                    "id": "gid://shopify/InventoryItem/1234567890",
                    "inventoryLevel": {
                      "quantities": [
                        {
                          "name": "available",
                          "quantity": {% if event.topic contains "shopify/orders/" %}3{% else %}0{% endif %}
                        }
                      ]
                    }
                  },
                  {% if pack_size_mode == "option" %}
                    "selectedOptions": [
                      {
                        "name": {{ pack_size_option_name | json }},
                        "value": "25"
                      }
                    ]
                  {% else %}
                    "metafield": {
                      "value": "25"
                    }
                  {% endif %}
                },
                {
                  "inventoryItem": {
                    "id": "gid://shopify/InventoryItem/2345678901",
                    "inventoryLevel": {
                      "quantities": [
                        {
                          "name": "available",
                          "quantity": {% if event.topic contains "shopify/orders/" %}1{% else %}0{% endif %}
                        }
                      ]
                    }
                  },
                  {% if pack_size_mode == "option" %}
                    "selectedOptions": [
                      {
                        "name": {{ pack_size_option_name | json }},
                        "value": "50"
                      }
                    ]
                  {% else %}
                    "metafield": {
                      "value": "50"
                    }
                  {% endif %}
                }
              ]
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = result_json | parse_json %}
  {% endif %}

  {% assign variants = result.data.product.variants.nodes %}

  {% assign inventory_adjustments = array %}

  {% for variant in variants %}
    {% assign pack_size = nil %}

    {% if pack_size_mode == "option" %}
      {% assign pack_size_option = variant.selectedOptions | where: "name", pack_size_option_name | first %}
      {% assign pack_size = pack_size_option.value %}
    {% else %}
      {% assign pack_size = variant.metafield.value %}
    {% endif %}

    {% if pack_size == blank %}
      {% continue %}
    {% endif %}

    {% assign new_inventory_level = product_inventory_new | times: 1.0 | divided_by: pack_size | floor %}

    {% if new_inventory_level == variant.inventoryItem.inventoryLevel.quantities.first.quantity %}
      {% continue %}
    {% endif %}

    {% assign inventory_adjustment = hash %}
    {% assign inventory_adjustment["inventoryItemId"] = variant.inventoryItem.id %}
    {% assign inventory_adjustment["locationId"] = primary_location.admin_graphql_api_id %}
    {% assign inventory_adjustment["delta"] = new_inventory_level | minus: variant.inventoryItem.inventoryLevel.quantities.first.quantity %}
    {% assign inventory_adjustments = inventory_adjustments | push: inventory_adjustment %}
  {% endfor %}

  {% capture mutations %}
    {% if product_inventory_old != product_inventory_new %}
      tagsRemove(
        id: {{ product_consideration.id | json }}
        tags: {{ product_inventory_tag_prefix | append: product_inventory_old | json }}
      ) {
        userErrors {
          field
          message
        }
      }

      tagsAdd(
        id: {{ product_consideration.id | json }}
        tags: {{ product_inventory_tag_prefix | append: product_inventory_new | json }}
      ) {
        userErrors {
          field
          message
        }
      }
    {% endif %}

    {% if inventory_adjustments != blank %}
      inventoryAdjustQuantities(
        input: {
          reason: "correction"
          name: "available"
          changes: {{ inventory_adjustments | graphql_arguments }}
        }
      ) {
        inventoryAdjustmentGroup {
          reason
          changes {
            name
            delta
            quantityAfterChange
            item {
              id
              sku
            }
            location {
              name
            }
          }
        }
        userErrors {
          code
          field
          message
        }
      }
    {% endif %}
  {% endcapture %}

  {% if mutations != blank %}
    {% action "shopify" %}
      mutation {
        {{ mutations }}
      }
    {% endaction %}
  {% endif %}
{% endfor %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more
Defaults
Product inventory tag prefix
inventory:
Variant pack size option name
Pack size