# Fiyat teklifi

Quote endpoint'i tek bir somut yapılandırmayı fiyatlar. Canlı bir toplam göstermek için alıcının seçimindeki her değişiklikte çağırın. Salt okunurdur: sepet veya sipariş oluşturulmaz ve yalnızca bir okuma scope'u gerekir: `catalog:read` veya `commerce:read` taşıyan bir anahtar kabul edilir.

## POST /v1/api/commerce/configurator/products/{id}/quote

Aktif bir made-to-measure ürünün (capability `made_to_measure`) belirli bir yapılandırmasını fiyatlar. Sunucu önce seçili seçimleri doğrular (varlık, kullanılabilirlik, `single` grup başına en fazla bir seçim, zorunlu grupların karşılanması, `requires` ve `excludes` kuralları), ardından boyutları doğrular (yapılandırılmış her boyutun mevcut olması, tam sayı olması, seçili seçimlerin kısıtlarıyla daraltıldıktan sonra min ve max sınırları içinde kalması), taban fiyatı kazanan fiyat matrisinden (veya ürünün boyutu yoksa ürün fiyatından) çözer, seçim deltalarını iki geçişte uygular (önce `fixed`, `per_area` ve `percent`, ardından her `percent_of_total` bu ara toplama karşı, bileşik etki olmadan) ve toplamı satır bazında dökümle birlikte döndürür. Algoritmanın tamamı için bölüm girişine bakın.

### Yol parametreleri

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| id | string (uuid) | Evet | Ürün ID'si. Capability değeri `made_to_measure` olan aktif bir ürün olmalıdır. |

### Gövde parametreleri

Request body JSON'dur.

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| dimensions | object | Koşullu | Boyut `key` değerlerinden (şemadan) boyutun biriminde (örneğin cm) tam sayı değere bir eşleme. Ondalıklı bir değer `dimensions.{key}` üzerinde 422 ile reddedilir. Ürünün boyutları olduğunda zorunludur: yapılandırılmış her boyut mevcut ve sayısal olmalıdır. Bilinmeyen anahtarlar 422 ile reddedilir. Yalnızca boyutu olmayan bir ürün için atlanabilir. |
| choices | array of string (uuid) | Hayır | Seçili seçenek seçimi ID'leri. Tekrarlar tekilleştirilir. Her öğe, bu ürünün kullanılabilir bir seçimine, yani şema endpoint'inin yayımladığı bir seçime referans veren bir UUID olmalıdır. Zorunlu gruplar karşılanmalı ve `single` grup başına en fazla bir seçime izin verilir. Yalnızca ürünün zorunlu grubu yoksa atlayın veya boş dizi gönderin. |

```bash title="Request"
curl -X POST "https://api.borkol.com/v1/api/commerce/configurator/products/9c2f1e6a-8b4d-4f2a-9e11-3d5a7b9c0e21/quote" \
  -H "Authorization: Bearer {api_key}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "dimensions": { "width": 420, "depth": 300 },
    "choices": [
      "c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
      "f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0"
    ]
  }'
```

```javascript title="Request (JS)"
const res = await fetch(
  `https://api.borkol.com/v1/api/commerce/configurator/products/${productId}/quote`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      dimensions: { width: 420, depth: 300 },
      choices: [
        "c1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
        "f6a7b8c9-d0e1-4f2a-b3c4-d5e6f7a8b9c0",
      ],
    }),
  }
);
if (res.status === 422) {
  const { errors } = await res.json();
  // errors is a map of field => [message]; render per-field.
} else {
  const { data } = await res.json();
}
```

```json title="Response"
{
  "data": {
    "total": 561500,
    "currency": "EUR",
    "breakdown": [
      { "label": "Base", "amount": 530000 },
      { "label": "LED lighting: LED set (6)", "amount": 31500 }
    ],
    "matrix": {
      "id": "d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a",
      "name": "Glass roof"
    }
  }
}
```

### Yanıt alanları

| Ad | Tür | Zorunlu | Açıklama |
| --- | --- | --- | --- |
| total | integer | her zaman | Küçük birim (kuruş/cent) cinsinden toplam fiyat. |
| currency | string | her zaman | Ürünün ISO para birimi kodu, örneğin `"EUR"`. |
| breakdown | array | her zaman | Satır kalemleri. Her zaman `{"label":"Base","amount":<base>}` ile başlar; ardından grup ve seçim pozisyon sırasına göre, fiyatlanan her seçim için `"{Group name}: {Choice name}"` etiketli birer satır gelir (tüm `percent_of_total` satırları diğerlerinden sonra). Deltası 0 olan seçimler yine de `amount: 0` ile görünür. Tüm tutarlar tamsayı küçük birimdir. |
| matrix | object or null | her zaman | Taban fiyatı sağlayan fiyat matrisinin `{id, name}` bilgisi. Taban, ürünün kendi fiyatından geldiğinde (boyutu olmayan bir ürün) `null`. |

### Hatalar

Kimlik doğrulama ve altyapı hataları, diğer storefront route'larıyla aynıdır:

| Durum | Gövde | Neden |
| --- | --- | --- |
| 401 | `{"message":"Unauthenticated."}` | Eksik veya geçersiz API anahtarı. |
| 403 | `{"message":"Invalid ability provided."}` | Anahtarda `catalog:read` scope'u da `commerce:read` scope'u da yok. |
| 403 | `{"error":"Module not enabled: configurator"}` | Tenant'ta configurator modülü etkin değil. `message` yerine `error` anahtarına dikkat edin. |
| 403 | `{"message":"Tenant suspended.","code":"tenant_suspended"}` | Tenant hesabı askıya alınmış. |
| 404 | `{"message":"..."}` | Ürün mevcut değil, aktif değil veya capability değeri `made_to_measure` değil. |
| 429 | `{"message":"Too Many Attempts."}` | Dakikada 120 request'lik rate limit aşıldı. |

### 422 doğrulama hataları

Tüm doğrulama hataları standart Laravel zarfını kullanır: `{"message":"<first error>","errors":{"<field>":["<message>", ...]}}`; en üst düzeyde `code` yoktur. Bu, aynı yapılandırma hatalarını en üst düzeyde `code` `INVALID_CONFIGURATION` ile bildiren sepete eklemeden farklıdır (bkz. [Sepet satırları](/tr/api/carts-and-checkout/cart-lines)). İki katman vardır.

**Girdi doğrulaması** (request body'nin şekli): `dimensions` bir nesne, `choices` bir dizi olmalı ve her `choices.*` öğesi bir UUID olmalıdır. Örnek:

```json title="Response (422, input validation)"
{
  "message": "The choices.0 field must be a valid UUID.",
  "errors": {
    "choices.0": ["The choices.0 field must be a valid UUID."]
  }
}
```

**Alan (domain) doğrulaması** (yapılandırmanın kendisi): aynı zarf; her hata anahtarı tam olarak bir mesaj taşır. Seçim hataları önce denetlenir ve kısa devre yapar: herhangi bir seçim hatası oluştuğunda, o response'ta boyutlar doğrulanmaz. Olası anahtarlar ve mesajlar:

| Hata anahtarı | Mesaj | Neden |
| --- | --- | --- |
| `choices` | `One or more choices are not available.` | Bir seçim ID'si bu üründe mevcut değil ya da seçim kullanılabilir değil: pasif, silinmiş veya karşılanabilir bir `requires` hedef grubu kalmamış. Mesaj bilinçli olarak geneldir ve kullanılabilir olmayan seçimin adını asla vermez. |
| `choices` | `Choose one {Group}.` | `single` tipindeki bir grupta iki veya daha fazla seçim seçilmiş. |
| `choices` | `{Group} is required.` | Zorunlu bir grupta seçili seçim yok. |
| `choices` | `Area-priced choice needs two dimensions.` | Tam olarak iki boyutu olmayan bir üründe `per_area` bir seçim seçilmiş. |
| `choices.{choiceId}` | `{Choice} cannot be combined with {Target}.` | Bir `excludes` kuralı ihlal edilmiş. |
| `choices.{choiceId}` | `{Choice} requires one of: {names}.` | Bir `requires` kuralı karşılanmamış (bir hedef grubundaki hedeflerin hiçbiri seçili değil). `{names}` yalnızca şemanın bu ürün için yayımladığı seçimleri listeler; bu nedenle mesajdaki bir ad her zaman alıcının seçebileceği bir seçimdir. |
| `dimensions.{key}` | `{Label} is required.` | Boyut request'te eksik veya değeri sayısal değil. |
| `dimensions.{key}` | `{Label} must be a whole number.` | Değerin ondalık kısmı var, örneğin `120.5`. |
| `dimensions.{key}` | `{Label} must be between {min} and {max} {unit}.` | Değer, boyutun aralığının dışında. |
| `dimensions.{key}` | `{Label} must be between {min} and {max} {unit} when {choice names} is selected.` | Değer, seçili seçimlerin kısıtlarıyla daraltıldıktan sonraki aralığın dışında. |
| `dimensions.{key}` | `{Label} has no valid range for the selected choices ({names}).` | Seçili seçimlerin kısıtları aralığı geçersiz kılıyor (geçerli min, geçerli max'i aşıyor). |
| `dimensions.{key}` | `Unknown dimension.` | Request, üründe yapılandırılmamış bir boyut anahtarı içeriyor. |
| `configuration` | `not_priceable` | Taban fiyat çözülemedi: boyutu olmayan bir ürünün fiyatı `null`, seçili seçimler hiçbir fiyat matrisinin koşullarını karşılamıyor veya kazanan matriste boyut değerlerini kapsayan bir fiyat kuralı yok. Kullanılabilir olmayan bir seçime koşullanmış bir matris asla karşılanamaz, bu yüzden hiçbir zaman taban fiyat sağlamaz; şema da böyle bir matrisi listelemez. |

Örnek bir alan doğrulaması hatası:

```json title="Response (422, domain validation)"
{
  "message": "Width must be between 200 and 600 cm when Glass is selected.",
  "errors": {
    "dimensions.width": [
      "Width must be between 200 and 600 cm when Glass is selected."
    ]
  }
}
```

Bir seçimdeki `requires_quote` bayrağının burada asla hataya yol açmadığını unutmayın: endpoint bu tür yapılandırmaları normal şekilde fiyatlar. Şemada sunulan bu bayrak, yalnızca storefront'un alıcıyı doğrudan ödeme (checkout) yerine manuel teklif talebine yönlendirmesi gerektiğini belirtir.
