3609 lines
214 KiB
Markdown
3609 lines
214 KiB
Markdown
# Low-Code Üretim Referansı (Authoring Reference)
|
||
|
||
Bu doküman, bir yapay zekâ ajanının **platformu bir geliştirici gibi kullanarak** ekran ve
|
||
bileşen üretebilmesi için gereken somut şemaları, alan anlamlarını, enum değerlerini ve
|
||
çalışan örnekleri içerir.
|
||
|
||
- Ne yapılacağına karar verme kuralları: `ai.instructions.md`
|
||
- **JSON kolon ve modül sözleşmelerinin tam listesi: `lowcode-reference.instructions.md`**
|
||
- `api/` kod standardı: `dotnet.instructions.md`
|
||
- Modül/liste ekleme prosedürü: `list.instructions.md`
|
||
- Neyin nerede yaşadığı: `../../README.md`
|
||
|
||
Bu dosya **nasıl üretileceğini** anlatır; `lowcode-reference.instructions.md` **neyin
|
||
ayarlanabildiğini** alan alan listeler (grid/pivot/chart JSON'ları, alan seviyesi JSON'lar, SQL
|
||
kancaları, tasarımcı envanteri, mail modülü) ve **§12'de Saas + Administration menülerindeki
|
||
hazır gelen bütün ekranları** — hangisi yeniden kullanılacak yetenek, hangisi lookup kaynağı,
|
||
hangisi taklit edilecek canlı örnek. Bir komponent üretmeden önce oradaki **§0 yetenek
|
||
envanterini** tara: platformun sunduğu bir katmanı atlamadığından böyle emin olursun.
|
||
|
||
Çelişki olursa `ai.instructions.md` kazanır. Buradaki her şey **"kod yazmadan üretim"**
|
||
tarafındadır; bu dosya platformun çekirdek kodunu geliştirmek için değil, **prompt ile
|
||
uygulamaya yeni ekran, menü, yetki, dil anahtarı, route ve süreç eklemek** içindir.
|
||
|
||
Bir istek geldiğinde çekirdek koda dokunmak değil, `configs/seeds/` altına uygulamanın kendi
|
||
üreteceği dosyaların aynılarını yazmak esastır — bkz. §0.
|
||
|
||
> **Altın kural:** Ürettiğin her şey bir seed dosyasına düşmelidir. Veritabanı silinip
|
||
> yeniden oluşturulduğunda geri gelmeyen bir artefakt teslim edilmiş sayılmaz.
|
||
|
||
---
|
||
|
||
## 0. Üretim protokolü (her prompt için zorunlu)
|
||
|
||
Kullanıcı "şöyle bir ekran/form/süreç istiyorum" dediğinde **uygulamayı kullanıyormuş gibi**
|
||
davran: Wizard'ın, Component Manager'ın ve SQL Query Manager'ın diske yazacağı dosyaların
|
||
aynılarını `configs/seeds/host/` altına sen yaz. Ekranlara tıklamanın yerine geçen şey budur.
|
||
|
||
Talep iki biçimde gelir ve ikisi de aynı hatta girer:
|
||
|
||
- **Yazılı tarif** → §0.6'daki keşif turunu işlet, tarifi kur, onaylat.
|
||
- **Ekran görüntüsü / mockup / fotoğraf** → §0.7 ile önce görseli oku, sonra §0.6 turuyla
|
||
görselden okunamayan boşlukları sor.
|
||
|
||
Tarif onaylanınca §0.2'deki sırayla dosyaları üret. **Sıra bozulamaz: önce sorular, sonra tarif
|
||
ve onay, en son dosyalar.**
|
||
|
||
### 0.1 Yazılacak klasörler
|
||
|
||
```
|
||
configs/seeds/host/
|
||
├── sql/object/{Nesne}.sql ← tablo / view / fonksiyon / prosedür (SQL Server)
|
||
├── sql/execute/{Ad}.sql ← bir kez çalışacak script (veri dolumu, migrasyon)
|
||
├── postgres/object|execute/… ← aynısının PostgreSQL diyalekti (hedef PG ise)
|
||
├── crud/{EntityName}.json ← tablonun REST uçları
|
||
├── custom/{ComponentName}.json ← Custom Component (Visual Designer dokümanı ya da kod)
|
||
├── wizard/{WizardName}.json ← ekran + menü + yetki + dil anahtarları
|
||
└── data/{ListFormCode}.json ← ekrandan girilen liste verisinin aynası (opsiyonel)
|
||
```
|
||
|
||
Tenant kapsamı isteniyorsa `host/` yerine `tenants/{tenantId}/`; iç düzen aynıdır.
|
||
|
||
### 0.2 Üretim sırası — bu sırayla yaz, bu sırayla çalışır
|
||
|
||
| # | Adım | Dosya | Neden bu sırada |
|
||
| --- | --- | --- | --- |
|
||
| 1 | Tablo / view | `sql/object/…` | Ekranın bağlanacağı nesne önce var olmalı |
|
||
| 2 | CRUD uçları (gerekiyorsa) | `crud/{Entity}.json` | Custom Component'in data source'ları buna bakar |
|
||
| 3 | Custom Component (gerekiyorsa) | `custom/{Name}.json` | Wizard `Custom` yolunda bileşen adını arar |
|
||
| 4 | Wizard | `wizard/{Ad}.json` | Menü, yetki, dil anahtarı ve ListForm'u üretir |
|
||
| 5 | Alt ekranlar (varsa) | ek `wizard/…` dosyaları | Ana wizard'dan **önceki** `SeededAt` damgasını alır |
|
||
| 6 | Liste verisi (isteniyorsa) | `data/{ListFormCode}.json` | Ekran ve tablo hazır olmalı; `ListForm.SeedFilePath` bu yolu gösterir |
|
||
|
||
**`Wizard.SeededAt` bir bağımlılık aracıdır.** Dosya adı tarih taşımaz (`{WizardName}.json`);
|
||
`WizardDataSeeder` `wizard/` klasörünü **`Wizard.SeededAt` damgasına göre sıralı** işler (eşitlikte
|
||
dosya adı). Bir wizard başka bir wizard'ın ürettiği menüye/ListForm'a dayanıyorsa, bağımlı olanın
|
||
damgası **daha büyük** olmalıdır. Alt form olarak kullanılacak menüsüz ekranı her zaman önce
|
||
damgala.
|
||
|
||
### 0.3 Yazmadan önce zorunlu keşif
|
||
|
||
Uydurma. Şu üçünü mutlaka kontrol et:
|
||
|
||
1. **Tablo/kolon adları** — `sql/object/` altındaki dosyalar ya da mevcut wizard dosyalarındaki
|
||
`SelectCommand`. Kolon adını tahmin etme; yoksa tabloyu da sen üret.
|
||
2. **Var olan menü kökü ve izin grubu** — aynı modül için ikinci bir kök menü açma. Mevcut
|
||
wizard dosyalarındaki `MenuParentCode` / `PermissionGroupName` değerlerini tara.
|
||
3. **Dil anahtarı çakışması** — `LanguagesData.json` ve diğer wizard dosyaları. Var olan bir
|
||
anahtarın metnini seed **ezmez**; yanlış metinle karşılaşırsan anahtarı değiştir.
|
||
|
||
### 0.4 Üretim sonrası
|
||
|
||
- Değişikliklerin devreye girmesi için **DB Migrate** (SQL Query Manager / Wizard Manager
|
||
üzerindeki buton, yetki `App.Setup.Migrate`) ya da migrator konteyneri (`SEED=true`) çalışmalı.
|
||
- Seeder **idempotenttir**: ekran zaten varsa dosya atlanır (§4.5). Var olan bir ekranı
|
||
değiştirmek için dosyayı düzenlemek yetmez — kayıtları silip yeniden seed etmek ya da
|
||
ekranı Wizard'dan `EditFileName` ile yeniden çalıştırmak gerekir.
|
||
- Ürettiğin dosyaları kullanıcıya **tek tek listele**: hangi dosya, ne üretiyor, hangi menüde
|
||
görünecek, hangi yetkiyi ister.
|
||
|
||
### 0.5 Dosya biçimi kuralları
|
||
|
||
| Kural | Değer |
|
||
| --- | --- |
|
||
| Kodlama | UTF-8 (BOM'suz) |
|
||
| JSON alan adları | **PascalCase** (`Wizard`, `ListFormCode`, `Groups`) — seeder `PropertyNameCaseInsensitive` okur ama dosyalar PascalCase üretilir |
|
||
| Custom component dosyası | Kök alanlar PascalCase; `Props` **içindeki** designer dokümanı camelCase (`visualDesigner`, `nodes`, `sourceMode`) |
|
||
| Enum'lar | Sayı olarak yazılır (§3) |
|
||
| `GeneratedAt` | ISO-8601 UTC (`2026-08-26T10:15:00Z`) — yalnızca bilgi amaçlı. **Yalnızca `custom/` ve `crud/` dosyalarında vardır**; wizard seed dosyasında karşılığı `Wizard.SeededAt` alanıdır |
|
||
| İç içe JSON | `EditorOptions`, `EditorScript`, `LookupQuery` → JSON **string** olarak kaçışlanır |
|
||
| Satır sonu | LF |
|
||
|
||
---
|
||
|
||
### 0.6 Zorunlu keşif turu — sormadan üretme
|
||
|
||
**Kullanıcı bir ekran/komponent istediğinde ilk cevabın dosya değil, sorudur.** Talep ne kadar
|
||
kısa gelirse gelsin ("stok listesi olsun", "şöyle bir sayfa istiyorum", bir ekran görüntüsü),
|
||
üretimden önce keşif turunu işlet. Tek satırlık bir talepten üretilen ekran çalışır ama
|
||
kullanılabilir olmaz; eksik olan şey kodda değil, tarifte olur.
|
||
|
||
Bu, dosyanın geri kalanındaki "varsayımını yaz ve devam et" tavrının **istisnasıdır** ve yalnızca
|
||
yeni ekran/komponent üretimi için geçerlidir. Var olan bir ekranı düzeltme, alan ekleme, hata
|
||
ayıklama, açıklama isteği gibi taleplerde soru turu yoktur — doğrudan yap.
|
||
|
||
**Turun kuralları**
|
||
|
||
1. **Soruları toplu sor.** Tek tek değil, `AskUserQuestion` ile turda **en fazla 4 soru**;
|
||
her sorunun 2–4 seçeneği ve **önerilen bir varsayılanı** olsun (`(Önerilen)`). Kullanıcı
|
||
okumadan onaylayabilmelidir.
|
||
2. **En fazla üç tur.** Tur 1 iskeleti kurar (§0.6.2), Tur 2 seçilen yolun ayrıntısını alır
|
||
(§0.6.3 / §0.6.4), Tur 3 yalnızca ilk iki turda ortaya çıkan **açık uçları** kapatır.
|
||
Üçüncü turdan sonra kalan boşluk varsayımla doldurulur ve tarifte belirtilir.
|
||
3. **Cevabı zaten bilinen şeyi sorma** (§0.6.5). Depoda okunabilen (tablo/kolon adları, mevcut
|
||
menü kökleri, yetki grupları) her şey **önce aranır**; soru ancak arama sonuçsuz kalırsa
|
||
ya da iki makul seçenek varsa sorulur.
|
||
4. **Her soru bir üretim kararını değiştirmelidir.** Cevabı ne olursa olsun aynı dosyayı
|
||
üreteceksen o soruyu sorma.
|
||
5. **Kaçış yolu her zaman açık:** kullanıcı "sen bilirsin / varsayılanlarla üret / hepsini sen
|
||
seç" derse turu bitir, varsayımlarla üret ve sonunda listele. Kullanıcı baştan "soru sorma"
|
||
dediyse hiç başlatma.
|
||
6. **Sonunda tarifi onaylat** (§0.6.6). Onay gelmeden `configs/seeds/` altına dosya yazma.
|
||
|
||
**Turun çıktısı** kapsamı belli bir ekran tarifidir; tarifin doldurulacak katmanları
|
||
`lowcode-reference.instructions.md` §0'daki 25 satırlık yetenek envanteridir. Turu o listeyi
|
||
tarayarak kur: her katman için ya cevabı al, ya varsayılanı uygula, ya da "bu ekranda yok" de.
|
||
|
||
#### 0.6.1 Hangi araç? — yol seçimi
|
||
|
||
Tur 1'in birinci sorusunun karşılığı budur; kullanıcı türü net söylemediyse tarife bakıp
|
||
**öner**, tek başına karar verme:
|
||
|
||
| Tarifte varsa | Yol |
|
||
| --- | --- |
|
||
| Kayıt listesi + ekle/düzenle/sil + filtre/arama | **SQL Query Manager + Wizard** (`ComponentKind: 0`) |
|
||
| Aynı verinin birden çok görünümü (pano, takvim, ağaç, grafik) | **Wizard** — tek dosya, birden çok layout (§9.8) |
|
||
| Ana kayıt + detay sekmeleri | **Wizard** + `SubForms` |
|
||
| Onay süreci | **Wizard** + `WorkflowDto` |
|
||
| Üstte KPI kartları | **Wizard** + `Widgets` |
|
||
| Serbest yerleşim: yan yana kartlar, özel bölümler, sihirbaz adımları, karışık bileşenler | **Custom Component** (`ComponentKind: 1`) |
|
||
| Var olan ekranların bir sayfada toplanması (dashboard) | **Custom Component** + `platform` düğümleri (`listFormCode`) |
|
||
| Tek kayıt üzerinde çalışan özel form (liste yok) | **Custom Component** + `Form` düğümü |
|
||
|
||
Şüphede kalırsan **Wizard'ı seç.** Grid'e benzeyen her şey Wizard'la daha ucuz, daha yetkin ve
|
||
daha bakımlıdır; Custom Component'e yalnızca yerleşim gerçekten grid/form kalıbına sığmıyorsa geç.
|
||
|
||
İki yol birlikte de kullanılır: veriyi Wizard ile ekran yap, sonra Custom Component içine
|
||
`platform` düğümü olarak göm.
|
||
|
||
---
|
||
|
||
#### 0.6.2 Tur 1 — her talepte sorulacak dört şey
|
||
|
||
Bu dört soru, talebin türünden bağımsız olarak sorulur; dördü de üretilecek dosya kümesini
|
||
doğrudan değiştirir.
|
||
|
||
| # | Soru | Neden belirleyici | Seçenekler |
|
||
| --- | --- | --- | --- |
|
||
| 1 | **Ekran türü** | Üretilecek artefaktı seçer | Liste ekranı (Wizard) · Serbest yerleşimli sayfa (Custom Component) · Tek kayıtlık form · Karma (Wizard + Custom) |
|
||
| 2 | **Veri kaynağı** | Tablo yoksa `sql/object/` de üretilecek | Var olan tablo/view (adı?) · Yeni tablo üretilsin · Birden çok tablo → view üretilsin · Var olan bir API/endpoint |
|
||
| 3 | **Kim kullanacak / hangi modül** | Menü kökü, yetki grubu ve isimlendirme buradan türer | Mevcut modüllerden biri (listele) · Yeni modül |
|
||
| 4 | **Kapsam** | `IsTenant`/`IsBranch`/`IsOrganizationUnit` ve tablo ön eki | Kiracı geneli (varsayılan) · Şube bazlı · Birim ağacına göre · Host (tüm kiracılar ortak) |
|
||
|
||
1. sorunun cevabı **yolu belirler**; belirsizse §0.6.1'deki yol tablosuna bak ve
|
||
şüphede **Wizard'ı öner**. 2. sorunun cevabı "var olan tablo" ise adını sormadan önce
|
||
`sql/object/` ve mevcut wizard dosyalarını tara — bulursan sorma, doğrulat.
|
||
|
||
#### 0.6.3 Tur 2 — List (Wizard) yolu soru seti
|
||
|
||
Sırayla tara; ekranın gerçekten ihtiyaç duyduğu satırları sor, gerisini varsayılanla geç.
|
||
Sütun listesi **en kritik olanıdır** — onsuz üretilen ekran her zaman yeniden yazılır.
|
||
|
||
| Konu | Sorulacak | Varsayılan (sorulmazsa) |
|
||
| --- | --- | --- |
|
||
| **Sütunlar** | Hangi alanlar görünecek, başlıkları ne? Tablo varsa "şu kolonların hepsi mi, bir kısmı mı?" | Tablodaki tüm kolonlar, audit kolonları gizli |
|
||
| **Alan tipleri** | Her alan için editör: metin / sayı / tarih / onay kutusu / açılır liste / çoklu seçim / zengin metin / görsel (§0.7.3, §4.3.2) | Kolon `DbType`'ından türetilir |
|
||
| **Zorunluluk ve doğrulama** | Hangileri zorunlu? Aralık/desen/uzunluk kuralı olan var mı? | `NOT NULL` kolonlar zorunlu, başka kural yok |
|
||
| **Lookup'lar** | Açılır listeler nereden beslenecek: sabit liste mi, başka tablo mu, var olan tanım tablosu mu (ülke/para birimi/departman…)? Bağlı liste (il→ilçe) var mı? | Sabit liste; FK varsa hedef tabloya `Query` |
|
||
| **Anahtar** | Anahtar kolon ve tipi | `Id` / `UNIQUEIDENTIFIER` (`KeyFieldDbSourceType: 9`) |
|
||
| **Görünümler** | Izgara dışında hangi görünüm açılsın: kart, pivot, grafik, ağaç, gantt, takvim, kanban? Varsayılan hangisi? | Yalnız `Grid` |
|
||
| **Görünüm eşlemeleri** | Açılan her görünümün zorunlu alanları: ağaçta `ParentId`, takvimde başlangıç/bitiş, kanbanda durum kolonu (§9.8) | Görünüm açılmaz |
|
||
| **Form düzeni** | Alanlar hangi gruplara ayrılsın, grup başına kaç kolon? Popup mu satır içi mi? | Tek grup, 2 kolon, popup |
|
||
| **Otomatik dolan alanlar** | Kullanıcı/tarih/tenant dışında otomatik dolacak alan var mı? Belge numarası gerekiyor mu (§9.9)? | Audit alanları + tenant |
|
||
| **Hesap ve koşul** | Alanlar arası hesap (miktar×fiyat), koşullu kilit, seçimle dolan alan var mı (§5)? | Yok |
|
||
| **Filtre/arama** | Filtre satırı, arama kutusu, ekran üstü sabit filtre (tarih aralığı/şube/durum)? | Filtre satırı + arama açık |
|
||
| **Özet** | Altta toplam/adet satırı gerekiyor mu, hangi kolonlarda? | Yok |
|
||
| **Aksiyonlar** | Ekle/düzenle/sil dışında buton: onaya gönder, çıktı al, dış sisteme aktar, başka ekran aç? (§4.9) | Yalnız CRUD |
|
||
| **Alt ekranlar** | Kaydın altında sekme (kalemler, ekler, geçmiş) olacak mı? İlişki alanı ne? | Yok |
|
||
| **KPI kartları** | Üstte sayaç/kart şeridi olacak mı, hangi sayılar? | Yok |
|
||
| **Onay süreci** | Kayıt onaya düşecek mi? Kim onaylayacak (rol/kullanıcı), kaç adım? | Yok — **onaycı asla varsayılmaz, sorulur** |
|
||
| **Koşullu renklendirme** | Duruma göre renklenecek satır/hücre var mı? | Yok |
|
||
| **Yetki** | Ayrı rol kırılımı gerekiyor mu, yoksa tek yetki ailesi yeterli mi? | `{MenuCode}` + standart son ekler |
|
||
| **Menü** | Hangi kök menünün altına, hangi sıraya, hangi ikonla? | Modülün kök menüsü, `max(Order)+1` |
|
||
| **Dil** | Menü/başlık/alan metinlerinin TR ve EN karşılıkları | Türkçeden çevrilir |
|
||
|
||
#### 0.6.4 Tur 2 — Custom Component yolu soru seti
|
||
|
||
Serbest yerleşimde tarif, **sayfanın bölgeleri** üzerinden çıkarılır. Ekran görüntüsü varsa
|
||
önce §0.7 ile oku, sonra buradaki boşlukları sor.
|
||
|
||
| Konu | Sorulacak | Varsayılan (sorulmazsa) |
|
||
| --- | --- | --- |
|
||
| **Sayfa iskeleti** | Kaç bölge var, nasıl dizilmiş? (üst şerit / tek kolon / iki kolon + kenar panel / sekmeli) | Tek kolon `PageContainer` |
|
||
| **Bölge içerikleri** | Her bölgede ne var: kart, form, liste, grafik, pano, metin, buton grubu? | — (sorulmadan geçilemez) |
|
||
| **Hazır ekran gömme** | Bölgelerden biri var olan bir ListForm ekranı mı? (öyleyse `platform` düğümü + `listFormCode`, primitiflerden kurma) | Sorulur |
|
||
| **Form bölgesi** | Tek kayıt üzerinde çalışan bir form var mı? Hangi alanlar, hangi uçlar (select/insert/update/delete), anahtar nereden gelir (query/route)? | `Form` yok |
|
||
| **Veri kaynakları** | Her bölge veriyi nereden alır: CRUD endpoint mi, Custom Endpoint mi, var olan bir API mi? | CRUD endpoint üretilir |
|
||
| **Filtreler** | Bölge verisi neye göre süzülür: sabit değer, URL parametresi, başka bölgedeki seçili kayıt? (§10.4 · reference) | Süzme yok |
|
||
| **Etkileşim** | Tıklama/seçim/kaydetme sonrası ne olsun: başka bölge yenilensin, sayfa açılsın, uyarı çıksın? (§7.8) | Yok |
|
||
| **Durum ve boşluk** | Veri yokken, yüklenirken, hata alındığında ne görünsün? | Standart boş/yükleniyor durumu |
|
||
| **Yetki** | Bölge bazlı gizleme gerekiyor mu? Hangi bölge hangi yetkiyle? | Sayfa yetkisi |
|
||
| **Route ve menü** | Sayfanın adresi ne olsun, menüde nerede dursun? | `/admin/{kebab-ad}`, modül kök menüsü |
|
||
| **Bağımlılık** | Sayfa başka bir custom component'i kullanacak mı? | Yok |
|
||
| **Mod** | Tasarımcıda düzenlenebilir kalsın mı (Visual), yoksa yerleşim tasarımcıya sığmıyor mu (Code)? (§7.1.1) | Visual |
|
||
|
||
> **Yeniden kullanım sorusu her Custom talebinde sorulur:** istenen bölgelerden biri
|
||
> Wizard'la daha ucuza çözülüyorsa bunu söyle ve karma yolu öner (veriyi Wizard ile ekran yap,
|
||
> Custom Component içine `platform` düğümü olarak göm). Custom Component'i, yerleşim gerçekten
|
||
> grid/form kalıbına sığmadığı için seç — "daha esnek olur" diye değil.
|
||
|
||
#### 0.6.5 Sormadan geçilecekler
|
||
|
||
Soru turu bir anket değildir. Aşağıdakiler **sorulmaz**, uygulanır ve tarifte tek satırla
|
||
bildirilir:
|
||
|
||
| Konu | Neden sorulmaz |
|
||
| --- | --- |
|
||
| Tenant + audit kolonları (`Id`, `TenantId`, `CreationTime`, `CreatorId`, `LastModificationTime`, `LastModifierId`, `IsDeleted`, `DeletionTime`, `DeleterId`) | Yeni tabloda varsayılan; kullanıcı "audit yok" demedikçe eklenir |
|
||
| Metinlerin EN + TR olması, dil anahtarı üzerinden verilmesi | Platform sözleşmesi |
|
||
| İsimlendirme (`ListFormCode`, menü kodu, yetki adları, dosya adları) | §2'den türer |
|
||
| Seed dosyası yazılıp yazılmayacağı | Her artefakt seed'lenir (§0.2) |
|
||
| Route kaydı açılıp açılmayacağı | List statik route kullanır, Custom `RoutePath`'ten türer (§9.7) |
|
||
| Parametreli SQL kullanımı, yetki kontrolü, tenant izolasyonu | Tartışmaya açık değil |
|
||
| Depoda okunabilen tablo/kolon/menü/yetki adları | Önce ara, bulduğunu **doğrulat**, sıfırdan sorma |
|
||
| Var olan bir tanım tablosunun (ülke, para birimi, departman, ünvan, birim) yeniden üretilip üretilmeyeceği | Üretilmez; mevcut tabloya lookup bağlanır (reference §12.5) |
|
||
|
||
Buna karşılık **her zaman sorulacak** üç şey vardır, çünkü yanlış varsayım geri dönülmez sonuç
|
||
üretir: **hangi tabloya yazılacağı**, **kimin onaylayacağı** (onay süreci varsa) ve
|
||
**hesap/iş kuralının formülü**.
|
||
|
||
#### 0.6.6 Ekran tarifi ve onay
|
||
|
||
Turlar bitince tarifi **tek blok hâlinde** göster ve onay iste. Kullanıcının cevapladığı satırlar
|
||
olduğu gibi, varsayılanla doldurulanlar `(varsayılan)` işaretiyle yazılır — böylece kullanıcı
|
||
neyi onayladığını görür.
|
||
|
||
```
|
||
Amaç : Ne işi görüyor, kim kullanıyor
|
||
Yol : Wizard (ComponentKind 0) | Custom Component (ComponentKind 1) | karma
|
||
Veri kaynağı : Tablo/view adı · yoksa "üretilecek" + hangi kolonlarla
|
||
Anahtar : Kolon + tip (DbType numarası)
|
||
Sütunlar : Alan → editör tipi → zorunlu mu → lookup kaynağı (List yolu)
|
||
Bölgeler : Bölge → bileşen → veri kaynağı → filtre (Custom yolu)
|
||
Layout(lar) : Grid / Card / Pivot / Chart / Tree / Gantt / Scheduler / Todo + varsayılan
|
||
Form düzeni : Gruplar ve kolon sayısı, düzenleme modu
|
||
Otomatikler : Varsayılan değerler, numaratör, hesaplar (EditorScript)
|
||
Doğrulama : Alan kuralları + sunucu tarafı kontrol
|
||
Aksiyonlar : Toolbar ve satır butonları, ne yapıyorlar
|
||
Alt ekranlar : Ana-detay sekmeleri + ilişki alanları
|
||
Widget'lar : Üstteki KPI kartları ve sorguları
|
||
Akış : Onay adımları ve onaycılar
|
||
Otomasyon : Zamanlanmış iş / bildirim / mail gerekiyor mu
|
||
Menü : Üst menü, sıra, ikon
|
||
Yetki : Grup adı + kimde hangi yetki
|
||
Eklemeler : Hangi ui bileşenine hangi olay/prop/küçük yetenek eklenecek (§7.3) — yeni bileşen yok
|
||
Kapsam : IsTenant / IsBranch / IsOrganizationUnit
|
||
Dil : EN + TR metinler
|
||
Üretilecek : Dosya listesi (§0.1 klasörlerine göre)
|
||
```
|
||
|
||
Onaydan sonra §0.2'deki sırayla üret. Üretim bittiğinde dosyaları tek tek listele: hangi dosya,
|
||
ne üretiyor, hangi menüde görünecek, hangi yetkiyi ister — ve tarifte `(varsayılan)` kalan
|
||
satırları "şunları ben seçtim, değiştirmek ister misin?" diye ayrıca hatırlat.
|
||
|
||
### 0.7 Görselden üretim (ekran görüntüsü / mockup)
|
||
|
||
Kullanıcı tarif yerine **bir fotoğraf, ekran görüntüsü ya da mockup** gönderdiğinde keşif turu
|
||
iptal olmaz, kısalır: görsel, Tur 2 sorularının çoğunu cevaplar. Önce görseli oku ve tarifi
|
||
doldur; sonra §0.6 turunu yalnızca görselden okunamayan boşluklar (§0.7.4) için işlet. Görseli
|
||
tahmine değil, gördüğün öğelere dayandır.
|
||
|
||
#### 0.7.1 Görseli okuma sırası
|
||
|
||
1. **Sayfa iskeleti** — kaç bölge var? Üstte KPI şeridi, ortada liste, sağda panel, altta sekme?
|
||
2. **Ana bölge ne?** Tablo mu, kart ızgarası mı, pano mu, takvim mi, form mu?
|
||
3. **Araç çubuğu** — hangi butonlar var, ne yazıyorlar, ikonları ne?
|
||
4. **Sütunlar** — başlıklar, hizalama, biçim (para, tarih, yüzde), rozet/renkli durum hücreleri.
|
||
5. **Filtre/arama** — başlık altı filtre satırı, arama kutusu, tarih aralığı seçicileri.
|
||
6. **Form alanları** — kontrol tipleri ve kolon düzeni (kaç kolon, hangi alan kaç sütun kaplıyor).
|
||
7. **Satır aksiyonları** — satır sonundaki ikonlar.
|
||
8. **Durum göstergeleri** — onay adımları, ilerleme çubukları, etiketler.
|
||
|
||
#### 0.7.2 Görsel öğe → artefakt eşlemesi
|
||
|
||
| Görselde gördüğün | Karşılığı |
|
||
| --- | --- |
|
||
| Başlık satırı + veri satırları olan tablo | Grid layout |
|
||
| Kart ızgarası | Card layout |
|
||
| Dikey kolonlar + sürüklenebilir kartlar | Todo (Kanban) layout · `TodoOptionDto.StatusExpr` |
|
||
| Ay/hafta takvimi, saat çizelgesi | Scheduler layout |
|
||
| Yatay zaman çubukları, bağımlılık okları | Gantt layout |
|
||
| Girintili ağaç, açılır düğümler | Tree layout |
|
||
| Satır/sütun kesişimli özet tablo, ara toplamlar | Pivot layout |
|
||
| Çubuk/çizgi/pasta grafik | Chart layout · `SeriesJson` |
|
||
| Üstte sayı gösteren küçük kutular | `Widgets` (`WidgetEditDto`) |
|
||
| Kaydın altında sekmeler (Kalemler, Ekler, Geçmiş) | `SubForms` |
|
||
| Araç çubuğundaki özel buton | `CommandColumnJson` · `ButtonPosition: 1` |
|
||
| Satır sonundaki ikon | `CommandColumnJson` · `ButtonPosition: 0` |
|
||
| Başlık altındaki filtre satırı | `FilterRowJson` |
|
||
| Sağ üstteki arama kutusu | `SearchPanelJson` |
|
||
| "Sürükleyip gruplayın" şeridi | `GroupPanelJson` |
|
||
| Üstte ek filtre çubuğu (tarih aralığı, şube seçimi) | `ExtraFilterJson` |
|
||
| Sayfalama şeridi | `PagerOptionJson` |
|
||
| Renkli durum rozeti | `ColumnStylingJson` / `ColumnCssClass` |
|
||
| Onayla / Reddet butonları | `WorkflowJson` (otomatik gelir, elle tanımlama) |
|
||
| Yan yana bağımsız bölümler, karışık yerleşim | Custom Component · `FlexRow` + düğümler |
|
||
|
||
#### 0.7.3 Görseldeki kontrol → `EditorType`
|
||
|
||
| Görselde | `EditorType` | Ek |
|
||
| --- | --- | --- |
|
||
| Tek satır metin kutusu | `dxTextBox` | |
|
||
| Çok satırlı kutu | `dxTextArea` | `autoResizeEnabled` |
|
||
| Sağında ok olan açılır liste | `dxSelectBox` | lookup zorunlu |
|
||
| İçinde etiket/çip olan açılır liste | `dxTagBox` | lookup zorunlu |
|
||
| Büyüteçli seçim + tablo açılıyor | `dxGridBox` / `dxLookup` | `columns` |
|
||
| Onay kutusu | `dxCheckBox` | |
|
||
| Aç/kapa anahtarı | `dxSwitch` | `switchedOnText` |
|
||
| Yan yana yuvarlak seçenekler | `dxRadioGroup` | `layout: horizontal` |
|
||
| Takvim ikonlu alan | `dxDateBox` | |
|
||
| İki tarihli aralık alanı | `dxDateRangeBox` | |
|
||
| Artı/eksi oklu sayı | `dxNumberBox` | `format.precision` |
|
||
| `₺ 1.250,00` biçimli alan | `dxNumberBox` | `format.type: currency` |
|
||
| `%` biçimli alan | `dxNumberBox` | `format.type: percent` |
|
||
| Renk kutucuğu | `dxColorBox` | |
|
||
| Kalın/italik araç çubuklu alan | `dxHtmlEditor` | `toolbar.items` |
|
||
| Kaydırıcı | `dxSlider` / `dxRangeSlider` | `min`, `max` |
|
||
| Görsel küçük resmi / yükleme alanı | `dxImageViewer` / `dxImageUpload` | `accept`, `maxFileSize` |
|
||
| Gri, tıklanamaz alan | ilgili editör + `EditorOptions: {"readOnly": true}` | |
|
||
| Kırmızı yıldızlı etiket | `IsRequired: true` | |
|
||
|
||
#### 0.7.4 Görselden okunamayacak şeyler
|
||
|
||
Bunlar bir ekran görüntüsünde **görünmez**; varsayımını yaz ve bildir:
|
||
|
||
| Bilgi | Varsayılan davranış |
|
||
| --- | --- |
|
||
| Tablo/kolon adları | Görünen başlıklardan PascalCase kolon adı türet, tabloyu `sql/object/` altında sen üret |
|
||
| Anahtar alan ve tipi | `Id` / `UNIQUEIDENTIFIER` (`KeyFieldDbSourceType: 9`) |
|
||
| Lookup'ların kaynağı | Az sayıda sabit seçenek görünüyorsa `StaticData`, kod/isim çifti gerektiren yerlerde `Query` |
|
||
| Tenant/şube kırılımı | `IsTenant: true` (§CLAUDE varsayılanı) |
|
||
| Yetki grubu ve menü kökü | Ekranın ait olduğu modülden türet, kullanıcıya sor değil bildir |
|
||
| Dil metinleri | Görseldeki dilde yaz, diğer dile çevir; ikisi de doldurulur |
|
||
| Onaycılar | Yer tutucu bırak (`<roleOrUserId>`) ve kullanıcıya sor — **bu, sorulması gereken bir şeydir** |
|
||
| İş kuralları (hesap, koşul) | Görselde formül görünmüyorsa `EditorScript` yazma; sor |
|
||
|
||
#### 0.7.5 Görsel için de aynı protokol
|
||
|
||
Görselden çıkardığın tarifi §0.6.1'deki yol tablosuna sok, görselden okunamayan boşlukları
|
||
§0.7.4 + §0.6 turuyla sor, tarifi §0.6.6 ile onaylat, sonra §0.2'deki sırayla dosyaları üret.
|
||
Görsel bir "tasarım sözleşmesi" değildir: platformun kendi bileşenleriyle **en yakın** karşılığı
|
||
üretilir, piksel taklidi yapılmaz. Görseldeki bir öğenin platformda karşılığı yoksa bunu üretim
|
||
sonunda açıkça yaz.
|
||
|
||
---
|
||
|
||
### 0.8 Çerçeve sınırı — uygulama kendini geliştirir, dışına çıkmaz
|
||
|
||
Platform kendi mekanizmalarıyla büyür; **çerçevenin dışına çıkan her talep üretimden önce
|
||
kullanıcıya sorulur ve yalnızca onayla yapılır.** "Yapılamaz" cevabı yoktur, "sormadan
|
||
çerçeve dışına çık" da yoktur.
|
||
|
||
**Çerçeve içi — sormadan yapılır** (keşif turu tarifinde gösterilir, onay tarifle gelir):
|
||
|
||
| Alan | Örnek |
|
||
| --- | --- |
|
||
| Seed artefaktları | Wizard, custom component (visual/code modu), `crud/`, SQL nesneleri (tablo/view/SP/fonksiyon), Custom Endpoint, Dynamic Service, BackgroundWorker, bildirim türü/kuralı, dil anahtarı, yetki kaydı |
|
||
| Mevcut bileşeni genişletme | Kullanılan bir `ui` bileşenine **eksik olay / prop / küçük yetenek** ekleme (§7.3 notu) — komponentin %70'i hazır araçlarla kuruluyorsa kalanı bu eklemelerle tamamlanır; `componentProps.json` girdisini tamamlama |
|
||
| Mevcut sözlüğü genişletme | Yeni EditorScript tarifi (`scriptRecipes.ts` ↔ `Domain.Shared/Editors` birlikte), yeni `EditorOptions` anahtarı, yeni `DefaultValue` token'ı — **geriye dönük uyumlu** olmak şartıyla |
|
||
| Tasarımcı sözleşmesi | Var olan düğüme prop/varsayılan ekleme, yeni Script Builder tarifi |
|
||
|
||
**Çerçeve dışı — önce sor, onay olmadan dokunma:**
|
||
|
||
| Alan | Örnek |
|
||
| --- | --- |
|
||
| Yeni React bileşeni / sayfası | Yeni `ui` bileşeni, yeni toolbox ailesi, `EXCLUDED_COMPONENTS` değişikliği, `views/` altına elle sayfa, Developer Kit menüsüne yeni ekran, var olan bileşeni yeniden yazmak ya da mevcut davranışını değiştirmek |
|
||
| Çekirdek motor | ListForm runtime, seeder'lar, `codeGenerator.ts`, yetki/kimlik altyapısı, `DataSourceManager`, dispatcher/derleyici (`CustomEndpointAppService`, `DynamicServiceCompiler`) |
|
||
| Backend şeması | Yeni ABP modülü/entity/migration, platform tablolarında (`{Önek}_{H\|T\|B}_{Ad}` — reference §12.1) kolon değişikliği, `TableNameEnum`/`TableNameResolver` değişikliği, DTO/JSON kolon sözleşmesini **bozan** değişiklik |
|
||
| Bağımlılık | Yeni NuGet/npm paketi, sürüm yükseltme |
|
||
| Sabit yapılar | `MenusData.json` → `Routes`, `PlatformConsts` içindeki mevcut değerlerin değişmesi |
|
||
| Dış dünya | Yeni harici entegrasyon, yeni bağlantı tipi, üçüncü taraf servis |
|
||
|
||
**Sorma biçimi:** çerçeve içinde yapılabilen her şeyi **önce bitir**, sonra tek soruda
|
||
`AskUserQuestion` ile sor — neden konfigürasyon/seed yolu yetmiyor (bir iki cümle), en küçük
|
||
hangi değişiklik yeter, hangi dosyalara dokunacak, geri alma nasıl olacak. Seçenekler arasında
|
||
daima "çerçeve içinde daraltılmış çözüm" alternatifi de bulunsun. Onay gelmeden `api/` ya da
|
||
`ui/` altında çerçeve dışı bir satır yazılmaz; onay bir talebe özeldir, sonraki talebe taşınmaz.
|
||
|
||
## 1. Hangi istek → hangi artefakt
|
||
|
||
| İstek | Üretilecek artefakt | Bölüm |
|
||
| --- | --- | --- |
|
||
| "X tablosunun listesi/ekranı olsun" | Wizard seed dosyası (`ComponentKind: 0`) | 4 |
|
||
| "Şu alanı otomatik hesaplasın / şarta göre kilitlensin" | `EditorScript` (Script Builder tarifi) | 5 |
|
||
| "Alan şu formatta / maskeyle görünsün" | `EditorOptions` (Options Builder) | 6 |
|
||
| "Ekran bir tablodan değil, birleştirilmiş veriden beslensin" | SQL View Designer → `View` + Wizard | 8 |
|
||
| "Serbest yerleşimli bir sayfa / dashboard / özel form" | Custom Component + Wizard (`ComponentKind: 1`) | 7 |
|
||
| "Bir tabloya REST API açalım" | CRUD Endpoint | 9 |
|
||
| "Elle yazdığım bir SQL'i API olarak açalım" | Custom Endpoint | 9.2 |
|
||
| "API'de iş mantığı/hesap/entegrasyon lazım" | Dynamic Service | 9.3 |
|
||
| "Onay süreci olsun" | `WorkflowDto` + workflow kriterleri | 4.7 |
|
||
| "Ana-detay ekranı" | `SubForms` | 4.6 |
|
||
| "Üstte KPI kartları" | `Widgets` | 4.6 |
|
||
| "Alan otomatik dolsun (kullanıcı, tarih, tenant, sorgu sonucu)" | `*FieldsDefaultValueJson` | 4.3.5 |
|
||
| "Belge/sipariş numarası otomatik üretilsin" | `Sequence` kaydı + varsayılan değer | 9.9 · 4.3.5 |
|
||
| "Bağlı açılır listeler (il → ilçe)" | `LookupJson` cascade alanları | 4.3.4 |
|
||
| "Şu koşuldaki satır renkli olsun" | `ColumnStylingJson` | 4.3.7 |
|
||
| "Ekranın üstünde tarih/şube/durum filtresi" | `ExtraFilterJson` | 4.3.8 |
|
||
| "Kayıt kurallara uymadan kaydedilmesin" | `ValidationRuleJson` (+ sunucu tarafı SQL kontrolü) | 4.3.6 |
|
||
| "Kaydederken/silerken ek SQL çalışsın (stok, log, durum)" | `*BeforeCommand` / `*AfterCommand` | 4.8 |
|
||
| "Şu iş her gece / her 5 dakikada çalışsın" | `BackgroundWorker` kaydı + stored procedure | 9.10 |
|
||
| "Şu olayda bildirim gitsin" | Notification Type + Rule | 9.11 |
|
||
| "Fatura/teklif/iş emri çıktısı" | Rapor şablonu (ya da hazır `DynamicGrid`) | 9.12 |
|
||
| "Ayar ekranından açılıp kapansın" | Setting Definition | 9.14 |
|
||
| Hiçbiri yetmiyorsa | Dynamic Service → en son çare kod | `ai.instructions.md` §4 |
|
||
|
||
---
|
||
|
||
## 2. İsimlendirme sözleşmesi
|
||
|
||
Tutarlılık zorunludur; menü, route, yetki ve dil anahtarı aynı kökten türer.
|
||
|
||
> **Kapsam:** Buradaki isimlendirme ve dosyanın genelindeki kod standartları (parametreli SQL,
|
||
> yetki sözleşmesi, EN+TR dil anahtarı, seed zorunluluğu, tenant izolasyonu) **artefakt türünden
|
||
> bağımsızdır** — Menu, Route, Wizard, ListForm, ListFormField, Custom Component, CRUD/Custom
|
||
> Endpoint, Dynamic Service, SQL nesnesi, Sequence, BackgroundWorker, Notification… hangisini
|
||
> üretirsen üret aynı kurallar geçerlidir. "Bu küçük bir yardımcı kayıt" diye standart dışına
|
||
> çıkma.
|
||
|
||
| Şey | Kalıp | Örnek |
|
||
| --- | --- | --- |
|
||
| Tablo | `{Modul}_T_{Entity}` | `Mrp_T_Order` |
|
||
| View | `{Modul}_V_{Ad}` | `Mrp_V_OrderSummary` |
|
||
| `ListFormCode` | `App.{Modul}.{Liste}` | `App.Mrp.Orders` |
|
||
| `MenuCode` | `ListFormCode` ile aynı | `App.Mrp.Orders` |
|
||
| Üst menü kodu | `App.{Modul}` | `App.Mrp` |
|
||
| Yetki grubu | `App.{Modul}` | `App.Mrp` |
|
||
| Yetkiler | `{MenuCode}` + `.Create/.Update/.Delete/.Export/.Import/.Note` | `App.Mrp.Orders.Create` |
|
||
| Dil anahtarı (menü/başlık) | `{MenuCode}` | `App.Mrp.Orders` |
|
||
| Dil anahtarı (alan) | `App.Listform.ListformField.{Alan}` | `App.Listform.ListformField.OrderNo` |
|
||
| Route (List) | `/admin/list/{ListFormCode}` — statik `:listFormCode` route'u karşılar, DB'ye kayıt yazılmaz | `/admin/list/App.Mrp.Orders` |
|
||
| Route (Custom) | Bileşenin `RoutePath` değeri; route doğrudan bundan üretilir | `/admin/order-board` |
|
||
| Wizard seed dosyası | `{WizardName}.json` | `Orders.json` |
|
||
| Custom component seed | `{Name}.json` | `OrderBoard.json` |
|
||
| CRUD endpoint seed | `{EntityName}.json` | `Mrp_T_Order.json` |
|
||
|
||
Kapsam klasörü: host bağlamında `configs/seeds/host/…`, tenant bağlamında
|
||
`configs/seeds/tenants/{tenantId}/…`. Çözüm her zaman `SeedPathResolver` üzerinden yapılır;
|
||
yol elle kurulmaz.
|
||
|
||
---
|
||
|
||
## 3. Ortak enum değerleri (seed dosyalarına **sayı** olarak yazılır)
|
||
|
||
**`ComponentKind`** (`WizardComponentKindEnum`) — `0` List · `1` Custom
|
||
|
||
**`SelectCommandType`** — `1` Table · `2` View · `3` TableValuedFunction · `4` Query ·
|
||
`5` StoredProcedure
|
||
|
||
**`LookupDataSourceType`** — `1` StaticData · `2` Query · `3` WebService
|
||
|
||
**`CustomValueType`** (`FieldCustomValueTypeEnum`) — `1` Value · `2` CustomKey · `3` DbQuery ·
|
||
`4` QueryParams · `5` Sequence (§4.3.5)
|
||
|
||
**`SortMode`** (`UiGridSortModeEnum`) — `1` Single · `2` Multiple · `3` None
|
||
|
||
**`ButtonPosition`** (`UiCommandButtonPositionTypeEnum`) — `0` CommandColumn (satır sonu) ·
|
||
`1` Toolbar (§4.9)
|
||
|
||
**`ValidationRule.Type`** (`UiColumnValidationRuleTypeEnum`) — seed'e **metin** olarak yazılır:
|
||
`required` `numeric` `range` `stringLength` `custom` `compare` `pattern` `email` `async` (§4.3.6)
|
||
|
||
**`WorkerType`** (`WorkerTypeEnum`) — `1` MailQueueWorker · `2` SqlWorker ·
|
||
`3` NotificationWorker · `4` SessionCleanupWorker · `5` BackupWorker (§9.10)
|
||
|
||
**`ResetPeriod`** (`SequenceResetPeriod`) — `0` None · `1` Yearly · `2` Monthly · `3` Daily ·
|
||
`4` Hourly · `5` Minutely (§9.9)
|
||
|
||
**`DataSourceType`** (`DataSourceTypeEnum`) — `1` Mssql · `2` Postgresql
|
||
|
||
**`AuthorizationType`** (`AuthorizationTypeEnum`) — `0` Deny · `1` Read · `2` Create ·
|
||
`3` Update · `4` Delete · `5` Export · `6` Import
|
||
|
||
**`ListFormCustomizationType`** (`ListFormCustomizationTypeEnum`) — `1` UserUiFilter ·
|
||
`2` GridState · `3` ServerJoin · `4` ServerWhere. Kullanıcının kaydettiği filtre ve grid
|
||
tercihinin nerede durduğunu belirler; seed dosyasına yazılmaz.
|
||
|
||
**Metin sabiti olan "enum"lar** — bunlar `enum` değil `const string`'tir, seed'e **metin**
|
||
yazılır:
|
||
|
||
| Sabit | Değerler |
|
||
| --- | --- |
|
||
| `ListFormType` (`ListFormTypeEnum`) | `List` · `Form` · `Chart` |
|
||
| `SubForms[].TabType` (`ListFormTabTypeEnum`) | `List` · `Card` · `Tree` · `Gantt` · `Scheduler` · `Todo` · `Form` · `Chart` · `Pivot` |
|
||
| `DefaultLayout` | `grid` · `card` · `pivot` · `chart` · `tree` · `gantt` · `scheduler` · `todo` (küçük harf) |
|
||
|
||
**`DbSourceType` / `KeyFieldDbSourceType`** (`System.Data.DbType`):
|
||
|
||
| # | Ad | SQL karşılığı |
|
||
| --- | --- | --- |
|
||
| 1 | Binary | `binary`, `varbinary`, `image` |
|
||
| 2 | Byte | `tinyint` |
|
||
| 3 | Boolean | `bit` |
|
||
| 5 | Date | `date` |
|
||
| 6 | DateTime | `datetime`, `datetime2`, `smalldatetime` |
|
||
| 7 | Decimal | `decimal`, `numeric`, `money` |
|
||
| 8 | Double | `float`, `real` |
|
||
| 9 | Guid | `uniqueidentifier` |
|
||
| 10 | Int16 | `smallint` |
|
||
| 11 | Int32 | `int` |
|
||
| 12 | Int64 | `bigint` |
|
||
| 16 | String | `nvarchar`, `varchar`, `nchar`, `char`, `text` |
|
||
| 17 | Time | `time` |
|
||
| 25 | Xml | `xml` |
|
||
| 27 | DateTimeOffset | `datetimeoffset` |
|
||
|
||
**Editör tipleri (`EditorType2`)** — 21 tip, alana **metin** olarak yazılır. DevExtreme'den
|
||
gelenler: `dxAutocomplete`, `dxCalendar`, `dxCheckBox`, `dxColorBox`, `dxDateBox`,
|
||
`dxDateRangeBox`, `dxDropDownBox`, `dxHtmlEditor`, `dxLookup`, `dxNumberBox`, `dxRadioGroup`,
|
||
`dxRangeSlider`, `dxSelectBox`, `dxSlider`, `dxSwitch`, `dxTagBox`, `dxTextArea`, `dxTextBox`.
|
||
Platformun kendi editörleri (`PlatformEditorTypes`): `dxGridBox`, `dxImageViewer`,
|
||
`dxImageUpload` (+ `dxTagBox` platform tarafında da özel işlenir).
|
||
|
||
> **Tuzak:** `PlatformConsts.EditorTypes.dxLookup` sabiti bozuk bir metin taşır
|
||
> (`"dxHtmlEdidxLookuptor"`). Hiçbir yerde kullanılmadığı için etkisi yoktur, ama C# seeder
|
||
> yazarken bu sabiti kullanma; `EditorType2 = "dxLookup"` diye düz metin yaz. Diğer 20 sabit
|
||
> doğrudur.
|
||
|
||
Tip seçimi kuralı: `Boolean → dxCheckBox`/`dxSwitch`, tarih → `dxDateBox`, sayı → `dxNumberBox`,
|
||
FK/lookup → `dxSelectBox` (az kayıt) veya `dxGridBox`/`dxLookup` (çok kayıt/çok kolon),
|
||
çok değerli FK → `dxTagBox`, uzun metin → `dxTextArea`, HTML → `dxHtmlEditor`.
|
||
Her editörün kendine özgü seçenekleri için §4.3.2.
|
||
|
||
---
|
||
|
||
## 4. Artefakt A — ListForm ekranı (Wizard seed dosyası)
|
||
|
||
Konum: `configs/seeds/{kapsam}/wizard/{WizardName}.json` (dosya adında tarih yoktur)
|
||
Okuyan: `WizardDataSeeder` · Yazan: `ListFormWizardAppService` ve `WizardSeedSynchronizer`
|
||
|
||
### 4.1 Dosya iskeleti
|
||
|
||
```jsonc
|
||
{
|
||
"Wizard": { // yalnızca ekrandan üretilemeyen bilgiler — 4.2
|
||
"ComponentKind": 0, "WizardName": "Tags", "SeededAt": "2026-09-01T09:03:00",
|
||
"WizardCode": "App.Prj.Tags", "CustomComponentName": "",
|
||
"PermissionGroupName": "App.Prj", "DataSourceConnectionString": "", "ModuleId": "Prj",
|
||
"Menu": { "MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…",
|
||
"MenuParentCode": "…", "MenuParentIcon": "…" }, // "CreateMenu": false ise eklenir
|
||
"Language": { "MenuEn": "…", /* Menu/Title/Desc/MenuParent/PermissionGroup · EN + TR */ },
|
||
"Groups": [ { "Items": [ { "FieldName": "…", "CaptionName": "…",
|
||
"En": "…", "Tr": "…" } ] } ]
|
||
},
|
||
"ListForm": { /* GridOptionsEditDto — ekranın kendisi, ListForm detayıyla aynı sözleşme */ },
|
||
"Fields": [ /* ColumnFormatEditDto — alanlar, alan detayıyla aynı sözleşme */ ],
|
||
"IsDeletedField": true, // tabloda IsDeleted var mı (soft delete)
|
||
"IsCreatedField": true, // tabloda CreatorId var mı (audit)
|
||
"InsertedRecords": { // bu çalıştırmanın GERÇEKTEN yarattığı kayıtlar
|
||
"LanguageKeys": [], "PermissionGroupNames": [], "PermissionNames": [],
|
||
"MenuCodes": [], "DataSourceCodes": []
|
||
}
|
||
}
|
||
```
|
||
|
||
`Wizard` bölümü yalnızca ekrandan üretilemeyen bilgileri taşır (menü, yetki, dil metinleri,
|
||
bileşen yolu, alan başlıklarının dil karşılıkları); `ListForm`/`Fields` içinde karşılığı olan
|
||
hiçbir alan orada tekrar edilmez (§4.2).
|
||
|
||
**`ListForm` + `Fields` ekranın tek gerçeğidir.** Seeder ekranı bu iki bölümden kurar; `Wizard`
|
||
bölümünden yalnızca menü, yetki, dil anahtarları ve veri kaynağı üretilir. Ekran **ListForm
|
||
detayından** (liste ayarları, alan ayarları, JSON satır editörleri) düzenlendiğinde sunucu aynı
|
||
dosyanın `ListForm`/`Fields` bölümlerini ve `Wizard` bölümündeki karşılıklarını geri yazar
|
||
(`WizardSeedSynchronizer`); böylece wizard yeniden deploy edildiğinde tasarımcıdaki iş kaybolmaz.
|
||
Elle bu dosyayı yazarken `ListForm`/`Fields` bölümlerini uydurma — wizard'ı deploy et, dosyayı
|
||
sunucu üretsin.
|
||
|
||
`InsertedRecords` **silme sözleşmesidir**: wizard silindiğinde yalnızca burada listelenen
|
||
kayıtlar silinir, paylaşılan kayıtlara (var olan izin grubu, var olan üst menü) dokunulmaz.
|
||
Elle dosya yazarken buraya var olan bir kaydı koyma — silme onu da götürür.
|
||
|
||
### 4.2 `Wizard` bloğu — alan alan
|
||
|
||
`Wizard` bloğu **yalnızca ekranın sözleşmesinden üretilemeyen** bilgileri taşır: menü, yetki,
|
||
dil metinleri, bileşen yolu ve alan başlıklarının iki dilli karşılıkları. Ekrana ait olan her
|
||
şey (veri kaynağı, select komutu, anahtar alan, kırılım bayrakları, düzen/görünüm bayrakları,
|
||
düzenleme izinleri, görünüm option blokları, alt formlar, widget'lar, iş akışı, alan tanımları)
|
||
`ListForm`/`Fields` bölümlerindedir ve dosyaya **ikinci kez yazılmaz**.
|
||
|
||
Dosyada wizard alanları üç başlık altında toplanır — `Menu` (menü kaydı), `Language` (dil
|
||
metinleri), `Groups` (alan başlıklarının dil karşılıkları); geri kalanı kök seviyededir.
|
||
Gruplama ile `ListFormCode → WizardCode` ve `MenuParentModuleId → ModuleId` adlandırmaları
|
||
yalnızca **dosya biçimidir**; C#
|
||
sözleşmesi (`ListFormWizardDto`) düzdür ve dosya okunurken düzleştirilir
|
||
(`WizardSeedFileDto.FromSeedJson` ↔ `ToSeedJson`). Aşağıdaki tablolar C# alan adlarını kullanır.
|
||
|
||
Aşağıdaki tablolarda `[ListForm]` işaretli alanlar **istek girdisidir**: sihirbaz bunları POST
|
||
eder, sunucu ekranı bunlardan üretir, ama seed dosyasında `ListForm` bölümündeki karşılıkları
|
||
saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldurulur
|
||
(`WizardSeedFileDto.FromSeedJson`), dolayısıyla wizard'ı okuyan kod tam nesneyi görür.
|
||
|
||
**Kimlik ve yol**
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `ComponentKind` | `0` List (varsayılan) · `1` Custom. Dosyadaki ilk alan olmalı. |
|
||
| `WizardName` | Dosya adının (`{WizardName}.json`) ve export zip adının kaynağı. |
|
||
| `SeededAt` | İlk deploy anı; seeder dosyaları bu damgaya göre sıralar. Sunucu üretir, düzenlemede korunur. |
|
||
| `ListFormCode` | Ekranın kodu; route, yetki adları ve dil anahtarları bu koddan türer. Dosyada `WizardCode` adıyla, üçüncü sırada yazılır. |
|
||
| `MenuCode` | Menü kaydının kodu; yetki adlarının kökü de budur. |
|
||
| `MenuUrl` | List'te `/admin/list/{MenuCode}` hesaplanır; Custom'da bileşenin `RoutePath`'i yazılır. |
|
||
| `CustomComponentName` | Yalnızca Custom yolunda; bağlanacak bileşenin adı. |
|
||
| `EditFileName` | Yalnızca **istek girdisi**: doluysa güncellemedir, sunucu önce eski dosyayı ve ürettiği kayıtları siler. Seed dosyasına yazılmaz. |
|
||
|
||
**Menü**
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
> Dosyada bu bloğun tamamı `Menu` başlığı altındadır; `MenuParentModuleId` ise kök seviyede
|
||
> `ModuleId` adıyla yazılır. Menüsüz wizard'da da blok korunur: `MenuCode` yetkilerin kökü,
|
||
> Custom yolunda `MenuUrl` rotanın kaynağıdır.
|
||
|
||
| `CreateMenu` | `false` ise menü ve üst menü kaydı **hiç üretilmez**; ListForm, yetki ve dil anahtarları yine üretilir. SubGrid/parça olarak kullanılacak ekranlar için. Varsayılan `true` olduğu için dosyaya **yalnızca `false` iken** yazılır. |
|
||
| `MenuParentCode` / `MenuParentModuleId` / `MenuParentIcon` | Üst menü; yoksa oluşturulur. |
|
||
| `MenuIcon` | React-icons adı (`FcBiohazard`, `FaBox`…). |
|
||
| `MenuOrder` | Kardeşler arası sıra. |
|
||
|
||
**Yetki ve dil**
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
> Dil metinlerinin tamamı dosyada `Language` başlığı altındadır; başlık zaten bölümü işaretlediği
|
||
> için anahtarlar ortak `LanguageText` öneki olmadan yazılır (C# sözleşmesinde önek durur).
|
||
> `ListForm` yalnızca dil **anahtarını** taşır (`Title = "{ListFormCode}.Title"`), metinlerin tek
|
||
> kaynağı burasıdır.
|
||
|
||
| `PermissionGroupName` | Var olan grup ya da yeni grup adı. |
|
||
| `Language.PermissionGroupEn` / `Tr` | Grup adıyla aynı dil anahtarına yazılır. Var olan grup seçilip boş bırakılırsa sunucu veritabanındaki değeri geri yazar. |
|
||
| `Language.MenuEn` / `Tr` | Menü etiketi. |
|
||
| `Language.TitleEn` / `Tr` | Ekran başlığı (yalnızca List yolunda üretilir). |
|
||
| `Language.DescEn` / `Tr` | Ekran açıklaması (yalnızca List yolunda). |
|
||
| `Language.MenuParentEn` / `Tr` | Üst menü etiketi. |
|
||
|
||
Üretilen yetkiler: `{MenuCode}` (okuma, kök) + List yolunda `.Create .Update .Delete .Export
|
||
.Import .Note`, Custom yolunda yalnızca `.Create .Update .Delete`. Hepsi admin rolüne grant
|
||
edilir.
|
||
|
||
**Veri**
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `DataSourceCode` | `[ListForm]` Bağlantı kodu; varsayılan `"Default"`. |
|
||
| `DataSourceConnectionString` | Yalnızca **yeni** bir bağlantı tanımlanıyorsa doldurulur; boşsa dosyaya hiç yazılmaz. **Asla gerçek kimlik bilgisi yazma.** |
|
||
| `SelectCommandType` | `[ListForm]` Bkz. §3. |
|
||
| `SelectCommand` | `[ListForm]` Tablo/view adı, fonksiyon çağrısı ya da sorgu metni. |
|
||
| `KeyFieldName` / `KeyFieldDbSourceType` | `[ListForm]` Birincil anahtar ve tipi. |
|
||
|
||
**Davranış**
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `IsTenant` / `IsBranch` / `IsOrganizationUnit` | `[ListForm]` Otomatik kırılım filtresi. Tabloda `TenantId` varsa `IsTenant: true` **olmalı**. |
|
||
| `AllowAdding` / `AllowUpdating` / `AllowDeleting` / `ConfirmDelete` | `[ListForm]` Toolbar ve satır aksiyonları (`EditingOptionDto`). |
|
||
| `AllowDetail` | `[ListForm]` Satırda **Detay** düğmesi çıkar; `/admin/form/{ListFormCode}/{anahtar}` adresini yeni sekmede açar — aynı ekranın tek kayıtlık form görünümüdür. `KeyFieldName` boşsa çalışmaz. |
|
||
| `DefaultLayout` | `[ListForm]` `"grid"`, `"card"`, `"pivot"`, `"chart"`, `"tree"`, `"gantt"`, `"scheduler"`, `"todo"`. |
|
||
| `Grid` `Card` `Pivot` `Chart` `Tree` `Gantt` `Scheduler` `Todo` | `[ListForm]` Hangi görünümlerin açık olduğu (`LayoutDto`). Açtığın her görünümün option bloğunu da doldur. |
|
||
|
||
**Görünüm option blokları** — `[ListForm]`; yalnızca ilgili bayrak `true` ise anlamlıdır:
|
||
|
||
| Blok | Zorunlu alanlar |
|
||
| --- | --- |
|
||
| `TreeOptionDto` | `KeyExpr`, `ParentIdExpr`; opsiyonel `HasItemsExpr`, `RootValue`, `AutoExpandAll`, `RecursiveSelection` |
|
||
| `GanttOptionDto` | `KeyExpr`, `ParentIdExpr`, `TitleExpr`, `StartExpr`, `EndExpr`; `ProgressExpr`, `ScaleType` (`auto`/`minutes`/`hours`/`sixHours`/`days`/`weeks`/`months`/`quarters`/`years`, varsayılan `weeks`), `Allow*` bayrakları |
|
||
| `SchedulerOptionDto` | `TextExpr`, `StartDateExpr`, `EndDateExpr`; `AllDayExpr`, `RecurrenceRuleExpr`, `RecurrenceExceptionExpr`, `StartDayHour`, `EndDayHour`, `DefaultView` (`day`/`week`/`workWeek`/`month`/`timelineDay`/`timelineWeek`/`timelineMonth`/`agenda`, varsayılan `week`), `CellDuration`, `FirstDayOfWeek`, `Allow*` |
|
||
| `TodoOptionDto` | `TitleExpr`, `StatusExpr`; `DescriptionExpr`, `DueDateExpr`, `TagExpr`, `AssigneeExpr`, `PriorityExpr`, `CompletedExpr`, `OrderExpr`, `StatusOrder` (virgülle ayrılmış kolon sırası), `AllowDragging` |
|
||
|
||
### 4.3 `Groups` — alanlar ve düzenleme formu
|
||
|
||
`Groups`, hem grid sütunlarını hem düzenleme formunun düzenini tanımlar. Buradaki her `Items`
|
||
öğesi bire bir bir **`ListFormField` kaydına** dönüşür (§4.8); ekranda görünen sütunların tek
|
||
kaynağı odur. Grup yapısı yalnızca düzenleme formunun yerleşimini belirler — grid sütun sırası
|
||
`Items` sırasından gelir.
|
||
|
||
```jsonc
|
||
"Groups": [
|
||
{
|
||
"Caption": "Genel",
|
||
"ColCount": 2,
|
||
"Items": [ /* WizardColumnItemInputDto */ ]
|
||
}
|
||
]
|
||
```
|
||
|
||
Seed dosyasında `Groups` **kırpılmış** hâlde durur: grup başlığı/kolon sayısı ve alanların
|
||
editör bilgileri `ListForm.EditingFormDto` ve `Fields` içinde olduğu için tekrarlanmaz; dosyada
|
||
her öğe yalnızca `FieldName`, `CaptionName` ve `En`/`Tr` taşır — çünkü
|
||
alan başlıklarının dil metinlerinin başka kaynağı yoktur. Okurken tümü geri doldurulur.
|
||
|
||
Her `Items` öğesi:
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `FieldName` | Veri kolonunun adı (SQL'deki adıyla birebir). |
|
||
| `CaptionName` | Dil anahtarı ya da düz başlık. |
|
||
| `En` / `Tr` | Alan başlığının dil metinleri; ikisi de doldurulur. |
|
||
| `EditorType` | `[Fields]` Bkz. §3. |
|
||
| `EditorOptions` | `[Fields]` DevExtreme editör JSON'u — §6. |
|
||
| `EditorScript` | `[Fields]` Alan davranış script'i — §5. |
|
||
| `DbSourceType` | `[Fields]` Bkz. §3. |
|
||
| `IsRequired` | `[Fields]` Zorunluluk (`ValidationRuleDto`). |
|
||
| `IncludeInEditingForm` | `[Fields]` `false` ise yalnızca grid sütunu olur, forma girmez (`EditGroupOrderNo`). |
|
||
| `ColSpan` | `[Fields]` Formda kaç kolon kaplar (grubun `ColCount` değeri içinde). |
|
||
| `LookupDataSourceType` | `[Fields]` `1/2/3` — bkz. §3. `0` = lookup yok. |
|
||
| `LookupQuery` | `[Fields]` `Query` tipinde SQL; `StaticData` tipinde JSON dizisi; `WebService` tipinde URL. |
|
||
| `ValueExpr` / `DisplayExpr` | `[Fields]` Lookup'ın değer ve etiket kolonları. |
|
||
|
||
### 4.3.1 Sütunlar nasıl otomatik üretilir
|
||
|
||
Gösterilecek **tüm sütunlar `ListFormField` kayıtlarında** durur. Wizard bu kayıtları veritabanı
|
||
kolon metadata'sından (`GetTableColumnsAsync`) otomatik üretir; sen yalnızca farklı olmasını
|
||
istediğin şeyi belirtirsin.
|
||
|
||
Bir kolon `Groups[].Items[]` içine eklendiğinde varsayılanlar şöyle türetilir:
|
||
|
||
| Alan | Nereden gelir |
|
||
| --- | --- |
|
||
| `EditorType` | SQL tipinden çıkarılır (aşağıdaki tablo) |
|
||
| `DbSourceType` | SQL tipinden `System.Data.DbType` karşılığına eşlenir (§3) |
|
||
| `IsRequired` | Kolon `NOT NULL` ise `true` |
|
||
| Anahtar alan | `KeyFieldName` ilk grubun ilk alanı olarak `IncludeInEditingForm: false` ile eklenir (`DbSourceType` = `KeyFieldDbSourceType`); listede gizli sütun olur, forma girmez |
|
||
| `En` / `Tr` | Kolon adı PascalCase'ten kelimelere ayrılır: `OrderNo` → `Order No` |
|
||
| `CaptionName` | `App.Listform.ListformField.{KolonAdı}` |
|
||
| `ColSpan` | `1` |
|
||
| `IncludeInEditingForm` | `true` |
|
||
| `ValueExpr` / `DisplayExpr` | `Key` / `Name` (lookup tanımlanırsa değiştirilir) |
|
||
| `EditorOptions` / `EditorScript` | Boş — ihtiyaç varsa sen yazarsın |
|
||
|
||
**SQL tipi → `EditorType` çıkarımı** (`inferEditorType`):
|
||
|
||
| SQL tipi | Üretilen editör |
|
||
| --- | --- |
|
||
| `bit` | `dxCheckBox` |
|
||
| `int`, `bigint`, `smallint`, `tinyint`, `decimal`, `numeric`, `float`, `real`, `money`, `smallmoney` | `dxNumberBox` |
|
||
| `date`, `datetime`, `datetime2`, `smalldatetime`, `datetimeoffset` | `dxDateBox` |
|
||
| diğer hepsi | `dxTextBox` |
|
||
|
||
Çıkarım kasıtlı olarak dardır. **Foreign key kolonları `dxNumberBox`/`dxTextBox` olarak gelir** —
|
||
bunları `dxSelectBox` / `dxGridBox` / `dxTagBox` yapmak ve lookup tanımlamak senin işindir.
|
||
Uzun metin (`nvarchar(max)`) için `dxTextArea`, HTML içerik için `dxHtmlEditor`, durum kolonları
|
||
için `dxRadioGroup`/`dxSwitch` seçilmelidir; otomatik çıkarım bunları bilemez.
|
||
|
||
### 4.3.2 Editör tipine göre özellikler
|
||
|
||
Her editör kendi `EditorOptions` sözlüğüne sahiptir. Aşağıdaki tablo, o editöre **özgü** olan
|
||
anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir.
|
||
|
||
| `EditorType` | Ne için | Uygun `DbSourceType` | Kendine özgü `EditorOptions` |
|
||
| --- | --- | --- | --- |
|
||
| `dxTextBox` | Kısa metin | String (16) | — (ortak: `maxLength`, `mask`, `placeholder`) |
|
||
| `dxTextArea` | Uzun metin | String (16) | `autoResizeEnabled`, `minHeight` |
|
||
| `dxNumberBox` | Sayı | Int32/Int64/Decimal/Double (7,8,10,11,12) | `format.type` (`fixedPoint`/`currency`/`percent`), `format.precision`, `format.currency`, `useMaskBehavior`, `useLargeSpinButtons`, `invalidValueMessage` |
|
||
| `dxDateBox` | Tarih / tarih-saat | Date/DateTime (5,6,26,27) | `useMaskBehavior` (+ ortak: `type`, `displayFormat`, `dateSerializationFormat`, `pickerType`, `interval`, `calendarOptions.*`, `invalidDateMessage`) |
|
||
| `dxDateRangeBox` | Tarih aralığı | Date/DateTime | ortak tarih anahtarları |
|
||
| `dxCalendar` | Gömülü takvim | Date/DateTime | `calendarOptions.zoomLevel`, `calendarOptions.firstDayOfWeek` |
|
||
| `dxCheckBox` | Evet/hayır | Boolean (3) | `text` (kutunun yanındaki etiket) |
|
||
| `dxSwitch` | Evet/hayır (anahtar) | Boolean (3) | `switchedOnText`, `switchedOffText` |
|
||
| `dxRadioGroup` | Az sayıda seçenek | String/Int32 | `layout` (`horizontal`/`vertical`) + **lookup zorunlu** |
|
||
| `dxSelectBox` | Tek seçim (az kayıt) | FK tipi | `acceptCustomValue`, `searchEnabled`, `minSearchLength`, `searchExpr`, `searchMode`, `searchTimeout`, `noDataText`, `showDataBeforeSearch` + **lookup zorunlu** |
|
||
| `dxLookup` | Tek seçim (çok kayıt, mobil dostu) | FK tipi | `dropDownOptions.*`, `searchEnabled`, `applyValueMode` + **lookup zorunlu** |
|
||
| `dxDropDownBox` | Özel içerikli açılır kutu | FK tipi | `dropDownOptions.width/height/hideOnOutsideClick`, `deferRendering`, `openOnFieldClick` |
|
||
| `dxGridBox` | Çok kolonlu seçim ızgarası | FK tipi | `columns`\*, `filterRowVisible`\*, `selectionMode`\*, `acceptCustomValue`\* + **lookup zorunlu** |
|
||
| `dxTagBox` | Çok değerli seçim | String (`\|` ile ayrılmış — §4.3.2.1) | `showSelectionControls`\*, `maxDisplayedTags`\*, `showMultiTagOnly`\*, `applyValueMode`\*, `searchEnabled`\*, `acceptCustomValue`\*, `hideSelectedItems`, `multiline` + **lookup zorunlu** |
|
||
| `dxAutocomplete` | Serbest metin + öneri | String (16) | `minSearchLength`, `searchExpr`, `searchTimeout` |
|
||
| `dxColorBox` | Renk | String (16) | `editAlphaChannel`, `keyStep` |
|
||
| `dxSlider` | Tek değerli kaydırıcı | Sayı | `min`, `max`, `tooltip.showMode` |
|
||
| `dxRangeSlider` | Aralık kaydırıcı | Sayı | `min`, `max`, `showRange` |
|
||
| `dxHtmlEditor` | Zengin metin | String (16) | `toolbar.items`, `toolbar.multiline`, `valueType` (`html`/`markdown`), `mediaResizing.enabled`, `imageUpload.uploadUrl`, `imageUpload.fileUploadMode` |
|
||
| `dxImageUpload` | Görsel yükleme (platform) | String (16) | `uploadUrl`\*, `accept`\*, `multiple`\*, `maxFileSize`\*, `width`\*, `height`\* |
|
||
| `dxImageViewer` | Görsel gösterimi (platform) | String (16) | `width`, `height` |
|
||
|
||
`\*` ile işaretli anahtarlar backend'de **tipli DTO'ya** çözülür:
|
||
`GridBoxOptionsDto`, `TagBoxOptionsDto`, `ImageUploadOptionsDto`. Yanlış tipte yazılan bir değer
|
||
hata vermez, **sessizce yok sayılır** — bu yüzden `columns` bir dizi, `maxDisplayedTags` bir
|
||
sayı, `multiple` bir boolean olmalıdır.
|
||
|
||
**Lookup zorunlu** yazan editörlerde `LookupDataSourceType` + `LookupQuery` + `ValueExpr` +
|
||
`DisplayExpr` dolu olmalıdır; aksi hâlde alan boş bir liste gösterir:
|
||
|
||
```jsonc
|
||
{
|
||
"FieldName": "StatusId", "EditorType": "dxSelectBox", "DbSourceType": 11,
|
||
"LookupDataSourceType": 1,
|
||
"LookupQuery": "[{\"Key\":1,\"Name\":\"Taslak\"},{\"Key\":2,\"Name\":\"Onayda\"},{\"Key\":3,\"Name\":\"Onaylandı\"}]",
|
||
"ValueExpr": "Key", "DisplayExpr": "Name"
|
||
}
|
||
```
|
||
|
||
`LookupDataSourceType` `1` (StaticData) ise `LookupQuery` bir JSON dizisi, `2` (Query) ise SQL,
|
||
`3` (WebService) ise noktalı virgülle ayrılmış bir çağrı tarifidir. Tam sözleşme ve cascade
|
||
(ebeveyn–çocuk) kurulumu için §4.3.4.
|
||
|
||
### 4.3.2.1 Çok değerli alanların depolama sözleşmesi
|
||
|
||
`dxTagBox` (ve `selectionMode: "multiple"` verilmiş `dxGridBox`) tek bir **metin kolonunda**,
|
||
değerleri `|` ile ayırarak saklar (`PlatformConsts.MultiValueDelimiter`). İlişki tablosu
|
||
açmaya gerek yoktur; kolon `NVARCHAR(n)` olarak tanımlanır ve `DbSourceType: 16` verilir.
|
||
|
||
Zincirin iki yönü **farklı yerlerde** kapanır — yeni ekran üretirken üçünü birden kontrol et:
|
||
|
||
| Yön | Nerede olur | Not |
|
||
| --- | --- | --- |
|
||
| Okuma (`"A\|B"` → `["A","B"]`) | UI, `useListFormCustomDataSource` | Alanın `EditorType2` değeri `dxTagBox` ise kolon `extras.multiValue` alır; ayrıştırma buna bakar |
|
||
| Yazma, **SQL yolu** (`["A","B"]` → `"A\|B"`) | Backend, `QueryHelper.GetFormattedValue` | Yalnızca alanın `DbSourceType`'ı String (16) ise birleştirir — sayısal tip verirsen dizi sessizce bozulur |
|
||
| Yazma, **tipli DTO yolu** | Yok — elle kurulur | Aşağıya bak |
|
||
|
||
**Tuzak:** ekranın `*ServiceAddress` alanı varsayılan `list-form-data/*` yerine tipli bir DTO
|
||
alan bir uca (`list-form-dynamic-api/...`, Custom Endpoint, Dynamic Service) bakıyorsa
|
||
`QueryHelper` devreye girmez; gelen dizi `string` property'ye bağlanamaz ve istek
|
||
`400 Bad Request` + `"The JSON value could not be converted to System.String"` ile düşer.
|
||
Bu durumda DTO'daki alana `[JsonConverter(typeof(MultiValueStringJsonConverter))]` eklenir
|
||
(`Application.Contracts/ListForms/DynamicApi/`); converter hem diziyi hem düz metni kabul eder
|
||
ve `|` ile birleştirilmiş tek string üretir. Salt okuma DTO'larına eklenmez.
|
||
|
||
### 4.3.3 Her editörde geçerli ortak seçenekler
|
||
|
||
Aşağıda en sık kullanılanlar özetlenir; **tam sözlük ve grup grup liste §6.3'tedir** —
|
||
ikisi çeliştiğinde `optionSpecs.ts` esastır.
|
||
|
||
| Grup | Anahtarlar |
|
||
| --- | --- |
|
||
| Genel | `disabled`, `readOnly`, `visible`, `hint`, `tabIndex`, `showClearButton`, `valueChangeEvent`, `validationMessageMode`, `validationMessagePosition` |
|
||
| Görünüm | `width`, `height`, `stylingMode`, `label`, `labelMode`, `elementAttr.class`, `inputAttr.style`, `inputAttr.aria-label`, `buttons` |
|
||
| Metin | `placeholder`, `maxLength`, `spellcheck`, `mode`, `mask`, `maskChar`, `maskRules.X`, `maskInvalidMessage`, `showMaskMode`, `useMaskedValue`, `encodeHtml` |
|
||
| Sayı | `min`, `max`, `format`, `showSpinButtons` |
|
||
| Tarih | `type`, `displayFormat`, `dateSerializationFormat`, `pickerType`, `interval`, `invalidDateMessage` |
|
||
| Açılır liste | `noDataText`, `searchEnabled`, `minSearchLength`, `searchExpr`, `searchMode`, `searchTimeout`, `showDataBeforeSearch`, `openOnFieldClick`, `deferRendering`, `wrapItemText`, `dropDownOptions.*` |
|
||
|
||
Elle JSON yazmak yerine C# tarafında `EditorOptions` akıcı yapısını kullan (§6); hazır
|
||
başlangıçlar bu anahtarların doğru bileşimlerini üretir.
|
||
|
||
### 4.3.4 Lookup ve cascade (`LookupJson`)
|
||
|
||
Wizard'daki `LookupDataSourceType` + `LookupQuery` + `ValueExpr` + `DisplayExpr` alanları alanın
|
||
`LookupJson` kolonuna dönüşür. Tam sözleşme:
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `DataSourceType` | `1` StaticData · `2` Query · `3` WebService |
|
||
| `ValueExpr` / `DisplayExpr` | Varsayılan `Key` / `Name` |
|
||
| `LookupQuery` | Kaynağın kendisi (tipe göre biçim değişir, aşağıda) |
|
||
| `CascadeParentFields` | **Ebeveyn alanda** doldurulur; bu alan değişince listesi yenilenecek çocuk alan(lar), virgülle |
|
||
| `CascadeRelationField` | **Çocuk alanda** doldurulur; ebeveynin değeriyle karşılaştırılacak alan |
|
||
| `CascadeFilterOperator` | Cascade filtresinin operatörü; varsayılan `=` |
|
||
| `CascadeEmptyFields` | Ebeveyn değişince içeriği boşaltılacak alanlar, virgülle |
|
||
|
||
**`LookupQuery` biçimleri:**
|
||
|
||
| Tip | Biçim | Not |
|
||
| --- | --- | --- |
|
||
| `1` StaticData | `[{"key":1,"name":"Taslak"},{"key":2,"name":"Onayda"}]` | Kolon **sayı** tipindeyse `key` sayı, **metin** tipindeyse `key` metin olmalıdır; aksi hâlde seçim eşleşmez. Cascade gerekiyorsa her öğeye `group` eklenir. |
|
||
| `2` Query | `SELECT Id AS [Key], Name FROM ... WHERE IsDeleted = 0` | Cascade için ayrıca ebeveyn değerini taşıyan bir `group` kolonu döndürülmelidir. |
|
||
| `3` WebService | `METHOD;url;bodyJson;keySelector;nameSelector;groupSelector` | Noktalı virgülle **altı parça**. Selector'lar her satır `a` üzerinde çalışan JS ifadeleridir (`a.id`, `a.name`, `null`). Gövdedeki `@param0`, `@param1`… cascade filtre değerleriyle doldurulur. Örnek: `GET;notification-rule/notification-types;{};a.id;a.name;null` |
|
||
|
||
Cascade örneği — İl → İlçe (aşağıdaki nesneler `LookupJson` kolonuna **tek satır, kaçışlanmış
|
||
JSON string** olarak yazılır):
|
||
|
||
```jsonc
|
||
// İl alanı (ebeveyn)
|
||
{
|
||
"DataSourceType": 2, "ValueExpr": "Key", "DisplayExpr": "Name",
|
||
"LookupQuery": "SELECT Id AS [Key], Name FROM Def_T_City",
|
||
"CascadeParentFields": "DistrictId",
|
||
"CascadeEmptyFields": "DistrictId"
|
||
}
|
||
|
||
// İlçe alanı (çocuk)
|
||
{
|
||
"DataSourceType": 2, "ValueExpr": "Key", "DisplayExpr": "Name",
|
||
"LookupQuery": "SELECT Id AS [Key], Name, CityId AS [group] FROM Def_T_District",
|
||
"CascadeRelationField": "CityId",
|
||
"CascadeFilterOperator": "="
|
||
}
|
||
```
|
||
|
||
> Lookup verisi önbelleklenir; aynı sorgu aynı filtrelerle bir kez yüklenir.
|
||
|
||
### 4.3.5 Varsayılan değerler — `*FieldsDefaultValueJson`
|
||
|
||
Kullanıcının doldurmayacağı alanlar (audit, tenant, durum, **belge numarası**) script ile değil,
|
||
bu JSON'larla doldurulur. Dört ayrı liste vardır ve hepsi aynı öğe şeklini taşır:
|
||
|
||
| Kolon | Ne zaman çalışır |
|
||
| --- | --- |
|
||
| `InsertFieldsDefaultValueJson` | Insert ve Duplicate |
|
||
| `UpdateFieldsDefaultValueJson` | Update |
|
||
| `DeleteFieldsDefaultValueJson` | Delete (soft delete alanları) |
|
||
| `FormFieldsDefaultValueJson` | Form açılışı (Select) — ekrana gelen varsayılanlar |
|
||
|
||
```jsonc
|
||
[
|
||
{ "FieldName": "Id", "FieldDbType": 9, "Value": "@NEWID", "CustomValueType": 2 },
|
||
{ "FieldName": "CreationTime", "FieldDbType": 6, "Value": "@NOW", "CustomValueType": 2 },
|
||
{ "FieldName": "CreatorId", "FieldDbType": 9, "Value": "@USERID", "CustomValueType": 2 },
|
||
{ "FieldName": "IsDeleted", "FieldDbType": 3, "Value": "false", "CustomValueType": 1 },
|
||
{ "FieldName": "OrderNo", "FieldDbType": 16, "Value": "SiparisNo","CustomValueType": 5 },
|
||
{ "FieldName": "ExchangeRate", "FieldDbType": 7, "Value": "", "CustomValueType": 3,
|
||
"SqlQuery": "SELECT TOP 1 Rate FROM Fin_T_ExchangeRate ORDER BY Date DESC" }
|
||
]
|
||
```
|
||
|
||
**`CustomValueType` (`FieldCustomValueTypeEnum`)**
|
||
|
||
| # | Tip | `Value` ne demek |
|
||
| --- | --- | --- |
|
||
| 1 | `Value` | Sabit değer (`false`, `3`, `Taslak`, `GETDATE()`) |
|
||
| 2 | `CustomKey` | Aşağıdaki token'lardan biri |
|
||
| 3 | `DbQuery` | Değer `SqlQuery` alanındaki **tek hücre** döndüren sorgudan gelir (ekranın `DataSourceCode`/tenant bağlamında çalışır) |
|
||
| 4 | `QueryParams` | Değer, adresteki query string parametresinden okunur; `Value` parametre adıdır |
|
||
| 5 | `Sequence` | `Value` bir **Sequence adıdır** (§9.9); yazma anında sıradaki numara alınır, form açılışında (Select) yalnızca ad döner ve UI numarayı ayrıca ister |
|
||
|
||
**`CustomKey` token'ları** (`PlatformConsts.DefaultValues`) — `DefaultValueManager`'ın tanıdığı
|
||
tam küme:
|
||
|
||
| Token | Değer |
|
||
| --- | --- |
|
||
| `@USERID` · `@USERNAME` · `@ROLES` | Oturumdaki kullanıcının id'si, adı, rol listesi (dizi) |
|
||
| `@DATE` · `@NOW` | Bugünün tarihi (saat 00:00) · tarih + saat |
|
||
| `@YEAR` · `@MONTH` · `@DAY` | Sayı olarak yıl/ay/gün |
|
||
| `@NEWID` | Yeni `Guid` |
|
||
| `@ID` | Kaydın anahtarı; **Delete'te seçili anahtarların tamamı** |
|
||
| `@SELECTED_IDS` | Seçili anahtarların tamamı (her operasyonda) |
|
||
| `@TENANTID` | Aktif kiracı |
|
||
|
||
Listedeki tokenlar dışında bir `CustomKey` değeri **`null` üretir** ve o alan yazılmaz. Bu
|
||
yüzden `@AUTONUMBER`, `CustomKey` değil **`CustomValueType: 1` (Value)** ile yazılır:
|
||
|
||
```jsonc
|
||
{ "FieldName": "SiraNo", "FieldDbType": 11, "Value": "@AUTONUMBER", "CustomValueType": 1 }
|
||
```
|
||
|
||
`FormFieldsDefaultValueJson`'a böyle yazılan alan istemciye `column.defaultValue` olarak iner ve
|
||
yeni satır **tarayıcıda** artan bir numara alır — geçici, oturum içi bir numaradır; kalıcı belge
|
||
numarası için `Sequence` (`CustomValueType: 5`, §9.9) kullan.
|
||
|
||
> **`DefaultValue` / `DefaultValueType` bir `ListFormField` kolonu değildir.** İstemcinin
|
||
> gördüğü bu iki alan, `ListFormSelectAppService`'in her metadata isteğinde
|
||
> `FormFieldsDefaultValueJson`'ı çözerek ürettiği hesaplanmış değerlerdir. Yani "alanın
|
||
> varsayılanı" her zaman ekran seviyesindeki dört listenin birine yazılır.
|
||
|
||
> Boş/`null` üretilen değer yazılmaz. `FieldDbType` yanlış verilirse değer
|
||
> `QueryHelper.GetFormattedValue` tarafından sessizce yanlış biçimlenir — kolon tipiyle aynı
|
||
> `DbType` numarasını yaz (§3).
|
||
|
||
### 4.3.6 Doğrulama — `ValidationRuleJson`
|
||
|
||
Alan başına DevExtreme doğrulama kuralları dizisi. Wizard `IsRequired: true` alanlara `required`
|
||
kuralını kendi yazar; gerisi elle eklenir.
|
||
|
||
| `Type` | Kullanılan alanlar |
|
||
| --- | --- |
|
||
| `required` | `Message`, `Trim` |
|
||
| `numeric` | `Message`, `IgnoreEmptyValue` |
|
||
| `range` | `Min`, `Max` (sayı ya da tarih), `Reevaluate` |
|
||
| `stringLength` | `Min`, `Max`, `Trim` |
|
||
| `pattern` | `Pattern` (regex) |
|
||
| `email` | `Message`, `IgnoreEmptyValue` |
|
||
| `compare` | `ComparisonType` (`==`, `===`, `!=`, `!==`, `<`, `<=`, `>`, `>=`) |
|
||
| `custom` | JS ile doğrulama; `Reevaluate` |
|
||
| `async` | Sunucuya sorarak doğrulama; `Reevaluate` |
|
||
|
||
```jsonc
|
||
"ValidationRuleJson": "[{\"Type\":\"required\",\"Message\":\"App.Validation.Required\"},
|
||
{\"Type\":\"range\",\"Min\":0,\"Max\":1000000,\"Message\":\"App.Validation.Amount\"}]"
|
||
```
|
||
|
||
### 4.3.7 Koşullu biçimlendirme — `ColumnStylingJson`
|
||
|
||
"Gecikmiş satır kırmızı olsun", "onaylı kayıt yeşil rozetli olsun" istekleri buradan çözülür;
|
||
script gerekmez.
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `RowType` | `data` (varsayılan) · `group` · `header` · `filter` · `detail` · `groupFooter` · `totalFooter` |
|
||
| `UseRow` | `true` ise stil hücreye değil **tüm satıra** uygulanır |
|
||
| `Condition` | `=`, `<>`, `<`, `<=`, `>`, `>=`, `contains`, `notcontains`, `startswith`, `endswith`, `isblank`, `isnotblank`, `between`, `anyof`, `noneof` |
|
||
| `ConditionValue` | Boş bırakılırsa koşul aranmaz, stil her zaman uygulanır |
|
||
| `CssStyles` | `anahtar:değer` çiftleri, aralarında `|`: `background-color:#fee | font-weight:bold` |
|
||
| `CssClassName` | Hazır bir CSS sınıfı kullanılacaksa |
|
||
|
||
Alan üzerindeki `ColumnCssClass` / `ColumnCssValue` kolonları da aynı işi koşulsuz yapar.
|
||
|
||
### 4.3.8 Ekran üstü ek filtre — `ExtraFilterJson`
|
||
|
||
Grid'in üstünde duran, kullanıcının seçtiği değere göre listeyi süzen filtre çubuğu
|
||
(tarih aralığı, şube, durum). Filtre satırından (`FilterRowJson`) farkı: her zaman görünür,
|
||
varsayılan değeri olur ve **yeni kayıtta o alanı otomatik doldurur**.
|
||
|
||
```jsonc
|
||
[
|
||
{ "FieldName": "Status", "Caption": "Durum", "Operator": "=", "ControlType": "Select",
|
||
"DefaultValue": "1",
|
||
"Items": [ { "Key": "1", "Value": "Taslak" }, { "Key": "2", "Value": "Onayda" } ] },
|
||
{ "FieldName": "OrderNo", "Caption": "Sipariş No", "Operator": "contains", "ControlType": "Text" }
|
||
]
|
||
```
|
||
|
||
- `ControlType`: `Select` · diğer her değer metin kutusudur (Enter ya da odak kaybında uygulanır).
|
||
- `Select` seçenekleri ya `Items` dizisinden gelir ya da `SqlQuery` alanına yazılan sorgudan;
|
||
sorgu **`Key` ve `Value` adlı iki kolon** döndürmelidir ve ekranın veri kaynağında/tenant
|
||
bağlamında çalışır.
|
||
- Seçilen değerler `[FieldName, Operator, Value]` üçlüsüne çevrilir, URL'deki `filter` parametresiyle
|
||
`and` ile birleştirilir. Böylece filtreli ekran adresi paylaşılabilir.
|
||
- `DefaultValue` dolu ise seçim temizlenemez ve liste ilk açılışta o değerle gelir.
|
||
- Ek filtre ile yönetilen alan, yeni kayıt varsayılanlarında **atlanır** (§4.3.5) — değeri
|
||
filtreden gelir.
|
||
### 4.4 Çalışan örnek — tablo üzerinden liste ekranı
|
||
|
||
```jsonc
|
||
{
|
||
"Wizard": {
|
||
"ComponentKind": 0,
|
||
"WizardName": "Orders",
|
||
"SeededAt": "2026-08-26T10:15:00",
|
||
"ListFormCode": "App.Mrp.Orders",
|
||
"MenuCode": "App.Mrp.Orders",
|
||
"MenuOrder": 1,
|
||
"CreateMenu": true,
|
||
"MenuUrl": "/admin/list/App.Mrp.Orders",
|
||
"IsTenant": true, "IsBranch": false, "IsOrganizationUnit": false,
|
||
"AllowAdding": true, "AllowUpdating": true, "AllowDeleting": true,
|
||
"AllowDetail": false, "ConfirmDelete": true,
|
||
"DefaultLayout": "grid",
|
||
"Grid": true, "Card": true, "Pivot": true, "Chart": true,
|
||
"Tree": false, "Gantt": false, "Scheduler": false, "Todo": false,
|
||
|
||
"MenuEn": "Orders", "MenuTr": "Siparişler",
|
||
"TitleEn": "Orders", "TitleTr": "Siparişler",
|
||
"DescEn": "Order list", "DescTr": "Sipariş listesi",
|
||
"MenuParentEn": "MRP", "MenuParentTr": "MRP",
|
||
|
||
"PermissionGroupName": "App.Mrp",
|
||
"PermissionGroupEn": "MRP", "PermissionGroupTr": "MRP",
|
||
"MenuParentCode": "App.Mrp", "MenuParentModuleId": "MRP", "MenuParentIcon": "FcFactory",
|
||
"MenuIcon": "FcShipped",
|
||
|
||
"DataSourceCode": "Default", "DataSourceConnectionString": "",
|
||
"SelectCommandType": 1,
|
||
"SelectCommand": "Mrp_T_Order",
|
||
"KeyFieldName": "Id",
|
||
"KeyFieldDbSourceType": 11,
|
||
|
||
"Groups": [
|
||
{
|
||
"Caption": "Genel", "ColCount": 2,
|
||
"Items": [
|
||
{
|
||
"FieldName": "Id", "CaptionName": "App.Listform.ListformField.Id",
|
||
"Tr": "Kimlik", "En": "Id",
|
||
"EditorType": "dxTextBox", "DbSourceType": 11,
|
||
"IsRequired": false, "IncludeInEditingForm": false, "ColSpan": 1
|
||
},
|
||
{
|
||
"FieldName": "OrderNo", "CaptionName": "App.Listform.ListformField.OrderNo",
|
||
"Tr": "Sipariş No", "En": "Order No",
|
||
"EditorType": "dxTextBox", "DbSourceType": 16,
|
||
"IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
|
||
"EditorOptions": "{\"showClearButton\":true,\"maxLength\":20}"
|
||
},
|
||
{
|
||
"FieldName": "CustomerId", "CaptionName": "App.Listform.ListformField.Customer",
|
||
"Tr": "Cari", "En": "Customer",
|
||
"EditorType": "dxSelectBox", "DbSourceType": 11,
|
||
"IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
|
||
"LookupDataSourceType": 2,
|
||
"LookupQuery": "SELECT Id, Name FROM Crm_T_Customer WHERE IsDeleted = 0",
|
||
"ValueExpr": "Id", "DisplayExpr": "Name"
|
||
},
|
||
{
|
||
"FieldName": "Quantity", "CaptionName": "App.Listform.ListformField.Quantity",
|
||
"Tr": "Miktar", "En": "Quantity",
|
||
"EditorType": "dxNumberBox", "DbSourceType": 7,
|
||
"IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
|
||
"EditorOptions": "{\"format\":{\"type\":\"fixedPoint\",\"precision\":2},\"showSpinButtons\":true}"
|
||
},
|
||
{
|
||
"FieldName": "UnitPrice", "CaptionName": "App.Listform.ListformField.UnitPrice",
|
||
"Tr": "Birim Fiyat", "En": "Unit Price",
|
||
"EditorType": "dxNumberBox", "DbSourceType": 7,
|
||
"IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
|
||
"EditorOptions": "{\"format\":{\"type\":\"fixedPoint\",\"precision\":2}}"
|
||
},
|
||
{
|
||
"FieldName": "Total", "CaptionName": "App.Listform.ListformField.Total",
|
||
"Tr": "Tutar", "En": "Total",
|
||
"EditorType": "dxNumberBox", "DbSourceType": 7,
|
||
"IsRequired": false, "IncludeInEditingForm": true, "ColSpan": 2,
|
||
"EditorOptions": "{\"readOnly\":true,\"format\":{\"type\":\"fixedPoint\",\"precision\":2}}",
|
||
"EditorScript": "// @builder {\"v\":1,\"rules\":[{\"id\":\"r1\",\"recipe\":\"multiply\",\"condition\":{\"operator\":\"always\",\"source\":\"\",\"value\":\"\"},\"params\":{\"left\":\"Quantity\",\"right\":\"UnitPrice\",\"target\":\"Total\",\"digits\":\"2\"}}]}\nset('Total', round(num('Quantity') * num('UnitPrice'), 2))"
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"SubForms": [], "Widgets": [],
|
||
"WorkflowDto": { "ApprovalUserFieldName": "", "ApprovalDateFieldName": "",
|
||
"ApprovalStatusFieldName": "", "ApprovalDescriptionFieldName": "",
|
||
"ApprovalIsFilterUserName": false, "ApprovalIsResetWorkflow": false, "Criteria": [] }
|
||
},
|
||
"IsDeletedField": true,
|
||
"IsCreatedField": true,
|
||
"InsertedRecords": {
|
||
"LanguageKeys": ["App.Mrp.Orders", "App.Mrp"],
|
||
"PermissionGroupNames": ["App.Mrp"],
|
||
"PermissionNames": ["App.Mrp.Orders", "App.Mrp.Orders.Create", "App.Mrp.Orders.Update",
|
||
"App.Mrp.Orders.Delete", "App.Mrp.Orders.Export", "App.Mrp.Orders.Import",
|
||
"App.Mrp.Orders.Note"],
|
||
"MenuCodes": ["App.Mrp", "App.Mrp.Orders"],
|
||
"DataSourceCodes": []
|
||
}
|
||
}
|
||
```
|
||
|
||
> `EditorScript` alanındaki `// @builder` başlığı ile onu izleyen kod satırı **birebir
|
||
> tutarlı** olmalıdır. Elle yazmak yerine C# tarafında `EditorScript.Build(...)` kullan ya da
|
||
> Script Builder'da üret; uyuşmazsa dialog script'i "elle düzenlenmiş" sayar.
|
||
|
||
### 4.4.1 Seeder'ın senin yerine ürettikleri — bunları dosyaya yazma
|
||
|
||
`WizardDataSeeder`, dosyadaki `Wizard` bloğundan yola çıkarak aşağıdakileri **kendisi** üretir.
|
||
Bunları seed dosyasında aramaya kalkma, orada yokturlar:
|
||
|
||
| Üretilen | Kural |
|
||
| --- | --- |
|
||
| Dil anahtarları | `{MenuCode}` (menü), `{ListFormCode}.Title`, `{ListFormCode}.Desc`, `{PermissionGroupName}` ve her alan için `CaptionName` |
|
||
| Yetkiler | `{MenuCode}` kökü + `.Create/.Update/.Delete` (+ List yolunda `.Export/.Import/.Note`) |
|
||
| Yetkilerin dil anahtarları | Ortak sabitler: `App.Platform.Create/Update/Delete/Export/Import/Note` |
|
||
| Üst menü | `MenuParentCode` yoksa yaratılır, `Order = max(kök Order) + 1` |
|
||
| Menü sırası | `MenuOrder` 0/boş ise `max(kardeş Order) + 1` |
|
||
| `DataSource` | `DataSourceCode` yoksa yaratılır; bağlantı metninde `Server` geçiyorsa `Mssql`, aksi hâlde `Postgresql` |
|
||
| `LayoutJson` | Görünüm bayrakları + `DefaultLayout` |
|
||
| `PermissionJson` / alan `PermissionJson` | `{MenuCode}` kökünden türetilir |
|
||
| `EditingFormJson` | `Groups` içinden; anahtar alan ve `IncludeInEditingForm: false` olanlar hariç |
|
||
| Form genişlik/yükseklik | Grup kolon sayısı ve satır sayısından hesaplanır (en fazla 3 kolon) |
|
||
| `FilterRowJson`, `HeaderFilterJson`, `SearchPanelJson`, `GroupPanelJson`, `ColumnOptionJson`, `PagerOptionJson`, `ExportJson` | Varsayılanlar |
|
||
| `DeleteCommand` | `IsDeletedField: true` ise soft delete `UPDATE` komutu; `false` ise `null` |
|
||
| `DefaultFilter` | `IsDeletedField: true` ise `"IsDeleted" = 'false'` |
|
||
| `InsertFieldsDefaultValueJson` | `IsCreatedField: true` ise `CreationTime=@NOW`, `CreatorId=@USERID`, `IsDeleted=false`, `Id=@NEWID`; değilse yalnızca `Id=@NEWID` |
|
||
| `DeleteFieldsDefaultValueJson` | `IsDeletedField: true` ise `DeleterId=@USERID`, `Id=@ID`; değilse yalnızca `Id=@ID` |
|
||
| `ValidationRuleJson` | `IsRequired: true` olan alanlara required kuralı |
|
||
| `LookupJson` | `LookupQuery` doluysa `LookupDataSourceType` + `DisplayExpr` + `ValueExpr` ile |
|
||
| `ShowNote` | Alt form veya iş akışı varsa açılır |
|
||
| `SelectionJson` | İş akışı varsa `single`, yoksa `none` |
|
||
| Görünüm option JSON'ları | İlgili bayrak açık **ve** zorunlu alanı dolu ise yazılır; eksikse görünüm sessizce kapanır |
|
||
|
||
**Dikkat edilecek tuzaklar:**
|
||
|
||
- `Tree`/`Gantt` için `ParentIdExpr`, `Scheduler` için `TextExpr`, `Todo` için `TitleExpr` **ve**
|
||
`StatusExpr` boşsa o görünümün JSON'u hiç yazılmaz — bayrağı `true` yapmak yetmez.
|
||
- Anahtar alanı (`KeyFieldName`) `Groups` içine **her zaman** koyulur — ilk grubun ilk alanı
|
||
olarak, `IncludeInEditingForm: false` ile. Seeder onu kendiliğinden eklemez; yoksa listede
|
||
anahtar kolonun `ListFormField` kaydı hiç oluşmaz ve satır anahtarı okunamadığı için düzenleme,
|
||
silme, detay ve alt form ilişkileri çalışmaz. Yazdığında sütun otomatik gizlenir (`Visible =
|
||
false`) ve forma girmez; tip bilgisi de oradan okunur.
|
||
- `IsDeletedField: false` verirsen ekranda **kalıcı silme** olmaz (`DeleteCommand` null kalır);
|
||
soft delete kolonları olmayan tabloda silme istiyorsan ListForm editöründen komut yazmalısın.
|
||
- Dil anahtarı zaten varsa metni **ezilmez**. Yanlış metin görüyorsan anahtar başka bir yerde
|
||
tanımlanmıştır.
|
||
|
||
### 4.5 Güncelleme, idempotency ve silme semantiği
|
||
|
||
**Seeder idempotenttir — bir ekran zaten varsa dosya atlanır.** "Zaten var" ölçütü yola göre
|
||
değişir:
|
||
|
||
| Yol | Kontrol |
|
||
| --- | --- |
|
||
| `List` | `ListForm` kaydında bu `ListFormCode` var mı |
|
||
| `Custom` + menülü | Bu `MenuCode` ile menü var mı |
|
||
| `Custom` + menüsüz | Bu `MenuCode` adında yetki var mı |
|
||
|
||
Sonuç: **var olan bir ekranı seed dosyasını düzenleyerek değiştiremezsin.** Değiştirmenin üç
|
||
yolu vardır:
|
||
|
||
1. Wizard ekranından `EditFileName` ile yeniden çalıştır (önce eski dosya ve
|
||
`InsertedRecords` kayıtları silinir, sonra yenisi üretilir) — **tercih edilen yol**.
|
||
2. ListForm editöründen ince ayar yap (seed dosyasına yansımaz, §9.6 uyarısı).
|
||
3. İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır.
|
||
|
||
> `List` yolunda seeder ekranı uygularken `ListForm`, `ListFormField` ve `ListFormWorkflow`
|
||
> kayıtlarını **silip yeniden yazar**; idempotency kapısını geçen dosya için bu güvenlidir.
|
||
|
||
**Silme:** yalnızca `InsertedRecords` içindekiler silinir. Paylaşılan üst menü ve izin grubu
|
||
başka bir wizard tarafından yaratıldıysa kalır. Bu yüzden `InsertedRecords` listesine var olan
|
||
bir kaydı yazmak veri kaybettirir.
|
||
|
||
### 4.6 Alt formlar ve widget'lar
|
||
|
||
**`SubForms` (`SubFormDto`)** — ana kaydın altında sekme olarak açılan ekranlar:
|
||
|
||
```jsonc
|
||
"SubForms": [
|
||
{
|
||
"TabType": "List", // List|Card|Tree|Gantt|Scheduler|Todo|Form|Chart|Pivot
|
||
"TabTitle": "App.Mrp.OrderItems", // dil anahtarı
|
||
"Code": "App.Mrp.OrderItems", // hedef ListFormCode
|
||
"IsRefresh": true, // ana kayıt değişince yenilensin
|
||
"Relation": [
|
||
{ "ParentFieldName": "Id", "ChildFieldName": "OrderId", "DbType": 11 }
|
||
]
|
||
}
|
||
]
|
||
```
|
||
|
||
`Relation` bir dizidir — bileşik anahtarda birden fazla eşleme verilir.
|
||
|
||
**`Widgets` (`WidgetEditDto`)** — ekranın üstündeki KPI kartları. **`Title`, `SubTitle`,
|
||
`Value`, `Icon`, `Color`, `OnClick` sabit metin değil, `SqlQuery` sonucundaki kolon adlarıdır**;
|
||
widget, sorgunun döndürdüğü **her satır için bir kart** çizer:
|
||
|
||
```jsonc
|
||
"Widgets": [
|
||
{
|
||
"SqlQuery": "SELECT 'App.Mrp.Widget.OpenOrders' AS Baslik, COUNT(*) AS Deger, 'blue' AS Renk, 'FcClock' AS Ikon FROM Mrp_T_Order WHERE IsDeleted = 'false' AND Status = 1",
|
||
"Title": "Baslik", // sorgudaki kolon adı
|
||
"Value": "Deger", // sorgudaki kolon adı
|
||
"Color": "Renk",
|
||
"Icon": "Ikon",
|
||
"SubTitle": "", // karşılığı olan kolon yoksa boş bırak
|
||
"OnClick": "", // tıklanınca açılacak adresi taşıyan kolonun adı
|
||
"ValueClassName": "text-3xl",
|
||
"ColSpan": 1,
|
||
"ColGap": 16,
|
||
"ClassName": "",
|
||
"IsActive": true
|
||
}
|
||
]
|
||
```
|
||
|
||
- `SqlQuery` **boşsa widget hiç çizilmez**; `IsActive: false` olan da çalıştırılmaz.
|
||
- Tek sorguyla birden çok kart üretmek için `UNION ALL` kullan — üç durum için üç widget kaydı
|
||
açmana gerek yok.
|
||
- Widget sorgusu ekranın otomatik tenant/şube filtresine tabi değildir ve `@TENANTID`/`@USERID`
|
||
gibi token'lar burada **çözülmez** (bağlanmamış parametre hatası verir). Kırılım gerekiyorsa
|
||
kiracıya özel bir `DataSource` kullan ya da kırılımı view'ın içine göm.
|
||
Ayrıntı: `lowcode-reference.instructions.md` §5.3.
|
||
|
||
Alt form olarak kullanılacak ekranı **menüsüz wizard** (`CreateMenu: false`) ile üret; menüde
|
||
görünmesin ama kendi yetkileri ve alan tanımları olsun. `Wizard.SeededAt` damgası ana
|
||
wizard'ınkinden **küçük** olmalıdır (§0.2).
|
||
|
||
### 4.7 İş akışı (onay süreci)
|
||
|
||
`WorkflowDto` onay alanlarını tabloda karşılığı olan kolonlara bağlar:
|
||
|
||
| Alan | Tabloda karşılığı |
|
||
| --- | --- |
|
||
| `ApprovalUserFieldName` | Onaylayan kullanıcı |
|
||
| `ApprovalDateFieldName` | Onay tarihi |
|
||
| `ApprovalStatusFieldName` | Durum |
|
||
| `ApprovalDescriptionFieldName` | Onay/ret açıklaması |
|
||
| `ApprovalIsFilterUserName` | Liste yalnızca kullanıcının onayına düşenleri göstersin |
|
||
| `ApprovalIsResetWorkflow` | Kayıt güncellenince akış başa dönsün |
|
||
|
||
`Criteria` düğüm grafiğidir. `Kind` değerleri: **`Start`**, **`Compare`**, **`Approval`**,
|
||
**`Inform`**, **`End`**.
|
||
|
||
```jsonc
|
||
"WorkflowDto": {
|
||
"ApprovalUserFieldName": "ApproverId",
|
||
"ApprovalDateFieldName": "ApprovalDate",
|
||
"ApprovalStatusFieldName": "Status",
|
||
"ApprovalDescriptionFieldName": "ApprovalNote",
|
||
"ApprovalIsFilterUserName": true,
|
||
"ApprovalIsResetWorkflow": true,
|
||
"Criteria": [
|
||
{ "Id": "N1", "Kind": "Start", "Title": "Başlangıç",
|
||
"NextOnStart": "N2", "PositionX": 40, "PositionY": 40 },
|
||
|
||
{ "Id": "N2", "Kind": "Compare", "Title": "Tutar kontrolü",
|
||
"CompareColumn": "Amount", "CompareOperator": ">", "CompareValue": 5000,
|
||
"CompareOutcomes": [
|
||
{ "Label": "> 5000", "TargetId": "N3",
|
||
"Conditions": [ { "CompareColumn": "Amount", "CompareOperator": ">", "CompareValue": 5000 } ] },
|
||
{ "Label": "<= 5000", "TargetId": "N4",
|
||
"Conditions": [ { "CompareColumn": "Amount", "CompareOperator": "<=", "CompareValue": 5000 } ] }
|
||
],
|
||
"PositionX": 40, "PositionY": 160 },
|
||
|
||
{ "Id": "N3", "Kind": "Approval", "Title": "Genel Müdür onayı",
|
||
"Approver": "<roleOrUserId>", "NextOnApprove": "N5", "NextOnReject": "N6",
|
||
"PositionX": 300, "PositionY": 260 },
|
||
|
||
{ "Id": "N4", "Kind": "Approval", "Title": "Yönetici onayı",
|
||
"Approver": "<roleOrUserId>", "NextOnApprove": "N5", "NextOnReject": "N6",
|
||
"PositionX": 40, "PositionY": 260 },
|
||
|
||
{ "Id": "N5", "Kind": "Inform", "Title": "Talep sahibine bilgi",
|
||
"Approver": "<roleOrUserId>", "NextOnStart": "N7", "PositionX": 170, "PositionY": 380 },
|
||
|
||
{ "Id": "N6", "Kind": "End", "Title": "Reddedildi", "PositionX": 340, "PositionY": 480 },
|
||
{ "Id": "N7", "Kind": "End", "Title": "Onaylandı", "PositionX": 170, "PositionY": 480 }
|
||
]
|
||
}
|
||
```
|
||
|
||
Kurallar:
|
||
|
||
- **Id sözleşmesi:** `N` ile başlayan kısa id'ler seeder tarafından `{ListFormCode}-{Id}` hâline
|
||
getirilir; hedef alanlar (`NextOn*`, `CompareOutcomes[].TargetId`) da aynı normalizasyondan
|
||
geçer. Bu yüzden dosyada `N1`, `N2` yazman yeterlidir ve **tekil olmaları şarttır**.
|
||
- `Compare` düğümünde çok dallı karar için `CompareOutcomes` kullanılır; iki dallı basit karar
|
||
için `NextOnTrue` / `NextOnFalse` yeterlidir.
|
||
- `Approval` düğümü `NextOnApprove` / `NextOnReject`, `Start` ve `Inform` düğümleri
|
||
`NextOnStart` alanını kullanır. `End` düğümünün çıkışı yoktur.
|
||
- **`Inform` düğümü e-posta gönderir** (MailQueue üzerinden, `Approver` alanındaki kullanıcı/rol
|
||
adreslerine, önceki adımların notlarıyla birlikte). Uygulama içi bildirim ya da SMS isteniyorsa
|
||
ayrıca bir bildirim kuralı tanımlanır (§9.11).
|
||
- `CompareValue` **sayısaldır** (`decimal`); metin karşılaştırması bu düğümle yapılmaz.
|
||
- `Title` tekil olmalıdır; seeder tekrar eden başlıkları ayrıştırır ama okunabilirliği bozar.
|
||
- Akış varsa ListForm `SelectionMode = single` olur ve not paneli otomatik açılır.
|
||
- Görsel tasarım `/admin/listform/edit/{kod}` → Workflow sekmesindedir.
|
||
|
||
### 4.8 `ListForm` entity — List component'in tek kaynağı
|
||
|
||
Bir List component'in **bütün** davranışı `ListForm` (ekran) ve `ListFormField` (alan) kayıtlarında
|
||
durur. Wizard bunları üretir; `/admin/listform/edit/{kod}` düzenler. Prompt ile üretim yaparken
|
||
wizard dosyası yazılır, ama hangi ayarın hangi kolona düştüğünü bilmek gerekir — çünkü wizard'ın
|
||
kapsamadığı bir istek geldiğinde cevap "kod yazalım" değil, "şu JSON kolonunu şöyle ayarlayalım"
|
||
olmalıdır.
|
||
|
||
**Kimlik ve varyant**
|
||
|
||
| Kolon | Ne yapar |
|
||
| --- | --- |
|
||
| `ListFormCode` | Ekranın kodu; kullanıcı/rol/dil özelleştirmeleri buna bağlanır |
|
||
| `CultureName` · `UserId` · `RoleId` | **Aynı ekranın varyantları.** Aynı `ListFormCode` ile ikinci bir kayıt açıp yalnızca bir rol ya da dil için farklı sütun/düzen verebilirsin |
|
||
| `ListFormType` | `List` · `Form` — ekranın türü |
|
||
| `IsSubForm` / `SubFormsListFormType` | Alt form olarak açıldığında davranış |
|
||
|
||
**Veri**
|
||
|
||
| Kolon | Ne yapar |
|
||
| --- | --- |
|
||
| `DataSourceCode` | Bağlantı |
|
||
| `SelectCommandType` + `SelectCommand` | Kaynak türü ve kaynağın kendisi |
|
||
| `TableName` | Tablo adı/alias — yazma komutlarının hedefi |
|
||
| `KeyFieldName` + `KeyFieldDbSourceType` | Anahtar |
|
||
| `SelectFieldsDefaultValueJson` | Select'e geçirilen varsayılan parametreler |
|
||
| `DefaultFilter` | Her sorgunun sonuna eklenen WHERE (soft delete filtresi buradadır) |
|
||
| `IsTenant` · `IsBranch` · `IsOrganizationUnit` | Otomatik kırılım filtreleri |
|
||
| `DataSourceJson` · `CommonJson` | Kaynak ve ortak ayarlar |
|
||
|
||
**Yazma (CRUD)**
|
||
|
||
| Kolon | Ne yapar |
|
||
| --- | --- |
|
||
| `InsertCommand` / `UpdateCommand` / `DeleteCommand` | Özel SQL komutları; boşsa platform üretir |
|
||
| `*BeforeCommand` / `*AfterCommand` | Komut öncesi/sonrası çalışan SQL kancaları — **stok düşme, log yazma, durum güncelleme gibi işleri kod yazmadan burada yaparsın** |
|
||
| `Insert/Update/DeleteFieldsDefaultValueJson` | Otomatik doldurulan alanlar (`@NOW`, `@USERID`, `@NEWID`, `@ID`) |
|
||
| `Insert/Update/DeleteServiceAddress` | Varsayılan `list-form-data/*`; özel endpoint ya da Dynamic Service'e yönlendirilebilir |
|
||
| `FormFieldsDefaultValueJson` | Form açılışındaki varsayılanlar |
|
||
|
||
**Görünüm ve düzen — 8 layout**
|
||
|
||
`LayoutJson` hangi layout'ların açık olduğunu ve varsayılanı taşır. Her layout'un kendi ayar
|
||
kolonu vardır:
|
||
|
||
| Layout | Ayar kolonu | Zorunlu alan |
|
||
| --- | --- | --- |
|
||
| **Grid** | `ColumnOptionJson`, `RowJson`, `PagerOptionJson`, `SelectionJson`, `StateStoringJson` | — |
|
||
| **Card** | `ColumnOptionJson` (kart alanları sütun tanımlarından türer) | — |
|
||
| **Pivot** | `PivotOptionJson` + alan bazında `PivotSettingsJson` | — |
|
||
| **Chart** | `SeriesJson`, `LegendJson`, `ArgumentAxisJson`, `ValueAxisJson`, `TooltipJson`, `PanesJson`, `AnnotationsJson`, `CrosshairJson`, `ScrollBarJson`, `ZoomAndPanJson`, `SizeJson`, `MarginJson`, `TitleJson`, `AnimationJson`, `Common*Json` | `SeriesJson` |
|
||
| **Tree** | `TreeOptionJson` | `KeyExpr` + `ParentIdExpr` |
|
||
| **Gantt** | `GanttOptionJson` | `ParentIdExpr` + `TitleExpr` + `StartExpr` + `EndExpr` |
|
||
| **Scheduler** | `SchedulerOptionJson` | `TextExpr` + `StartDateExpr` + `EndDateExpr` |
|
||
| **Todo (Kanban)** | `TodoOptionJson` | `TitleExpr` + `StatusExpr` |
|
||
|
||
Sekiz layout **aynı `SelectCommand` üzerinden** beslenir; ayrı sorgu, ayrı ekran, ayrı menü
|
||
gerekmez. Kullanıcı ekranın üstünden geçiş yapar, tercihi kullanıcı bazlı saklanır.
|
||
|
||
Bu, tek bir metadata kaydından DevExtreme'in `DataGrid`, `CardView`, `PivotGrid`, `Chart`,
|
||
`TreeList`, `Gantt` ve `Scheduler` bileşenlerinin tamamının sürülmesi demektir. **Todo layout
|
||
Kanban görünümüdür** ve diğerleriyle aynı veri hattından beslenir — kolonları `StatusExpr`
|
||
değerlerinden üretir, kart sürüklendiğinde ilgili kolonu günceller. Yani "aynı veriyi pano
|
||
olarak da görelim" isteği yeni bir ekran değil, `Todo: true` + `TodoOptionDto` demektir.
|
||
|
||
**Filtre, arama, düzenleme**
|
||
|
||
| Kolon | Ne yapar |
|
||
| --- | --- |
|
||
| `FilterRowJson` · `HeaderFilterJson` · `FilterPanelJson` · `SearchPanelJson` · `GroupPanelJson` | Filtreleme/arama/gruplama panelleri |
|
||
| `ExtraFilterJson` | Ekrana özel ek filtre araç çubuğu |
|
||
| `EditingOptionJson` | Düzenleme modu (row/cell/batch/form/popup), başlık, boyut, izinler |
|
||
| `EditingFormJson` | Düzenleme formunun grup/sekme düzeni |
|
||
| `CommandColumnJson` | Satır aksiyon sütunu |
|
||
| `PermissionJson` | Ekran yetkileri (create/read/update/delete/export/import/note) |
|
||
| `SubFormsJson` · `WidgetsJson` · `WorkflowJson` | Alt formlar, KPI kartları, onay akışı |
|
||
| `CustomJsSourcesJson` · `CustomStyleSourcesJson` | Sayfa yüklenince çalışan JS/CSS — **son çare** |
|
||
| `Width` · `Height` · `FullHeight` · `AdaptiveLayoutJson` | Boyut ve uyarlanabilir yerleşim |
|
||
| `ExportJson` | xlsx/csv/pdf dışa aktarma |
|
||
|
||
**`ListFormField` — alan başına ayarlar**
|
||
|
||
| Grup | Kolonlar |
|
||
| --- | --- |
|
||
| Bağlama | `FieldName`, `SourceDbType`, `JoinTableJson` (kolon başka tablodan geliyorsa) |
|
||
| Görünüm | `CaptionName`, `PlaceHolder`, `Visible`, `IsActive`, `Width`, `ListOrderNo`, `Alignment`, `Format`, `BandName` |
|
||
| Sıralama/filtre | `SortIndex`, `SortDirection`, `AllowSearch`, `ColumnFilterJson`, `ColumnHeaderJson`, `GroupingJson`, `ColumnCustomizationJson` |
|
||
| Özet | `TotalSummaryJson`, `GroupSummaryJson` |
|
||
| Düzenleme | `EditorType2`, `EditorOptions`, `EditorScript`, `EditOrderNo`, `EditGroupOrderNo`, `ColSpan`, `AllowEditing`, `AllowAdding`, `ValidationRuleJson` |
|
||
| Lookup | `LookupJson` |
|
||
| Biçimlendirme | `ColumnStylingJson`, `ColumnCssClass`, `ColumnCssValue` |
|
||
| Pivot | `PivotSettingsJson` |
|
||
| Yetki | `PermissionJson` — `Deny` (bool) + `C`/`R`/`U` (yetki **adları**) + `E`/`I` (bool). Ekran seviyesindeki yedi harfli sözleşmeyle karıştırma; `CanRead`/`CanCreate`/`CanUpdate`/`CanExport` istek başına hesaplanan `[NotMapped]` sonuçlardır (`lowcode-reference` §5.1) |
|
||
| Varyant | `UserId`, `RoleId`, `CultureName` |
|
||
|
||
> **Kural:** "Şunu da yapabilir miyiz?" sorusuna cevap ararken önce bu iki tabloya bak. Aradığın
|
||
> davranışın bir JSON kolonu varsa çözüm konfigürasyondur; `CustomJsSources` ve kod yazmak
|
||
> ondan sonra gelir. Bu kolonların **alan alan sözleşmesi**
|
||
> `lowcode-reference.instructions.md` içindedir: grid/pivot/chart blokları, düzenleme ve popup
|
||
> ayarları, SQL kancaları ve token'ları, sütun filtresi/özeti/join'i, yetki ve varyant kolonları.
|
||
|
||
### 4.9 Toolbar ve satır butonları — `CommandColumnJson`
|
||
|
||
Grid'in araç çubuğuna ve satır aksiyon sütununa **özel butonlar** eklenir. Süreç yönetiminin
|
||
(onaya gönder, iptal et, belge üret, dış sisteme aktar) kod yazmadan yapıldığı yer burasıdır.
|
||
|
||
`ListForm.CommandColumnJson` bir `CommandColumnDto[]` taşır:
|
||
|
||
```jsonc
|
||
[
|
||
{
|
||
"ButtonPosition": 1, // 0 = satır aksiyon sütunu · 1 = toolbar
|
||
"AuthName": "App.Hr.AdvanceRequests.Update", // yetki yoksa buton hiç çizilmez
|
||
"Text": "App.Hr.AdvanceRequests.SendToApproval", // dil anahtarı (başına :: eklenir)
|
||
"Hint": "App.Hr.AdvanceRequests.SendToApprovalHint",
|
||
"Icon": "check",
|
||
"IsVisible": true,
|
||
"VisibleExpression": "",
|
||
"Url": "",
|
||
"UrlTarget": "_blank",
|
||
"DialogName": "",
|
||
"DialogParameters": "",
|
||
"OnClick": ""
|
||
}
|
||
]
|
||
```
|
||
|
||
Bir butonun davranışı **üç moddan biridir** ve bu sırayla değerlendirilir:
|
||
|
||
| Mod | Dolu alan | Ne yapar |
|
||
| --- | --- | --- |
|
||
| **Adres** | `Url` (+ `UrlTarget`) | Adresi açar. `@FieldName` token'ları **seçili ilk satırın** kolon değerleriyle değiştirilir (`/admin/form/App.Hr.AdvanceRequests/@Id`). PWA modunda hedef `_self` olur. |
|
||
| **Diyalog** | `DialogName` + `DialogParameters` | Platformun **sabit diyalog listesinden** birini açar (aşağıya bak). `DialogParameters` bir JSON nesnesidir; `@Kolon` değerleri satırdan doldurulur (`{"requestId":"@Id","amount":"@Amount"}`). |
|
||
| **Script** | `OnClick` | Serbest JS (`eval`). Son çaredir; önce diğer iki mod denenir. Platform aksiyonları için §9.13. |
|
||
|
||
Kurallar:
|
||
|
||
- `AuthName` boş bırakılırsa buton **herkese görünür**. Süreç butonlarında bu alanı boş bırakma:
|
||
yetki kontrolünden geçmeyen buton hiç çizilmez, gizlemenin tek doğru yolu budur.
|
||
- `Text` ve `Hint` dil anahtarıdır; çeviri `::` öneki eklenerek çözülür. Düz metin yazma.
|
||
- Toolbar butonu **seçili satırlarla** çalışır; `SelectionJson` uygun modda (`single`/`multiple`)
|
||
olmalıdır. İş akışı varsa wizard bunu zaten `single` yapar.
|
||
- Satır bazlı aksiyon istiyorsan `ButtonPosition: 0` kullan; buton komut sütununda görünür.
|
||
- **`IsVisible` ve `VisibleExpression` yalnızca satır butonlarında (`ButtonPosition: 0`)
|
||
çalışır**; toolbar butonu her zaman görünür (görünürlüğü `AuthName` belirler).
|
||
`VisibleExpression` bir ok fonksiyonudur ve `eval` edilir:
|
||
`"(e) => e.row.data.Status === 'Taslak'"`.
|
||
- URL token'ları: satır butonunda `@LISTFORMCODE` ve **grid sütunu olarak tanımlı** her alan için
|
||
`@FieldName`; toolbar butonunda seçili ilk satırın tüm kolonları. Sütun listesinde olmayan bir
|
||
alan adı satır butonunda değiştirilmez.
|
||
- **Diyalog modu satır butonu içindir.** `@` ile başlayan parametreler satır verisinden okunur;
|
||
toolbar'da satır bağlamı olmadığı için yalnızca sabit parametrelerle çalışır. Satır butonunda
|
||
diyalog kapanınca grid otomatik yenilenir. `DialogParameters` boşsa diyalog hiç açılmaz.
|
||
- `DialogName` **kayıtlı bir custom component adı değildir**; platformda tanımlı şu diyalogları
|
||
açar: `RolesPermission`, `UsersPermission`, `TenantsConnectionString`,
|
||
`CreateTenantFromOrderDialog`, `CreateNotification`, `AuditLogDetail`, `BranchSeed`.
|
||
Kendi ekranını açmak istiyorsan **Adres** modunu kullan (custom component route'u ya da
|
||
`/admin/list/{ListFormCode}`).
|
||
- Onay akışının kendi butonları (Onayla/Reddet) `WorkflowJson` üzerinden **otomatik** gelir;
|
||
onları `CommandColumnJson` ile tekrar tanımlama.
|
||
|
||
Tipik süreç kalıbı: `Url` modunda bir Custom Endpoint'i ya da Dynamic Service'i çağıran adres
|
||
verilir, dönüşte liste yenilenir. İş mantığı SQL/serviste kalır; ekranda yalnızca buton tanımı
|
||
durur.
|
||
|
||
### 4.10 Wizard adım haritası — ekrandaki her adımın seed karşılığı
|
||
|
||
`/admin/listform/wizard` sihirbazı (kaynak: `wizard/Wizard.tsx` + `WizardStep*.tsx`) aşağıdaki
|
||
adımlardan geçer. Seed dosyası yazarken de aynı sırayla düşün: **her adım `Wizard` bloğunun bir
|
||
bölümünü doldurur.** Keşif turu (§0.6) soruları da bu adımlarla bire bir eşleşir.
|
||
|
||
| # | Adım | Görünme koşulu | Doldurduğu seed alanları |
|
||
| --- | --- | --- | --- |
|
||
| 0 | Komponent Tipi | her zaman | `ComponentKind` (`0` List · `1` Custom) |
|
||
| 1 | Custom Component | yalnız Custom yolu | `CustomComponentName`, `MenuUrl` (bileşenin `RoutePath`'i) |
|
||
| 2 | Menü | her zaman | `WizardName`, `CreateMenu`, `MenuParentCode`, `MenuCode`, `Language.MenuEn/Tr`, `PermissionGroupName` (+ görünen adlar), `MenuIcon`, `MenuOrder` |
|
||
| 3 | Ayarlar | yalnız List yolu | `ListFormCode`, `DataSourceCode` (+`DataSourceConnectionString`), `SelectCommandType`, `SelectCommand`, `KeyFieldName`, `KeyFieldDbSourceType`, `IsTenant/IsBranch/IsOrganizationUnit`, `AllowAdding/Updating/Deleting`, `ConfirmDelete`, `AllowDetail`, `DefaultLayout` + 8 görünüm bayrağı, `Language.TitleEn/Tr`, `Language.DescEn/Tr` |
|
||
| 4 | Alanlar | yalnız List | `Groups[]` (aşağıda) |
|
||
| 5 | Alt Formlar | yalnız List | `SubForms[]` |
|
||
| 6 | Widget'lar | yalnız List | `Widgets[]` |
|
||
| 7 | İş Akışı | yalnız List | `WorkflowDto` |
|
||
| 8–11 | Todo / Tree / Gantt / Scheduler | List + ilgili bayrak açıksa | `TodoOptionDto` / `TreeOptionDto` / `GanttOptionDto` / `SchedulerOptionDto` |
|
||
| 12 | Deploy | her zaman | — (özet + üretim; dosyaya alan yazmaz) |
|
||
|
||
Custom yolunda 3–11 adımları **hiç görünmez**; menü + yetki + dil + bileşen bağlama üretilir.
|
||
Bir görünüm bayrağı sonradan kapatılırsa aktif adım listeden düşer, sihirbaz en yakın geçerli
|
||
adıma döner.
|
||
|
||
**Adım 2 — Menü.** `WizardName` her yolda zorunludur. `CreateMenu` anahtarı kapatılırsa menü
|
||
alanlarının tamamı gizlenir ve menü/üst menü kaydı üretilmez (alt form ekranı kalıbı, §4.6).
|
||
Açıkken zorunlular: üst menü (ağaçtan seçilir ya da yeni kök tanımlanır), `MenuCode`, EN+TR menü
|
||
etiketi, izin grubu (listeden seçilir ya da yeni ad; görünen adları EN+TR yazılır), `MenuIcon`
|
||
(react-icons adı) ve `MenuOrder ≥ 1`. Custom yolunda ayrıca `MenuUrl` gösterilir — bileşenin
|
||
`RoutePath`'inden gelir, elle route açılmaz.
|
||
|
||
**Adım 3 — Ayarlar.** `SelectCommand` üç yoldan doldurulur: var olan tablo/view seçimi, **SQL
|
||
Table Designer** diyaloğu (yeni tablo; §8.1 varsayılan kolonlarıyla) ya da serbest sorgu/SP
|
||
(`SelectCommandType`'a göre). Aynı adımda **CRUD Endpoints** diyaloğu açılabilir (yetki:
|
||
`App.SqlQueryManager.CrudEndpoints`) — Custom Component'in veri kaynağı olacak tabloysa
|
||
endpoint'leri burada üret. `AllowDetail` için `KeyFieldName` şarttır.
|
||
|
||
**Adım 4 — Alanlar.** Sütunlar `GetTableColumnsAsync` ile tablodan gelir; "tümünü ekle" ya da
|
||
tek tek sürükle-bırak. Her grup `Caption` + `ColCount`, her alan `WizardColumnItemInputDto`
|
||
taşır. Sihirbazın kendi otomatikleri — dosya yazarken aynısını uygula:
|
||
|
||
| Otomatik | Kural |
|
||
| --- | --- |
|
||
| `EditorType` çıkarımı | `bit → dxCheckBox` · sayısal tipler → `dxNumberBox` · tarih tipleri → `dxDateBox` · gerisi → `dxTextBox` |
|
||
| Başlık | Kolon adı CamelCase'ten kelimelere ayrılır (`OrderNo` → "Order No"); TR/EN başlangıç değeri budur, dil anahtarı `App.Listform.ListformField.{Alan}` |
|
||
| `IsRequired` | Kolon `NOT NULL` ise `true` |
|
||
| Lookup sorgusu üreticisi | Tablo + anahtar kolon + ad kolonu seçilir → `SELECT "K" AS "Key", "N" AS "Name" FROM "Tablo" [WHERE "IsDeleted" = 0] ORDER BY "N";` — tabloda `IsDeleted` varsa filtre kendiliğinden eklenir |
|
||
| `ValueExpr`/`DisplayExpr` | `Key` / `Name` |
|
||
|
||
Alan kartındaki **Seçenekler** düğmesi Editor Options Builder'ı (§6), **Script** düğmesi Script
|
||
Builder'ı (§5) açar — çıktıları `EditorOptions`/`EditorScript` string'leridir.
|
||
|
||
**Adım 7 — İş Akışı.** Zorunlu ikili: `ApprovalUserFieldName` + `ApprovalStatusFieldName`;
|
||
opsiyonel `ApprovalDateFieldName`, `ApprovalDescriptionFieldName`, `ApprovalIsFilterUserName`
|
||
(liste yalnız sıradaki onaycıya düşsün), `ApprovalIsResetWorkflow` (kayıt değişince akış başa
|
||
dönsün). Onay düğümleri (`Criteria`) görsel akış tasarımcısında kurulur ve `ListFormWorkflow`
|
||
kayıtlarına gider (§4.7).
|
||
|
||
**Adım 12 — Deploy.** Sol tarafta tüm adımların özeti, sağda üretim log'u. Üretim sırası —
|
||
seed dosyası uygulanırken `WizardDataSeeder`'ın izlediği sıranın aynısıdır:
|
||
|
||
1. Konfigürasyon doğrulama
|
||
2. Menü kaydı (`CreateMenu: false` ise atlanır)
|
||
3. Dil metinleri
|
||
4. İzin grubu + yetkiler
|
||
5. List yolunda: veri kaynağı → ListForm → alan grupları · Custom yolunda: bileşen bağlama
|
||
6. Deploy + tamamlandı
|
||
|
||
Başarıda sihirbaz `/admin/list/{ListFormCode}`'a yönlendirir ve seed dosyası
|
||
`configs/seeds/{kapsam}/wizard/` altına yazılmış olur. **Düzenleme modu** Wizard Manager'dan
|
||
`EditFileName` ile açılır: aynı adımlar dolu gelir, kaydetmede önce eski dosya ve
|
||
`InsertedRecords` kayıtları silinir (§4.5).
|
||
|
||
---
|
||
|
||
## 5. Artefakt B — `EditorScript` (alan davranışı)
|
||
|
||
Bir alanın **davranışı** (hesapla, doldur, kilitle, sor, çağır) `EditorScript`; **görünümü ve
|
||
editör özellikleri** (§6) `EditorOptions`'tır. İkisi de ListForm alan kaydının birer metin
|
||
kolonudur ve ikisi de aynı kutudan düzenlenir:
|
||
|
||
| Dialog | Nerede açılır | Ne düzenler | Lehçe/sözlük |
|
||
| --- | --- | --- | --- |
|
||
| **Script Builder** (`ScriptBuilderDialog`) | Wizard → Alanlar adımı · ListForm editörü → Alan → **Seçenekler** sekmesi | `EditorScript` | `formScriptDialect.ts` + `scriptRecipes.ts` |
|
||
| **Editor Options Builder** (`EditorOptionsBuilderDialog`) | Wizard → Alanlar adımı · ListForm editörü → Alan → **Detay** sekmesi | `EditorOptions` | `optionSpecs.ts` + `presets.ts` |
|
||
| **Event Script Builder** (`DesignerScriptBuilderDialog`) | Visual Designer → JavaScript sekmesi | Custom component düğüm `events` script'i | `designerScriptDialect.ts` (§7.8) |
|
||
|
||
Script Builder'ın iki görünümü vardır ve **ikisi de aynı metni** üretir:
|
||
|
||
- **Kod** (varsayılan): Monaco editörü + lehçeye bağlı IntelliSense (alan adları yalnızca string
|
||
literali içinde önerilir), sağda arama yapılabilen **Snippet** ve **Runtime API** paneli.
|
||
TypeScript teşhisleri kapalıdır — script bir handler gövdesidir, tek başına `return`/`await`
|
||
geçerli görünmez.
|
||
- **Sihirbaz**: kural kartları. Her kart = bir tetikleyici + koşullar + bir tarif (recipe). Kart
|
||
altında o kuralın üreteceği satır canlı gösterilir; sağ panelde script'in tamamı.
|
||
|
||
### 5.1 Script metninin şekli ve gidiş-dönüş sözleşmesi
|
||
|
||
```
|
||
// @builder {"v":1,"rules":[ … ]} ← ilk satır: kuralların JSON'u
|
||
// @runOnOpen ← yalnızca bir kural open/both ise
|
||
<her kural için tek satır runtime çağrısı>
|
||
```
|
||
|
||
Dialog script'i regex ile çözmez; başlıktaki kurallardan script'i **yeniden üretip** metinle
|
||
karakter karakter karşılaştırır:
|
||
|
||
| Durum | Sonuç |
|
||
| --- | --- |
|
||
| Metin, kurallardan üretilenle birebir aynı | Sihirbaz kuralları açar (`inSync`) |
|
||
| Tetikleyici kavramından önce yazılmış script (`trigger: change` ile yeniden üretim tutuyorsa) | Kurallar geçerli sayılır, yeni varsayılanlarla yükseltilir |
|
||
| Metin farklı (elle düzenlenmiş) | Sihirbaz kuralları **ezmez**; sarı "kod ayrıştı" bandı çıkar. Kullanıcı bir kurala dokunana kadar kod olduğu gibi kalır; **Yeniden üret** düğmesi kodu kurallardan tazeler |
|
||
| `// @builder` başlığı yok | Sihirbaz boş açılır; kural eklenirse **elle yazılmış kod tamamen değişir** |
|
||
|
||
Bunun pratik karşılığı: **başlık ile gövdeyi ayrı ayrı düzenleme.** Seeder ile basılan script'in
|
||
dialogda açılabilir kalması, C# tarafının (§5.9) TS tarifleriyle birebir aynı metni üretmesine
|
||
bağlıdır.
|
||
|
||
### 5.2 Bir kuralın anatomisi
|
||
|
||
```
|
||
[tetikleyici] + [koşul 1 (and|or) koşul 2 …] + [tarif + parametreleri]
|
||
```
|
||
|
||
- **Koşullar** `and` ya da `or` ile birleşir (tek bağlaç, kural genelinde). Her koşul
|
||
`operatör + kaynak alan + değer` üçlüsüdür; birden fazlaysa parçalar parantezlenir.
|
||
- Koşul normalde `if (...)` sarmalayıcısına dönüşür. `readOnly` tarifinde ise
|
||
`conditionIsArgument` işaretlidir: koşul `if` olmaz, doğrudan aksiyonun argümanı olur —
|
||
koşul bozulduğunda alan yeniden açılabilsin diye (aksi halde bir kez kilitlenir, kalırdı).
|
||
- **Eksik kural üretilmez.** Zorunlu parametresi ya da koşul değeri boş kural script'e hiç
|
||
girmez; kartta "Eksik" rozeti çıkar. Bu, C# tarafında da aynıdır.
|
||
- Kural sırası çıktı sırasıdır; kartlardaki ok düğmeleri sırayı değiştirir. Aynı alana yazan iki
|
||
kural varsa **son kural kazanır**.
|
||
|
||
### 5.3 Tarifler (recipes) — üretilen satırın tamamı
|
||
|
||
| Grup | `recipe` | Parametreler | Ürettiği satır |
|
||
| --- | --- | --- | --- |
|
||
| calc | `multiply` | left, right, target, digits? | `set('T', round(num('A') * num('B'), 2))` |
|
||
| calc | `subtract` | left, right, target, digits? | `set('T', round(num('A') - num('B'), 2))` |
|
||
| calc | `percent` | amount, rate, target, digits?, mode? | `set('T', round(num('A') * num('R') / 100, 2))` · `mode=add` → `num('A') + …` · `mode=subtract` → `num('A') - …` |
|
||
| calc | `sum` | target, sources[], digits? | `set('T', round(sum('A', 'B'), 2))` |
|
||
| calc | `formula` | target, expression | `set('T', <ifade>)` — ifade ham JS'tir, runtime yardımcıları kullanılabilir |
|
||
| calc | `today` | target, offset? | `set('T', new Date().toISOString().slice(0, 10))` · offset varsa `new Date(Date.now() + n * 86400000)` |
|
||
| calc | `days` | start, end, target | `set('T', days('Bas', 'Bit'))` — bitiş günü **dahil** |
|
||
| calc | `hours` | start, end, target | `set('T', hours('Bas', 'Bit'))` — bitiş küçükse ertesi güne taşar |
|
||
| data | `copy` | source (seçili kayıttaki kolon/yol), target | `copy('unitPrice', 'UnitPrice')` |
|
||
| data | `setValue` | target, text | `set('T', tpl('{Musteri} - {value}'))` |
|
||
| data | `clear` | sources[] | `clear('Il', 'Ilce')` |
|
||
| view | `readOnly` | target, invert? | `readOnly('Alan', <koşul>)` · invert → `readOnly('Alan', !(<koşul>))` · koşulsuz → `readOnly('Alan', true)` |
|
||
| interaction | `notify` | message | `notify('Limit aşıldı')` — mesajda `{Alan}` şablonu geçerli |
|
||
| interaction | `ask` | message | `if (!ask('Emin misiniz?')) return` — vazgeçilirse alan eski değerine döner ve script durur |
|
||
| interaction | `openUrl` | url, target? | `openUrl('/report?id={Id}')` · `_self` verilirse ikinci argüman eklenir |
|
||
| integration | `apiToField` | target, url, path? | `set('T', await api('/api/x/{value}', 'data.name'))` |
|
||
| integration | `custom` | code | Satırın kendisi. Son çare; sondaki `;` atılır |
|
||
|
||
`sum` ve `clear` çoklu alan alır (`fields` listesi); diğerlerinde her parametre tek alandır.
|
||
Sayı bekleyen parametrelere sabit yazılabilir: `20` olduğu gibi, `KdvOrani` ise `num('KdvOrani')`
|
||
olarak geçer.
|
||
|
||
### 5.4 Koşul operatörleri
|
||
|
||
| Operatör | Kaynak | Değer | Üretilen ifade |
|
||
| --- | --- | --- | --- |
|
||
| `always` | — | — | (koşul yok) |
|
||
| `equals` | ✔ | ✔ | `str('A') === 'x'` |
|
||
| `notEquals` | ✔ | ✔ | `str('A') !== 'x'` |
|
||
| `contains` | ✔ | ✔ | `str('A').includes('x')` |
|
||
| `empty` | ✔ | — | `!get('A')` |
|
||
| `notEmpty` | ✔ | — | `!!get('A')` |
|
||
| `greaterThan` | ✔ | ✔ | `num('A') > 100` (değer alan adıysa tırnaklanır) |
|
||
| `lessThan` | ✔ | ✔ | `num('A') < 100` |
|
||
| `isTrue` | ✔ | — | `bool('A')` |
|
||
| `isFalse` | ✔ | — | `!bool('A')` |
|
||
|
||
Karşılaştırmalar **metin** üzerindendir (`equals`/`notEquals`/`contains`); sayısal karşılaştırma
|
||
istiyorsan `greaterThan`/`lessThan` kullan ya da `formula`/`custom` yaz.
|
||
|
||
### 5.5 Tetikleyiciler ve `isReady` guard'ları
|
||
|
||
| Tetikleyici | Ne zaman çalışır |
|
||
| --- | --- |
|
||
| `change` | Yalnızca alanın değeri değiştiğinde |
|
||
| `open` | Yalnızca form açılırken (varsayılan değer üretmek için) |
|
||
| `both` | İkisinde de |
|
||
|
||
Belirtilmezse **grup varsayılanı** geçerlidir: `calc`, `data`, `view` → `both`;
|
||
`interaction`, `integration` → `change`. Gerekçe: değer üreten kurallar form açılışında da
|
||
çalışmalıdır (türetilmiş alan boş kalmasın); uyarı gösteren ya da dış çağrı yapan kurallar
|
||
yalnızca değişimde çalışmalıdır (form her açıldığında uyarı çıkmasın, boşuna istek atılmasın).
|
||
|
||
Script'in **herhangi bir** kuralı açılışta çalışıyorsa metnin başına `// @runOnOpen` düşer ve
|
||
her satır kendi tetikleyicisine göre sarmalanır: `change` → `if (!isReady) …`,
|
||
`open` → `if (isReady) …`. Hiçbir kural açılışta çalışmıyorsa guard üretilmez, kod kısa kalır.
|
||
|
||
Açılışta çalışma güvenliği runtime tarafında korunur: değişmeyen değer yazılmaz
|
||
(`Object.is` kontrolü), yazmalar bir tick sonraya alınır ve aynı turda tekrar girilmez —
|
||
yoksa `contentReady` kendini besleyip sonsuz döngü olurdu.
|
||
|
||
### 5.6 Runtime API — script'in içinde ne varsa
|
||
|
||
Script `AsyncFunction` ile derlenir, aşağıdaki adlar parametre olarak enjekte edilir.
|
||
`await` serbesttir.
|
||
|
||
| İmza | Anlamı / davranışı |
|
||
| --- | --- |
|
||
| `value` · `field` | Değişen alanın değeri ve adı |
|
||
| `data` | Script'in üzerinde çalıştığı canlı form verisi (kopya) |
|
||
| `isReady` | `true` ise script form açılışında çalışıyor |
|
||
| `get('Alan')` | Form verisinden okur. **Nokta yolu** (`Musteri.Unvan`) ve **büyük/küçük harf duyarsız** eşleşme destekler (SQL kolon adları tutarsız gelebiliyor) |
|
||
| `num()` · `str()` · `bool()` | Tip dönüşümü. `bool` için `true`/`'true'`/`1`/`'1'` doğrudur |
|
||
| `dateOf('Alan')` | `Date` ya da `null` |
|
||
| `pick(nesne, 'a.b')` | Herhangi bir nesneden yol okuma |
|
||
| `set('Alan', deger)` · `set({A:1, B:2})` | Yazar. Değişmeyen değer yazılmaz; yazmalar biriktirilip **tek flush** ile forma ve düzenlenen grid hücresine uygulanır |
|
||
| `clear('A','B')` | Verilen alanları `null` yapar |
|
||
| `copy('kolon','Alan')` | Lookup/GridBox'ta **seçili kayıttan** taşır; hedef verilmezse yolun son parçası hedef olur |
|
||
| `selected('Yol')` · `snum()` · `sstr()` | Seçili kaydın tamamı / sayı / metin hâli |
|
||
| `readOnly('Alan', kosul)` | Formdaki **herhangi bir** editörü kilitler/açar (yalnızca script'in bağlı olduğu alanı değil) |
|
||
| `round(x, 2)` · `sum('A','B')` | Matematik |
|
||
| `days('Bas','Bit')` · `hours('Bas','Bit')` | Gün farkı (bitiş dahil) · saat farkı (negatifse +24) |
|
||
| `tpl('{Alan} - {value}')` | Şablon: `{Alan}`, `{value}`, `{selected.Yol}` |
|
||
| `notify('mesaj')` | `window.alert` (şablon çözülür) |
|
||
| `ask('mesaj')` | `window.confirm`; hayır denirse `revert()` çalışır ve `false` döner |
|
||
| `revert()` | Alanı değişiklik öncesi değerine döndürür (editöre de yazar) |
|
||
| `openUrl('/adres', '_blank')` | Yeni sekme/pencere |
|
||
| `await api(url, 'data.name', options?)` | Ham `fetch`. **Kimlik doğrulama başlığı eklemez**; korumalı bir uç için Custom Endpoint tarafında erişim açık olmalıdır |
|
||
| `$` | Tüm yardımcıların tek nesne hâli |
|
||
|
||
Eski (uzun) script'ler bozulmasın diye geriye dönük uyum vardır: script içinde yardımcı adlarıyla
|
||
çakışan bir tanım varsa (`const data = …` gibi) script yalnızca eski beş parametreyle
|
||
(`formData, e, editor, runtimeSetEditorReadOnly, setFormData`) derlenir ve **yardımcılar
|
||
tanımsız kalır**. Yardımcı adlarını yeniden tanımlama.
|
||
|
||
### 5.7 Yetenek sınırları — script ile yapılamayanlar
|
||
|
||
- **Alan gizleme yok.** Runtime'da `visible` yardımcısı yoktur; koşullu görünürlük yerine
|
||
`readOnly` kullan, kalıcı gizleme `EditorOptions.visible` ya da `IncludeInEditingForm`'dur.
|
||
- **Lookup sorgusu değiştirilemez**; cascade davranışı ListForm lookup ayarlarından kurulur.
|
||
- **Sunucu tarafı doğrulama değildir.** `ask`/`notify` yalnızca tarayıcıdadır; iş kuralı
|
||
zorunluysa SQL/endpoint tarafında da uygula.
|
||
- **Satırlar arası hesap yok**: script yalnızca düzenlenen kaydı ve seçili lookup kaydını görür.
|
||
- Script bir alana bağlıdır: `X` alanının script'i yalnızca `X` değiştiğinde (ve açılışta)
|
||
çalışır. Üç alandan beslenen bir toplam için ya her birine kural yaz ya da toplamı üreten
|
||
kuralı üç alanın script'ine de koy.
|
||
- Hata yönetimi: script hatası konsola yazılır, kullanıcıya gösterilmez ve form akışını kesmez.
|
||
|
||
### 5.8 Kod modunda çalışmak
|
||
|
||
Sihirbazın karşılamadığı bir şey için önce `formula`, sonra `custom` tarifini dene — bunlar
|
||
sihirbazı **korur**. Tamamen elle yazılan script sihirbazı kapatır. Yardım panelindeki hazır
|
||
kalıplar (koşullu yazma, açılış/değişim ayrımı, toplu `set`, seçili kayıttan doldurma, onay,
|
||
`try/catch`'li API çağrısı, alan kilitleme) doğrudan editöre eklenir.
|
||
|
||
```js
|
||
// Açılışta ve değişimde farklı davranış
|
||
if (isReady) {
|
||
set('Durum', str('Durum') || 'Taslak')
|
||
} else if (!ask('Fiyatı değiştirmek istediğinize emin misiniz?')) {
|
||
return
|
||
}
|
||
```
|
||
|
||
### 5.9 Seeder tarafı (C#) — tercih edilen yol
|
||
|
||
`api/src/Sozsoft.Platform.Domain.Shared/Editors/` — `scriptRecipes.ts` dosyasının portudur.
|
||
|
||
| Tarif | C# |
|
||
| --- | --- |
|
||
| multiply / subtract | `EditorScript.Multiply(left, right, target, digits)` · `Subtract(...)` |
|
||
| percent | `EditorScript.Percent(amount, rate, target, digits, EditorScriptPercentMode.Add\|Subtract\|Only)` |
|
||
| sum | `EditorScript.Sum(target, "A", "B")` |
|
||
| formula | `EditorScript.Formula(target, "num('Gross') * 0.18")` |
|
||
| today / days / hours | `Today(target, offsetDays)` · `Days(start, end, target)` · `Hours(start, end, target)` |
|
||
| copy / setValue / clear | `Copy(sourcePath, target)` · `SetValue(target, text)` · `Clear("Il", "Ilce")` |
|
||
| readOnly | `ReadOnly(target, invert)` |
|
||
| notify / ask / openUrl | `Notify(message)` · `Ask(message)` · `OpenUrl(url, target)` |
|
||
| apiToField / custom | `ApiToField(target, url, responsePath)` · `Custom(code)` |
|
||
|
||
Akıcı ekler: `.When(...)` (hepsi sağlanmalı), `.WhenAny(...)` (biri yeterli),
|
||
`.OnChange()` / `.OnOpen()` / `.OnOpenAndChange()` / `.WithTrigger(EditorScriptTrigger.X)`,
|
||
`.WithId("multiply_1")` (verilmezse tarif adı + sıra numarasından üretilir).
|
||
|
||
Koşullar: `EditorScriptCondition.Is / IsNot / Contains / IsEmpty / IsNotEmpty / GreaterThan /
|
||
LessThan / IsTrue / IsFalse / Always`.
|
||
|
||
```csharp
|
||
EditorScript = EditorScript.Build(
|
||
EditorScript.Multiply("Quantity", "UnitPrice", "Total", digits: 2),
|
||
EditorScript.Percent("Total", "VatRate", "VatAmount", 2, EditorScriptPercentMode.Add)
|
||
.When(EditorScriptCondition.IsNotEmpty("VatRate")),
|
||
EditorScript.ReadOnly("Total"),
|
||
EditorScript.Today("OrderDate").OnOpen(),
|
||
EditorScript.Copy("Customer.TaxNumber", "TaxNumber"),
|
||
EditorScript.Ask("Fiyatı değiştirmek istediğinize emin misiniz?")
|
||
.When(EditorScriptCondition.GreaterThan("UnitPrice", "1000")));
|
||
```
|
||
|
||
Tek kurallı script için `EditorScriptRule` doğrudan `string`'e dönüşür:
|
||
|
||
```csharp
|
||
EditorScript = EditorScript.Days("StartDate", "EndDate", "DayCount");
|
||
```
|
||
|
||
> **Eşleme kuralı:** `scriptRecipes.ts` ile bu sınıflar aynı çıktıyı üretmek zorundadır. Birinde
|
||
> bir tarif değişirse aynı görevde diğeri de değişir; aksi hâlde seeder ile basılan script
|
||
> dialogda "elle düzenlenmiş" sayılır ve kural editörü kapanır.
|
||
|
||
### 5.10 Senaryo → kural dizisi
|
||
|
||
| İstek | Kurallar (hangi alana) |
|
||
| --- | --- |
|
||
| "Miktar × fiyat toplamı, KDV dahil" | `Quantity`+`UnitPrice` → `multiply(Total)`; `Total` → `percent(mode=add)`; `Total` → `readOnly` |
|
||
| "Müşteri seçilince vergi no ve adres dolsun" | `CustomerId` → `copy('taxNumber','TaxNumber')`, `copy('address','Address')` |
|
||
| "İl değişince ilçe boşalsın" | `City` → `clear(District)` |
|
||
| "Kapalı kayıtta hiçbir şey düzenlenemesin" | Her alanda `readOnly` + koşul `Status equals Kapali` — ya da tek alanda birden çok `readOnly` kuralı |
|
||
| "Talep tarihi bugün gelsin, geçmiş tarih seçilemesin" | `RequestDate` → `today` (`OnOpen`) + `EditorOptions` `min` (§6) |
|
||
| "Tutar 1000'i geçerse onay istensin" | `Amount` → `ask` + koşul `greaterThan 1000` |
|
||
| "Vergi no girilince unvan servisten gelsin" | `TaxNumber` → `apiToField(target=Title, path=data.name)` + koşul `notEmpty` |
|
||
| "İzin gün sayısı otomatik" | `EndDate` → `days(StartDate, EndDate, DayCount)`; `DayCount` → `readOnly` |
|
||
|
||
---
|
||
|
||
## 6. Artefakt C — `EditorOptions`
|
||
|
||
DevExtreme editörüne (ve platformun kendi editörlerine) geçirilen JSON. UI sözlüğü
|
||
`optionSpecs.ts`, hazır kalıplar `presets.ts`, C# tarafı `EditorOptions` + `EditorOptionsBuilder`.
|
||
|
||
### 6.1 Dialog nasıl çalışır
|
||
|
||
- **Ayarlar** sekmesi tamamen `optionSpecs.ts` sözlüğünden üretilir; alanlar gruplara ayrılır,
|
||
arama kutusu yol ve etiket üzerinde çalışır.
|
||
- Alanlar **kolonun `EditorType2` değerine göre süzülür**. "Tüm ayarlar" kutusu süzgeci kapatır;
|
||
"Yalnızca dolu olanlar" yalnızca yazılmış ayarları gösterir. Zaten değer yazılmış bir ayar,
|
||
editöre uymasa bile gizlenmez ("kaydettim ama göremiyorum" durumu oluşmasın diye).
|
||
- **Boolean alanlar üç durumludur**: tanımsız / `true` / `false`. Temizlemek anahtarı JSON'dan
|
||
tamamen siler; geride kalan boş ara nesneler (`{"format":{}}`) de temizlenir.
|
||
- Bir ayar sözlükteki tipiyle uyuşmayan bir değer taşıyorsa alan **kilitlenir** ve uyarı gösterir;
|
||
düzeltme Ham JSON sekmesinde yapılır.
|
||
- **Sözlük dışı ayarlar** bölümü: sözlükte olmayan her yol burada listelenir ve
|
||
`path.to.option` + tip (`string` / `number` / `boolean` / `json`) + değer ile **yeni ayar
|
||
eklenebilir**. Yani DevExtreme'in sözlükte olmayan herhangi bir seçeneği de buradan yazılır —
|
||
sözlük bir sınır değil, bir kısayoldur.
|
||
- **Ham JSON** sekmesi metnin tamamını düzenler. JSON bozukken Ayarlar sekmesi ve Kaydet
|
||
düğmesi kilitlidir; bozuk metin sessizce silinmez.
|
||
- Kaydedilen değer **tek satır** JSON'dur; hiç ayar yoksa boş metindir.
|
||
|
||
### 6.2 Yol yazımı ve tipler
|
||
|
||
| Tip | Girdi | JSON'a yazılan |
|
||
| --- | --- | --- |
|
||
| `boolean` | üç durumlu seçim | `true` / `false` / (yok) |
|
||
| `number` | sayı | `12` |
|
||
| `text` | metin | `"dd/MM/yyyy"` |
|
||
| `select` | listeden seçim (bilinmeyen mevcut değer korunur) | seçilen değer |
|
||
| `size` | sayı ya da CSS | `200` veya `"100%"` |
|
||
| `stringList` | virgülle ayrılmış | `["key","name"]` |
|
||
| `json` | ham JSON | nesne/dizi |
|
||
|
||
İç içe ayar noktayla yazılır: `format.precision` → `{"format":{"precision":2}}`.
|
||
Dizi elemanları yaprak sayılmaz; `toolbar.items` tek bir `json` alanıdır.
|
||
|
||
### 6.3 Ayar sözlüğü — gruplar
|
||
|
||
| Grup | Anahtarlar | Hangi editörlerde |
|
||
| --- | --- | --- |
|
||
| Genel | `readOnly`, `disabled`, `visible`, `hint`, `tabIndex`, `valueChangeEvent`, `validationMessageMode`, `validationMessagePosition`, `elementAttr.class`, `inputAttr.style`, `inputAttr.aria-label` | hepsi |
|
||
| Görünüm | `width`, `height`, `placeholder`, `label`, `labelMode`, `stylingMode`, `showClearButton` | hepsi (giriş kutusu olmayanlarda `placeholder`/`showClearButton` anlamsız) |
|
||
| Metin | `mode`, `maxLength`, `mask`, `maskChar`, `maskRules.X`, `maskInvalidMessage`, `showMaskMode`, `useMaskedValue`, `spellcheck`, `autoResizeEnabled`, `minHeight`, `maxHeight` | `dxTextBox`, `dxTextArea`, `dxAutocomplete` |
|
||
| Sayı | `min`, `max`, `step`, `showSpinButtons`, `useLargeSpinButtons`, `format.type`, `format.precision`, `format.currency`, `invalidValueMessage`, `useMaskBehavior` | `dxNumberBox` |
|
||
| Tarih | `type`, `pickerType`, `displayFormat`, `dateSerializationFormat`, `interval`, `min`, `max`, `openOnFieldClick`, `applyValueMode`, `invalidDateMessage`, `calendarOptions.firstDayOfWeek`, `calendarOptions.zoomLevel` | `dxDateBox`, `dxCalendar`, `dxDateRangeBox` |
|
||
| Açılır liste | `searchEnabled`\*, `searchMode`, `searchExpr`, `searchTimeout`, `minSearchLength`, `showDataBeforeSearch`, `acceptCustomValue`\*, `noDataText`, `deferRendering`, `wrapItemText`, `dropDownOptions.width/height/hideOnOutsideClick` | `dxSelectBox`, `dxLookup`, `dxTagBox`, `dxDropDownBox`, `dxGridBox`, `dxAutocomplete` |
|
||
| TagBox | `showSelectionControls`\*, `maxDisplayedTags`\*, `showMultiTagOnly`\*, `applyValueMode`\*, `multiline`, `hideSelectedItems` | `dxTagBox` |
|
||
| GridBox | `columns`\*, `selectionMode`\*, `filterRowVisible`\* | `dxGridBox` |
|
||
| Görsel | `uploadUrl`\*, `fileFieldName`, `accept`\*, `multiple`\*, `maxFileSize`\* | `dxImageUpload`, `dxImageViewer` |
|
||
| Seçim | `text` (`dxCheckBox`), `switchedOnText`/`switchedOffText` (`dxSwitch`), `layout` (`dxRadioGroup`), `editAlphaChannel`/`keyStep` (`dxColorBox`) | ilgili editör |
|
||
| Kaydırıcı | `min`, `max`, `step`, `tooltip.enabled`, `tooltip.showMode`, `showRange` | `dxSlider`, `dxRangeSlider` |
|
||
| HTML | `valueType`, `toolbar.multiline`, `toolbar.items`, `mediaResizing.enabled`, `imageUpload.fileUploadMode`, `imageUpload.uploadUrl` | `dxHtmlEditor` |
|
||
| Liste sütunu | `format` (metin hâli), `encodeHtml`, `buttons` | hepsi — bu üçü **grid sütununu** da etkiler |
|
||
|
||
`\*` işaretli anahtarlar backend'de tipli DTO'ya (`GridBoxOptionsDto`, `TagBoxOptionsDto`,
|
||
`ImageUploadOptionsDto`) çözülür; yanlış tipte yazılan değer hata vermez, **sessizce yok sayılır**.
|
||
`columns` dizi, `maxDisplayedTags` sayı, `multiple` boolean olmalıdır.
|
||
|
||
### 6.4 Hazır kalıplar (preset)
|
||
|
||
Preset mevcut JSON ile **birleşir**, diğer ayarları silmez; aynı anahtar varsa üzerine yazar.
|
||
|
||
| Preset | Ürettiği |
|
||
| --- | --- |
|
||
| Salt okunur / Pasif | `{"readOnly":true}` · `{"disabled":true}` |
|
||
| Sağa hizalı | `{"inputAttr":{"style":"text-align: right"}}` |
|
||
| 2 ondalık · Artırmalı sayı · Para | `format.fixedPoint(2)` · + `useMaskBehavior` + `showSpinButtons` · `format.currency TRY` |
|
||
| Tarih · Tarih-saat · Saat | `type`+`displayFormat`+`dateSerializationFormat` üçlüsü (saatte ayrıca `pickerType:list`, `interval`, `width:100%`) |
|
||
| Telefon maskesi | `mask`, `maskRules.X`, `maskInvalidMessage`, `placeholder` |
|
||
| Otomatik büyüyen metin | `autoResizeEnabled`, `minHeight`, `maxHeight` |
|
||
| Aranabilir liste | `searchEnabled`, `searchMode:contains`, `searchTimeout`, `showClearButton` |
|
||
| TagBox anında seçim | `showSelectionControls`, `applyValueMode:instantly`, `searchEnabled` |
|
||
| GridBox tek/çok seçim | `columns`, `selectionMode`, `filterRowVisible`, `height`, `width` |
|
||
| Çoklu görsel | `width`, `height`, `multiple`, `accept:image/*` |
|
||
| HTML tam araç çubuğu | `toolbar.items` (tam liste), `toolbar.multiline`, `mediaResizing`, `imageUpload` |
|
||
| Yükseklik 100 / 200 | `{"height":100}` · `{"height":200}` |
|
||
|
||
### 6.5 Seeder tarafı (C#)
|
||
|
||
| İhtiyaç | C# | Ürettiği JSON (özet) |
|
||
| --- | --- | --- |
|
||
| Pasif / salt okunur | `EditorOptions.Disabled()` · `ReadOnly()` | `{"disabled":true}` · `{"readOnly":true}` |
|
||
| Temizle düğmesi | `EditorOptions.ShowClearButton()` | `{"showClearButton":true}` |
|
||
| Çok satırlı | `EditorOptions.Multiline(80)` | `{"height":80}` |
|
||
| Sabit ondalık | `EditorOptions.Number(2)` | format + maske davranışı + spin |
|
||
| Yüzde | `EditorOptions.Percent()` | |
|
||
| Tarih / tarih-saat / saat | `EditorOptions.Date()` · `DateTime()` · `Time(15)` | |
|
||
| Telefon | `EditorOptions.Phone()` | global format + maske + örnek |
|
||
| Kaydırıcı | `EditorOptions.Slider()` | `{"tooltip":{"enabled":true}}` |
|
||
| Görsel yükleme | `EditorOptions.ImageUpload(multiple: true, 80, 80)` | |
|
||
| Zengin metin | `EditorOptions.Html(240)` | tam araç çubuğu + `encodeHtml` |
|
||
|
||
Akıcı ekler: `.Placeholder()`, `.MaxLength()`, `.Height(int\|string)`, `.Width(int\|string)`,
|
||
`.Format()`, `.FixedPointFormat(p)`, `.DisplayFormat()`, `.DateSerializationFormat()`,
|
||
`.Mask(mask, invalidMessage, useMaskedValue)`, `.ShowSpinButtons()`, `.UseMaskBehavior()`,
|
||
`.Disabled()`, `.ReadOnly()`, `.ShowClearButton()` ve tip bazlı genel yazıcılar:
|
||
`.Text(key, value)`, `.Number(key, value)`, `.Flag(key, value)`, `.Raw(key, json)`.
|
||
|
||
```csharp
|
||
EditorOptions = EditorOptions.Number(4).ShowClearButton(),
|
||
EditorOptions = EditorOptions.Multiline(60).Placeholder("Açıklama"),
|
||
EditorOptions = EditorOptions.New().Flag("acceptCustomValue", true),
|
||
EditorOptions = EditorOptions.New().Raw("columns", "[\"key\",\"name\"]").Text("selectionMode", "single"),
|
||
```
|
||
|
||
Kurallar:
|
||
|
||
- Aynı anahtar iki kez verilirse sonuncusu geçerlidir; anahtar sırası korunur.
|
||
- `.Raw` değeri **geçerli JSON** olmalıdır; doğrulanmaz.
|
||
- Hiç seçenek yoksa `Build()` boş metin döner — alan `null` kalır, `{}` yazılmaz.
|
||
|
||
### 6.6 `EditorScript` mi `EditorOptions` mı?
|
||
|
||
| İhtiyaç | Yer |
|
||
| --- | --- |
|
||
| Alan her koşulda kilitli / gizli | `EditorOptions`: `readOnly`, `visible` |
|
||
| Alan **koşula göre** kilitli | `EditorScript`: `readOnly` tarifi |
|
||
| Biçim, maske, ondalık, tarih formatı, arama, açılır liste davranışı | `EditorOptions` |
|
||
| Değer üretme, kopyalama, temizleme, uyarı, servis çağrısı | `EditorScript` |
|
||
| Alt/üst sınır (tarih/sayı) | `EditorOptions` `min`/`max` — script ile doğrulama yerine editörle engelle |
|
||
|
||
---
|
||
|
||
## 7. Artefakt D — Custom Component (Visual Designer)
|
||
|
||
ListForm ile ifade edilemeyen ekranın yolu **fiziksel React sayfası değil**, tasarımcıda
|
||
üretilen bir custom component'tir.
|
||
|
||
### 7.1 Seed dosyası
|
||
|
||
`configs/seeds/{kapsam}/custom/{Name}.json`
|
||
|
||
```jsonc
|
||
{
|
||
"GeneratedAt": "2026-08-20T11:23:45Z",
|
||
"CustomComponents": [
|
||
{
|
||
"Name": "OrderBoard",
|
||
"RoutePath": "/admin/order-board",
|
||
"Code": "/*__SOZSOFT_VISUAL_DESIGNER__<url-encoded designer doc>__*/\nconst OrderBoard = () => { … }\n\nexport default OrderBoard",
|
||
"Props": "{\"visualDesigner\":{ … }}",
|
||
"Description": "",
|
||
"IsActive": true,
|
||
"Dependencies": ["OrderCard"],
|
||
"DataSources": [
|
||
{
|
||
"Name": "Mrp_T_Order — GetList",
|
||
"Method": "GET",
|
||
"Url": "/api/app/crudendpoint/Mrp_T_Order",
|
||
"ResponsePath": null,
|
||
"EntityName": "Mrp_T_Order",
|
||
"OperationType": "GetList",
|
||
"SeedFile": "crud/Mrp_T_Order.json"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
- `DataSources` elle yazılmaz: kaydetme sırasında `CustomComponentDataSourceResolver`
|
||
tasarımcı dokümanındaki `dataSources` listesini `method + path` ile CRUD endpoint kataloğunda
|
||
arayarak üretir. Eşleşenler `EntityName` + `SeedFile` taşır; elle yazılmış endpoint'ler listede
|
||
kalır ama bu alanları boş gelir.
|
||
- `Dependencies`, kanvasa bırakılan diğer custom component'lerin adlarıdır.
|
||
- `OperationType` değerleri: `GetList`, `GetById`, `Create`, `Update`, `Delete` (§9.1 ile aynı küme).
|
||
|
||
### 7.1.1 İki mod: hangisini yazacaksın?
|
||
|
||
**Runtime `Code` alanını her zaman kullanmaz.** `getComponentRuntimeCode` şu kararı verir:
|
||
|
||
```
|
||
Props.visualDesigner varsa && version === 1 && sourceMode === 'visual' && nodes bir dizi ise
|
||
→ kod tasarımcı dokümanından YENİDEN ÜRETİLİR (generateDesignerCode), Code alanı yok sayılır
|
||
aksi hâlde
|
||
→ Code alanı olduğu gibi Babel ile derlenir
|
||
```
|
||
|
||
Bu, prompt ile üretim için belirleyicidir:
|
||
|
||
| Mod | Ne yazarsın | Ne zaman |
|
||
| --- | --- | --- |
|
||
| **Visual** (`sourceMode: "visual"`) | Yalnızca `Props.visualDesigner` dokümanı. `Code` alanı okunmaz; marker'lı bir yer tutucu bırakılır. | Ekran, tasarımcının bileşenleriyle ifade edilebiliyorsa. Kullanıcı sonradan kanvastan düzenleyebilir. **Varsayılan tercih.** |
|
||
| **Code** (`Props: null`) | Doğrudan JSX. Tasarımcı kanvası olmaz. | Tasarımcı sözlüğünün karşılamadığı yerleşim/etkileşim gerekiyorsa. |
|
||
|
||
Visual modda `Code` alanına şunu yaz — hem marker'ı taşır (tasarımcı dosyayı açabilsin) hem de
|
||
derlense bile zararsızdır:
|
||
|
||
```
|
||
/*__SOZSOFT_VISUAL_DESIGNER__<encodeURIComponent(JSON.stringify(document))>__*/
|
||
const {Name} = () => {
|
||
return (
|
||
<>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default {Name}
|
||
```
|
||
|
||
Marker'ın içi `Props.visualDesigner` ile **aynı dokümanın** URL-encode edilmiş hâlidir. Tutarsız
|
||
kalırsa tasarımcı `Props`'u esas alır; yine de ikisini eşit tut.
|
||
|
||
### 7.1.2 Code modunda çalışma ortamı
|
||
|
||
Derlenen kod paylaşılan bir fonksiyon kapsamında çalışır. **`import` satırları temizlenir** —
|
||
ihtiyacın olan her şey kapsamdan gelir:
|
||
|
||
| Kapsamdaki ad | Ne |
|
||
| --- | --- |
|
||
| `React` | React'in tamamı (`React.useState`, `React.useEffect`…) |
|
||
| `components/ui` dışa aktarımlarının **tamamı** | `Button`, `Input`, `Select`, `Dialog`, `Card`, `Notification`, `toast`, … (yeni bir UI bileşeni eklendiğinde otomatik gelir) |
|
||
| `UiKit` | Aynı kitin isim uzayı hâli |
|
||
| `PlatformViewHost` | Bir ListForm ekranını gömmek için |
|
||
| `apiService` | Kimlik doğrulamalı HTTP istemcisi — **tercih edilen yol** |
|
||
| `axios` | Ham HTTP (kimlik doğrulama eklemez) |
|
||
| `DOMPurify` | HTML temizleme |
|
||
| `translate(key)` | Dil anahtarı çözümü |
|
||
| `checkPermission(name)` | Yetki kontrolü |
|
||
| `getCulture()` | Aktif dil |
|
||
|
||
Kurallar:
|
||
|
||
- `export default {ComponentName}` zorunludur; ad dosya/kayıt adıyla birebir aynı olmalıdır.
|
||
- Kullanıcıya görünen metni gömme; `translate('App.…')` kullan ve dil anahtarını wizard
|
||
dosyasında ya da `LanguagesData.json`'da tanımla.
|
||
- Veriye erişimde `apiService` kullan; taban adres, token ve tenant başlığı ondan gelir.
|
||
Örneklerde gördüğün `axios.create({ baseURL: 'https://localhost:44344/' })` kalıbı **eski bir
|
||
kayıttır, taklit etme**.
|
||
- Yetkiyle gizlenecek her blok `checkPermission('App.…')` ile sarılır. Custom component
|
||
route'u `authority: []` ile üretilir (§7.6 uyarısı) — kontrol bileşenin içindedir.
|
||
- Başka bir custom component'i JSX olarak kullanabilirsin; adını `Dependencies` listesine ekle.
|
||
|
||
### 7.2 Tasarımcı dokümanı (`Props.visualDesigner`)
|
||
|
||
```ts
|
||
DesignerDocument = {
|
||
version: 1
|
||
sourceMode: 'visual' | 'code' // 'code' tek yönlüdür, kanvasa dönülmez
|
||
nodes: DesignerNode[]
|
||
canvas: { width: 'responsive' | 'desktop' | 'tablet' | 'mobile' }
|
||
lifecycle: { onMount: string } // sayfa açılışında çalışan script (§7.8)
|
||
dataSources: DesignerDataSource[]
|
||
permissionCode?: string // Wizard'ın bu bileşen için ürettiği okuma yetkisi
|
||
}
|
||
|
||
DesignerNode = {
|
||
id: string // 'cmp_…' — opak
|
||
type: string // 'Form', 'Grid', 'Input', 'ListView', 'FlexRow' …
|
||
kind: 'html' | 'ui' | 'layout' | 'platform' | 'custom'
|
||
slot?: string
|
||
ref?: string // 'btnSave' — script'ler birbirine bununla erişir
|
||
props: Record<string, unknown>
|
||
events: Record<string, string> // olay adı → script (§7.8)
|
||
bindings: Record<string, DesignerBinding>
|
||
children: DesignerNode[]
|
||
}
|
||
|
||
DesignerBinding = {
|
||
sourceId: string // 'source_…' (data source) ya da 'cmp_…' (kapsayıcı Form kaydı)
|
||
path: string // okunacak kolon
|
||
labelPath?: string
|
||
valuePath?: string
|
||
columns?: string[] // ekranda görünmeyen ek kolonlar; refs.<ad>.getColumn('x') ile okunur
|
||
}
|
||
|
||
DesignerDataSource = {
|
||
id: string
|
||
name: string
|
||
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||
url: string
|
||
responsePath: string // yanıt içindeki liste yolu; boşsa yanıtın kendisi
|
||
filters?: DesignerDataSourceFilter[]
|
||
}
|
||
|
||
DesignerDataSourceFilter = {
|
||
id: string
|
||
field: string
|
||
operator: 'eq'|'ne'|'contains'|'startswith'|'endswith'|'gt'|'gte'|'lt'|'lte'|'in'|'isnull'|'notnull'
|
||
source: 'static' | 'query' | 'route' | 'record'
|
||
value: string // static → değer · query/route → URL parametre adı · record → <formRef>.<kolon>
|
||
previewValue?: string // yalnızca tasarımcı önizlemesi; üretilen koda girmez
|
||
required?: boolean // true ise değer yoksa istek hiç atılmaz
|
||
}
|
||
```
|
||
|
||
Filtre → query string sözleşmesi: `eq` çıplak parametredir (`?RoleId=5`), diğerleri adını son ek
|
||
olarak taşır (`?Name.contains=abc`). Bu, CrudEndpoint `GetList`'in okuduğu sözleşmedir.
|
||
|
||
> `required: true` varsayılandır ve bilinçlidir: değeri henüz gelmemiş bir filtre isteği
|
||
> **tutmalıdır**; filtresiz koleksiyonu yüklemek "filtre çalışmıyor" gibi okunur.
|
||
|
||
### 7.3 Toolbox
|
||
|
||
| Aile (`toolboxGroup`) | Bileşenler |
|
||
| --- | --- |
|
||
| `layout` | `PageContainer` (maxWidth, padding, gap, className), `FlexRow` (columns, firstColumnWidth, gap, wrap, align, className), `Table` |
|
||
| `data` | `Form` (dört CRUD endpoint'ini sahiplenen kapsayıcı) + veri bağlanabilen bileşenler: `Select`, `AutoComplete`, `Dropdown`, `Menu`, `Pagination`, `Radio.Group`, `Tabs`, `Grid` |
|
||
| `platform` | `ListView`, `DataGridView`, `TreeView`, `GanttView`, `TodoBoard`, `CardView`, `SchedulerView`, `PivotView`, `ChartView` — her biri `listFormCode` ile bir ListForm ekranını gömer |
|
||
| `html` | `div`, `p`, `span`, `h1`–`h5`, `img` — yalnız bu dokuz etiket (`input`/`button`/`textarea`/`select`/`checkbox` kutuda yok, `ui` karşılıkları kullanılır) |
|
||
| `ui` | `components/ui` bileşenleri (`Spacer` dâhil) — sözleşmeleri `componentProps.json` metadata'sından okunur |
|
||
| `custom` | Diğer custom component'ler |
|
||
|
||
**Önce yeniden kullan:** istenen şey zaten bir ListForm ekranının verdiği liste/ağaç/grafikse,
|
||
onu primitiflerden yeniden kurma — ilgili `platform` düğümünü `listFormCode` ile bırak.
|
||
|
||
> **Önemli — eksik olan eklenir, yeni bileşen yazılmaz.** İstenen komponent Developer Kit
|
||
> araçlarıyla (toolbox + konfigürasyon) büyük ölçüde — diyelim %70 — kuruluyorsa geri kalanı
|
||
> "yapılamaz" ya da sessizce atlanacak bir şey değildir; ama çözüm **yeni bir bileşen / aile /
|
||
> Developer Kit ekranı üretmek de değildir**. Kullanılan **var olan bileşene küçük eklemeler**
|
||
> yapılır ve komponent tamamlanır. Eklenebilecekler: eksik bir **olay** (`onBlur`, `onKeyDown`,
|
||
> `onDoubleClick`…), eksik bir **prop** (`maxLength`, `prefix`, `readOnly`, `variant` değeri,
|
||
> `size`…) ya da bileşenin var olan davranışını bozmadan eklenen **küçük bir yetenek**
|
||
> (ikon slotu, ek görünüm seçeneği, `ref` API'sine yeni metot). Geliştirme zinciri:
|
||
>
|
||
> | Durum | Yapılacak (hepsi mevcut dosyalarda, yeni bileşen dosyası yok) |
|
||
> | --- | --- |
|
||
> | `components/ui/{Name}` olayı/prop'u zaten taşıyor, panel göstermiyor | Olaysa: bileşen `DESIGNER_PRIMARY_EVENTS` kaydındaysa panel **yalnız o dizideki** olayları gösterir → `catalog.ts`'te diziye olay adını ekle (isteğe bağlı `DESIGNER_EVENT_SNIPPETS` başlangıç script'i). Prop'sa önce filtrelere bak: `HIDDEN_GENERATED_PROPERTIES` (`field`, `form`) ve `catalog.ts`'teki bileşene özel elemeler (`Select.items`, `Menu.variant`, `Table.columns/data`, `Checkbox.defaultChecked`); filtrede değilse `componentProps.json` girdisi eksiktir, tamamla |
|
||
> | `components/ui/{Name}` olayı/prop'u hiç taşımıyor | Aynı bileşene `on*` prop'unu ya da yeni prop'u ekle (varsayılanı mevcut davranışı koruyacak şekilde) → `generated/componentProps.json`'daki bileşen girdisine **elle** ekle (`type: "function"` olay için; dosya otomatik üretilmez, README) → olaysa adını `DESIGNER_PRIMARY_EVENTS`'e; değer kümesi metadata'da tip adıyla geliyorsa `NAMED_UNION_OPTIONS` (anahtar **tsType**), tek bileşene özel varsayılan/seçenek gerekiyorsa `PROPERTY_OVERRIDES` (anahtar **bileşen → prop**). Kod üreticisi (`codeGenerator.ts`) prop'u JSX niteliği olarak basar, olay için `handle_{düğüm}_{olay}` handler'ını `node.events`'ten kendisi üretir |
|
||
> | Script Builder'da da kullanılacak | `designerScriptDialect.ts` / `designerScriptRecipes.ts`'te olay `cancelable` mı, hangi tarifler listelenecek — ekle; `ref` API'sine yeni metot eklediysen §7.8 tablosu |
|
||
> | Sonrasında | `npm run typecheck` + `npm run lint`; `lowcode-reference.instructions.md` §10.9 prop tablosu / §10.10 olay tablosu güncellenir |
|
||
>
|
||
> **Sınır — bunlar yapılmaz:** yeni `ui`/`data`/`layout` bileşeni, yeni toolbox ailesi,
|
||
> `EXCLUDED_COMPONENTS`'ten bileşen çıkarma, Developer Kit menüsüne yeni ekran/komponent,
|
||
> bileşenin mevcut davranışını/varsayılanını değiştiren ekleme (geriye dönük uyumluluk şart —
|
||
> var olan custom component'ler aynı çıktıyı vermeye devam etmeli). Ekleme "küçük" değilse
|
||
> (bileşeni yeniden yazmak, ikinci bir bileşen gibi davranmasını sağlamak) bu çerçeve dışıdır
|
||
> ve §0.8'e göre sorulur.
|
||
>
|
||
> İhtiyaç görsel davranış değil de veri/aksiyonsa Custom Endpoint (§9.2), iş mantığıysa
|
||
> Dynamic Service (§9.3, §10.6) her zaman açıktır.
|
||
>
|
||
> Keşif turu tarifinde (§0.6.6) hangi bileşene ne ekleneceği açıkça yazılır ve tarifle
|
||
> birlikte onaylatılır; bu **çerçeve içi** bir genişletmedir (§0.8).
|
||
|
||
### 7.3.1 Çalışan minimal doküman — bir Form + gömülü liste
|
||
|
||
Aşağıdaki `Props` değeri (JSON string olarak yazılır) tek başına çalışır: üstte bir CRUD formu,
|
||
altında var olan bir ListForm ekranı.
|
||
|
||
```jsonc
|
||
{
|
||
"visualDesigner": {
|
||
"version": 1,
|
||
"sourceMode": "visual",
|
||
"canvas": { "width": "responsive" },
|
||
"lifecycle": { "onMount": "" },
|
||
"permissionCode": "App.Hr.AdvanceRequest",
|
||
"dataSources": [
|
||
{ "id": "source_list", "name": "Hr_T_AdvanceRequest — GetList",
|
||
"method": "GET", "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest", "responsePath": "" },
|
||
{ "id": "source_byid", "name": "Hr_T_AdvanceRequest — GetById",
|
||
"method": "GET", "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest/{id}", "responsePath": "" },
|
||
{ "id": "source_create", "name": "Hr_T_AdvanceRequest — Create",
|
||
"method": "POST", "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest", "responsePath": "" },
|
||
{ "id": "source_update", "name": "Hr_T_AdvanceRequest — Update",
|
||
"method": "PUT", "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest/{id}", "responsePath": "" },
|
||
{ "id": "source_delete", "name": "Hr_T_AdvanceRequest — Delete",
|
||
"method": "DELETE", "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest/{id}", "responsePath": "" }
|
||
],
|
||
"nodes": [
|
||
{
|
||
"id": "cmp_page", "type": "PageContainer", "kind": "layout", "ref": "page",
|
||
"props": { "maxWidth": "1280px", "padding": 24, "gap": 16 },
|
||
"events": {}, "bindings": {},
|
||
"children": [
|
||
{
|
||
"id": "cmp_form", "type": "Form", "kind": "layout", "ref": "form1",
|
||
"props": {
|
||
"title": "::App.Hr.AdvanceRequest.Title",
|
||
"selectEndpoint": "source_byid",
|
||
"insertEndpoint": "source_create",
|
||
"updateEndpoint": "source_update",
|
||
"deleteEndpoint": "source_delete",
|
||
"keyFieldName": "Id",
|
||
"collectionPath": "",
|
||
"keySource": "route",
|
||
"keyParamName": "id",
|
||
"autoLoad": true,
|
||
"showToolbar": true,
|
||
"columnCount": 2,
|
||
"gap": 16,
|
||
"selectPermission": "",
|
||
"insertPermission": "",
|
||
"updatePermission": "",
|
||
"deletePermission": ""
|
||
},
|
||
"events": {}, "bindings": {},
|
||
"children": [
|
||
{
|
||
"id": "cmp_amount", "type": "Input", "kind": "ui", "ref": "inputAmount",
|
||
"props": { "size": "md" },
|
||
"events": {},
|
||
"bindings": { "value": { "sourceId": "cmp_form", "path": "Amount" } },
|
||
"children": []
|
||
},
|
||
{
|
||
"id": "cmp_note", "type": "Input", "kind": "ui", "ref": "inputNote",
|
||
"props": { "textArea": true, "rows": 3 },
|
||
"events": {},
|
||
"bindings": { "value": { "sourceId": "cmp_form", "path": "Description" } },
|
||
"children": []
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "cmp_list", "type": "ListView", "kind": "platform", "ref": "listView1",
|
||
"props": {
|
||
"listFormCode": "App.Hr.AdvanceRequests",
|
||
"height": "520px",
|
||
"className": "mt-4",
|
||
"designerPermission": "App.Hr.AdvanceRequests",
|
||
"filters": [
|
||
{
|
||
"id": "filter_1", "field": "EmployeeId", "operator": "eq",
|
||
"source": "record", "value": "form1.EmployeeId",
|
||
"previewValue": "", "required": true
|
||
}
|
||
]
|
||
},
|
||
"events": {}, "bindings": {}, "children": []
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
Doküman yazarken uyulacak kurallar:
|
||
|
||
- **`id` benzersiz ve kalıcıdır.** Bağlamalar (`bindings.sourceId`) bu id'lere işaret eder;
|
||
değiştirirsen bağ kopar. `cmp_…` / `source_…` öneki bir sözleşmedir, zorunlu değildir ama koru.
|
||
- **`ref` script'lerin adres defteridir**; olay script'leri birbirine `refs.inputAmount…` ile
|
||
erişir. Okunur ve tekil tut.
|
||
- Form içindeki bir alanın kaynağı **Form düğümünün `id`'sidir** (`sourceId: "cmp_form"`),
|
||
data source değil. Data source'a bağlanan şeyler listeler/seçim bileşenleridir.
|
||
- Her düğümde `props`, `events`, `bindings`, `children` **var olmalıdır** (boş olsalar bile).
|
||
- `kind` değeri toolbox ailesiyle uyumlu olmalıdır; yanlış `kind` düğümü çizdirmez.
|
||
- **`::` öneki dil anahtarıdır.** Metin taşıyan bir prop `::` ile başlıyorsa üretilen kodda
|
||
`translate('::…')` çağrısına dönüşür; başlamıyorsa düz metin olarak gömülür. Kullanıcıya
|
||
görünen her metni `::App.…` biçiminde yaz (`"title": "::App.Hr.AdvanceRequest.Title"`).
|
||
- **`platform` düğümleri `filters` prop'u alır.** Gömülü ListForm ekranını ana kayda ya da URL'ye
|
||
bağlamak için `DesignerDataSourceFilter` ile aynı şekli kullanır; `source: "record"` iken
|
||
`value` `"{formRef}.{Kolon}"` biçimindedir (`"form1.EmployeeId"`). `required: true` ise ana
|
||
kayıt gelmeden liste yüklenmez.
|
||
- Anahtar kolon adında **büyük/küçük harf endpoint'in döndürdüğüyle aynı olmalıdır**
|
||
(`keyFieldName: "id"` ile `"Id"` farklı şeylerdir); CRUD endpoint'in yanıtına bak.
|
||
|
||
### 7.4 `Form` bileşeni
|
||
|
||
| Prop | Anlam |
|
||
| --- | --- |
|
||
| `selectEndpoint` / `insertEndpoint` / `updateEndpoint` / `deleteEndpoint` | **Data source `id`'si** (`"source_5w_5ibpv"`) — URL değil. Endpoint'in kendisi `dataSources` listesinde tanımlıdır. |
|
||
| `keyFieldName` | Anahtar kolon |
|
||
| `collectionPath` | Select yanıtında liste yolu |
|
||
| `keySource` + `keyParamName` | Anahtarın nereden geldiği (route/query/sabit) |
|
||
| `previewKeyValue` | Yalnızca tasarımcı önizlemesi |
|
||
| `autoLoad` | Açılışta Select çalışsın mı |
|
||
| `showToolbar` | Kaydet/Sil araç çubuğu |
|
||
| `columnCount` / `gap` | İç yerleşim |
|
||
| `title` | Dolu ise Card başlığı; boş ise başlık düşer |
|
||
|
||
İçine bırakılan her bileşen Select sonucunun bir kolonuna bağlanır (`bindings.value.sourceId` =
|
||
Form düğümünün `id`'si) ve Save/Delete üzerinden geri yazar. Olaylar: `onLoad`,
|
||
`onRecordChange`, `onFieldChange`, `onNewRecord`, `onModeChange`, `onBeforeSave`, `onAfterSave`,
|
||
`onBeforeDelete`, `onAfterDelete`, `onError`.
|
||
|
||
### 7.5 Yetki modeli (zorunlu)
|
||
|
||
İki katman:
|
||
|
||
1. **Düğüm görünürlüğü** — `props.designerPermission` dolu olan düğüm, yetki yoksa hiç
|
||
render edilmez. Veriyi göremeyecek kullanıcıya boş bir Grid göstermek yerine bileşen çizilmez.
|
||
2. **Form komutları** — `selectPermission` / `insertPermission` / `updatePermission` /
|
||
`deletePermission`.
|
||
- `Otomatik` mod: temel, dokümandaki `permissionCode`'dur (Wizard'ın bu bileşen için ürettiği
|
||
okuma yetkisi, menü kaydının korunduğu yetkinin aynısı). Komutlar sırasıyla `''`,
|
||
`.Create`, `.Update`, `.Delete` son eklerini alır.
|
||
- `Özel` mod: yetki adı elle yazılır.
|
||
- Boş bırakılan komut serbesttir.
|
||
|
||
Manager ekranı yetkileri: `App.DeveloperKit.Components{,.Create,.Update,.Delete}`.
|
||
|
||
### 7.6 Bileşeni menüye bağlama
|
||
|
||
Bileşen tek başına bir ekran değildir; menü ve yetki Wizard'ın `Custom` yolundan gelir:
|
||
|
||
```jsonc
|
||
{
|
||
"Wizard": {
|
||
"ComponentKind": 1,
|
||
"WizardName": "OrderBoard",
|
||
"ListFormCode": "App.Mrp.OrderBoard",
|
||
"MenuCode": "App.Mrp.OrderBoard",
|
||
"CustomComponentName": "OrderBoard",
|
||
"MenuUrl": "/admin/order-board",
|
||
"CreateMenu": true,
|
||
"MenuParentCode": "App.Mrp",
|
||
"PermissionGroupName": "App.Mrp",
|
||
"PermissionGroupEn": "MRP", "PermissionGroupTr": "MRP",
|
||
"MenuEn": "Order Board", "MenuTr": "Sipariş Panosu",
|
||
"MenuIcon": "FcKanban", "MenuOrder": 2
|
||
},
|
||
"IsDeletedField": false, "IsCreatedField": false,
|
||
"InsertedRecords": {
|
||
"LanguageKeys": ["App.Mrp.OrderBoard"],
|
||
"PermissionGroupNames": [],
|
||
"PermissionNames": ["App.Mrp.OrderBoard", "App.Mrp.OrderBoard.Create",
|
||
"App.Mrp.OrderBoard.Update", "App.Mrp.OrderBoard.Delete"],
|
||
"MenuCodes": ["App.Mrp.OrderBoard"], "DataSourceCodes": []
|
||
}
|
||
}
|
||
```
|
||
|
||
Custom yolunda `ListForm` **üretilmez**; `Title`/`Desc` dil anahtarları da üretilmez.
|
||
`Export`/`Import`/`Note` yetkileri bu yolda yoktur.
|
||
|
||
> **Güvenlik — route yetkisiz üretilir.** Custom component route'ları `RoutePath` değerinden
|
||
> türetilir ve `authority: []` ile kaydedilir; `/admin/` ile başlayan yol `protected`, diğerleri
|
||
> `public` olur. Yani **URL'yi bilen her oturum açmış kullanıcı sayfayı açabilir**. Menü yetkisi
|
||
> yalnızca menüde görünmeyi engeller. Gerçek koruma bileşenin içindedir:
|
||
> `designerPermission` ile düğümleri gizle, Form komutlarını `*Permission` ile kapat, kod modunda
|
||
> `checkPermission()` ile sar. Yetkisiz kullanıcıya boş bir sayfa gösteren bir bileşen doğru
|
||
> sonuçtur; veri gösteren bileşen açıktır.
|
||
>
|
||
> `RoutePath` `/admin/` ile başlamıyorsa sayfa **herkese açıktır** — bilinçli değilse başlat.
|
||
|
||
### 7.6.1 Depodaki gerçek örnek — `RoleComponent` + `RoleList` wizard
|
||
|
||
Bu çift, custom component'in menüye nasıl bağlandığının çalışan örneğidir. İncelenecek dosyalar:
|
||
|
||
```
|
||
configs/seeds/host/custom/RoleComponent.json
|
||
configs/seeds/host/crud/AbpRoles.json · AbpUsers.json · AbpUserRoles.json
|
||
configs/seeds/host/wizard/RoleList.json
|
||
```
|
||
|
||
Nasıl bağlanıyorlar:
|
||
|
||
| Bağ | Değer |
|
||
| --- | --- |
|
||
| Wizard `ComponentKind` | `1` (Custom) |
|
||
| Wizard `CustomComponentName` | `RoleComponent` |
|
||
| Component `RoutePath` | `/admin/roles` |
|
||
| Wizard `MenuUrl` | `/admin/roles` — component'in `RoutePath` değeriyle **aynı** |
|
||
| Wizard `MenuCode` / `ListFormCode` | `App.Wizard.RoleList` |
|
||
| Component `Props.visualDesigner.permissionCode` | `App.Wizard.RoleList` — wizard'ın ürettiği okuma yetkisi |
|
||
| Wizard `InsertedRecords.PermissionNames` | `App.Wizard.RoleList` + `.Create` + `.Update` + `.Delete` |
|
||
| Wizard `InsertedRecords.LanguageKeys` | `App.Wizard.RoleList` |
|
||
| Component `DataSources[].SeedFile` | `crud/AbpRoles.json` vb. |
|
||
|
||
**Okunacak dersler:**
|
||
|
||
1. `permissionCode` **wizard'ın ürettiği yetkinin aynısıdır**. Form komutları `Otomatik` modda
|
||
bu koda `.Create`/`.Update`/`.Delete` ekleyerek çözülür. Component'i yazarken bu değeri
|
||
wizard'daki `MenuCode` ile aynı yap.
|
||
2. `RoutePath` ile wizard'ın `MenuUrl` değeri **birebir aynı** olmalıdır; farklıysa menü başka
|
||
bir yere gider.
|
||
3. Component'in `DataSources` listesi `crud/*.json` dosyalarına `SeedFile` ile işaret eder —
|
||
export zip'inin bu dosyaları da toplamasını sağlayan şey budur.
|
||
4. Wizard `Custom` yolunda olsa bile `ListFormCode`, `Grid/Card/Pivot/Chart` bayrakları gibi
|
||
List alanlarını taşır; **bunlar kullanılmaz**, varsayılan değerleriyle dosyada kalır.
|
||
Yeni dosya yazarken bunları temizlemeye çalışma, şablonu koru.
|
||
5. `platform` düğümündeki `listFormCode` (`AbpIdentity.Roles`) **var olan bir ListForm kodudur**;
|
||
component ekranın kendisini yeniden kurmaz, gömer.
|
||
|
||
### 7.7 Taşıma
|
||
|
||
Bir ekranı başka ortama taşımak tek dosya kopyalamak değildir. Wizard File Manager'ın **Export**
|
||
butonu zip üretir:
|
||
|
||
```
|
||
wizard/{dosya}.json
|
||
custom/{component}.json (bağımlılıklarıyla)
|
||
crud/{entity}.json (component'lerin kullandığı endpoint'ler)
|
||
{sql|postgres}/{object|execute}/{nesne}.sql (List yolunda SelectCommand'ın nesnesi)
|
||
```
|
||
|
||
Import iki adımlıdır: analiz (`New`/`Identical`/`Conflict`, çakışmalar diff editörde çözülür) →
|
||
uygulama (dosya dosya yazım, önceki hâl yedeklenir, `RollbackImport` ile toplu geri alma).
|
||
Sınırlar: yalnızca `wizard`, `crud`, `custom`, `sql`, `postgres` kök klasörleri (SQL sağlayıcı
|
||
klasörlerinin altında yalnızca `object`/`execute`); 5 MB/dosya, 50 MB/arşiv, 500 girdi.
|
||
Yetkiler: `App.Listforms.Wizard.Export` / `.Import`.
|
||
|
||
### 7.8 Event Script — düğüm olay script'leri
|
||
|
||
`DesignerNode.events` ve `lifecycle.onMount` içindeki her metin bir olay script'idir ve
|
||
tasarımcının **JavaScript** sekmesinde aynı Script Builder ile düzenlenir (lehçe:
|
||
`designerScriptDialect.ts` + `designerScriptRecipes.ts`). §5'teki bütün mekanizma aynen geçerlidir:
|
||
`// @builder` başlığı, kuralı yeniden üretip metinle karşılaştırma, kod/sihirbaz geçişi, snippet
|
||
ve Runtime API paneli. Fark, başlıkta ayrıca **`sqlRef`** saklanmasıdır — kurallar hangi `Form`
|
||
ref'iyle üretildiyse geri okuma da onunla yapılır.
|
||
|
||
Script'ler `Props.visualDesigner` içinde saklanır ve kod üreticisi tarafından tek argümanlı
|
||
(`event`) handler gövdesine gömülür. Metin gömüldüğü için **her artefakt gibi seed dosyasına da
|
||
yazılır**; `custom/{Name}.json` güncellenmeden bırakılan bir script veritabanı sıfırlandığında
|
||
geri gelmez.
|
||
|
||
#### Olaylar
|
||
|
||
| Olay | Ne zaman | `event` içeriği | İptal edilebilir |
|
||
| --- | --- | --- | --- |
|
||
| `onLoad` | Select cevabı forma yerleştiğinde | `rows`, `count`, `record`, `index` | — |
|
||
| `onRecordChange` | Aktif kayıt değiştiğinde | `record`, `index`, `rows`, `count` | — |
|
||
| `onFieldChange` | Kayda değer yazıldığında | `field`, `value`, `previous`, `record` | — |
|
||
| `onNewRecord` | Yeni kayıt açıldığında | `record` | — |
|
||
| `onModeChange` | Mod değiştiğinde | `mode` (`new`/`edit`), `record` | — |
|
||
| `onBeforeSave` | Kaydetmeden önce | `record`, `original`, `payload`, `mode`, `isNew` | **`return false` → kaydetmeyi durdurur** |
|
||
| `onAfterSave` | Kaydettikten sonra | `record`, `response`, `mode`, `isNew` | — |
|
||
| `onBeforeDelete` | Silmeden önce | `record`, `key` | **`return false` → silmeyi durdurur** |
|
||
| `onAfterDelete` | Sildikten sonra | `record` | — |
|
||
| `onError` | Hata oluştuğunda | `message`, `action` (`reload`/`save`/`delete`), `error`, `record` | — |
|
||
|
||
Bunlar `Form` düğümünün kayıt yaşam döngüsüdür. Diğer bileşenlerin kendi olayları vardır:
|
||
`Button.onClick`, `Input.onChange`, `Select.onChange`, `Checkbox.onChange`, `Tabs.onChange`,
|
||
`Menu.onSelect`, `Dropdown.onSelect`, `Pagination.onChange`, `AutoComplete.onInputChange/onSelect`.
|
||
Bu olaylarda `event` bileşenin kendi yükünü taşır (`Input` → DOM olayı, `Select` → seçilen option
|
||
nesnesi, `Menu`/`Dropdown` → seçilen değer).
|
||
|
||
> "İşlemi iptal et" tarifi yalnızca `cancelable` olaylarda listelenir; diğerlerinde üretilen
|
||
> `return false` sessizce hiçbir şey yapardı.
|
||
|
||
#### Koşul kaynakları
|
||
|
||
Form script'inden farklı olarak koşulun kaynağı dört türden biri olabilir:
|
||
|
||
| Tür | Okunan ifade |
|
||
| --- | --- |
|
||
| Kayıt kolonu | `refs.<sqlRef>.getField('Kolon')` |
|
||
| Komponent değeri | `refs.<ad>.getValue()` |
|
||
| Event verisi | `event?.a?.b` |
|
||
| Serbest ifade | Yazdığın kodun kendisi (`refs.form1.getMode()`) |
|
||
|
||
Operatörler §5.4 ile aynıdır; üretilen ifade `String((...) ?? "") === "x"`,
|
||
`Number((...) || 0) > 100` biçiminde null-güvenlidir.
|
||
|
||
#### Tarifler
|
||
|
||
| Grup | `recipe` | Ürettiği |
|
||
| --- | --- | --- |
|
||
| record | `setField` | `refs.form1.setField('Status', 'Taslak')` — değer modu: metin / sayı / boolean / **ifade** / null |
|
||
| record | `multiply` · `sumFields` · `formula` · `dayDiff` | kayıt kolonlarından hesaplayıp `setField` ile yazar |
|
||
| record | `copyFromEvent` | `refs.form1.setField('OldValue', event?.previous)` |
|
||
| record | `clearFields` | `refs.form1.clearFields('City', 'District')` |
|
||
| record | `save` · `remove` · `reload` · `newRecord` | `await refs.form1.save()` … |
|
||
| record | `goToRow` | `nextRow()` / `prevRow()` / `firstRow()` / `lastRow()` / `goToRow(n)` |
|
||
| record | `selectColumnToField` | `refs.form1.setField('UnitPrice', refs.select1.getColumn('unitPrice'))` |
|
||
| component | `selectColumnToRef` | `refs.input1.setValue(refs.select1.getColumn('unitPrice'))` |
|
||
| component | `refSetValue` · `refSetText` · `refSetProp` | `setValue` / `setText` / `setProp('placeholder', …)` |
|
||
| component | `refVisible` · `refEnabled` · `refReadOnly` | koşul argüman olur: `refs.card1.setVisible(<koşul>)` (`invert` ile tersi) |
|
||
| interaction | `notify` | `notify('Kayıt güncellendi', 'success')` — `info`/`success`/`warning`/`danger` |
|
||
| interaction | `confirm` | `if (!window.confirm('Emin misiniz?')) return` (ya da `return false`) |
|
||
| interaction | `openUrl` | `window.open('/report?id=1', '_blank')` |
|
||
| integration | `apiToField` | `refs.form1.setField('Title', (await api.get(url))?.name)` |
|
||
| integration | `apiPost` | `await api.post(url, refs.form1.getRecord())` — gövde: kayıt / event / yok |
|
||
| integration | `custom` | Serbest satır |
|
||
| flow | `cancel` · `stop` · `log` | `return false` · `return` · `console.log(...)` |
|
||
|
||
Kayıt tarifleri bir `Form` ref'i gerektirir; sayfada Form yoksa kart "eksik" kalır ve script'e
|
||
girmez.
|
||
|
||
#### Runtime — script'in kapsamındakiler
|
||
|
||
Her düğüm `ref` adıyla adreslenir; bir script başka bir bileşeni prop geçirmeden değiştirebilir.
|
||
|
||
| Herhangi bir `refs.<ad>` | Ne yapar |
|
||
| --- | --- |
|
||
| `getValue()` / `setValue(x)` | Değeri okur/yazar (Form içindeyse kayda, değilse bileşenin kendi state'ine) |
|
||
| `show()` / `hide()` / `setVisible(b)` / `isVisible()` | Görünürlük |
|
||
| `setEnabled(b)` / `isEnabled()` / `setReadOnly(b)` | Etkinlik ve salt okunurluk |
|
||
| `getProps()` / `setProps(patch)` / `setProp(ad, deger)` / `setText(metin)` | Prop'ları çalışma zamanında ezer |
|
||
| `reset()` | Bu ref üzerindeki tüm ezmeleri kaldırır |
|
||
|
||
| Select/AutoComplete/Menu ref'i | Ne yapar |
|
||
| --- | --- |
|
||
| `getSelectedOption()` / `getOptions()` / `getLabel()` | Seçili option nesnesi, tüm seçenekler, etiket |
|
||
| `getColumn('yol')` / `getColumnNames()` | Seçili satırın **ekranda görünmeyen** kolonlarını okur (binding'deki `columns`) |
|
||
|
||
| `Form` ref'i | Ne yapar |
|
||
| --- | --- |
|
||
| `getRecord()` / `setRecord(o)` / `getOriginal()` | Aktif kayıt ve yüklendiği hâli |
|
||
| `getField(yol)` / `setField(yol, x)` / `setFields({...})` / `clearFields(...)` | Kolon yazma — her yazma `onFieldChange` tetikler |
|
||
| `getChanges()` / `hasChanges()` | Değişen kolonlar (anahtar hariç) |
|
||
| `getKey()` / `getKeyField()` | Anahtar değeri / adı |
|
||
| `getRows()` / `getRowCount()` / `getIndex()` | Yüklenen satırlar ve konum |
|
||
| `goToRow(i)` / `nextRow()` / `prevRow()` / `firstRow()` / `lastRow()` | Gezinme |
|
||
| `getMode()` / `isNew()` / `isBusy()` / `getError()` / `setError(m)` | Durum |
|
||
| `await save()` / `remove()` / `reload()` / `newRecord()` | CRUD komutları (Form'un endpoint slotlarını kullanır) |
|
||
|
||
| Genel | Ne yapar |
|
||
| --- | --- |
|
||
| `event` | Olayın yükü |
|
||
| `await api.get/post/put/patch/delete/request` | `apiService` üzerinden **kimlik doğrulamalı** HTTP; `api.errorMessage(error)` kullanıcıya gösterilecek mesajı çıkarır |
|
||
| `notify(mesaj, tip)` | Toast (`info` varsayılan) |
|
||
| `React`, `components/ui` bileşenleri, `apiService`, `translate`, `checkPermission` | §7.1.2'deki kapsam burada da geçerlidir |
|
||
|
||
Sınırlar: script'ler tasarımcı dokümanına gömülüdür, aralarında paylaşılan modül yoktur —
|
||
tekrar eden mantık ya bir Custom Endpoint'e ya da Dynamic Service'e taşınır. Kullanıcıya görünen
|
||
metin `translate('App.…')` ile verilir; yetki ile gizlenecek bir davranış `checkPermission` ile
|
||
sarılır.
|
||
|
||
---
|
||
|
||
## 8. Artefakt E — SQL nesneleri
|
||
|
||
### 8.1 Tablo tasarımcısı (`SqlTableDesignerDialog`) — çalışma mantığı
|
||
|
||
Diyalog altı adımlı bir mini sihirbazdır: **Kolonlar → Ayarlar → Index'ler → İlişkiler →
|
||
CRUD** (yalnız `App.SqlQueryManager.CrudEndpoints` yetkisi varsa) **→ SQL önizleme**.
|
||
Prompt ile tablo üretirken bu adımların çıktısını sen yazarsın; kurallar aynıdır.
|
||
|
||
**Kolonlar adımı.** Her kolon: ad, tip, uzunluk, null izin, varsayılan değer, açıklama.
|
||
Tip listesi (SQL Server): `nvarchar`, `nvarchar(MAX)`, `int`, `bigint`, `decimal (18,4)`,
|
||
`float`, `bit`, `datetime`, `datetime2`, `date`, `uniqueidentifier`, `money`.
|
||
|
||
Sağ üstteki **kolon şablonları** hazır kolon setlerini ekler (var olan adla çakışan atlanır) —
|
||
seed dosyası yazarken aynı setleri kullan:
|
||
|
||
| Şablon | Eklediği kolonlar |
|
||
| --- | --- |
|
||
| Multi-Tenant | `TenantId` (`uniqueidentifier`, null) |
|
||
| Full Audited | `Id` (`uniqueidentifier`, PK, `NEWID()`), `CreationTime` (`datetime2`, `GETUTCDATE()`), `CreatorId`, `LastModificationTime`, `LastModifierId`, `IsDeleted` (`bit`, `0`), `DeletionTime`, `DeleterId` |
|
||
| Workflow | `UserName` (`nvarchar(256)`), `Status` (`nvarchar(50)`), `Date` (`datetime`), `Description` (`nvarchar(200)`) — `WorkflowDto`'nun dört alan adının karşılığı |
|
||
| Todo | `Title` (`nvarchar(300)`), `Status` (`nvarchar(50)`, `N'Backlog'`), açıklama/öncelik/sıra kolonları — Kanban ekranının veri tabanı |
|
||
| Gantt | `Start` / `End` / `Progress` kolonları |
|
||
| Scheduler | Randevu başlangıç/bitiş/tekrar kolonları |
|
||
|
||
Yani "onaylı talep formu" ya da "kanban panosu" isteğinde tablo kolonlarını uydurma: ilgili
|
||
şablon setini Full Audited + Multi-Tenant ile birleştir.
|
||
|
||
**Ayarlar adımı.** Modül (menü ağacından seçilir) + entity adı girilir; tablo adı otomatik
|
||
üretilir: `{ModülÖnEki}_{T|D}_{EntityName}` — **`T`** tabloda `TenantId` kolonu varsa, yoksa
|
||
**`D`**. (Platformun kendi `H`/`B` harfleri `TableNameResolver`'a aittir; tasarımcı proje
|
||
tabloları için yalnız `T`/`D` üretir.)
|
||
|
||
**Index'ler:** `PrimaryKey` / `UniqueKey` / `Index`, clustered seçimi, çok kolonlu (ASC/DESC).
|
||
**İlişkiler** (`SqlTableRelation`): `OneToOne`/`OneToMany`, FK kolonu → hedef tablo.kolon,
|
||
`ON DELETE`/`ON UPDATE` davranışı (`NoAction`/`Cascade`/`SetNull`/`Restrict`), zorunluluk.
|
||
|
||
**Deploy zinciri** — sırayla:
|
||
|
||
1. Üretilen SQL hedef veri kaynağında çalıştırılır (yeni tabloda `CREATE`, düzenleme modunda
|
||
**`ALTER` diff'i**).
|
||
2. Seed dosyası kaydedilir: her zaman **tam `CREATE TABLE` script'i** (diff değil),
|
||
`save-table-script` ucu ile `configs/seeds/{kapsam}/{sql|postgres}/object/{TabloAdı}.sql`
|
||
altına (`SeedPathResolver`). Dosya kaydı başarısız olsa deploy geri alınmaz — log'a düşer.
|
||
3. CRUD adımında işaretlendiyse endpoint'ler üretilir (`crud/{Entity}.json`); onay kutusu
|
||
**varsayılan kapalıdır**, Custom Component'in kullanacağı tabloda açılır.
|
||
|
||
Kullanıcı açıkça "tenant yok / audit yok" demedikçe Multi-Tenant + Full Audited setleri dahil
|
||
edilir; her seferinde sorma.
|
||
|
||
### 8.2 View tasarımcısı (`SqlViewDesignerDialog`) — çalışma mantığı
|
||
|
||
Bir ekran birden fazla tablodan besleniyorsa, `SelectCommandType: 4` (Query) yerine **tasarlanmış
|
||
bir view** (`SelectCommandType: 2`) tercih edilir: yeniden kullanılabilir, seed'lenir ve
|
||
tasarımcıda görsel olarak düzenlenebilir kalır.
|
||
|
||
Diyalog SSMS'in view designer'ının karşılığıdır: üstte **diagram paneli**, altta **criteria
|
||
grid**, sağda üretilen T-SQL önizlemesi. Model (`viewModel.ts`):
|
||
|
||
| Parça | Alanlar |
|
||
| --- | --- |
|
||
| Kaynak (`ViewSource`) | şema + nesne adı + takma ad; `JoinKind` (`INNER`/`LEFT`/`RIGHT`/`FULL`/`CROSS`); join koşulları (`sol.kolon <op> sağ.kolon`, op: `=` `<>` `>` `>=` `<` `<=`); **türetilmiş alt sorgu** (`derivedSql`) ve `CROSS`/`OUTER APPLY` |
|
||
| Criteria satırı (`CriteriaRow`) | kolon **ya da serbest ifade** (`expression` — alias zorunlu), `Alias`, `Output` (SELECT'e girsin mi), `GroupMode`, `SortType` + `SortOrder`, `filters[]` (index = **OR grubu**; hücre serbest yüklem: `> 100`, `LIKE '%abc%'`, `IN (1,2)`, `IS NULL`) |
|
||
| Ayarlar (`ViewSettings`) | şema + view adı, `DISTINCT`, Group By anahtarı (sigma), `TOP n` / `TOP n PERCENT` |
|
||
|
||
`GroupMode` değerleri: `GroupBy`, `Where` (satır çıktıya girmez, yalnızca filtre taşır),
|
||
`SUM`, `COUNT`, `COUNT_DISTINCT`, `AVG`, `MIN`, `MAX`. Sigma kapalıyken Group By sütunu ve
|
||
`GROUP BY` üretimi yoktur.
|
||
|
||
**Deploy**, üretilen `CREATE OR ALTER VIEW`'ı hedef veri kaynağında çalıştırır — **seed
|
||
dosyası yazmaz** (aşağıdaki kalıcılık uyarısı). Var olan bir view açılırken tanım
|
||
`parseViewSql` ile geri okunmaya çalışılır; kanonik şekle uymayan tanımda diyalog **ham SQL
|
||
moduna** düşer ve tasarımcı devre dışı kalır — tasarlanmış bir view'ı elle düzenlemek
|
||
tasarımcıyı kaybettirir.
|
||
|
||
> **Kalıcılık — kritik ayrım:** diske otomatik seed yazan tek tasarımcı **tablo
|
||
> tasarımcısıdır**. View Designer'ın deploy'u, sorgu editöründen çalıştırılan
|
||
> `CREATE PROCEDURE`/`CREATE FUNCTION` ve her türlü elle DDL **yalnızca veritabanına** gider.
|
||
> Bu nesnelerin `configs/seeds/{kapsam}/{sql|postgres}/object/{Ad}.sql` dosyasını **sen
|
||
> yazarsın** — prompt ile üretimde zaten dosyayı doğrudan yazdığın için bu senin doğal yolun;
|
||
> uygulama içinden üretim yapıldıysa dosyaya alınmamış nesne veritabanı sıfırlanınca kaybolur.
|
||
|
||
Klasör sözleşmesi: `object` = yeniden oluşturulabilir nesne (tablo/view/fonksiyon/prosedür),
|
||
`execute` = bir kez çalıştırılacak script (§8.4).
|
||
|
||
### 8.2.1 SP / Function üretimi — ne zaman, nasıl
|
||
|
||
List ve Custom komponent üretirken SQL nesnesi ihtiyacı dört kalıpta çıkar:
|
||
|
||
| İhtiyaç | Nesne | Nereye bağlanır |
|
||
| --- | --- | --- |
|
||
| Çok tablolu okuma | **View** | `SelectCommandType: 2`, `SelectCommand: "{Modul}_V_{Ad}"` |
|
||
| Parametreli okuma (kullanıcıya/duruma göre değişen küme) | **Table-Valued Function** | `SelectCommandType: 3`, `SelectCommand: "dbo.Fn(@USERID)"` — parametre token'ları çözülür |
|
||
| Yazma sonrası iş mantığı (stok, log, durum, kuyruk) | **Stored Procedure** | `*BeforeCommand`/`*AfterCommand` → `EXEC {Modul}_S_{Ad} @ID, @USERID` (§4.8) |
|
||
| Zamanlanmış iş gövdesi | **Stored Procedure** | `BackgroundWorker.BeforeSp` (§9.10) |
|
||
| Veri dolumu / migration sonrası düzeltme | **Prosedür olarak `execute/` dosyası** | `AfterAllMigrationsSqlExecutor` dosya adındaki prosedürü çağırır (§8.4) |
|
||
|
||
Sorgu editörünün hazır şablonları (`select`, `insert`, `update`, `delete`, `create-view`,
|
||
`create-procedure`, `create-scalar-function`, `create-table-function`) her iki diyalektte de
|
||
vardır; seed dosyası yazarken aynı iskeleti kullan ve §8.3'ün idempotency kuralına çevir
|
||
(`CREATE OR ALTER` / `CREATE OR REPLACE`). Nesne editörde açıldığında baştaki
|
||
`CREATE`/`ALTER` her zaman `CREATE OR ALTER`'a normalize edilir — dosyada da böyle yaz.
|
||
|
||
### 8.3 SQL seed dosyası yazım kuralları
|
||
|
||
| Kural | Açıklama |
|
||
| --- | --- |
|
||
| Dosya adı | Nesne adıyla **birebir aynı**: `Hr_T_AdvanceRequest.sql` |
|
||
| Klasör | `object` = idempotent nesne tanımı · `execute` = nesne olarak da oluşturulur, ayrıca tüm migration'lar bittikten sonra çalıştırılır |
|
||
| Sağlayıcı | Hedef SQL Server ise `sql/`, PostgreSQL ise `postgres/`. **İkisini birden destekliyorsan iki dosya da yazılır**; içerikler aynı nesnenin iki diyalektidir. |
|
||
| İdempotency | SQL Server: `CREATE OR ALTER VIEW/PROCEDURE/FUNCTION`, tablolar için `IF NOT EXISTS` koruması. PostgreSQL: `CREATE OR REPLACE VIEW/FUNCTION`, `CREATE TABLE IF NOT EXISTS`. |
|
||
| Şema | `dbo` kullanılır; PostgreSQL tarafında tanımlayıcılar çift tırnaklıdır (`dbo."Hr_T_AdvanceRequest"`) çünkü kolon adları PascalCase'tir. |
|
||
| Toplu ayraç | SQL Server'da prosedür sonunda `GO` kullanılabilir; PostgreSQL'de kullanılmaz. |
|
||
| Yorum | Dosyanın ilk satırında ne ürettiğini yaz (`-- Create View`). |
|
||
|
||
Tablo yazarken §8.1'deki varsayılan kolonlar dahil edilir. Örnek iskelet (SQL Server):
|
||
|
||
```sql
|
||
-- Create Table
|
||
IF OBJECT_ID(N'[dbo].[Hr_T_AdvanceRequest]', N'U') IS NULL
|
||
BEGIN
|
||
CREATE TABLE [dbo].[Hr_T_AdvanceRequest] (
|
||
[Id] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [PK_Hr_T_AdvanceRequest] PRIMARY KEY,
|
||
[TenantId] UNIQUEIDENTIFIER NULL,
|
||
-- iş kolonları buraya
|
||
[CreationTime] DATETIME2 NOT NULL,
|
||
[CreatorId] UNIQUEIDENTIFIER NULL,
|
||
[LastModificationTime] DATETIME2 NULL,
|
||
[LastModifierId] UNIQUEIDENTIFIER NULL,
|
||
[IsDeleted] BIT NOT NULL CONSTRAINT [DF_Hr_T_AdvanceRequest_IsDeleted] DEFAULT (0),
|
||
[DeletionTime] DATETIME2 NULL,
|
||
[DeleterId] UNIQUEIDENTIFIER NULL
|
||
);
|
||
END
|
||
```
|
||
|
||
> `Id` tipini ne seçtiysen wizard dosyasındaki `KeyFieldDbSourceType` ile aynı olmalıdır
|
||
> (`UNIQUEIDENTIFIER` → `9`, `INT IDENTITY` → `11`). Uyumsuzluk sessiz veri hatası üretir.
|
||
|
||
### 8.4 Seeder bu dosyaları nasıl çalıştırır
|
||
|
||
Yeni bir entity/tablo `sql/object/` altına yazılır ve veritabanı `SqlDataSeeder` tarafından bu
|
||
dosyalardan kurulur. Mekanik:
|
||
|
||
1. **Yalnızca aktif sağlayıcının klasörü çalışır.** `DefaultDatabaseProvider` SQL Server ise
|
||
`{kapsam}/sql/`, PostgreSQL ise `{kapsam}/postgres/`. İki dosya da yazmak taşınabilirlik
|
||
içindir; aynı anda ikisi birden çalışmaz.
|
||
2. Sıra: `object/` → `execute/`, her klasörün içinde **dosya adına göre alfabetik**. Bir tablo
|
||
başka bir tabloya FK ile bağlıysa dosya adları o sırayı vermelidir (ör. `Hr_T_Employee.sql`
|
||
`Hr_T_EmployeeAdvance.sql`'den önce gelir).
|
||
3. Dosya içeriği `GO` satırlarından batch'lere bölünür ve sırayla çalıştırılır. PostgreSQL'de
|
||
`GO` kullanılmaz.
|
||
4. **Hata seed'i durdurur.** Bir batch patlarsa istisna yukarı fırlatılır ve o kapsamın seed'i
|
||
yarım kalır. Bu yüzden her script tekrar tekrar çalıştırılabilir (idempotent) olmalıdır:
|
||
`CREATE OR ALTER` / `CREATE OR REPLACE` / `IF NOT EXISTS`.
|
||
5. SQL nesneleri bittikten hemen sonra **aynı kapsam için `CrudDataSeeder`** çalışır; böylece
|
||
CRUD endpoint'leri dayandıkları tablolardan sonra oluşur. Tablo dosyasını yazmadan
|
||
`crud/{Entity}.json` yazmak anlamsızdır.
|
||
6. Klasör kökünde kalmış `.sql` dosyaları geriye dönük uyumluluk için (ve `object/` klasöründen
|
||
**önce**) çalıştırılır ama **uyarı loglar**; yeni dosyayı her zaman `object/` ya da `execute/`
|
||
altına koy.
|
||
|
||
**`execute/` klasörünün özel sözleşmesi:** buradaki dosyalar iki aşamalıdır.
|
||
|
||
- 1. aşama: `SqlDataSeeder` dosyayı çalıştırır → nesne oluşur/güncellenir.
|
||
- 2. aşama: tüm migration ve seed işlemleri bittikten sonra `AfterAllMigrationsSqlExecutor`
|
||
**dosya adından türettiği stored procedure'ü çağırır**.
|
||
|
||
Yani `execute/` altındaki bir dosya, **adı prosedür adıyla aynı olan bir stored procedure
|
||
tanımlamalıdır** (`Hr_T_AdvanceRequest_Backfill.sql` → `Hr_T_AdvanceRequest_Backfill`
|
||
prosedürü). `schema.Procedure.sql` biçimi de desteklenir. Prosedür adı güvenli tanımlayıcı
|
||
deseninden geçer (harf/alt çizgi ile başlar, en fazla 127 karakter). Sadece bir DDL çalıştırmak
|
||
istiyorsan dosya `object/` altına aittir; `execute/` veri dolumu ve migration sonrası düzeltme
|
||
içindir.
|
||
|
||
### 8.5 Yeni entity/tablo eklerken izlenecek yol
|
||
|
||
1. `sql/object/{Modul}_T_{Entity}.sql` — tabloyu §8.3 iskeletiyle yaz (tenant + audit kolonları
|
||
varsayılan). PostgreSQL hedefi de destekleniyorsa `postgres/object/` altına diyalekt eşini yaz.
|
||
2. İndeks ve FK'leri **aynı dosyada**, varlık kontrolüyle ekle (`IF NOT EXISTS`); ayrı dosya
|
||
sıralamayı kırılgan hâle getirir.
|
||
3. Ekran bu tabloyu doğrudan kullanacaksa CRUD endpoint gerekmez — ListForm `SelectCommandType: 1`
|
||
ile tabloya bağlanır. Custom Component kullanacaksa `crud/{Entity}.json` de yaz (§9).
|
||
4. Wizard dosyasında `SelectCommand` = tablo adı, `KeyFieldName` = `Id`,
|
||
`KeyFieldDbSourceType` = tablodaki `Id` tipinin sayısı, `IsTenant` = tabloda `TenantId` varsa
|
||
`true`, `IsDeletedField`/`IsCreatedField` = ilgili kolonlar varsa `true`. Anahtar alanı
|
||
`Groups[0].Items[0]` olarak `IncludeInEditingForm: false` ile de yaz (§4.4.1).
|
||
5. DB Migrate çalıştır; log'da `Executing: CREATE TABLE …` satırını gör.
|
||
|
||
---
|
||
|
||
## 9. Artefakt F — CRUD Endpoint ve Developer Kit
|
||
|
||
Bu bölüm CRUD Endpoint (9.1), elle yazılmış SQL'i API'ye çeviren Custom Endpoint (9.2), runtime
|
||
derlenen Dynamic Service (9.3), Developer Kit ekranları (9.4–9.8) ve platformun hazır gelen
|
||
konfigürasyon ekranlarını (9.9–9.14) kapsar.
|
||
|
||
### 9.1 CRUD Endpoint seed dosyası
|
||
|
||
Konum: `configs/seeds/{kapsam}/crud/{EntityName}.json` · Okuyan: `CrudDataSeeder`
|
||
|
||
```jsonc
|
||
{
|
||
"EntityName": "Mrp_T_Order",
|
||
"GeneratedAt": "2026-08-20T06:13:28Z",
|
||
"Endpoints": [
|
||
{ "Method": "GET", "Path": "/api/app/crudendpoint/Mrp_T_Order", "OperationType": "GetList", "IsActive": true },
|
||
{ "Method": "GET", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "GetById", "IsActive": true },
|
||
{ "Method": "POST", "Path": "/api/app/crudendpoint/Mrp_T_Order", "OperationType": "Create", "IsActive": true },
|
||
{ "Method": "PUT", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "Update", "IsActive": true },
|
||
{ "Method": "DELETE", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "Delete", "IsActive": true }
|
||
]
|
||
}
|
||
```
|
||
|
||
Üretilen C# kodu dosyada tutulmaz; entity adı ve operasyondan yeniden türetilir.
|
||
|
||
- Üretim/aktivasyon yeri: SQL Query Manager nesne gezgininde tablo satırının **CRUD Endpoints**
|
||
aksiyonu, toolbar'daki toplu üretim, tablo tasarımcısının deploy öncesi adımı ve Wizard'ın
|
||
veri ayarları adımı — hepsi aynı `CrudEndpointDialog`.
|
||
- Yetki: `App.SqlQueryManager.CrudEndpoints`. İzin yoksa butonlar görünmez ve
|
||
`crud-endpoint-generate` uçlarının tamamı reddedilir.
|
||
- Dispatcher üzerinden çağrı kapıları: `App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove`;
|
||
endpoint bazlı User/Role/Global kuralları bunun üstünde denetlenir.
|
||
- `GetList` filtre sözleşmesi §7.2'deki ile aynıdır.
|
||
|
||
---
|
||
|
||
### 9.2 Custom Endpoint — elle yazılmış SQL'i API olarak açmak
|
||
|
||
Ekran: `/admin/list/App.DeveloperKit.CustomEndpoints` (bir ListForm ekranıdır).
|
||
Entity: `CustomEndpoint` (`FullAuditedEntity` + `IMultiTenant` — kayıtlar **kiracı bazlıdır**).
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `Name` / `Description` | Tanımlayıcı |
|
||
| `Url` | Yayınlanacak yol — dispatcher önekinin **arkasına** eklenir (aşağıya bak) |
|
||
| `Method` | `GET` / `POST` / `PUT` / `DELETE` |
|
||
| `DataSourceCode` | Hangi bağlantıdan okunacağı. **`"!Tenant"`** özel değeridir: isteği yapan kullanıcının kendi tenant veritabanı kullanılır; başka her değer `Sas_H_DataSource` kaydına gider |
|
||
| `Sql` | Parametreli sorgu — `@paramAdi` yer tutucuları `ParametersJson`'daki adlarla eşleşir. String birleştirme yasak |
|
||
| `ParametersJson` | `CustomEndpointParameter[]` |
|
||
| `PermissionsJson` | `CustomEndpointPermission[]` |
|
||
|
||
#### 9.2.1 Dispatcher — istek nasıl çözülür
|
||
|
||
Dört sabit uç tüm custom endpoint trafiğini karşılar (`CustomEndpointAppService`):
|
||
|
||
```
|
||
GET|POST|PUT|DELETE /api/app/custom-endpoint/{**path}
|
||
```
|
||
|
||
Çözüm sırası:
|
||
|
||
1. **Verb kapısı:** `App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove` — kullanıcıda bu
|
||
yetki yoksa istek endpoint'e hiç ulaşmaz.
|
||
2. **Eşleme:** istek yolu URL-decode edilir, `/api/app/custom-endpoint` öneki atılır, başına ve
|
||
sonuna `/` garanti edilir; kayıt `path.StartsWith(Url) && Method == verb` ile bulunur.
|
||
Bu bir **önek eşlemesidir** — `Url: "/orders"` hem `/orders/` hem `/orders/123/` isteklerini
|
||
yakalar; ilk eşleşen kayıt kazanır. Çakışmayan, ayırt edici yollar seç.
|
||
3. **Endpoint yetkisi:** `PermissionsJson` kurallarından **en az biri** tutmalıdır:
|
||
|
||
| `ResourceType` | `ResourceId` | Kim geçer |
|
||
| --- | --- | --- |
|
||
| `Global` | — | Verb kapısını geçen herkes |
|
||
| `Role` | rol **adı** | O roldeki kullanıcılar |
|
||
| `User` | kullanıcı **adı** (`UserName`, id değil) | O kullanıcı |
|
||
|
||
> **Boş `PermissionsJson` = kimse çağıramaz (401).** Endpoint'i açmak bilinçli bir karardır;
|
||
> en azından bir `Role` kuralı yaz, herkese açıksa `Global` yaz.
|
||
|
||
4. **Parametre çözümü** (sırayla; hepsi tek `param` sözlüğünde toplanır ve SQL'e **adıyla**
|
||
bağlanır):
|
||
|
||
| `Type` | Değer nereden | Kurallar |
|
||
| --- | --- | --- |
|
||
| `Static` | `DefaultValue` | Token'lar çözülür (aşağıda) |
|
||
| `Query` | `?name=…` | Yoksa: `IsRequired` → hata; değilse `DefaultValue` (token'lı) |
|
||
| `Path` | URL segmenti | `Path` alanı bir **desen**dir: `/orders/:id/` — `:name` hangi segmentteyse gerçek yoldan aynı sıradaki segment okunur |
|
||
| `Body` | JSON gövde ya da form (yalnız POST/PUT) | Yalnızca **kök seviye** özellikler okunur, ad eşleşmesi büyük/küçük harf duyarsızdır. `Path` alanı Body'de **kullanılmaz** — iç içe yol çözülmez, gövdeyi düz tut |
|
||
|
||
`Static`/varsayılan değerlerde çözülen token'lar (`DefaultValueHelper`): `@USERID`,
|
||
`@USERNAME`, `@ROLES`, `@DATE`, `@NOW`, `@DAY`, `@MONTH`, `@YEAR`, `@TENANTID`
|
||
(host bağlamında `= '@TENANTID'` deseni `IS NULL`'a çevrilir).
|
||
|
||
5. **Çalıştırma:** sorgu seçilen bağlantıda `QueryAsync(Sql, conn, param)` ile koşar; sonuç
|
||
satır dizisi olarak JSON döner. Hata → 500 + `{ Message, CorrelationId }` (iç hata mesajı
|
||
kullanıcıya sızdırılmaz). Sorgu, dönüşsüz bir `UPDATE`/`EXEC` de olabilir — `Method`'u
|
||
anlamına uygun seç.
|
||
|
||
```jsonc
|
||
// ParametersJson
|
||
[
|
||
{ "Type": "Query", "Name": "customerId", "DefaultValue": "", "Path": "", "IsRequired": true },
|
||
{ "Type": "Path", "Name": "id", "DefaultValue": "", "Path": "/orders/:id/", "IsRequired": true },
|
||
{ "Type": "Body", "Name": "note", "DefaultValue": "", "Path": "", "IsRequired": false },
|
||
{ "Type": "Static","Name": "userId", "DefaultValue": "@USERID", "Path": "", "IsRequired": true }
|
||
]
|
||
|
||
// PermissionsJson
|
||
[ { "ResourceType": "Role", "ResourceId": "SalesManager" },
|
||
{ "ResourceType": "User", "ResourceId": "ali.akman" } ]
|
||
```
|
||
|
||
Endpoint **tanımlarını** düzenleme yetkileri ListForm ekranının
|
||
`App.DeveloperKit.CustomEndpoints{,.Create,.Update,.Delete}` yetkileridir — çağırma
|
||
yetkilerinden (`.Get/.Post/.Put/.Remove` + `PermissionsJson`) ayrıdır.
|
||
|
||
**Seed:** ekrandan girilen kayıt yalnızca veritabanında yaşar. Proje teslimi için kaydı
|
||
`sql/execute/{Ad}.sql` upsert prosedürüyle geri getirilebilir yap (§9.14 uyarısı); platform
|
||
varsayılanı ise çekirdeğin `Seeds/TenantData.json` → `CustomEndpoints` bloğundadır.
|
||
|
||
**Ne zaman CRUD, ne zaman Custom Endpoint?** Tek tabloya standart CRUD gerekiyorsa CRUD Endpoint
|
||
(otomatik üretilir, `crud/{Entity}.json` ile seed'lenir, filtre sözleşmesi hazırdır).
|
||
Birleştirilmiş/şekillendirilmiş sorgu, rapor, farklı kaynaktan okuma ya da tek atımlık yazma
|
||
gerekiyorsa Custom Endpoint. `EditorScript`'in `api()` çağrısı ve Visual Designer data
|
||
source'ları için de tercih edilen hedef budur.
|
||
|
||
### 9.3 Dynamic Service — çalışma zamanında derlenen C# servisi
|
||
|
||
Ekran: `/admin/developerkit/dynamic-services` (Monaco editörü). Entity: `DynamicService`
|
||
(`FullAuditedEntity` + `IMultiTenant`). Karar sırasının **3. adımıdır**: konfigürasyon ve SQL
|
||
yetmediğinde gelir, çekirdek kod değişikliğinden önce denenir.
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `Name` | Benzersiz servis adı (`DynamicCustomerAppService`) |
|
||
| `DisplayName` / `Description` | Kullanıcı dostu başlık ve açıklama |
|
||
| `Code` | Tam C# kaynak dosyası — `using`'ler ve `namespace` dahil |
|
||
| `ControllerName` | Swagger'da görünen ad (sınıf adından türetilir) |
|
||
| `PrimaryEntityType` | Kullandığı ana entity (bilgi amaçlı) |
|
||
| `IsActive` | Pasifse route'lanmaz; açılışta da yüklenmez |
|
||
| `CompilationStatus` | `0` Pending · `1` Success · `2` Failed · `3` InProgress |
|
||
| `LastCompilationError` / `LastSuccessfulCompilation` | Son derleme sonucu |
|
||
| `Version` / `CodeHash` | Kod her değiştiğinde `Version` artar, SHA-256 hash yenilenir, durum `Pending`'e döner |
|
||
|
||
#### 9.3.1 Kod sözleşmesi
|
||
|
||
Editörün başlangıç şablonu geçerli asgari iskelettir:
|
||
|
||
```csharp
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using Volo.Abp.Application.Services;
|
||
using Volo.Abp.Domain.Repositories;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
|
||
namespace DynamicServices
|
||
{
|
||
[Authorize]
|
||
public class DynamicCustomerAppService : ApplicationService
|
||
{
|
||
public virtual async Task<string> GetHelloWorldAsync()
|
||
=> await Task.FromResult("Hello World from Dynamic AppService!");
|
||
}
|
||
}
|
||
```
|
||
|
||
- Sınıf **`AppService` ya da `ApplicationService` ile bitmeli** ve `ApplicationService`
|
||
tabanından türemelidir; aksi hâlde derlense bile controller olarak keşfedilmez.
|
||
- Bağımlılıklar **constructor injection** ile gelir (`DynamicControllerActivator` çözer):
|
||
`IRepository<TEntity, TKey>`, `ICurrentTenant`, `ICurrentUser`, platform servisleri —
|
||
derleme referansları yüklü tüm `Volo.Abp`, `Sozsoft.Platform` ve `Microsoft.AspNetCore`
|
||
assembly'leridir, yani platform entity ve servislerinin tamamı görünürdür.
|
||
- **Route türetimi** (ABP konvansiyonu): sınıf adından `AppService` eki atılır → kebab-case;
|
||
metot adından `Async` eki atılır → kebab-case. `DynamicCustomerAppService.GetCustomersAsync`
|
||
→ `GET /api/app/dynamic-customer/get-customers`.
|
||
- **HTTP verb sezgisi:** `Get/Find/List*` → GET · `Create/Insert/Add*` → POST ·
|
||
`Update/Edit*` → PUT · `Delete/Remove*` → DELETE · diğerleri: parametresiz/basit
|
||
parametreli → GET, karmaşık parametreli → POST. Metodu buna göre adlandır.
|
||
|
||
**Güvenlik sınırları** — derleme öncesi **metin taraması** yapılır ve şunlar reddedilir:
|
||
`System.IO`, `System.Diagnostics`, `System.Environment`, `System.Net.Sockets`,
|
||
`System.Reflection.Emit`, `System.Runtime.InteropServices`, `Microsoft.Win32`,
|
||
`System.Security.Cryptography`, `System.Net.NetworkInformation` namespace'leri; `Process`,
|
||
`File`, `Directory`, `FileStream`, `StreamWriter/Reader`, `Socket`, `TcpClient`, `UdpClient`,
|
||
`HttpWebRequest`, `WebClient`, `Assembly.Load`, `ThreadStart` tipleri; `unsafe` kod.
|
||
Tarama düz `Contains` ile çalışır — yasaklı bir ad **yorum satırında ya da başka bir
|
||
tanımlayıcının içinde geçse bile** (`Profile` içindeki `File` gibi) derleme reddedilir;
|
||
adlandırmanı buna göre seç. Dosya işi gerekiyorsa platformun dosya altyapısını, HTTP çağrısı
|
||
gerekiyorsa ABP'nin `IHttpClientFactory` soyutlamalarını kullan.
|
||
|
||
#### 9.3.2 Yaşam döngüsü
|
||
|
||
```
|
||
TestCompile ──► Publish ──► kuyruğa kayıt ──► assembly tenant bağlamıyla yüklenir
|
||
(Roslyn, (önce yine derler; (DynamicAssemblyRegistrationService,
|
||
hata satır/ başarısızsa yayınlamaz) arka plan servisi)
|
||
kolonuyla │
|
||
UI'a döner) ActionDescriptorChangeProvider
|
||
MVC route tablosunu tazeler —
|
||
► uygulama YENİDEN BAŞLATILMAZ
|
||
```
|
||
|
||
- Uygulama açılışında `IsActive && CompilationStatus == Success` olan tüm servisler yeniden
|
||
derlenip yüklenir — kod veritabanında yaşar, assembly diske yazılmaz.
|
||
- Assembly'ler **tenant başına** izlenir; bir kiracının servisi diğerinde route'lanmaz.
|
||
Yeniden publish eski assembly kaydını düşürür, yenisini yükler.
|
||
- Derleme `Debug` seviyesinde, `allowUnsafe: false` ile yapılır; hatalar satır/kolon
|
||
bilgisiyle `LastCompilationError`'a ve UI'a düşer.
|
||
|
||
Yönetim yetkileri:
|
||
`App.DeveloperKit.DynamicServices{,.Create,.Edit,.Delete,.Manage,.TestCompile,.Publish,.ViewCode}` —
|
||
`Manage` aktif/pasif ve toplu işlemler, `ViewCode` kaynak görüntüleme kapısıdır. Servisin
|
||
**kendi uçlarının** yetkisi ise koddaki `[Authorize(...)]` özniteliğidir; sınıfı yetkisiz
|
||
bırakma, gerekiyorsa `PermissionsData.json`'a yeni yetki tanımla (CLAUDE.md kesişen değişiklik
|
||
kuralı).
|
||
|
||
**Seed:** `DynamicService` kayıtları yalnızca veritabanında yaşar; hiçbir seed dosyası okunmaz.
|
||
Veritabanı sıfırlanınca geri gelmesi gereken bir servis için `sql/execute/{Ad}.sql` upsert
|
||
prosedürü yaz (§9.14) — `Code` alanını string literal olarak taşır.
|
||
|
||
Yazarken `dotnet.instructions.md` §5 geçerlidir: tenant bağlamını `ICurrentTenant` üzerinden
|
||
kullan, SQL'i parametreli çalıştır, kullanıcıya görünen metni dil anahtarından ver,
|
||
sabit/secret gömme.
|
||
|
||
### 9.3.3 Ortak altyapı — Data Source çözümü (host + tenant bağlantıları)
|
||
|
||
ListForm'un `DataSourceCode`'u, Custom Endpoint'in `"!Tenant"`'ı, worker'ların `DataSourceCode`'u
|
||
ve widget sorguları — hepsi aynı kayıt kümesinden beslenir: **`Sas_H_DataSource`**
|
||
(ekran: `/admin/list/App.DataSource`). Kayıt üç alandır: `Code`, `DataSourceType`
|
||
(`1` Mssql · `2` Postgresql), `ConnectionString`.
|
||
|
||
Tabloda **iki tür kayıt yan yana durur**:
|
||
|
||
| Tür | `Code` | Nereden gelir |
|
||
| --- | --- | --- |
|
||
| Host / harici bağlantı | Elle verilen ad (`"Default"`, `"ErpReadOnly"`…) | Ekrandan ya da wizard'ın `DataSourceConnectionString` alanından |
|
||
| **Tenant bağlantısı** | **Kiracının adı** | Otomatik: Tenants ekranından bir kiracıya bağlantı dizesi tanımlanınca `TenantConnectionStringEventHandler` `Code = tenant adı` olan kaydı **ekler/günceller/siler**. Elle dokunma |
|
||
|
||
Çözüm kuralı (`DataSourceManager.GetDataSourceAsync(isTenant, code)`):
|
||
|
||
```
|
||
isTenant && CurrentTenant.Name != null → önce Code == {tenant adı} aranır
|
||
bulunamazsa / isTenant değilse → Code == {dataSourceCode} aranır
|
||
```
|
||
|
||
Pratik sonuçları:
|
||
|
||
- `IsTenant: true` bir ekran, kiracının **kendi** veritabanı kaydı varsa oradan okur; yoksa
|
||
ekranın `DataSourceCode`'una (çoğunlukla `"Default"`) düşer. Yani kiracıya özel veritabanına
|
||
geçiş, ekran tanımına dokunmadan Tenants ekranından yapılır.
|
||
- Custom Endpoint'te `DataSourceCode: "!Tenant"` aynı mekanizmanın kısa yoludur: çağıran
|
||
kullanıcının tenant bağlantısı kullanılır.
|
||
- `DataSourceType`, bağlantı metninde `Server` geçiyorsa `Mssql`, aksi hâlde `Postgresql`
|
||
olarak türetilir (wizard ve tenant event handler aynı kuralı kullanır).
|
||
- **Bağlantı dizesi hiçbir seed dosyasına, koda ya da örneğe yazılmaz** — yalnızca bu tabloda
|
||
ve kiracı kaydında durur; dokümana/örneğe yer tutucu yaz.
|
||
|
||
---
|
||
|
||
### 9.4 Developer Kit menüsü — tam kapsam
|
||
|
||
Menü: `App.DeveloperKit` (üst) → aşağıdaki alt ekranlar. Her satır bu dokümandaki bölümüne
|
||
bağlıdır; hiçbiri "kod yaz" adımının yerine geçmez, hepsi ondan öncedir.
|
||
|
||
| Ekran | Route | Yetki | Ne üretir | Bölüm |
|
||
| --- | --- | --- | --- | --- |
|
||
| **SQL Query Manager** | `/admin/sqlQueryManager` | `App.SqlQueryManager` | SQL nesneleri, CRUD endpoint'ler | 9.5 |
|
||
| **Custom Endpoints** | `/admin/list/App.DeveloperKit.CustomEndpoints` | `App.DeveloperKit.CustomEndpoints` | Elle yazılmış SQL'den REST endpoint | 9.2 |
|
||
| **Dynamic Services** | `/admin/developerkit/dynamic-services` | `App.DeveloperKit.DynamicServices` | Runtime derlenen C# AppService | 9.3 |
|
||
| **Components** | `/admin/developerkit/components` | `App.DeveloperKit.Components` | Custom Component + Visual Designer | 7 |
|
||
| **ListForm** | `/admin/list/App.Listforms.Listform` | `App.Listforms.Listform` | Var olan ekranların ham tanımı | 9.6 |
|
||
| **Wizard Manager** | `/admin/listform/wizardManager` | `App.Listforms.Wizard` | Wizard seed dosyalarının yönetimi, export/import | 4, 7.7 |
|
||
|
||
### 9.5 SQL Query Manager — bileşen bileşen
|
||
|
||
Tek ekranda toplanmış araç seti (`SqlQueryManager.tsx`):
|
||
|
||
| Panel / araç | Dosya | Yapabildikleri |
|
||
| --- | --- | --- |
|
||
| **Veri kaynağı seçici** | — | `DataSource` kayıtları arasında geçiş; SQL Server ve PostgreSQL |
|
||
| **Nesne gezgini** | `SqlObjectExplorer.tsx` | Tablolar, view'lar, stored procedure'ler, fonksiyonlar ve hazır şablonlar (`SqlObjectExplorerDto`); arama, nesne tanımını açma, kopyalama, silme, satır bazında **CRUD Endpoints** aksiyonu, çoklu seçimle toplu endpoint üretimi |
|
||
| **Sorgu editörü** | `SqlEditor.tsx` | Monaco tabanlı; çalıştırma (`ExecuteQueryAsync`), çoklu sekme, nesne tanımını editöre yükleme (`GetNativeObjectDefinitionAsync`), tablo create script'i üretme (`GetTableCreateScriptAsync`) |
|
||
| **Sonuç grid'i** | `SqlResultsGrid.tsx` | Sonuç kümesi görüntüleme ve dışa aktarma |
|
||
| **Tablo tasarımcısı** | `SqlTableDesignerDialog.tsx` | 6 adımlı diyalog: kolonlar (+ hazır şablon setleri) → ayarlar (ad üretimi) → index'ler → FK ilişkileri → CRUD seçimi → SQL önizleme; deploy'da seed dosyasını da yazar — **tam mekanik §8.1** |
|
||
| **View tasarımcısı** | `SqlViewDesignerDialog.tsx` + `sqlViewDesigner/` | Diagram + criteria grid + T-SQL önizleme; tek yönlü parse, ham SQL moduna düşme — **tam mekanik §8.2** |
|
||
| **CRUD endpoint diyaloğu** | `CrudEndpointDialog.tsx` | Endpoint üretme, aktif/pasif etme, test, silme; aynı diyalog Wizard'ın veri adımında da açılır | 9 |
|
||
| **DB Migrate** | `DbMigrateButton` | Migration + seed tetikleme (`App.Setup.Migrate`) |
|
||
| **File Manager kısayolu** | `/admin/files` | `configs/seeds` altındaki seed dosyalarının yönetimi |
|
||
| **Kolon bilgisi** | `GetTableColumnsAsync` | Wizard alan adımının ve tasarımcıların kolon kaynağı |
|
||
|
||
Servis yüzeyi (`SqlObjectManagerAppService`): `GetAllObjectsAsync`, `ExecuteQueryAsync`,
|
||
`GetNativeObjectDefinitionAsync`, `GetTableColumnsAsync`, `GetTableCreateScriptAsync`,
|
||
`SaveTableScriptAsync` (tablo seed'i), `DeleteSqlDataFilesAsync` (nesne silinince seed dosyası
|
||
da silinir).
|
||
|
||
Editör davranışları:
|
||
|
||
- Nesne gezgininden bir tanım editöre yüklendiğinde baştaki `CREATE`/`ALTER` **her zaman
|
||
`CREATE OR ALTER`'a normalize edilir** — aynı metin tekrar tekrar çalıştırılabilir kalsın diye.
|
||
- Hazır şablonlar: `select` / `insert` / `update` / `delete` / `create-view` /
|
||
`create-procedure` / `create-scalar-function` / `create-table-function`; aktif veri
|
||
kaynağının diyalektine göre (SQL Server / PostgreSQL) gelir.
|
||
- Sorgu, seçili **DataSource** üzerinde çalışır — tenant bağlamı otomatik değildir; yazdığın
|
||
DDL/DML hangi kaynağı seçtiysen oraya gider.
|
||
|
||
Kalıcılık kuralı (§8.2'deki uyarının özeti): **seed dosyasını otomatik yazan tek akış tablo
|
||
tasarımcısının deploy'udur.** View Designer deploy'u ve editörden çalıştırılan her DDL
|
||
(view/SP/function) yalnızca veritabanına gider; kalıcı olması için
|
||
`configs/seeds/{kapsam}/{sql|postgres}/{object|execute}/{ad}.sql` dosyası ayrıca yazılır.
|
||
Prompt ile üretimde bu ayrım seni etkilemez — dosyayı zaten sen yazarsın (§0.2 sırası) —
|
||
ama "uygulamadan ürettim, seed'i var mı?" sorusunun cevabı budur.
|
||
|
||
### 9.6 ListForm editörü — tam sekme envanteri
|
||
|
||
Wizard bir ekranı **üretir**; ince ayar `/admin/listform/edit/{listFormCode}` üzerinden yapılır
|
||
(kaynak: `edit/FormEdit.tsx` + `FormTab*.tsx`). Görünen sekmeler ekranın `ListFormType`'ına
|
||
bağlıdır (`tabVisibilityConfig`): **`Form` tipinde yalnızca** Detay, Veritabanı, Yetkiler,
|
||
Komutlar, Düzenleme, Alanlar, Form, Widget ve Alt Form sekmeleri açılır; `List` tipinde tamamı.
|
||
|
||
Her sekmenin düzenlediği kolonlar — alan alan sözleşmeleri `lowcode-reference` §1–§9'dadır:
|
||
|
||
| Sekme | Düzenlediği | Sözleşme |
|
||
| --- | --- | --- |
|
||
| **Detay** | `CultureName`, `ListFormType`, `Title`, `Name`, `Description`, `ShowNote`, `Width`/`Height`/`FullHeight`, `IsSubForm`, `SubFormsListFormType`, `LayoutJson` (varsayılan + bayraklar) | ref §5.4 · §5.6 |
|
||
| **Veritabanı** | beş alt sekme, aşağıda | ref §1 · §4 |
|
||
| **Yetkiler** | `PermissionJson` — C/R/U/D/E/I/N yetki adları | ref §5.1 |
|
||
| **Komutlar** | `CommandColumnJson` satır ekle/düzenle diyaloğu | §4.9 |
|
||
| **Sütunlar** | `ColumnOptionJson` (grid genel görünümü: kenarlık, satır rengi, kolon seçici, sabitleme, RTL…) | ref §2.1 |
|
||
| **Düzenleme** | `EditingOptionJson` + `Popup` alt nesnesi | ref §3.1 |
|
||
| **Sıralama** | `SortMode` (Single/Multiple/None) | §3 |
|
||
| **Filtreleme** | `FilterRowJson`, `HeaderFilterJson`, `FilterPanelJson` | ref §2.6 |
|
||
| **Satır** | `RowJson` (yükseklik, metin kaydırma) | ref §2.2 |
|
||
| **Arama** | `SearchPanelJson` | ref §2.6 |
|
||
| **Gruplama** | `GroupPanelJson` | ref §2.6 |
|
||
| **Seçim** | `SelectionJson` | ref §2.4 |
|
||
| **Sayfalama** | `PagerOptionJson` | ref §2.3 |
|
||
| **Durum** | `StateStoringJson` | ref §2.5 |
|
||
| **Ek Filtre** | `ExtraFilterJson` satır diyaloğu | §4.3.8 |
|
||
| **Form** | `EditingFormJson` — düzenleme formunun grup satırları | ref §3.2 |
|
||
| **Alanlar** | `ListFormField` kayıtları — alan başına 15 alt sekme, aşağıda | ref §9 |
|
||
| **Kişiselleştirme** | `ListFormCustomization` kayıtları: tür (`UserUiFilter`/`GridState`/`ServerJoin`/`ServerWhere`), ad, rol/kullanıcı, veri | ref §2.5 |
|
||
| **Pivot / Tree / Gantt / Scheduler / Todo** | `PivotOptionJson` / `TreeOptionJson` / `GanttOptionJson` / `SchedulerOptionJson` / `TodoOptionJson` | ref §6–§7 |
|
||
| **Alt Form** | `SubFormsJson` | ref §5.2 |
|
||
| **Widget** | `WidgetsJson` | ref §5.3 |
|
||
| **Workflow** | `WorkflowJson` + `ListFormWorkflow` düğümleri | §4.7 |
|
||
| **Grafik sekmeleri** (Ortak, Eksen, Bölme, Seri, Animasyon, Açıklama, Zoom, Legend, Crosshair, Export) | 22 grafik JSON kolonu | ref §8 |
|
||
|
||
**Veritabanı alt sekmeleri:**
|
||
|
||
| Alt sekme | Alanlar |
|
||
| --- | --- |
|
||
| Veri Kaynağı | `IsTenant`/`IsBranch`/`IsOrganizationUnit`, `DataSourceCode`, `SelectCommandType` + `SelectCommand`, `TableName` (alias), `KeyFieldName` + tipi |
|
||
| Select | `DefaultFilter`, `SelectFieldsDefaultValueJson` satır diyaloğu |
|
||
| Insert / Update / Delete | ilgili `*ServiceAddress`, `*Command`, `*BeforeCommand`, `*AfterCommand`, `*FieldsDefaultValueJson` |
|
||
|
||
**Alan editörü** (Alanlar sekmesinde bir satır açılınca; `form-fields/FormFieldEdit.tsx`) —
|
||
15 alt sekme; her biri `ListFormField`'ın bir kolonunu düzenler:
|
||
|
||
| Alt sekme | Kolon | Sözleşme |
|
||
| --- | --- | --- |
|
||
| Detay | `FieldName`, `CaptionName`, `PlaceHolder`, `BandName`, `AllowSearch`, `IsActive`, `Visible`, `SourceDbType`, `Alignment`, `Format`, `EditorOptions` (ham) | ref §9.7 |
|
||
| Seçenekler | `ListOrderNo`, `Width`, `SortIndex`/`SortDirection`, `AllowEditing`/`AllowAdding`, `EditorType2`, `EditOrderNo`/`EditGroupOrderNo`/`ColSpan` + **Script Builder** (`EditorScript`, §5) ve **Options Builder** (`EditorOptions`, §6) | ref §9.7 |
|
||
| Yetkiler | `PermissionJson` (Deny + C/R/U + E/I) | ref §5.1 |
|
||
| Lookup | `LookupJson` (tip, cascade, sorgu) | §4.3.4 |
|
||
| Kişiselleştirme | `ColumnCustomizationJson` (sabitleme) | ref §9.4 |
|
||
| Filtreleme | `ColumnFilterJson` | ref §9.1 |
|
||
| Başlık Filtresi | `ColumnHeaderJson` | ref §9.2 |
|
||
| Gruplama | `GroupingJson` | ref §9.3 |
|
||
| Grup Özeti / Toplam | `GroupSummaryJson` / `TotalSummaryJson` | ref §9.5 |
|
||
| Join | `JoinTableJson` | ref §9.6 |
|
||
| CSS | `ColumnCssClass` / `ColumnCssValue` | ref §9.7 |
|
||
| Doğrulama | `ValidationRuleJson` | §4.3.6 |
|
||
| Koşullu Biçim | `ColumnStylingJson` | §4.3.7 |
|
||
| Pivot | `PivotSettingsJson` | ref §6.2 |
|
||
|
||
Ayrıca `CustomJsSourcesJson` / `CustomStyleSourcesJson` ekran yüklendiğinde çalışacak JS/CSS
|
||
taşır — son çaredir, önce `EditorScript`/`EditorOptions` denenmelidir.
|
||
|
||
Editörün üstündeki dil/rol/kullanıcı seçicileri **varyant** düzenler (ref §5.7): seçim yapınca
|
||
aynı ekranın o bağlama özgü kopyası üzerinde çalışırsın.
|
||
|
||
> **Uyarı:** Bu ekrandan yapılan değişiklikler doğrudan veritabanına yazılır ve wizard seed
|
||
> dosyasına **yansımaz**. Kalıcı olması gereken bir değişikliği ya wizard'ı `EditFileName` ile
|
||
> yeniden çalıştırarak ya da seed dosyasını güncelleyerek yap. Prompt ile üretimde bu editör
|
||
> senin "hangi anahtar nerede" haritandır: kullanıcı "ekranda şunu değiştir" dediğinde önce bu
|
||
> tablodan hangi JSON kolonuna dokunacağını bul, sonra seed dosyasında o kolonu güncelle.
|
||
|
||
---
|
||
|
||
### 9.7 Menü Yönetimi ekranları
|
||
|
||
Menü: `App.Menus` (üst) → dört ekran. **Wizard bunların çoğunu senin yerine yapar**; bu ekranlar
|
||
elle düzeltme ve wizard'ın kapsamadığı düzenlemeler içindir.
|
||
|
||
| Ekran | Route | Yetki | Ne yapar |
|
||
| --- | --- | --- | --- |
|
||
| **Routes** | `/admin/list/App.Menus.Routes` | `App.Menus.Routes` | Fiziksel React sayfalarının route kayıtları (`Key`, `Path`, `ComponentType`, `ComponentPath`, `RouteType`, `Authority[]`) |
|
||
| **Modules** | `/admin/list/App.Menus.Module` | `App.Menus.Module` | Modül adları (`Sas`, `Adm` gibi üst kümeler); kök menü `ModuleId` ile buraya bağlanır |
|
||
| **Menu List** | `/admin/list/App.Menus.Menu` | `App.Menus.Menu` | Menü kayıtlarının ham listesi |
|
||
| **Menu Manager** | `/admin/menuManager` | `App.Menus.Manager` | Sürükle-bırak menü ağacı; sıra ve hiyerarşi düzenleme |
|
||
|
||
**`Menu` kaydının alanları** (wizard bunları üretir; elle eklemen gerekirse):
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `Code` | Benzersiz kod; yetki adının kökü |
|
||
| `DisplayName` | **Dil anahtarı** (düz metin değil) |
|
||
| `ParentCode` | Üst menü; boş ise kök menü |
|
||
| `Url` | Açılacak adres |
|
||
| `Icon` | react-icons adı |
|
||
| `Order` | Kardeşler arası sıra |
|
||
| `ModuleId` | Kısa rozet (`HR`, `MRP`) — kök menülerde kullanılır |
|
||
| `RequiredPermissionName` | Görünürlük yetkisi |
|
||
| `Target` / `CssClass` / `ElementId` / `IsDisabled` | Bağlantı davranışı ve görünüm |
|
||
| `UserId` / `RoleId` / `CultureName` | Kullanıcı, rol ve dil bazlı menü varyantı |
|
||
|
||
**Kurallar — prompt ile üretim yaparken:**
|
||
|
||
1. **Menüyü elle ekleme.** Yeni bir ekranın menüsü wizard seed dosyasından gelir; `MenusData.json`
|
||
yalnızca platformun kendi ekranları içindir ve orada da `Routes` bölümüne dokunulmaz.
|
||
2. **Route kaydı üretme.** Dinamik liste ekranları statik `/admin/list/:listFormCode` route'unu
|
||
kullanır; custom component'lerin route'u `RoutePath` alanından türetilir. `Route` tablosu
|
||
yalnızca fiziksel React sayfaları içindir ve prompt ile ekran üretirken böyle bir sayfa
|
||
yazılmaz.
|
||
3. **Menü sırası** için wizard'ın `MenuOrder` alanını kullan; Menu Manager'da elle taşımak seed
|
||
dosyasına yansımaz ve veritabanı sıfırlandığında kaybolur.
|
||
4. **Rol/dil bazlı menü varyantı** gerekiyorsa Menu List ekranından ikinci bir kayıt açılır —
|
||
aynı `Url`, farklı `RoleId`/`CultureName`. Wizard bunu üretmez.
|
||
|
||
---
|
||
|
||
### 9.8 Wizard Manager — layout matrisi
|
||
|
||
Wizard Manager platformun en yetenekli üretim ekranıdır: tek bir tanımdan **hem Custom Component
|
||
sayfası hem de yedi farklı görünümü olan List ekranı** üretir. Bir `List` wizard'ında birden fazla
|
||
görünüm aynı anda açık olabilir; kullanıcı ekranın üstünden geçiş yapar, tercihi kullanıcı bazlı
|
||
saklanır.
|
||
|
||
| Görünüm | Bayrak | `DefaultLayout` | Zorunlu option alanı | Ne zaman aç |
|
||
| --- | --- | --- | --- | --- |
|
||
| Grid | `Grid` | `"grid"` | — | Neredeyse her zaman; varsayılan |
|
||
| Card | `Card` | `"card"` | — | Görsel/özet kayıtlar, mobil kullanım |
|
||
| Pivot | `Pivot` | `"pivot"` | — | Sayısal kırılım/analiz ihtiyacı |
|
||
| Chart | `Chart` | `"chart"` | — (grafik ayarları ListForm editöründe) | Trend/dağılım |
|
||
| Tree | `Tree` | `"tree"` | `TreeOptionDto.KeyExpr` + `ParentIdExpr` | Kendine referanslı hiyerarşi |
|
||
| Gantt | `Gantt` | `"gantt"` | `GanttOptionDto.ParentIdExpr` + `TitleExpr` + `StartExpr` + `EndExpr` | Zaman çizelgesi, bağımlılıklı görevler |
|
||
| Scheduler | `Scheduler` | `"scheduler"` | `SchedulerOptionDto.TextExpr` + `StartDateExpr` + `EndDateExpr` | Takvim/randevu |
|
||
| TodoBoard (Kanban) | `Todo` | `"todo"` | `TodoOptionDto.TitleExpr` + `StatusExpr` | Durum akışı olan işler |
|
||
|
||
Kurallar:
|
||
|
||
- Zorunlu option alanı boşsa seeder o görünümün JSON'unu **yazmaz**; bayrak açık kalsa bile
|
||
görünüm çalışmaz. Açtığın her görünümün option bloğunu doldur.
|
||
- `DefaultLayout`, bayrağı `true` olan bir görünüm olmalıdır.
|
||
- Aynı veri birden fazla görünümde aynı `SelectCommand` üzerinden sunulur; ayrı sorgu yazılmaz.
|
||
- Bir ekranın hem liste hem pano hem takvim olarak istenmesi **tek wizard dosyasıdır** — ikinci
|
||
bir ekran açma.
|
||
|
||
Wizard Manager'ın kendi aksiyonları: liste/kart görünümü, arama, düzenleme (`EditFileName`),
|
||
silme, DB Migrate, Export (zip), Import (diff + rollback). Yetkiler
|
||
`App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}`.
|
||
|
||
### 9.9 Numaratör — `Sequence` (`App.Definitions.Sequence`)
|
||
|
||
"Sipariş numarası otomatik gelsin", "talep numarası her yıl 1'den başlasın" istekleri için kod
|
||
yazılmaz; bir `Sequence` kaydı açılır ve alanın varsayılan değeri `CustomValueType: 5` ile o
|
||
kayda bağlanır (§4.3.5).
|
||
|
||
Ekran: `/admin/list/App.Definitions.Sequence` · Yetki: `App.Definitions.Sequence{,.Create,.Update,.Delete}`
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `Name` | Varsayılan değerde referans verilen ad (`SiparisNo`) |
|
||
| `Prefix` / `Suffix` | Sabit ön/son ek |
|
||
| `PaddingSize` | Sayının soldan sıfırla doldurulacağı basamak (varsayılan `5`) |
|
||
| `StartNumber` / `NextNumber` / `IncrementStep` | Başlangıç, sıradaki değer, artış adımı |
|
||
| `ResetPeriod` | `0` None · `1` Yearly · `2` Monthly · `3` Daily · `4` Hourly · `5` Minutely |
|
||
| `LastPeriodKey` | Sıfırlamanın hangi dönemde yapıldığını tutar (`2026`, `202608`…). **Platform yönetir, elle yazma** |
|
||
| `FormatTemplate` | Varsayılan `{Prefix}{Number}{Suffix}` |
|
||
| `IsActive` | Pasif kayıt numara üretmez (istek hata verir) |
|
||
|
||
`FormatTemplate` token'ları — tarih token'ları .NET biçim harfleridir, **`M` ay / `m` dakikadır**:
|
||
|
||
| Token | Karşılığı |
|
||
| --- | --- |
|
||
| `{Prefix}` · `{Suffix}` | Sabit ön/son ek |
|
||
| `{Number}` | `PaddingSize` kadar soldan sıfırlanmış sıra numarası |
|
||
| `{yyyy}` · `{yy}` | Yıl (4/2 hane) |
|
||
| `{MM}` · `{M}` | **Ay** (2 hane / sıfırsız) |
|
||
| `{dd}` · `{d}` | Gün |
|
||
| `{HH}` · `{H}` | Saat |
|
||
| `{mm}` · `{m}` | **Dakika** |
|
||
| `{ss}` · `{s}` | Saniye |
|
||
|
||
Şablonda geçmeyen token yok sayılır; `{Number}` yazmayı unutursan numaratör sabit bir metin
|
||
üretir ve bunu kimse fark etmez — şablonu her zaman `{Number}` ile yaz.
|
||
|
||
Örnek — `SIP-2026-00042`: `Prefix = "SIP-"`, `PaddingSize = 5`, `ResetPeriod = 1` (Yearly),
|
||
`FormatTemplate = "{Prefix}{yyyy}-{Number}"`.
|
||
|
||
Numara **yazma anında** (Insert) alınır ve `NextNumber` ilerler; form açılışında yalnızca
|
||
önizleme istenir. Bu yüzden iptal edilen bir kayıt numarayı tüketebilir — boşluksuz numara
|
||
gerekiyorsa numarayı `InsertAfterCommand` ile SQL tarafında üret.
|
||
|
||
### 9.10 Zamanlanmış işler — `BackgroundWorker` (`App.BackgroundWorkers.RecurringJobs`)
|
||
|
||
"Her gece şu prosedür çalışsın", "her 5 dakikada bildirim kuyruğu boşalsın" istekleri.
|
||
Ekranlar: `App.BackgroundWorkers.RecurringJobs` (tanımlar) ve `App.BackgroundWorkers.Jobs`
|
||
(Hangfire çalıştırma kayıtları).
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `Name` | Benzersiz ad; dağıtık kilit (`DistributedLock`) bu adla alınır — aynı iş iki sunucuda aynı anda çalışmaz |
|
||
| `Cron` | Cron ifadesi (`*/5 * * * *`, `0 3 * * *`) |
|
||
| `WorkerType` | `1` MailQueueWorker · `2` SqlWorker · `3` NotificationWorker · `4` SessionCleanupWorker · `5` BackupWorker |
|
||
| `DataSourceCode` | `BeforeSp` / `AfterSp` hangi bağlantıda çalışacak |
|
||
| `BeforeSp` / `AfterSp` | İş gövdesinden **önce ve sonra** çağrılan stored procedure adları |
|
||
| `Options` | Tipe özel JSON (MailQueueWorker için kuyruk ayarları) |
|
||
| `IsActive` | Pasifse zamanlanmaz |
|
||
|
||
**Hangfire mekaniği** (`BackgroundWorkerInitializer` + `PlatformBackgroundWorker`) — tanımlar
|
||
Hangfire'a şöyle çevrilir:
|
||
|
||
- Aktif her kayıt bir **recurring job** olur: id `{WorkerType}:{Name}`, kuyruk `platform`,
|
||
zaman dilimi **Europe/Istanbul** — cron ifadeleri Türkiye saatine göre yazılır.
|
||
- Yükleme sırasında `platform` kuyruğunda kalan ama artık bir kayda denk gelmeyen job'lar
|
||
**silinir**; yani iş listesinin tek kaynağı bu tablodur, Hangfire panelinden elle job açma.
|
||
- `MailQueueWorker` tanımı yüklenirken `Options.MailTemplate` Razor şablonu worker adıyla
|
||
şablon deposuna da kaydedilir.
|
||
|
||
Her tetiklemede çalışma sırası:
|
||
|
||
1. `Name` ile **dağıtık kilit** alınır — alınamazsa (başka instance çalışıyor) sessizce çıkılır.
|
||
2. `BeforeSp` doluysa `DataSourceCode` bağlantısında çağrılır.
|
||
3. Tipe göre gövde: `MailQueueWorker` → kuyruğu gönderir (§11 · reference) ·
|
||
`SqlWorker`/`BackupWorker` → SQL gövdesi (backup hedefi `App:BackupPath` ayarı) ·
|
||
`NotificationWorker` → bildirim kuyruğunu kanallara dağıtır ·
|
||
`SessionCleanupWorker` → süresi geçen oturumları düşürür.
|
||
4. `AfterSp` doluysa çağrılır.
|
||
5. Hata `IExceptionNotifier`'a bildirilir ve loglanır — iş sessizce yutulmaz; çalıştırma
|
||
geçmişi `App.BackgroundWorkers.Jobs` ekranındadır (`ExecutionDate`, `Duration`, `Status`,
|
||
`FailureReason`, `ExceptionDetails`).
|
||
|
||
**Önemli:** `SqlWorker` ve `BackupWorker` tiplerinde asıl iş `BeforeSp`/`AfterSp`
|
||
prosedürlerindedir. Yani "her gece stok kapanışı" demek = `WorkerType: 2` +
|
||
`BeforeSp: "Wms_S_NightlyClose"`. Prosedürü `sql/object/` altına yazmayı unutma.
|
||
|
||
Tanımlar değiştikten sonra zamanlayıcıya yansıması için **"Zamanlanmış işleri yeniden üret"**
|
||
çalıştırılır: toolbar butonu `UiEvalService.ApiGenerateBackgroundWorkers()` (§9.13).
|
||
|
||
**Otomasyon reçetesi** — "belirli zamanda şu iş yapılsın" taleplerinin tamamı bu üçlüyle
|
||
kurulur, kod yazılmaz:
|
||
|
||
| Talep | Kurulum |
|
||
| --- | --- |
|
||
| Zamanlanmış SQL işi (kapanış, özet tablo, temizlik) | `WorkerType: 2` + `BeforeSp` + `sql/object/` prosedürü |
|
||
| Zamanlanmış e-posta (rapor maili, hatırlatma) | `WorkerType: 1` + `Options` (Mail şablonu + `TableName`) + kuyruğa satır yazan `BeforeSp` |
|
||
| Olay bazlı bildirimlerin dağıtımı | `WorkerType: 3` (kuyruğu boşaltır) + Notification Type/Rule kayıtları (§9.11) |
|
||
| Oturum temizliği / yedekleme | `WorkerType: 4` / `5` (+ `App:BackupPath`) |
|
||
|
||
**Zamanlanmış e-posta (`MailQueueWorker`)** — "her sabah geciken siparişler listesi şu kişilere
|
||
mail atılsın" isteğinin yolu budur. Worker'ın `Options` alanı bir `MailQueueWorkerOptions`
|
||
JSON'udur:
|
||
|
||
```jsonc
|
||
{ "MailType": "OrderDelay", "MailSubject": "Geciken Siparişler",
|
||
"MailTemplate": "<h3>Geciken siparişler</h3>@Model.Tables[0]",
|
||
"TableName": "{0:MT3_GECIKEN_SIPARIS:IN:Order Delay Notification:0:}" }
|
||
```
|
||
|
||
- `MailTemplate` Razor ile render edilir (`EmailLayout` düzeni içinde).
|
||
- `TableName` maildeki tabloları tanımlar; `IN` gövdeye gömülür, `PDF`/`XLS`/`TXT` ek dosya olur.
|
||
- Gönderilecek satırlar `BackgroundWorker_MailQueue` kuyruk tablosuna yazılır
|
||
(`To`, `MailParameter`, `TableName`, `TableParameter`, `Attachment`, `RelatedRecordId`);
|
||
kuyruğa yazma işi genellikle `BeforeSp` prosedüründe yapılır. Ayrıntılı biçim
|
||
`api/modules/Sozsoft.MailQueue` modülündedir.
|
||
|
||
### 9.11 Bildirimler (`App.Notifications.*`)
|
||
|
||
Üç ekran: **Notification Types** (bildirim türü tanımı), **Notification Rules** (kim, hangi
|
||
türde, hangi kanaldan alacak), **Notifications** (üretilmiş bildirimler / geçmiş).
|
||
|
||
| `NotificationRule` alanı | Anlam |
|
||
| --- | --- |
|
||
| `NotificationTypeId` | Hangi tür olay (`YeniSiparis`, `SiparisOnaylandi`…) |
|
||
| `RecipientType` + `RecipientId` | `User` / `Role` / `OrganizationUnit` + kullanıcı adı, rol adı ya da birim kodu |
|
||
| `Channel` | `Mail`, `Sms`, `WhatsApp`, `Rocket`, `Desktop`, `UiToast`, `UiActivity` |
|
||
| `IsActive` | Kural açık mı |
|
||
| `IsFixed` | Kullanıcı profilinden kapatılamaz (zorunlu bildirim) |
|
||
| `IsCustomized` | Kullanıcı kendi tercihini yazmış |
|
||
|
||
Bildirim üretme yolu: bir `Notification` kaydı oluşur (tür, kural, alıcı, mesaj) ve
|
||
`NotificationWorker` (§9.10) kuyruğu kanala göre gönderir. Kayıt SQL tarafından
|
||
(`InsertAfterCommand`, prosedür) ya da Dynamic Service'ten yazılabilir.
|
||
|
||
Onay akışındaki **`Inform` düğümü e-posta gönderir** (MailQueue üzerinden); UI bildirimi
|
||
istiyorsan ayrıca bir bildirim kuralı gerekir.
|
||
|
||
### 9.12 Raporlar (`App.Reports.*`)
|
||
|
||
Raporlama DevExpress XtraReports tabanlıdır ve iki katmanı vardır:
|
||
|
||
| Katman | Ne |
|
||
| --- | --- |
|
||
| **Hazır (predefined) raporlar** | `DynamicGrid`, `DynamicTree`, `DynamicForm` — o anki ekranın sütunlarını, filtrelerini ve verisini alıp yazdırılabilir çıktı üretir |
|
||
| **Rapor şablonları** | `App.Reports.ReportTemplates` + `App.Reports.Categories` ekranlarındaki kayıtlar; tasarımcıda çizilir, adıyla açılır |
|
||
|
||
Route'lar: görüntüleyici `/admin/reports/{report}/view/{id?}/{listFormCode?}`, tasarımcı
|
||
`/admin/reports/{report}/design/{id?}/{listFormCode?}`.
|
||
|
||
- Her List ekranının araç çubuğundaki rapor aksiyonu `DynamicGrid` (TreeList'te `DynamicTree`)
|
||
raporunu **o anki filtrelerle** açar; bunun için hiçbir tanım yapılmaz.
|
||
- Kendi düzeni istenen çıktı (fatura, teklif, iş emri) için `ReportTemplate` kaydı açılır ve
|
||
tasarımcıdan çizilir; ekrana `CommandColumnJson` butonu ile bağlanır
|
||
(`Url: "/admin/reports/{ReportAdi}/view/@Id"`).
|
||
- Rapor şablonları veritabanı kaydıdır; `configs/seeds/` altına düşmez (§9.14).
|
||
|
||
### 9.13 `UiEvalService` — buton ve script'ten çağrılabilen platform aksiyonları
|
||
|
||
`CommandColumnJson.OnClick` ve `EditorScript`'in `custom` tarifi `eval` ile çalıştığı için
|
||
platformun hazır aksiyonları buradan çağrılır. Uydurma metot yazma; kullanılabilir olanlar:
|
||
|
||
| Çağrı | Ne yapar | Sunucu yetkisi |
|
||
| --- | --- | --- |
|
||
| `UiEvalService.ApiGenerateBackgroundWorkers()` | Zamanlanmış iş tanımlarını yeniden üretir | — |
|
||
| `UiEvalService.ApiClearRedisCache()` | Dağıtık önbelleği temizler | — |
|
||
| `UiEvalService.ApiKickUser(userId, gridRef)` | Kullanıcının oturumunu düşürür, grid'i yeniler | — |
|
||
| `UiEvalService.ApiDbMigrate()` | Migration + seed akışını başlatır, log panelini açar | `App.Setup.Migrate` |
|
||
|
||
### 9.14 Platformun hazır gelen diğer konfigürasyon ekranları
|
||
|
||
Bir istek geldiğinde **önce bu listeye bak**: karşılığı olan bir ekran varsa yeni artefakt
|
||
üretilmez, var olan ekrandan kayıt girilir. Aşağıdaki tablo sık kullanılanların özetidir; Saas ve
|
||
Administration menülerindeki **ekranların tamamı, tabloları, kapsamı ve hangi talepte
|
||
kullanılacağı** `lowcode-reference.instructions.md` §12'dedir.
|
||
|
||
| İstek | Ekran / kayıt | Route |
|
||
| --- | --- | --- |
|
||
| "Belge numarası otomatik olsun" | Sequence (§9.9) | `/admin/list/App.Definitions.Sequence` |
|
||
| "Şu iş her gece çalışsın" | Recurring Jobs (§9.10) | `/admin/list/App.BackgroundWorkers.RecurringJobs` |
|
||
| "Şu olayda bildirim gitsin" | Notification Types + Rules (§9.11) | `/admin/list/App.Notifications.NotificationRules` |
|
||
| "Fatura/teklif çıktısı" | Report Templates (§9.12) | `/admin/list/App.Reports.ReportTemplates` |
|
||
| "Yeni bir veritabanı bağlantısı" | Data Source | `/admin/list/App.DataSource` |
|
||
| "Uygulama ayarı (e-posta, limit, açık/kapalı)" | Setting Definitions + Ayarlar ekranı | `/admin/list/App.SettingDefinitions` · `/admin/settings` |
|
||
| "Metin/çeviri değişsin" | Languages + Language Texts | `/admin/list/App.Languages.LanguageText` |
|
||
| "Yetki/rol/kullanıcı düzenlemesi" | Roles · Users · Permissions · Organization Units | `/admin/list/AbpIdentity.Roles` · `/admin/ous` |
|
||
| "Şube kırılımı" | Branches | `/admin/list/App.Branches` |
|
||
| "Kimin ne yaptığını görelim" | Audit Logs · Sessions · Activity Log | `/admin/list/App.IdentityManagement.AuditLogs` |
|
||
| "Belirli saatlerde/IP'lerden girilsin" | Work Hours · IP Restrictions | `/admin/list/App.Restrictions.WorkHour` |
|
||
| "Dosya yükleme/yönetme" | File Manager | `/admin/files` |
|
||
| "Global aramada çıksın" | Global Search kayıtları (`System`, `Group`, `Term`, `Weight`, `Url`) | `/admin/list/App.Definitions.GlobalSearch` |
|
||
| "Duyuru, anket, sosyal akış, etkinlik" | Intranet ekranları | `/admin/list/App.Intranet.*` |
|
||
| "Para birimi, ülke/il/ilçe, departman, ünvan, birim…" | Definitions ekranları | `/admin/list/App.Definitions.*` |
|
||
| "Menü düzeni" | Menu Manager (§9.7) | `/admin/menuManager` |
|
||
| "Genel siteye sayfa" | Public sayfa tasarımcıları | `/admin/public/{home\|about\|services\|contact}/designer` |
|
||
| "Yapay zekâ asistanı" | AI Bot tanımları | `/admin/list/App.Definitions.AiBot` |
|
||
|
||
> **Seed uyarısı — bu kayıtlar `configs/seeds/` altına düşmez.** Wizard, custom component, CRUD
|
||
> endpoint ve SQL nesneleri seed dosyası üretir; Sequence, BackgroundWorker, NotificationRule,
|
||
> ReportTemplate, GlobalSearch gibi kayıtlar **yalnızca veritabanında** yaşar. Veritabanı sıfırlanınca
|
||
> geri gelmesi gerekiyorsa iki yol vardır:
|
||
>
|
||
> 1. `sql/execute/{Ad}.sql` altında, kaydı **varsa güncelleyen/yoksa ekleyen** bir prosedür yaz
|
||
> (§8.4 sözleşmesi) — proje kapsamındaki doğru yol budur.
|
||
> 2. Platformun kendi varsayılanıysa `api/src/Sozsoft.Platform.DbMigrator/Migrations/HostData.json`
|
||
> (`AiBots`, `Settings`, `NotificationTypes`, `NotificationRules`, `BackgroundWorkers`,
|
||
> `Currencies`, `ContactTitles`) — bu dosya çekirdek koda aittir, proje talebi için tercih edilmez.
|
||
>
|
||
> "Ekranı ürettim ama numaratörü/zamanlanmış işi de kurdum" diyorsan, o kayıtların seed karşılığını
|
||
> da yazmadıysan teslim eksiktir.
|
||
|
||
---
|
||
|
||
## 9.1. Artefakt G — Liste verisi seed dosyası (`data/{ListFormCode}.json`)
|
||
|
||
Ekrandan girilen kayıtların (widget tanımı, parametre tablosu, sabit liste) veritabanı
|
||
sıfırlandığında geri gelmesi için kullanılır. Bağlantı ListForm kaydındaki `SeedFilePath`
|
||
alanıdır; boşsa ne geri yükleme ne de senkronizasyon yapılır. Ekrandaki hangi işlemin dosyaya
|
||
yansıyacağını `SeedSyncInsert`, `SeedSyncUpdate` ve `SeedSyncDelete` bayrakları belirler — üçü de
|
||
kapalıyken dosya yalnızca seed sırasında okunur, hiç yazılmaz.
|
||
|
||
```jsonc
|
||
{
|
||
"ListFormCode": "App.DeveloperKit.IntranetWidgets",
|
||
"KeyFieldName": "Id",
|
||
"GeneratedAt": "2026-09-04T00:00:00Z",
|
||
"Rows": [
|
||
{ "Id": "542e0ab0-…", "Code": "documents", "Column": 0, "Order": 20, "IsActive": true }
|
||
]
|
||
}
|
||
```
|
||
|
||
| Alan | Sözleşme |
|
||
| --- | --- |
|
||
| `ListFormCode` | Verinin ait olduğu ekran; dosya adıyla aynı olmalı. Boşsa dosya adı kullanılır |
|
||
| `KeyFieldName` | Satırların eşleştiği anahtar alan; boşsa `ListForm.KeyFieldName` |
|
||
| `Rows` | Ekranın select alanlarından oluşan satırlar. Enum'lar **sayı** olarak yazılır (SQL kolonu ne ise o); dil anahtarları `::` öneki olmadan yazılır |
|
||
|
||
Kurallar:
|
||
|
||
- **Anahtar dosyada durur.** Insert varsayılanları anahtarı `@NEWID` ile üretir; `ListFormSeedDataApplier`
|
||
dosyadaki anahtarı geri yazar, böylece aynı dosya birden çok kez uygulandığında kopya kayıt oluşmaz.
|
||
Elle dosya yazarken anahtarı deterministik üret (ör. koddan türeyen GUID).
|
||
- **Uygulama yalnızca ekler:** anahtarı veritabanında bulunmayan satırlar `INSERT` edilir; var olan
|
||
kayıt güncellenmez (her migrate'te ekrandaki güncel değerler dosyadaki eskisiyle ezilmesin diye)
|
||
ve dosyada olmayan satır silinmez.
|
||
- **Yazma yönü bayrağa bağlıdır:** `list-form-data/*` ve `list-form-dynamic-api/*` uçlarında
|
||
insert → `SeedSyncInsert`, update → `SeedSyncUpdate`, delete → `SeedSyncDelete`; ImportManager
|
||
toplu yüklemesi `SeedSyncInsert` bayrağına bakar ve dosyayı listenin tamamıyla yeniden yazar.
|
||
- Audit/tenant kolonları dosyaya yazılmaz; `InsertFieldsDefaultValueJson` + kapsam üretir.
|
||
|
||
## 10. Uçtan uca reçeteler
|
||
|
||
### 10.1 "Var olan bir tablo için ekran aç"
|
||
|
||
1. Tablo yoksa SQL Table Designer ile oluştur (varsayılan kolonlarla).
|
||
2. Ekran birden çok tablodan besleniyorsa View Designer ile view üret.
|
||
3. Wizard: menü/kimlik → veri ayarları (`SelectCommandType` + `SelectCommand` + anahtar) →
|
||
alanlar → (gerekiyorsa) alt form/widget/workflow → yayınla.
|
||
4. Hesaplanan alanlara `EditorScript`, biçimli alanlara `EditorOptions` ver.
|
||
5. Doğrula: menü görünüyor mu, yetkiler roldeki kullanıcı için doğru mu, tenant filtresi
|
||
çalışıyor mu, seed dosyası oluştu mu.
|
||
|
||
### 10.2 "Ana-detay ekranı"
|
||
|
||
1. Detay ekranını **menüsüz wizard** ile üret (`CreateMenu: false`).
|
||
2. Ana ekranın `SubForms` listesine `ParentFieldName` → `ChildFieldName` eşlemesini ve `DbType`
|
||
ekle.
|
||
3. İki ekranın yetkileri ayrıdır; detayın yetkisi verilmemişse sekme görünmez.
|
||
|
||
### 10.3 "Serbest yerleşimli sayfa / dashboard"
|
||
|
||
1. Gerekli endpoint'ler yoksa CRUD Endpoint üret.
|
||
2. Component Manager → yeni bileşen → Visual Designer:
|
||
`PageContainer` → `FlexRow` → içine `platform` düğümleri (`listFormCode` ile) ve/veya `Form`.
|
||
3. Veri kaynaklarını Data sekmesinden bağla; filtreleri `route`/`query`/`record` kaynaklarıyla
|
||
kur, `required` bayrağını bilinçli seç.
|
||
4. Görünürlük ve komut yetkilerini ver (`designerPermission`, `*Permission`).
|
||
5. Wizard `ComponentKind: 1` ile menüye bağla.
|
||
6. Doğrula: `custom/{Name}.json` ve ilgili `crud/*.json` dosyaları oluştu mu.
|
||
|
||
### 10.4 "Kanban panosu"
|
||
|
||
1. Tabloda durum kolonu olsun (`Status`).
|
||
2. Wizard'da `Todo: true`, `DefaultLayout: "todo"`, `TodoOptionDto` içinde `TitleExpr`,
|
||
`StatusExpr`, `StatusOrder` (kolon sırası), `AssigneeExpr`, `DueDateExpr` doldur.
|
||
3. Kolonlar `statusExpr` değerlerinden üretilir; kartlar sürüklenerek durum değiştirir, panodan
|
||
yeni kolon eklenebilir.
|
||
|
||
---
|
||
|
||
### 10.5 "Rapor sorgusunu API olarak aç"
|
||
|
||
1. Sorguyu SQL Query Manager'da yaz ve çalıştır, sonucu doğrula.
|
||
2. Tekrar kullanılacaksa view olarak deploy et (`sql/object/…`), tek seferlikse Custom Endpoint
|
||
içinde bırak.
|
||
3. Custom Endpoints ekranından kaydı oluştur: `Url`, `Method`, `DataSourceCode` (kullanıcının
|
||
kendi tenant veritabanı isteniyorsa `"!Tenant"`), `Sql`, `ParametersJson`, `PermissionsJson`.
|
||
4. Parametreleri `Query`/`Path`/`Body` olarak tanımla; hiçbirini SQL'e string birleştirme ile
|
||
koyma.
|
||
5. Erişimi `PermissionsJson` ile daralt; dispatcher kapısının (`…CustomEndpoints.Get` vb.)
|
||
ilgili rolde açık olduğunu doğrula.
|
||
|
||
### 10.6 "Konfigürasyonla çözülemeyen iş mantığı"
|
||
|
||
Eksik olan bir **bileşen olayı/prop'u/küçük yeteneğiyse** bu reçete değil §7.3'teki not
|
||
geçerlidir: var olan bileşene eklenir, yeni bileşen ya da Developer Kit ekranı üretilmez.
|
||
|
||
1. Önce gerçekten çözülemediğini göster: ListForm + SQL + Custom Endpoint neden yetmiyor,
|
||
bir iki cümleyle yaz.
|
||
2. Dynamic Service oluştur; `PlatformAppService` türet, `[Authorize(...)]` ile koru.
|
||
3. **TestCompile** → hatasız → **Publish**. `CompilationStatus` `Success` olmadan yayınlama.
|
||
4. Swagger'da `ControllerName` altında göründüğünü doğrula.
|
||
5. Ekran tarafında bu servisi ya ListForm'un `*ServiceAddress` alanından ya da Custom
|
||
Component'in bir data source'undan tüket.
|
||
|
||
### 10.7 Şablon senaryo — "onaylı talep formu" (avans, izin, masraf, satın alma…)
|
||
|
||
Bu tür isteklerin tamamı aynı iskeleti kullanır. Kullanıcı "avans talep formu istiyorum" dediğinde
|
||
üretilecek dosya seti:
|
||
|
||
```
|
||
configs/seeds/host/
|
||
├── sql/object/Hr_T_AdvanceRequest.sql ← tablo (+ PostgreSQL hedefse postgres/object/…)
|
||
├── crud/Hr_T_AdvanceRequest.json ← yalnızca Custom Component yolunda gerekir
|
||
├── custom/AdvanceRequestForm.json ← yalnızca serbest yerleşim isteniyorsa
|
||
├── wizard/AdvanceRequestApprovals.json ← (varsa) menüsüz alt ekran, küçük SeededAt
|
||
└── wizard/AdvanceRequests.json ← ana ekran + menü + yetki + dil + onay akışı
|
||
```
|
||
|
||
**Karar:** Talep formu **standart bir kayıt listesi + düzenleme formu + onay akışı** ise
|
||
`ComponentKind: 0` (List) yeter ve `custom/` + `crud/` dosyalarına hiç gerek yoktur. Custom
|
||
Component yoluna yalnızca serbest yerleşim (özet kartlar, çok adımlı sihirbaz, gömülü panolar)
|
||
isteniyorsa geçilir.
|
||
|
||
**Adım adım:**
|
||
|
||
1. **Tablo** — iş kolonları + §8.1 varsayılanları. Onay akışı için dört kolon şarttır:
|
||
durum (`Status`), onaylayan (`ApproverId`), onay tarihi (`ApprovalDate`), açıklama
|
||
(`ApprovalNote`). Talep sahibi için de bir kolon (`EmployeeId`) bulunmalıdır.
|
||
2. **Wizard dosyası** — §4.4'teki iskelet:
|
||
- `IsTenant: true` (tabloda `TenantId` var), `IsDeletedField: true`, `IsCreatedField: true`
|
||
- `MenuParentCode` = modül kökü (`App.Hr`); yoksa wizard yaratır, `Order = max + 1`
|
||
- `PermissionGroupName` = `App.Hr`, EN + TR görünen adlarıyla
|
||
- Görünümler: `Grid` + `Card` açık; durum akışı görsel isteniyorsa `Todo: true` +
|
||
`TodoOptionDto` (§9.8)
|
||
3. **Alanlar** — `Groups` içinde iki grup mantıklıdır: "Talep" (tutar, tarih, açıklama) ve
|
||
"Onay" (durum, onaylayan, onay tarihi, onay notu). Onay grubundaki alanlar
|
||
`EditorOptions: "{\"readOnly\":true}"` ile kilitlenir; kullanıcı doldurmaz, akış doldurur.
|
||
4. **Hesap ve varsayılanlar** — `EditorScript` ile:
|
||
- Talep tarihi form açılışında bugün: `today` tarifi, tetikleyici `open`
|
||
- Tutar × oran gibi hesaplar: `multiply` / `percent`
|
||
- Eşik üstü tutarda uyarı: `notify` ya da `ask`, koşulu `greaterThan`
|
||
5. **Onay akışı** — §4.7'deki `WorkflowDto`. Tipik grafik:
|
||
`Start → Compare (tutar eşiği) → Approval (yönetici) / Approval (üst yönetici) → Inform → End`.
|
||
`ApprovalIsFilterUserName: true` ile herkes yalnızca kendi onayına düşenleri görür,
|
||
`ApprovalIsResetWorkflow: true` ile kayıt güncellenince akış başa döner.
|
||
6. **Yetki** — wizard `App.Hr.AdvanceRequests` kökünü ve `.Create/.Update/.Delete/.Export/
|
||
.Import/.Note` alt yetkilerini üretir. Onaylayanların rolüne kök + `.Update` verilir;
|
||
talep sahiplerine kök + `.Create` yeter.
|
||
7. **Dil** — menü/başlık/açıklama metinleri wizard dosyasındaki `Language` bölümünden,
|
||
alan başlıkları her `Items` öğesinin `En`/`Tr` alanlarından üretilir.
|
||
Ayrıca `LanguagesData.json`'a dokunmaya gerek yoktur.
|
||
8. **Teslim** — dosyaları listele, DB Migrate gerektiğini söyle, §11 kontrol listesini geç.
|
||
|
||
**Bu iskeletin türevleri:** izin talebi (tutar yerine gün sayısı, `days` tarifi), masraf talebi
|
||
(alt form olarak masraf kalemleri → menüsüz wizard + `SubForms`), satın alma talebi (kalemler +
|
||
tedarikçi lookup'ı + iki kademeli onay).
|
||
|
||
---
|
||
|
||
## 11. Teslim öncesi kontrol listesi
|
||
|
||
- [ ] Menü, route ve ekran sözleşmesi birbirini gösteriyor.
|
||
- [ ] Kök yetki + aksiyon yetkileri var; menü `RequiredPermissionName` ile bağlı.
|
||
- [ ] Yetki grubunun EN + TR görünen adı var.
|
||
- [ ] Kullanıcıya görünen her metnin EN + TR dil anahtarı var; anahtar tekrar edilmemiş.
|
||
- [ ] `IsTenant` (ve gerekiyorsa `IsBranch`/`IsOrganizationUnit`) doğru; sorgular tenant güvenli.
|
||
- [ ] Tüm SQL parametreli; string birleştirme yok.
|
||
- [ ] `DeleteCommand = DefaultDeleteCommand("{Tablo}")`.
|
||
- [ ] Seed dosyası oluştu; `InsertedRecords` yalnızca gerçekten yaratılan kayıtları içeriyor.
|
||
- [ ] Bağımlılıklar (custom component, crud endpoint, sql nesnesi) da seed'li.
|
||
- [ ] Yalnızca veritabanında yaşayan konfigürasyon (Sequence, zamanlanmış iş, bildirim kuralı,
|
||
rapor şablonu) üretildiyse `sql/execute/` altında geri getirici script'i de yazıldı (§9.14).
|
||
- [ ] Export zip'i başka bir ortamda açıldığında ekran ayağa kalkıyor.
|
||
- [ ] Geri alma yolu yazılı: wizard dosyasını sil → `InsertedRecords` temizlenir.
|
||
|
||
Prompt ile üretim yaptıysan ek olarak:
|
||
|
||
- [ ] Dosyalar doğru kapsam klasöründe (`host/` ya da `tenants/{tenantId}/`).
|
||
- [ ] Üretim sırası doğru (§0.2) ve bağımlı wizard'ın `SeededAt` damgası daha büyük.
|
||
- [ ] Tablo/kolon adları uydurulmadı; ya var olan nesneden okundu ya da `sql/object/` altında
|
||
üretildi.
|
||
- [ ] `KeyFieldDbSourceType`, tablodaki `Id` tipiyle uyumlu.
|
||
- [ ] Açılan her görünümün zorunlu option alanı dolu (§9.8).
|
||
- [ ] Custom component'te route yetkisiz üretildiği için koruma bileşenin içinde
|
||
(`designerPermission` / `*Permission` / `checkPermission`).
|
||
- [ ] Custom component visual modda ise `Props.visualDesigner` ile `Code` marker'ı aynı dokümanı
|
||
taşıyor.
|
||
- [ ] Menü ve route elle eklenmedi; wizard dosyasından geliyor.
|
||
- [ ] Kullanıcıya hangi dosyaların üretildiği ve DB Migrate gerektiği söylendi.
|
||
- [ ] Keşif turu işletildi (§0.6): Tur 1 soruları soruldu, yol için gerekçe verildi ve tarif
|
||
üretimden **önce** onaylatıldı — kullanıcı "sorma / sen seç" demedikçe.
|
||
- [ ] Tarifte varsayılanla doldurulan satırlar `(varsayılan)` olarak işaretlendi ve üretim
|
||
sonunda ayrıca hatırlatıldı (§0.6.6).
|
||
- [ ] Hangi tabloya yazılacağı, onaycılar ve hesap/iş kuralı formülleri varsayılmadı, soruldu
|
||
(§0.6.5).
|
||
- [ ] Görselden üretildiyse: platformda karşılığı olmayan öğeler ve yapılan varsayımlar
|
||
(tablo/kolon adları, anahtar tipi, lookup kaynakları) açıkça bildirildi (§0.7.4).
|
||
- [ ] Talebin hazır araçlarla karşılanamayan kısmı için **yeni bileşen üretilmedi**; var olan
|
||
bileşene eklenen olay/prop/küçük yetenek tarifte yazıldı ve onaylandı (§7.3).
|
||
- [ ] Bileşene ekleme yapıldıysa geriye dönük uyumlu: mevcut varsayılan/davranış değişmedi,
|
||
`componentProps.json` + (olaysa) `DESIGNER_PRIMARY_EVENTS` + reference §10.9/§10.10
|
||
güncellendi; `npm run typecheck` ve `npm run lint` temiz.
|
||
- [ ] Çerçeve dışına çıkan hiçbir değişiklik onaysız yapılmadı (§0.8); çerçeve içinde
|
||
yapılabilen her şey sorudan önce bitirildi.
|