# Ürün başına minimum ve maksimum adet

> Her ürün için minimum ve maksimum sipariş adedini iki ürün metafield’ından okuyup uygulayan Shopify doğrulama Function’ı; test edilmiş JavaScript.

Sayfa: https://lintelagency.com/tr/kaynaklar/shopify-functions/urun-basina-minimum-ve-maksimum-adet

## Kısaca

Bu doğrulama Function’ı, ürün başına minimum ve maksimum adet uygular; ürünün tüm varyantlarını birlikte sayar. Sınırlar, mağaza sahibinin yönetim panelinden düzenlediği iki tam sayı (Integer) ürün metafield’ından gelir. Cart and Checkout Validation API’nin cart.validations.generate.run hedefini kullanır (sürüm 2026-10) ve herkese açık bir uygulamada her planda çalışır.

## Bilgiler

- **Function API:** Cart and Checkout Validation
- **Hedef (target):** cart.validations.generate.run
- **Plan:** Herkese açık uygulamada her plan; özel uygulamada Shopify Plus
- **Uygulama yetkisi:** write_validations
- **API sürümü:** 2026-10
- **Son kontrol:** 2 Kasım 2026

## Nasıl kullanılır

1. Yönetim panelinde Integer tipinde iki ürün metafield tanımı oluşturun: custom.min_quantity ve custom.max_quantity.
2. Shopify CLI ile bir uygulama oluşturun ve uzantıyı ekleyin: shopify app generate extension --template cart_checkout_validation, dil olarak JavaScript.
3. Oluşturulan shopify.extension.toml dosyasını ve src/ içindeki iki dosyayı aşağıdakilerle değiştirin, ardından testleri shopify app function run ile çalıştırın.
4. shopify app deploy ile deploy edin ve doğrulamayı validationCreate ile etkinleştirin.
5. Sınır gereken ürünlerde değerleri doldurun; değeri boş ürünlerde sınır yoktur.

## Kod

### shopify.extension.toml

```toml
api_version = "2026-10"

[[extensions]]
name = "Minimum and maximum quantity per product"
handle = "min-max-quantity-per-product"
type = "function"

  [[extensions.targeting]]
  target = "cart.validations.generate.run"
  input_query = "src/cart_validations_generate_run.graphql"
  export = "cart-validations-generate-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
```

### src/cart_validations_generate_run.graphql

```graphql
query CartValidationsGenerateRunInput {
  buyerJourney {
    step
  }
  cart {
    lines {
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          product {
            id
            title
            minQuantity: metafield(namespace: "custom", key: "min_quantity") {
              value
            }
            maxQuantity: metafield(namespace: "custom", key: "max_quantity") {
              value
            }
          }
        }
      }
    }
  }
}
```

### src/cart_validations_generate_run.js

```js
// @ts-check

/**
 * @typedef {import("../generated/api").CartValidationsGenerateRunInput} CartValidationsGenerateRunInput
 * @typedef {import("../generated/api").CartValidationsGenerateRunResult} CartValidationsGenerateRunResult
 */

/** Errors about the cart as a whole show at the top of checkout and on the cart. */
const CART_TARGET = "$.cart";

/** @param {{value: string} | null | undefined} metafield */
function readLimit(metafield) {
  const limit = Number.parseInt(metafield?.value ?? "", 10);
  return Number.isInteger(limit) && limit > 0 ? limit : null;
}

/**
 * @param {CartValidationsGenerateRunInput} input
 * @returns {CartValidationsGenerateRunResult}
 */
export function cartValidationsGenerateRun(input) {
  // While the cart is being built, a shopper adding one unit at a time would be
  // stopped by the minimum before reaching it; enforce it from checkout on.
  const checkMinimum = input.buyerJourney?.step !== "CART_INTERACTION";
  // Limits are per product, so add up all variants (sizes, colors) of it.
  /** @type {Map<string, {title: string, quantity: number, min: number | null, max: number | null}>} */
  const products = new Map();
  for (const line of input.cart.lines) {
    if (line.merchandise.__typename !== "ProductVariant") continue;
    const { product } = line.merchandise;
    const entry = products.get(product.id) ?? {
      title: product.title,
      quantity: 0,
      min: readLimit(product.minQuantity),
      max: readLimit(product.maxQuantity),
    };
    entry.quantity += line.quantity;
    products.set(product.id, entry);
  }

  const errors = [];
  for (const { title, quantity, min, max } of products.values()) {
    if (checkMinimum && min !== null && quantity < min) {
      errors.push({ message: `${title}: the minimum order quantity is ${min}.`, target: CART_TARGET });
    } else if (max !== null && quantity > max) {
      errors.push({ message: `${title}: you can buy at most ${max}.`, target: CART_TARGET });
    }
  }

  return { operations: errors.length > 0 ? [{ validationAdd: { errors } }] : [] };
}
```

## Test girdisi ve beklenen çıktı

### tests/above-maximum.input.json

```json
{
  "cart": {
    "lines": [
      {
        "quantity": 3,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567892", "title": "Limited Sneaker", "minQuantity": null, "maxQuantity": { "value": "2" } }
        }
      }
    ]
  }
}
```

### tests/above-maximum.output.json

```json
{
  "operations": [
    { "validationAdd": { "errors": [{ "message": "Limited Sneaker: you can buy at most 2.", "target": "$.cart" }] } }
  ]
}
```

### tests/below-minimum-across-variants.input.json

```json
{
  "cart": {
    "lines": [
      {
        "quantity": 1,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567890", "title": "Linen Napkin", "minQuantity": { "value": "4" }, "maxQuantity": null }
        }
      },
      {
        "quantity": 2,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567890", "title": "Linen Napkin", "minQuantity": { "value": "4" }, "maxQuantity": null }
        }
      }
    ]
  }
}
```

### tests/below-minimum-across-variants.output.json

```json
{
  "operations": [
    { "validationAdd": { "errors": [{ "message": "Linen Napkin: the minimum order quantity is 4.", "target": "$.cart" }] } }
  ]
}
```

### tests/below-minimum-while-building-cart.input.json

```json
{
  "buyerJourney": {
    "step": "CART_INTERACTION"
  },
  "cart": {
    "lines": [
      {
        "quantity": 1,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": {
            "id": "gid://shopify/Product/1234567890",
            "title": "Linen Napkin",
            "minQuantity": {
              "value": "4"
            },
            "maxQuantity": null
          }
        }
      },
      {
        "quantity": 2,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": {
            "id": "gid://shopify/Product/1234567890",
            "title": "Linen Napkin",
            "minQuantity": {
              "value": "4"
            },
            "maxQuantity": null
          }
        }
      }
    ]
  }
}
```

### tests/below-minimum-while-building-cart.output.json

```json
{
  "operations": []
}
```

### tests/within-limits.input.json

```json
{
  "cart": {
    "lines": [
      {
        "quantity": 4,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": {
            "id": "gid://shopify/Product/1234567890",
            "title": "Linen Napkin",
            "minQuantity": { "value": "4" },
            "maxQuantity": { "value": "24" }
          }
        }
      },
      {
        "quantity": 1,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567891", "title": "Gift Card Sleeve", "minQuantity": null, "maxQuantity": null }
        }
      }
    ]
  }
}
```

### tests/within-limits.output.json

```json
{ "operations": [] }
```

## Sınırlar ve özel durumlar

- Adetler ürün bazında toplanır; 2 küçük ve 2 büyük beden 4 sayılır.
- Hatalar sepet sayfasında ve checkout’ta görünür. Storefront API de onları Cart nesnesinde döndürür.
- Mesajlar kodda İngilizce; birden fazla dilde gerekiyorsa input sorgusunda localization.language’ı okuyun.
- Her metafield okuması, bir Function input’unun kullanabileceği 30 sorgu maliyeti puanından 3’ünü harcar; ürün başına iki metafield sorun değildir.

## Sorular

### Shopify’da bir ürün için minimum sipariş adedi nasıl belirlenir?

Bu örnekteki gibi minimumu bir ürün metafield’ından okuyup altında checkout’u engelleyen bir doğrulama Function’ı kullanın. Herkese açık bir uygulamada her planda çalışır.

### Bir müşterinin bir üründen kaç adet alacağını sınırlayabilir miyim?

Sipariş başına evet: custom.max_quantity’yi ayarlayın. Birden fazla siparişe yayılan sınır için müşterinin ürün bazında geçmiş siparişleri gerekir; Function input’unda bu bilgi yok.

### Temadaki adet seçici bu sınırlara uyar mı?

Hayır. Function, sepete maksimumdan fazla ürün eklenmesini durdurur; minimumu ise checkout’tan itibaren kontrol eder, böylece müşteri sepetini minimuma kadar doldurabilir. Daha akıcı bir deneyim için aynı metafield’ları temada okuyup adet alanına min ve max değerlerini verin.

## Shopify belgeleri

- [Cart and Checkout Validation Function API](https://shopify.dev/docs/api/functions/latest/cart-and-checkout-validation)
- [Function input query limits](https://shopify.dev/docs/api/functions)

## İlgili içerikler

- [Teslimat, ödeme ya da doğrulama Function’ını etkinleştirme](https://lintelagency.com/tr/kaynaklar/graphql/function-ozellestirmesini-etkinlestirme)
- [Kurallarınıza uymayan Shopify siparişlerini Cart and Checkout Validation ile durdurun](https://lintelagency.com/tr/blog/shopify-odeme-dogrulama)
- [Function’ları metafield (meta alan) ile yapılandırmak: kuralı yeni sürüm yayınlamadan değiştirin](https://lintelagency.com/tr/blog/shopify-functions-metafield-ile-yapilandirma)
- [Minimum sipariş tutarı zorunluluğu](https://lintelagency.com/tr/kaynaklar/shopify-functions/minimum-siparis-tutari)

Lisans: MIT. Kendi mağazanızda da müşteri işlerinizde de ücretsiz kullanabilirsiniz.
