> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flextell.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Teklifler

> Teklif oluşturmayı, durumları, PDF üretmeyi, çoğaltmayı ve satışa dönüştürmeyi anlatır.

`/v1/offers` kaynağı, bir hasta veya lead için hazırlanan fiyat teklifini yönetir. Teklif; tedavi ve ürün kalemlerinden, para biriminden, geçerlilik tarihinden ve bir durumdan oluşur. Kabul edilen teklifi tek çağrıyla taslak satışa dönüştürebilir, teklifin PDF'ini üretebilirsiniz.

Tüm istekler `https://app.flextell.ai/api/v1` altındadır ve `Authorization: Bearer <access_token>` ile `X-Tenant: <tenant_id>` başlıklarını gerektirir. Bkz. [Klinikler ve X-Tenant](/api/klinikler-ve-x-tenant).

<Info>
  Bu modül teklifi hastaya e-posta, WhatsApp veya SMS ile göndermez. `sent` durumu yalnızca elle işaretlenen bir bayraktır. Teklif şablonlarını yöneten bir API de yoktur; PDF her zaman kliniğin şablonuyla üretilir.
</Info>

## Endpoint özeti

| Metot | Endpoint | Açıklama | Scope | İzin |
| - | - | - | - | - |
| `GET` | `/v1/offers` | Teklifleri listeler. | `offers:read` | `View:Offer` |
| `POST` | `/v1/offers` | Teklif oluşturur. | `offers:write` | `Create:Offer` |
| `GET` | `/v1/offers/options/products` | Kalem seçimi için ürünleri listeler. | `offers:read` | `Create:Offer` veya `Update:Offer` |
| `GET` | `/v1/offers/options/treatments` | Kalem seçimi için tedavileri listeler. | `offers:read` | `Create:Offer` veya `Update:Offer` |
| `GET` | `/v1/offers/{id}` | Teklifi kalemleriyle getirir. | `offers:read` | `View:Offer` |
| `PUT` | `/v1/offers/{id}` | Teklifi günceller. | `offers:write` | `Update:Offer` |
| `DELETE` | `/v1/offers/{id}` | Teklifi siler. | `offers:write` | `Delete:Offer` |
| `POST` | `/v1/offers/{id}/convert` | Teklifi taslak satışa dönüştürür. | `offers:write` ve `sales:write` | `Convert:Offer` |
| `POST` | `/v1/offers/{id}/generate-pdf` | PDF üretimini kuyruğa alır. | `offers:read` | `View:Offer` |
| `GET` | `/v1/offers/{id}/pdfs` | Üretilmiş PDF'leri listeler. | `offers:read` | `View:Offer` |
| `GET` | `/v1/offers/{id}/pdfs/{fileId}/download-url` | Kısa ömürlü indirme bağlantısı üretir. | `offers:read` | `View:Offer` |
| `DELETE` | `/v1/offers/{id}/pdfs/{fileId}` | PDF'i siler. | `offers:write` | `Delete:Offer` |

Her endpoint için scope ve izin birlikte gerekir. Kalıcı silme (`force`) endpoint'i yoktur. İzin modeli için [İzinler ve görünürlük](/api/izinler-ve-gorunurluk) sayfasına bakın.

### Görünürlük

`ViewAllOffers:Offer` iznine sahip kullanıcı kliniğin tüm tekliflerini görür. İzni olmayan kullanıcı yalnızca sahibi olduğu teklifleri ve kendisine atanmış lead'lerin tekliflerini görür. `salesperson` rolündeki kullanıcı ayrıca lead aşamasını geçmiş hastaların tüm tekliflerini görür. Görünmeyen teklif için `404 RESOURCE_NOT_FOUND` döner, `403` dönmez.

### Listeleme

`GET /v1/offers` sayfalı döner. Liste yanıtında `items` yer almaz, yalnızca `total_price` gelir. Sayfalama için [Sayfalama](/api/istekler/sayfalama), genel sıralama kuralları için [Filtreleme ve sıralama](/api/istekler/filtreleme-ve-siralama) sayfasına bakın.

| Parametre | Açıklama |
| - | - |
| `search` | En fazla 255 karakter. Hastanın tam metin aramasında eşleşir. |
| `customer_id`, `lead_id` | Hasta veya lead'e göre süzer. `lead_id`, görebildiğiniz bir lead olmalıdır; aksi hâlde `422` döner. |
| `user_id` | Teklif sahibine göre süzer. |
| `status` | `draft`, `sent`, `accepted`, `rejected` veya `converted`. |
| `created_at_from`, `created_at_to` | Oluşturulma aralığı. Bitiş, başlangıçtan önce olamaz. |
| `sort_by`, `sort_order` | `created_at` (varsayılan), `updated_at`, `status`. Yön `asc` veya `desc` (varsayılan `desc`). |
| `per_page`, `page` | `per_page` 1 ile 100 arası, varsayılan 15. |

## Teklif nesnesi

| Alan | Tür | Açıklama |
| - | - | - |
| `id` | integer | Teklif kimliği. |
| `user_id` | integer | Teklif sahibi. Göndermezseniz isteği yapan kullanıcıdır. |
| `customer_id` | integer veya `null` | Hasta. Hasta kalıcı olarak silinirse teklif kalır ve alan `null` olur. |
| `sale_id` | integer veya `null` | Dönüştürülünce oluşan satış. Yazılamaz. |
| `currency_id`, `exchange_rate` | integer, number | Para birimi ve kur. |
| `valid_until` | string | Geçerlilik zamanı. Yalnızca bilgi amaçlıdır; süresi dolan teklif otomatik kapanmaz. |
| `notes` | string | Serbest not. |
| `status` | string | Değerler için aşağıdaki Durum akışı bölümüne bakın. |
| `is_locked` | boolean | `true` ise teklif güncellenemez ve silinemez. |
| `can_convert` | boolean | `true` ise teklif satışa dönüştürülebilir. |
| `total_price` | number | Kalem toplamlarının toplamı. |
| `record_state` | object | `is_read_only` ve `reason_code` (`CUSTOMER_ERASED`, `LEAD_NOT_ASSIGNED` veya `null`). |
| `customer` | object veya `null` | `id`, `first_name`, `last_name`, `phone_number`, `email`, `is_masked`, `is_lead_stage`, `lead_id`. Başka bir temsilcinin lead aşamasındaki hastası için kimlik alanları maskelenir. |
| `user`, `currency` | object | `user`: `id`, `name`. `currency`: `id`, `code`, `symbol`, `name`. |
| `items` | array | Yalnızca tek teklif yanıtlarında (oluşturma, getirme, güncelleme) bulunur. |
| `created_at`, `updated_at`, `deleted_at` | string | Zaman damgaları. |

Her kalemde (`items[]`) şu alanlar döner: `id`, `offer_id`, `order`, `itemable_type` (`Treatment` veya `Product`), `itemable_id`, `quantity`, `tax_rate`, `unit_price`, `discount_amount`, `discount_percentage`, `sub_total`, `total_discount`, `total_tax`, `total_price` ve `itemable` (`id`, `name`; kayıt silinmişse `null`).

## Durum akışı

| `status` | Etiket | Kilitli | Satışa dönüşür |
| - | - | - | - |
| `draft` | Taslak | Hayır | Evet |
| `sent` | Gönderildi | Hayır | Evet |
| `accepted` | Kabul Edildi | Evet | Evet |
| `rejected` | Reddedildi | Hayır | Hayır |
| `converted` | Satışa Dönüştü | Evet | Hayır |

Tipik akış aşağıdadır. Zorunlu bir sıra yoktur.

```mermaid theme={null}
flowchart LR
    A["Taslak"] --> B["Gönderildi"]
    B --> C["Kabul Edildi"]
    B --> D["Reddedildi"]
    A -- "convert" --> E["Satışa Dönüştü"]
    B -- "convert" --> E
    C -- "convert" --> E
```

* Oluştururken ve güncellerken `draft`, `sent`, `accepted`, `rejected` değerlerini verebilirsiniz. `converted` değerini API'ye veremezsiniz; yalnızca dönüştürme işlemi bu duruma geçirir.
* Geçişler arasında bir doğrulama yoktur. Kilitli değilken `rejected` durumundan `draft` durumuna da dönebilirsiniz.
* `accepted` ve `converted` teklifler kilitlidir. `PUT` ve `DELETE` istekleri `422 OFFER_LOCKED` döner. Kabul edilmiş bir teklifi API ile kilitten çıkaramazsınız.

## Oluşturma

`POST /v1/offers` başarılı olunca `201` ve kalemleriyle birlikte teklifi döner.

<ParamField body="customer_id" type="integer">
  Hasta. `lead_id` göndermiyorsanız zorunludur ve `lead_id` ile birlikte gönderilemez. Hasta kliniğe ait olmalıdır.
</ParamField>

<ParamField body="lead_id" type="integer">
  Lead. `customer_id` göndermiyorsanız zorunludur. Görebildiğiniz bir lead olmalıdır. Lead dönüştürülmez; gerekirse lead aşamasında bir kişi kaydı oluşturulur ya da telefon numarasıyla mevcut kayıt kullanılır.
</ParamField>

<ParamField body="currency_id" type="integer" required>
  Para birimi.
</ParamField>

<ParamField body="exchange_rate" type="number" required>
  Kur. 0 veya daha büyük olmalıdır.
</ParamField>

<ParamField body="valid_until" type="string" required>
  Geçerlilik tarihi. Saat dilimi vermezseniz kliniğin saat dilimi kabul edilir; yalnızca tarih verirseniz gün başlangıcı (`00:00`) alınır.
</ParamField>

<ParamField body="status" type="string" required>
  `draft`, `sent`, `accepted` veya `rejected`.
</ParamField>

<ParamField body="user_id" type="integer">
  Teklif sahibi. Kliniğin bir kullanıcısı olmalıdır.
</ParamField>

<ParamField body="notes" type="string">
  Serbest not.
</ParamField>

<ParamField body="items" type="array" required>
  En az bir kalem.
</ParamField>

### Kalemler

| Alan | Zorunlu | Kural |
| - | - | - |
| `itemable_type` | Evet | `Treatment` veya `Product`. |
| `itemable_id` | Evet | Kliniğinizde var olan tedavi veya ürün. Pasif kayıtlar da kabul edilir. |
| `quantity` | Evet | 0.0001 veya daha büyük sayı. |
| `unit_price` | Evet | Vergi hariç (net) birim fiyat, 0 veya daha büyük. |
| `tax_rate` | Evet | `0`, `1`, `10` veya `20`. |
| `discount_amount` | Evet | Tutar olarak indirim, 0 veya daha büyük. |
| `discount_percentage` | Evet | Yüzde olarak indirim, 0 ile 100 arası. |

`order`, `sub_total`, `total_discount`, `total_tax` ve `total_price` alanlarını göndermeniz gerekmez. Gönderirseniz yok sayılır; sıra, istekteki kalem sırasına göre 0'dan başlar.

### Toplamlar

Sunucu toplamları her yazmada yeniden hesaplar:

* `sub_total = quantity × unit_price`
* `total_discount = discount_amount + sub_total × discount_percentage / 100`
* `total_tax = (sub_total − total_discount) × tax_rate / 100`. İndirim ara toplamı aşarsa vergi tabanı 0 olur.
* `total_price = (sub_total − total_discount) + total_tax`

<Warning>
  `discount_amount` ve `discount_percentage` birbirine eklenir; ikisi de uygulanır. İndirimi tek biçimde vermek istiyorsanız diğer alanı `0` gönderin.
</Warning>

Kliniğin muhasebe ayarlarında "satış vergi oranını geçersiz kıl" seçeneği açıksa her kaleme kliniğin varsayılan vergi oranı uygulanır.

### Kalem seçenekleri

`GET /v1/offers/options/products` ve `GET /v1/offers/options/treatments`, teklif formundaki seçim listelerini besler. Yalnızca aktif kayıtları döndürür. Bu endpoint'ler `View:Product` veya `View:Treatment` izni gerektirmez; `Create:Offer` ya da `Update:Offer` yeterlidir. Parametreler: `search`, `per_page` (1–100, varsayılan 15), `page` ve `ids[]` (en fazla 100). `ids[]` verdiğinizde arama ve aktiflik süzgeci uygulanmaz; kayıtlı kalemleri etiketlemek için pasif kayıtlar da döner.

Katalog fiyatları vergi dahildir. Yanıttaki `unit_price_excl_tax` ve `tax_rate` değerlerini kalemde `unit_price` ve `tax_rate` olarak gönderin.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://app.flextell.ai/api/v1/offers" \
    -H "Authorization: Bearer <access_token>" \
    -H "X-Tenant: <tenant_id>" \
    -H "Content-Type: application/json" \
    -d '{
      "customer_id": 123,
      "currency_id": 1,
      "exchange_rate": 1,
      "valid_until": "2026-11-15T23:59:00+03:00",
      "status": "draft",
      "notes": "Örnek teklif",
      "items": [
        {
          "itemable_type": "Treatment",
          "itemable_id": 123,
          "quantity": 1,
          "unit_price": 1000,
          "tax_rate": 20,
          "discount_amount": 0,
          "discount_percentage": 10
        }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch("https://app.flextell.ai/api/v1/offers", {
    method: "POST",
    headers: {
      Authorization: "Bearer <access_token>",
      "X-Tenant": "<tenant_id>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      customer_id: 123,
      currency_id: 1,
      exchange_rate: 1,
      valid_until: "2026-11-15T23:59:00+03:00",
      status: "draft",
      items: [
        {
          itemable_type: "Treatment",
          itemable_id: 123,
          quantity: 1,
          unit_price: 1000,
          tax_rate: 20,
          discount_amount: 0,
          discount_percentage: 10,
        },
      ],
    }),
  });
  ```
</CodeGroup>

Bu örnekte `sub_total` 1000, `total_discount` 100, `total_tax` 180 ve `total_price` 1080 olur.

### Güncelleme

`PUT /v1/offers/{id}` kısmi güncellemedir; göndermediğiniz alanlar değişmez. `items` gönderirseniz mevcut kalemlerin tamamı yeni listeyle değiştirilir ve liste en az bir kalem içermelidir. `items` göndermezseniz kalemler korunur. `notes` ve `user_id` alanlarını `null` göndererek temizleyemezsiniz.

### Teklifi çoğaltma

Çoğaltma için ayrı bir endpoint yoktur. Kopyayı kendiniz oluşturun: kaynak teklifi `GET /v1/offers/{id}` ile okuyun, kalemleri ve alanları `POST /v1/offers` gövdesine taşıyın ve `status` değerini `draft` yapın. Kopyaya `valid_until` için yeni bir tarih vermeniz iyi olur. Kaynak teklifi okumak için `View:Offer`, kopyayı oluşturmak için `Create:Offer` izni gerekir.

### Silme

`DELETE /v1/offers/{id}` teklifi ve kalemlerini Geri Dönüşüm Kutusu'na taşır. Kilitli (`accepted`, `converted`) teklif silinemez. Bekleme süresi ve geri yükleme için [Silme yaşam döngüsü](/api/istekler/silme-yasam-dongusu) sayfasına bakın.

## PDF

PDF üretimi eşzamansızdır. `POST /v1/offers/{id}/generate-pdf` isteği işi kuyruğa alır ve hemen `202` döner. PDF A4 boyutundadır.

<ParamField body="template_id" type="integer">
  İsteğe bağlı teklif şablonu kimliği. Vermezseniz kliniğin aktif şablonlarından varsayılan olanı kullanılır. Her yeni klinikte `Varsayılan Teklif Şablonu` hazır gelir.
</ParamField>

```bash curl theme={null}
curl -X POST "https://app.flextell.ai/api/v1/offers/123/generate-pdf" \
  -H "Authorization: Bearer <access_token>" \
  -H "X-Tenant: <tenant_id>"
```

Yanıt: `{"message": "Teklif PDF dosyası oluşturma işi kuyruğa alındı."}`

Her üretim yeni bir dosya kaydı ekler; eski PDF'ler silinmez. Dosya adı `offer - <hasta adı> - <teklif id> - <tarih_saat>.pdf` biçimindedir. İş bitince isteği yapan kullanıcıya `Dosya Yüklendi` bildirimi gider. Üretim başarısız olursa bildirim gelmez, bu yüzden sonucu `GET /v1/offers/{id}/pdfs` ile kontrol edin.

`GET /v1/offers/{id}/pdfs` sayfalı döner (`per_page` 1–100, varsayılan 15; `sort_order`, `created_at_from`, `created_at_to`). Her kayıtta `id`, `name`, `description`, `mime_type`, `size`, `source`, `created_at` ve `updated_at` bulunur.

`GET /v1/offers/{id}/pdfs/{fileId}/download-url` kısa ömürlü bir bağlantı üretir.

| Parametre | Varsayılan | Açıklama |
| - | - | - |
| `disposition` | `attachment` | `attachment` indirir, `inline` tarayıcıda açar. |
| `expires_in` | 15 | Bağlantı ömrü, dakika (1–60). |

Yanıt `url`, `expires_at` (ISO 8601), `disposition` ve `file` alanlarını içerir. Dosya depolamadan kaybolmuşsa `404 OFFER_PDF_FILE_MISSING` döner. `DELETE /v1/offers/{id}/pdfs/{fileId}` PDF'i kalıcı siler; teklif kilitli olsa bile çalışır.

## Satışa dönüştürme

`POST /v1/offers/{id}/convert` teklifi, aynı kalemlerle yeni bir taslak satışa çevirir. İsteğin gövdesi yoktur. Token'ınızda hem `offers:write` hem `sales:write` scope'u, rolünüzde `Convert:Offer` izni olmalıdır. `Update:Offer` bu işlem için yeterli değildir.

Dönüştürme `draft`, `sent` ve `accepted` tekliflerde çalışır. Başarılı olunca `201` ve satış nesnesi döner; satışın `status` değeri `draft` olur. Teklif `converted` durumuna geçer ve `sale_id` alanı dolar.

* Satışa hasta, para birimi, kur ve notlar taşınır. Satışın sahibi, isteği yapan kullanıcı değil teklifin sahibidir.
* Her kalem sırası, miktarı, vergi oranı, fiyatı, indirimleri ve toplamlarıyla birebir kopyalanır. Ürün kalemlerinin maliyet fiyatı üründen alınır; tedavi kalemlerinde 0'dır.
* İkinci çağrı `422` döner. Satış nesnesi için [Satışlar](/api/kaynaklar/satislar) sayfasına bakın.

```bash curl theme={null}
curl -X POST "https://app.flextell.ai/api/v1/offers/123/convert" \
  -H "Authorization: Bearer <access_token>" \
  -H "X-Tenant: <tenant_id>"
```

## Hata kodları

| Kod | HTTP | Ne zaman |
| - | - | - |
| `OFFER_LOCKED` | 422 | Kabul edilmiş veya satışa dönüştürülmüş teklifi güncelleme ya da silme. |
| `OFFER_NOT_CONVERTIBLE` | 422 | `rejected` veya `converted` teklifi dönüştürme. |
| `OFFER_ALREADY_CONVERTED` | 422 | Teklif eşzamanlı bir istekle zaten dönüştürülmüş. |
| `OFFER_TEMPLATE_MISSING` | 422 | `template_id` yok ve kliniğin aktif şablonu yok. |
| `OFFER_TEMPLATE_INVALID` | 422 | `template_id` başka bir klinikteki şablona ait. Var olmayan bir kimlik `422 VALIDATION_FAILED` döner. |
| `OFFER_PDF_FILE_MISSING` | 404 | Bağlantısı istenen PDF dosyası depolamada yok. |
| `CUSTOMER_ERASED` | 409 | Hastanın verileri imha edilmiş; teklif üzerinde her yazma işlemi (güncelleme, silme, dönüştürme, PDF) reddedilir. Okuma çalışır, `record_state.is_read_only` `true` olur. |
| `LEAD_NOT_ASSIGNED` | 403 | Satış temsilcisi, başka bir temsilcinin ya da kimseye atanmamış lead'in teklifine yazmaya çalışıyor. `data.assigned_user` sahibi gösterir. |
| `RESOURCE_NOT_FOUND` | 404 | Teklif veya PDF yok ya da size görünmüyor. |
| `FORBIDDEN` | 403 | Scope veya izin eksik. |
| `VALIDATION_FAILED` | 422 | Doğrulama hatası; ayrıntılar `errors` içindedir. |

Ortak kodlar için [Hata kodları](/api/istekler/hata-kodlari) sayfasına bakın.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.