# Veri modeli ve kayıt işlemleri

Uygulama kayıt tutuyorsa (bakım kayıtları, denetimler, envanter, ziyaretler vb.) tabloyu, tipleri ve kayıt uçlarını
elle yazmayın. Kayıtların **neye benzediğini** `one.data.json` dosyasında tanımlayın; Devoplus One gerisini üretir:

- veritabanı tablosunu kuran **migration** (`one data generate`),
- TypeScript **tipleri** (`src/data/types.ts`),
- hazır **kayıt uçları**: listeleme, süzme, sıralama, sayfalama, ekleme, güncelleme, silme, Excel/CSV dışa aktarma ve
  yazdırma sayfası (SDK `createCrudHandler`).

Sunucu tarafı doğrulama, Türkçe hata iletileri, "kim ekledi / kim değiştirdi" alanları ve yumuşak silme bu yolda
hazırdır. Elle SQL yalnız bu modelin karşılamadığı özel bir sorgu gerektiğinde yazılır.

## Ne zaman kullanılır

| Kullanıcının söylediği | Yapılacak |
|---|---|
| "Talepleri kaydedip listeleyelim", "bir form doldurulsun, ben de göreyim" | Bir varlık (ör. `talepler`) + alanları |
| "Herkes yalnız kendi kaydını görsün" | `createCrudHandler({ records: 'own' })` |
| "Yalnız yöneticiler onaylasın / düzenlesin" | Uygulama izni (`permissions`) + `canWrite: 'One.<Uygulama>.<Eylem>'` |
| "Durumu açık / kapalı / beklemede olsun" | `choice` alanı (sonradan seçenek eklenebilir ama çıkarılamaz; aşağıdaki sınıra bakın) |
| "Tutar girilsin" | `money` alanı (kuruş hassasiyetinde; yuvarlama yok) |
| "Sorumlu kişi seçilsin" | `person` alanı (kullanıcı kimliği) |
| "Ek dosya yüklensin" | `file` alanı + dosya deposu (`services.files`, `OneFiles`) |

## Adımlar

1. **Kullanıcıyla kayıtları konuşun** (keşif görüşmesinde): her kayıt türü için hangi bilgiler tutulacak, hangisi
   zorunlu, hangisi tekrar edemez (ör. talep numarası), seçimli alanların bütün seçenekleri, kim neyi görecek. Teknik
   ad sormayın; adları siz koyun.
2. **`one.data.json` yazın** (uygulamanın kök dizini):

   ```json
   {
     "version": 1,
     "entities": {
       "talepler": {
         "title": { "tr": "Talepler", "en": "Requests" },
         "fields": {
           "baslik":  { "type": "text",   "title": { "tr": "Başlık", "en": "Title" }, "required": true, "maxLength": 120 },
           "aciklama": { "type": "longText", "title": { "tr": "Açıklama", "en": "Description" } },
           "tutar":   { "type": "money",  "title": { "tr": "Tutar", "en": "Amount" }, "min": 0 },
           "durum":   { "type": "choice", "title": { "tr": "Durum", "en": "Status" }, "required": true, "default": "acik", "index": true,
                        "choices": [ { "value": "acik",   "title": { "tr": "Açık", "en": "Open" } },
                                     { "value": "onaylandi", "title": { "tr": "Onaylandı", "en": "Approved" } },
                                     { "value": "reddedildi", "title": { "tr": "Reddedildi", "en": "Rejected" } } ] },
           "son_tarih": { "type": "date",  "title": { "tr": "Son tarih", "en": "Due date" } },
           "sorumlu": { "type": "person", "title": { "tr": "Sorumlu", "en": "Owner" } }
         }
       }
     }
   }
   ```

3. **`one data generate`** çalıştırın. Yeni migration (`migrations/NNNN_one_data.sql`), tipler ve
   `migrations/.one-data-snapshot.json` yazılır. Üçünü de depoya ekleyin; anlık görüntü dosyası elle düzenlenmez.
4. **Uçları bağlayın** (`src/index.ts`):

   ```ts
   import { createCrudHandler, problemResponse, readContext } from '@devoplus/one-sdk'
   import { dataModel } from './data/types.ts'

   const talepler = createCrudHandler({ model: dataModel, entity: 'talepler', records: 'shared', canWrite: 'One.Talep.Write' })

   // fetch(request, env) içinde, kendi uçlarınızdan önce:
   const res = await talepler.handle(request, env.DB)
   if (res) return res
   ```

5. **`one migrate`** ile yerel veritabanını kurun, **`one dev`** ile deneyin. Yayında migration'ı platform uygular.

## Alan türleri

| Tür | Kullanıcıya | Not |
|---|---|---|
| `text` | Kısa metin (tek satır) | `maxLength` varsayılan 200, en çok 1.000 |
| `longText` | Uzun metin, açıklama | varsayılan 10.000, en çok 50.000; tekil ve dizinli olamaz |
| `number` | Sayı | `min` / `max` |
| `money` | Tutar | API'de `"1250.50"` metni; üçüncü ondalık ve binlik ayırıcı reddedilir |
| `date` | Tarih | `YYYY-AA-GG` |
| `datetime` | Tarih ve saat | gönderirken saat dilimi zorunlu (`2026-10-18T09:00:00+03:00`) |
| `boolean` | Evet / hayır | |
| `choice` | Listeden seçim | `value` kod (küçük harf, rakam, `_`, `-`), `title` görünen ad |
| `person` | Kişi | kullanıcı kimliği; ad ve departman için organizasyon şeması okunur |
| `file` | Dosya | dosya deposundaki anahtar |

Her alanda `required`, `unique`, `index`, `default` kullanılabilir. Adlar küçük harf, rakam ve alt çizgidir
(`son_tarih`); `order`, `group`, `not` gibi veritabanı sözcükleri ve otomatik alan adları (`id`, `created_at`,
`created_by`, `updated_at`, `updated_by`, `deleted_at`) kullanılamaz; komut hangi adın neden reddedildiğini söyler.

## Uçlar

| Yöntem | Yol | Ne yapar |
|---|---|---|
| GET | `/api/talepler?_page=1&_size=50&_sort=-created_at` | sayfalı liste `{ items, page, pageSize, total }` |
| GET | `/api/talepler?durum=acik&baslik.contains=araç&tutar.from=100&son_tarih.to=2026-12-31` | süzme (eşitlik, içerir, aralık) |
| POST | `/api/talepler` | kayıt ekler |
| GET / PATCH / DELETE | `/api/talepler/<id>` | okur / gönderilen alanları günceller / siler (yumuşak) |
| GET | `/api/talepler/export.xlsx`, `export.csv`, `report.html` | aynı süzgeçlerle dışa aktarma ([Rapor ve dışa aktarma](/docs/exports.md)) |

**Hazır liste bileşeni bu uçlara doğrudan bağlanır:** `<one-table src="/api/talepler" paging="page" export="xlsx csv">`.
Uçlar bileşenin gönderdiği `limit`, `page`, `cursor`, `sort` + `order`, `q` (metin alanlarının herhangi birinde
arama), `<alan>From` / `<alan>To` ve `format=xlsx|csv` parametrelerini de anlar; ayrıca bir çeviri yazmayın.

Hata yanıtı `errors: [{ field, message }]` taşır; iletiler Türkçedir ve formda alanın yanında olduğu gibi
gösterilebilir. `created_by` ve `updated_by` oturumdan yazılır, gövdeyle gönderilemez.

## Kurallar ve sınırlar: kullanıcıya baştan söyleyin

- **Silme yumuşaktır.** Her kayıt türü varsayılan olarak yumuşak silinir (`deleted_at`). Kalıcı silme yalnız
  `"softDelete": false` ile birlikte en az 10 karakterlik `"hardDeleteReason"` gerekçesiyle seçilir; gerekçe yoksa
  `one data generate` ve yayın durur. Model `createCrudHandler`'a koddan verilirse gerekçesiz model kayıt uçlarında
  yapılandırma hatası (500) verir.
- **Model yalnız büyür.** Yeni kayıt türü, yeni alan ve yeni dizin eklenebilir. Alan **silmek**, **adını** ya da
  **türünü** değiştirmek, isteğe bağlı bir alanı **zorunlu** yapmak (ya da tersi), tekilliği değiştirmek ve bir
  seçim alanından **seçenek çıkarmak** otomatik yapılmaz; komut hiçbir dosya yazmadan durur. Seçim alanına **yeni
  seçenek eklemek** serbesttir (migration gerekmez; `one data generate` tipleri yeniler). Seçeneklerin **görünen
  adları** ve başlıklar serbestçe değişir.
- Var olan bir kayıt türüne **zorunlu** alan eklerken `default` verin (eski kayıtlar o değeri alır).
- Üretilmiş bir migration dosyası, yayımlandıktan sonra **değiştirilmez**; değişiklik her zaman yeni bir dosyadır.
- En çok 30 kayıt türü, kayıt türü başına 60 alan (ve tabloda arama kolonları dahil en çok 100 kolon); liste sayfası en çok 200 kayıt, dışa aktarma varsayılan en çok
  10.000 kayıt.
- Arama ("içerir" ve `q`) büyük/küçük harfe ve Türkçe harflere duyarsızdır: "istanbul" "İSTANBUL"u, "cay" "Çay"ı bulur.
  Her metin alanının `<alan>_fold` adlı bir arama kolonu vardır (alan adı `_fold` ile bitemez). Bir metin alanını elle
  yazdığınız SQL ile güncelliyorsanız arama kolonunu da `foldSearchText(değer)` ile yazın ya da `NULL` yapın.
- Vekalet ya da Yerine Görüntüle oturumunda kayıtlar varsayılan olarak **yalnız görüntülenir**; yazma gerekiyorsa
  manifest'te `actingSessions: "write"` ve `createCrudHandler`'da `actingSessions: 'write'` birlikte verilir.
- `records` seçeneği zorunludur: kayıtları kimin göreceğini kullanıcıya sorun (`shared` herkes, `own` yalnız
  ekleyen kişi). Rol gerekiyorsa e-posta listesi yazmayın; uygulama izni isteyin ([erişim talebi](/docs/access-requests.md)).
