204 KiB
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/{yyyyMMddHHmmss}_{WizardName}.json ← ekran + menü + yetki + dil anahtarları
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/{ts}_{Ad}.json |
Menü, yetki, dil anahtarı ve ListForm'u üretir |
| 5 | Alt ekranlar (varsa) | ek wizard/… dosyaları |
Ana wizard'dan önceki zaman damgasını alır |
Zaman damgası bir bağımlılık aracıdır. WizardDataSeeder wizard/ klasörünü dosya adına
göre sıralı işler. Bir wizard başka bir wizard'ın ürettiği menüye/ListForm'a dayanıyorsa,
bağımlı olanın zaman damgası daha büyük olmalıdır. Alt form olarak kullanılacak menüsüz
ekranı her zaman önce numaralandır.
0.3 Yazmadan önce zorunlu keşif
Uydurma. Şu üçünü mutlaka kontrol et:
- Tablo/kolon adları —
sql/object/altındaki dosyalar ya da mevcut wizard dosyalarındakiSelectCommand. Kolon adını tahmin etme; yoksa tabloyu da sen üret. - 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/PermissionGroupNamedeğerlerini tara. - Dil anahtarı çakışması —
LanguagesData.jsonve 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
EditFileNameile 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 böyle bir alan yoktur (zaman damgası dosya adındadı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ı
- Soruları toplu sor. Tek tek değil,
AskUserQuestionile turda en fazla 4 soru; her sorunun 2–4 seçeneği ve önerilen bir varsayılanı olsun ((Önerilen)). Kullanıcı okumadan onaylayabilmelidir. - 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.
- 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.
- Her soru bir üretim kararını değiştirmelidir. Cevabı ne olursa olsun aynı dosyayı üreteceksen o soruyu sorma.
- 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.
- 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) |
- 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
platformdüğü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ı
- Sayfa iskeleti — kaç bölge var? Üstte KPI şeridi, ortada liste, sağda panel, altta sekme?
- Ana bölge ne? Tablo mu, kart ızgarası mı, pano mu, takvim mi, form mu?
- Araç çubuğu — hangi butonlar var, ne yazıyorlar, ikonları ne?
- Sütunlar — başlıklar, hizalama, biçim (para, tarih, yüzde), rozet/renkli durum hücreleri.
- Filtre/arama — başlık altı filtre satırı, arama kutusu, tarih aralığı seçicileri.
- Form alanları — kontrol tipleri ve kolon düzeni (kaç kolon, hangi alan kaç sütun kaplıyor).
- Satır aksiyonları — satır sonundaki ikonlar.
- 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ı | {yyyyMMddHHmmss}_{WizardName}.json |
20260826101500_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.dxLookupsabiti 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/{yyyyMMddHHmmss}_{WizardName}.json
Okuyan: WizardDataSeeder · Yazan: ListFormWizardAppService
4.1 Dosya iskeleti
{
"Wizard": { /* ListFormWizardDto — 4.2 */ },
"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": []
}
}
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
Kimlik ve yol
| Alan | Anlam |
|---|---|
ComponentKind |
0 List (varsayılan) · 1 Custom. Dosyadaki ilk alan olmalı. |
WizardName |
Dosya adının ve export zip adının kaynağı. |
ListFormCode |
Ekranın kodu; route ve metadata bu koda göre çözü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 |
|---|---|
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. |
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 |
|---|---|
PermissionGroupName |
Var olan grup ya da yeni grup adı. |
PermissionGroupDisplayNameEn / 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. |
LanguageTextMenuEn / Tr |
Menü etiketi. |
LanguageTextTitleEn / Tr |
Ekran başlığı (yalnızca List yolunda üretilir). |
LanguageTextDescEn / Tr |
Ekran açıklaması (yalnızca List yolunda). |
LanguageTextMenuParentEn / 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 |
Bağlantı kodu; varsayılan "Default". |
DataSourceConnectionString |
Yeni bir bağlantı tanımlanıyorsa; aksi hâlde boş. Asla gerçek kimlik bilgisi yazma. |
SelectCommandType |
Bkz. §3. |
SelectCommand |
Tablo/view adı, fonksiyon çağrısı ya da sorgu metni. |
KeyFieldName / KeyFieldDbSourceType |
Birincil anahtar ve tipi. |
Davranış
| Alan | Anlam |
|---|---|
IsTenant / IsBranch / IsOrganizationUnit |
Otomatik kırılım filtresi. Tabloda TenantId varsa IsTenant: true olmalı. |
AllowAdding / AllowUpdating / AllowDeleting / ConfirmDelete |
Toolbar ve satır aksiyonları. |
AllowDetail |
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 |
"grid", "card", "pivot", "chart", "tree", "gantt", "scheduler", "todo". |
Grid Card Pivot Chart Tree Gantt Scheduler Todo |
Hangi görünümlerin açık olduğu. Açtığın her görünümün option bloğunu da doldur. |
Görünüm option blokları — 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.
"Groups": [
{
"Caption": "Genel",
"ColCount": 2,
"Items": [ /* WizardColumnItemInputDto */ ]
}
]
Her Items öğesi:
| Alan | Anlam |
|---|---|
FieldName |
Veri kolonunun adı (SQL'deki adıyla birebir). |
CaptionName |
Dil anahtarı ya da düz başlık. |
TurkishCaption / EnglishCaption |
Dil metinleri; ikisi de doldurulur. |
EditorType |
Bkz. §3. |
EditorOptions |
DevExtreme editör JSON'u — §6. |
EditorScript |
Alan davranış script'i — §5. |
DbSourceType |
Bkz. §3. |
IsRequired |
Zorunluluk. |
IncludeInEditingForm |
false ise yalnızca grid sütunu olur, forma girmez. |
ColSpan |
Formda kaç kolon kaplar (grubun ColCount değeri içinde). |
LookupDataSourceType |
1/2/3 — bkz. §3. 0 = lookup yok. |
LookupQuery |
Query tipinde SQL; StaticData tipinde JSON dizisi; WebService tipinde URL. |
ValueExpr / DisplayExpr |
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 |
TurkishCaption / EnglishCaption |
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 (virgüllü) / ilişki tablosu | 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:
{
"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.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):
// İ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 |
[
{ "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:
{ "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/DefaultValueTypebirListFormFieldkolonu değildir. İstemcinin gördüğü bu iki alan,ListFormSelectAppService'in her metadata isteğindeFormFieldsDefaultValueJson'ı çö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.FieldDbTypeyanlış verilirse değerQueryHelper.GetFormattedValuetarafından sessizce yanlış biçimlenir — kolon tipiyle aynıDbTypenumarası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 |
"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 ` |
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.
[
{ "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).Selectseçenekleri yaItemsdizisinden gelir ya daSqlQueryalanına yazılan sorgudan; sorguKeyveValueadlı 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'dekifilterparametresiyleandile birleştirilir. Böylece filtreli ekran adresi paylaşılabilir. DefaultValuedolu 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ı
{
"Wizard": {
"ComponentKind": 0,
"WizardName": "Orders",
"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,
"LanguageTextMenuEn": "Orders", "LanguageTextMenuTr": "Siparişler",
"LanguageTextTitleEn": "Orders", "LanguageTextTitleTr": "Siparişler",
"LanguageTextDescEn": "Order list", "LanguageTextDescTr": "Sipariş listesi",
"LanguageTextMenuParentEn": "MRP", "LanguageTextMenuParentTr": "MRP",
"PermissionGroupName": "App.Mrp",
"PermissionGroupDisplayNameEn": "MRP", "PermissionGroupDisplayNameTr": "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": "OrderNo", "CaptionName": "App.Listform.ListformField.OrderNo",
"TurkishCaption": "Sipariş No", "EnglishCaption": "Order No",
"EditorType": "dxTextBox", "DbSourceType": 16,
"IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
"EditorOptions": "{\"showClearButton\":true,\"maxLength\":20}"
},
{
"FieldName": "CustomerId", "CaptionName": "App.Listform.ListformField.Customer",
"TurkishCaption": "Cari", "EnglishCaption": "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",
"TurkishCaption": "Miktar", "EnglishCaption": "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",
"TurkishCaption": "Birim Fiyat", "EnglishCaption": "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",
"TurkishCaption": "Tutar", "EnglishCaption": "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": []
}
}
EditorScriptalanındaki// @builderbaşlığı ile onu izleyen kod satırı birebir tutarlı olmalıdır. Elle yazmak yerine C# tarafındaEditorScript.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/GanttiçinParentIdExpr,ScheduleriçinTextExpr,TodoiçinTitleExprveStatusExprboşsa o görünümün JSON'u hiç yazılmaz — bayrağıtrueyapmak yetmez.- Anahtar alanı (
KeyFieldName)Groupsiçine koyarsan sütun olarak gizlenir ve forma girmez; yine de tanımlaman doğrudur, çünkü tip bilgisi oradan okunur. IsDeletedField: falseverirsen ekranda kalıcı silme olmaz (DeleteCommandnull 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:
- Wizard ekranından
EditFileNameile yeniden çalıştır (önce eski dosya veInsertedRecordskayıtları silinir, sonra yenisi üretilir) — tercih edilen yol. - ListForm editöründen ince ayar yap (seed dosyasına yansımaz, §9.6 uyarısı).
- İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır.
Listyolunda seeder ekranı uygularkenListForm,ListFormFieldveListFormWorkflowkayı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:
"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:
"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
}
]
SqlQueryboşsa widget hiç çizilmez;IsActive: falseolan da çalıştırılmaz.- Tek sorguyla birden çok kart üretmek için
UNION ALLkullan — üç durum için üç widget kaydı açmana gerek yok. - Widget sorgusu ekranın otomatik tenant/şube filtresine tabi değildir ve
@TENANTID/@USERIDgibi token'lar burada çözülmez (bağlanmamış parametre hatası verir). Kırılım gerekiyorsa kiracıya özel birDataSourcekullan 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. Dosya adının zaman 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.
"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:
Nile 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 dosyadaN1,N2yazman yeterlidir ve tekil olmaları şarttır. Comparedüğümünde çok dallı karar içinCompareOutcomeskullanılır; iki dallı basit karar içinNextOnTrue/NextOnFalseyeterlidir.ApprovaldüğümüNextOnApprove/NextOnReject,StartveInformdüğümleriNextOnStartalanını kullanır.Enddüğümünün çıkışı yoktur.Informdüğümü e-posta gönderir (MailQueue üzerinden,Approveralanı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).CompareValuesayısaldır (decimal); metin karşılaştırması bu düğümle yapılmaz.Titletekil olmalıdır; seeder tekrar eden başlıkları ayrıştırır ama okunabilirliği bozar.- Akış varsa ListForm
SelectionMode = singleolur 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;
CustomJsSourcesve kod yazmak ondan sonra gelir. Bu kolonların alan alan sözleşmesilowcode-reference.instructions.mdiç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:
[
{
"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:
AuthNameboş 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.TextveHintdil anahtarıdır; çeviri::öneki eklenerek çözülür. Düz metin yazma.- Toolbar butonu seçili satırlarla çalışır;
SelectionJsonuygun modda (single/multiple) olmalıdır. İş akışı varsa wizard bunu zatensingleyapar. - Satır bazlı aksiyon istiyorsan
ButtonPosition: 0kullan; buton komut sütununda görünür. IsVisibleveVisibleExpressionyalnızca satır butonlarında (ButtonPosition: 0) çalışır; toolbar butonu her zaman görünür (görünürlüğüAuthNamebelirler).VisibleExpressionbir ok fonksiyonudur veevaledilir:"(e) => e.row.data.Status === 'Taslak'".- URL token'ları: satır butonunda
@LISTFORMCODEve 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.DialogParametersboşsa diyalog hiç açılmaz. DialogNamekayı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ıCommandColumnJsonile 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, LanguageTextMenuEn/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ğı, LanguageTextTitleEn/Tr, LanguageTextDescEn/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:
- Konfigürasyon doğrulama
- Menü kaydı (
CreateMenu: falseise atlanır) - Dil metinleri
- İzin grubu + yetkiler
- List yolunda: veri kaynağı → ListForm → alan grupları · Custom yolunda: bileşen bağlama
- 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/awaitgeç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
andya daorile birleşir (tek bağlaç, kural genelinde). Her koşuloperatö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.readOnlytarifinde iseconditionIsArgumentişaretlidir: koşulifolmaz, 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
visibleyardımcısı yoktur; koşullu görünürlük yerinereadOnlykullan, kalıcı gizlemeEditorOptions.visibleya daIncludeInEditingForm'dur. - Lookup sorgusu değiştirilemez; cascade davranışı ListForm lookup ayarlarından kurulur.
- Sunucu tarafı doğrulama değildir.
ask/notifyyalnı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:
Xalanının script'i yalnızcaXdeğ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.
// 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.
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:
EditorScript = EditorScript.Days("StartDate", "EndDate", "DayCount");
Eşleme kuralı:
scriptRecipes.tsile 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.tssözlüğünden üretilir; alanlar gruplara ayrılır, arama kutusu yol ve etiket üzerinde çalışır. - Alanlar kolonun
EditorType2değ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).
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.
.Rawdeğeri geçerli JSON olmalıdır; doğrulanmaz.- Hiç seçenek yoksa
Build()boş metin döner — alannullkalı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
{
"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"
}
]
}
]
}
DataSourceselle yazılmaz: kaydetme sırasındaCustomComponentDataSourceResolvertasarımcı dokümanındakidataSourceslistesinimethod + pathile CRUD endpoint kataloğunda arayarak üretir. EşleşenlerEntityName+SeedFiletaşı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.OperationTypedeğ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 daLanguagesData.json'da tanımla. - Veriye erişimde
apiServicekullan; taban adres, token ve tenant başlığı ondan gelir. Örneklerde gördüğünaxios.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'uauthority: []ile üretilir (§7.6 uyarısı) — kontrol bileşenin içindedir. - Başka bir custom component'i JSX olarak kullanabilirsin; adını
Dependencieslistesine ekle.
7.2 Tasarımcı dokümanı (Props.visualDesigner)
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: truevarsayı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,variantdeğ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,refAPI'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östermiyorOlaysa: bileşen DESIGNER_PRIMARY_EVENTSkaydındaysa panel yalnız o dizideki olayları gösterir →catalog.ts'te diziye olay adını ekle (isteğe bağlıDESIGNER_EVENT_SNIPPETSbaşlangıç script'i). Prop'sa önce filtrelere bak:HIDDEN_GENERATED_PROPERTIES(field,form) vecatalog.ts'teki bileşene özel elemeler (Select.items,Menu.variant,Table.columns/data,Checkbox.defaultChecked); filtrede değilsecomponentProps.jsongirdisi eksiktir, tamamlacomponents/ui/{Name}olayı/prop'u hiç taşımıyorAynı 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 geliyorsaNAMED_UNION_OPTIONS(anahtar tsType), tek bileşene özel varsayılan/seçenek gerekiyorsaPROPERTY_OVERRIDES(anahtar bileşen → prop). Kod üreticisi (codeGenerator.ts) prop'u JSX niteliği olarak basar, olay içinhandle_{düğüm}_{olay}handler'ınınode.events'ten kendisi üretirScript Builder'da da kullanılacak designerScriptDialect.ts/designerScriptRecipes.ts'te olaycancelablemı, hangi tarifler listelenecek — ekle;refAPI'sine yeni metot eklediysen §7.8 tablosuSonrasında npm run typecheck+npm run lint;lowcode-reference.instructions.md§10.9 prop tablosu / §10.10 olay tablosu güncellenirSınır — bunlar yapılmaz: yeni
ui/data/layoutbileş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ı.
{
"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:
idbenzersiz 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.refscript'lerin adres defteridir; olay script'leri birbirinerefs.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,childrenvar olmalıdır (boş olsalar bile). kinddeğeri toolbox ailesiyle uyumlu olmalıdır; yanlışkinddüğümü çizdirmez.::öneki dil anahtarıdır. Metin taşıyan bir prop::ile başlıyorsa üretilen koddatranslate('::…')ç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").platformdüğümlerifiltersprop'u alır. Gömülü ListForm ekranını ana kayda ya da URL'ye bağlamak içinDesignerDataSourceFilterile aynı şekli kullanır;source: "record"ikenvalue"{formRef}.{Kolon}"biçimindedir ("form1.EmployeeId").required: trueise 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:
- Düğüm görünürlüğü —
props.designerPermissiondolu olan düğüm, yetki yoksa hiç render edilmez. Veriyi göremeyecek kullanıcıya boş bir Grid göstermek yerine bileşen çizilmez. - Form komutları —
selectPermission/insertPermission/updatePermission/deletePermission.Otomatikmod: temel, dokümandakipermissionCode'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,.Deleteson eklerini alır.Özelmod: 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:
{
"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",
"PermissionGroupDisplayNameEn": "MRP", "PermissionGroupDisplayNameTr": "MRP",
"LanguageTextMenuEn": "Order Board", "LanguageTextMenuTr": "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ı
RoutePathdeğerinden türetilir veauthority: []ile kaydedilir;/admin/ile başlayan yolprotected, diğerleripublicolur. 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:designerPermissionile düğümleri gizle, Form komutlarını*Permissionile kapat, kod modundacheckPermission()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/20260819131500_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:
permissionCodewizard'ın ürettiği yetkinin aynısıdır. Form komutlarıOtomatikmodda bu koda.Create/.Update/.Deleteekleyerek çözülür. Component'i yazarken bu değeri wizard'dakiMenuCodeile aynı yap.RoutePathile wizard'ınMenuUrldeğeri birebir aynı olmalıdır; farklıysa menü başka bir yere gider.- Component'in
DataSourceslistesicrud/*.jsondosyalarınaSeedFileile işaret eder — export zip'inin bu dosyaları da toplamasını sağlayan şey budur. - Wizard
Customyolunda olsa bileListFormCode,Grid/Card/Pivot/Chartbayrakları 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. platformdüğümündekilistFormCode(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
cancelableolaylarda listelenir; diğerlerinde üretilenreturn falsesessizce 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:
- Üretilen SQL hedef veri kaynağında çalıştırılır (yeni tabloda
CREATE, düzenleme modundaALTERdiff'i). - Seed dosyası kaydedilir: her zaman tam
CREATE TABLEscript'i (diff değil),save-table-scriptucu ileconfigs/seeds/{kapsam}/{sql|postgres}/object/{TabloAdı}.sqlaltına (SeedPathResolver). Dosya kaydı başarısız olsa deploy geri alınmaz — log'a düşer. - 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 FUNCTIONve her türlü elle DDL yalnızca veritabanına gider. Bu nesnelerinconfigs/seeds/{kapsam}/{sql|postgres}/object/{Ad}.sqldosyası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):
-- 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
Idtipini ne seçtiysen wizard dosyasındakiKeyFieldDbSourceTypeile 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:
- Yalnızca aktif sağlayıcının klasörü çalışır.
DefaultDatabaseProviderSQL Server ise{kapsam}/sql/, PostgreSQL ise{kapsam}/postgres/. İki dosya da yazmak taşınabilirlik içindir; aynı anda ikisi birden çalışmaz. - 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.sqlHr_T_EmployeeAdvance.sql'den önce gelir). - Dosya içeriği
GOsatırlarından batch'lere bölünür ve sırayla çalıştırılır. PostgreSQL'deGOkullanılmaz. - 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. - 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ı yazmadancrud/{Entity}.jsonyazmak anlamsızdır. - Klasör kökünde kalmış
.sqldosyaları geriye dönük uyumluluk için (veobject/klasöründen önce) çalıştırılır ama uyarı loglar; yeni dosyayı her zamanobject/ya daexecute/altına koy.
execute/ klasörünün özel sözleşmesi: buradaki dosyalar iki aşamalıdır.
-
- aşama:
SqlDataSeederdosyayı çalıştırır → nesne oluşur/güncellenir.
- aşama:
-
- aşama: tüm migration ve seed işlemleri bittikten sonra
AfterAllMigrationsSqlExecutordosya adından türettiği stored procedure'ü çağırır.
- aşama: tüm migration ve seed işlemleri bittikten sonra
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
sql/object/{Modul}_T_{Entity}.sql— tabloyu §8.3 iskeletiyle yaz (tenant + audit kolonları varsayılan). PostgreSQL hedefi de destekleniyorsapostgres/object/altına diyalekt eşini yaz.- İndeks ve FK'leri aynı dosyada, varlık kontrolüyle ekle (
IF NOT EXISTS); ayrı dosya sıralamayı kırılgan hâle getirir. - Ekran bu tabloyu doğrudan kullanacaksa CRUD endpoint gerekmez — ListForm
SelectCommandType: 1ile tabloya bağlanır. Custom Component kullanacaksacrud/{Entity}.jsonde yaz (§9). - Wizard dosyasında
SelectCommand= tablo adı,KeyFieldName=Id,KeyFieldDbSourceType= tablodakiIdtipinin sayısı,IsTenant= tablodaTenantIdvarsatrue,IsDeletedField/IsCreatedField= ilgili kolonlar varsatrue. - 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
{
"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 vecrud-endpoint-generateuç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. GetListfiltre 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ı:
-
Verb kapısı:
App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove— kullanıcıda bu yetki yoksa istek endpoint'e hiç ulaşmaz. -
Eşleme: istek yolu URL-decode edilir,
/api/app/custom-endpointöneki atılır, başına ve sonuna/garanti edilir; kayıtpath.StartsWith(Url) && Method == verbile 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ç. -
Endpoint yetkisi:
PermissionsJsonkurallarından en az biri tutmalıdır:ResourceTypeResourceIdKim geçer Global— Verb kapısını geçen herkes Rolerol adı O roldeki kullanıcılar Userkullanı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 birRolekuralı yaz, herkese açıksaGlobalyaz. -
Parametre çözümü (sırayla; hepsi tek
paramsözlüğünde toplanır ve SQL'e adıyla bağlanır):TypeDeğer nereden Kurallar StaticDefaultValueToken'lar çözülür (aşağıda) Query?name=…Yoksa: IsRequired→ hata; değilseDefaultValue(token'lı)PathURL segmenti Pathalanı bir desendir:/orders/:id/—:namehangi segmentteyse gerçek yoldan aynı sıradaki segment okunurBodyJSON 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. Pathalanı Body'de kullanılmaz — iç içe yol çözülmez, gövdeyi düz tutStatic/varsayılan değerlerde çözülen token'lar (DefaultValueHelper):@USERID,@USERNAME,@ROLES,@DATE,@NOW,@DAY,@MONTH,@YEAR,@TENANTID(host bağlamında= '@TENANTID'deseniIS NULL'a çevrilir). -
Ç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 birUPDATE/EXECde olabilir —Method'u anlamına uygun seç.
// 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:
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
AppServiceya daApplicationServiceile bitmeli veApplicationServicetabanı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ümVolo.Abp,Sozsoft.PlatformveMicrosoft.AspNetCoreassembly'leridir, yani platform entity ve servislerinin tamamı görünürdür. - Route türetimi (ABP konvansiyonu): sınıf adından
AppServiceeki atılır → kebab-case; metot adındanAsynceki 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 == Successolan 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
Debugseviyesinde,allowUnsafe: falseile yapılır; hatalar satır/kolon bilgisiyleLastCompilationError'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: truebir ekran, kiracının kendi veritabanı kaydı varsa oradan okur; yoksa ekranınDataSourceCode'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ı metnindeServergeçiyorsaMssql, aksi hâldePostgresqlolarak 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 |
| 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/ALTERher zamanCREATE 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'ı
EditFileNameile 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:
- Menüyü elle ekleme. Yeni bir ekranın menüsü wizard seed dosyasından gelir;
MenusData.jsonyalnızca platformun kendi ekranları içindir ve orada daRoutesbölümüne dokunulmaz. - Route kaydı üretme. Dinamik liste ekranları statik
/admin/list/:listFormCoderoute'unu kullanır; custom component'lerin route'uRoutePathalanından türetilir.Routetablosu yalnızca fiziksel React sayfaları içindir ve prompt ile ekran üretirken böyle bir sayfa yazılmaz. - Menü sırası için wizard'ın
MenuOrderalanını kullan; Menu Manager'da elle taşımak seed dosyasına yansımaz ve veritabanı sıfırlandığında kaybolur. - 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ğıtrueolan 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}, kuyrukplatform, zaman dilimi Europe/Istanbul — cron ifadeleri Türkiye saatine göre yazılır. - Yükleme sırasında
platformkuyruğ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. MailQueueWorkertanımı yüklenirkenOptions.MailTemplateRazor şablonu worker adıyla şablon deposuna da kaydedilir.
Her tetiklemede çalışma sırası:
Nameile dağıtık kilit alınır — alınamazsa (başka instance çalışıyor) sessizce çıkılır.BeforeSpdoluysaDataSourceCodebağlantısında çağrılır.- Tipe göre gövde:
MailQueueWorker→ kuyruğu gönderir (§11 · reference) ·SqlWorker/BackupWorker→ SQL gövdesi (backup hedefiApp:BackupPathayarı) ·NotificationWorker→ bildirim kuyruğunu kanallara dağıtır ·SessionCleanupWorker→ süresi geçen oturumları düşürür. AfterSpdoluysa çağrılır.- Hata
IExceptionNotifier'a bildirilir ve loglanır — iş sessizce yutulmaz; çalıştırma geçmişiApp.BackgroundWorkers.Jobsekranı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:
{ "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:}" }
MailTemplateRazor ile render edilir (EmailLayoutdüzeni içinde).TableNamemaildeki tabloları tanımlar;INgövdeye gömülür,PDF/XLS/TXTek dosya olur.- Gönderilecek satırlar
BackgroundWorker_MailQueuekuyruk tablosuna yazılır (To,MailParameter,TableName,TableParameter,Attachment,RelatedRecordId); kuyruğa yazma işi genellikleBeforeSpprosedüründe yapılır. Ayrıntılı biçimapi/modules/Sozsoft.MailQueuemodü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'teDynamicTree) 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
ReportTemplatekaydı açılır ve tasarımcıdan çizilir; ekranaCommandColumnJsonbutonu 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:
sql/execute/{Ad}.sqlaltında, kaydı varsa güncelleyen/yoksa ekleyen bir prosedür yaz (§8.4 sözleşmesi) — proje kapsamındaki doğru yol budur.- 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.
10. Uçtan uca reçeteler
10.1 "Var olan bir tablo için ekran aç"
- Tablo yoksa SQL Table Designer ile oluştur (varsayılan kolonlarla).
- Ekran birden çok tablodan besleniyorsa View Designer ile view üret.
- Wizard: menü/kimlik → veri ayarları (
SelectCommandType+SelectCommand+ anahtar) → alanlar → (gerekiyorsa) alt form/widget/workflow → yayınla. - Hesaplanan alanlara
EditorScript, biçimli alanlaraEditorOptionsver. - 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ı"
- Detay ekranını menüsüz wizard ile üret (
CreateMenu: false). - Ana ekranın
SubFormslistesineParentFieldName→ChildFieldNameeşlemesini veDbTypeekle. - İki ekranın yetkileri ayrıdır; detayın yetkisi verilmemişse sekme görünmez.
10.3 "Serbest yerleşimli sayfa / dashboard"
- Gerekli endpoint'ler yoksa CRUD Endpoint üret.
- Component Manager → yeni bileşen → Visual Designer:
PageContainer→FlexRow→ içineplatformdüğümleri (listFormCodeile) ve/veyaForm. - Veri kaynaklarını Data sekmesinden bağla; filtreleri
route/query/recordkaynaklarıyla kur,requiredbayrağını bilinçli seç. - Görünürlük ve komut yetkilerini ver (
designerPermission,*Permission). - Wizard
ComponentKind: 1ile menüye bağla. - Doğrula:
custom/{Name}.jsonve ilgilicrud/*.jsondosyaları oluştu mu.
10.4 "Kanban panosu"
- Tabloda durum kolonu olsun (
Status). - Wizard'da
Todo: true,DefaultLayout: "todo",TodoOptionDtoiçindeTitleExpr,StatusExpr,StatusOrder(kolon sırası),AssigneeExpr,DueDateExprdoldur. - Kolonlar
statusExprdeğerlerinden üretilir; kartlar sürüklenerek durum değiştirir, panodan yeni kolon eklenebilir.
10.5 "Rapor sorgusunu API olarak aç"
- Sorguyu SQL Query Manager'da yaz ve çalıştır, sonucu doğrula.
- Tekrar kullanılacaksa view olarak deploy et (
sql/object/…), tek seferlikse Custom Endpoint içinde bırak. - Custom Endpoints ekranından kaydı oluştur:
Url,Method,DataSourceCode(kullanıcının kendi tenant veritabanı isteniyorsa"!Tenant"),Sql,ParametersJson,PermissionsJson. - Parametreleri
Query/Path/Bodyolarak tanımla; hiçbirini SQL'e string birleştirme ile koyma. - Erişimi
PermissionsJsonile daralt; dispatcher kapısının (…CustomEndpoints.Getvb.) 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.
- Önce gerçekten çözülemediğini göster: ListForm + SQL + Custom Endpoint neden yetmiyor, bir iki cümleyle yaz.
- Dynamic Service oluştur;
PlatformAppServicetüret,[Authorize(...)]ile koru. - TestCompile → hatasız → Publish.
CompilationStatusSuccessolmadan yayınlama. - Swagger'da
ControllerNamealtında göründüğünü doğrula. - Ekran tarafında bu servisi ya ListForm'un
*ServiceAddressalanı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/20260826101000_AdvanceRequestApprovals.json ← (varsa) menüsüz alt ekran, önce
└── wizard/20260826101500_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:
- 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. - Wizard dosyası — §4.4'teki iskelet:
IsTenant: true(tablodaTenantIdvar),IsDeletedField: true,IsCreatedField: trueMenuParentCode= modül kökü (App.Hr); yoksa wizard yaratır,Order = max + 1PermissionGroupName=App.Hr, EN + TR görünen adlarıyla- Görünümler:
Grid+Cardaçık; durum akışı görsel isteniyorsaTodo: true+TodoOptionDto(§9.8)
- Alanlar —
Groupsiçinde iki grup mantıklıdır: "Talep" (tutar, tarih, açıklama) ve "Onay" (durum, onaylayan, onay tarihi, onay notu). Onay grubundaki alanlarEditorOptions: "{\"readOnly\":true}"ile kilitlenir; kullanıcı doldurmaz, akış doldurur. - Hesap ve varsayılanlar —
EditorScriptile:- Talep tarihi form açılışında bugün:
todaytarifi, tetikleyiciopen - Tutar × oran gibi hesaplar:
multiply/percent - Eşik üstü tutarda uyarı:
notifyya daask, koşulugreaterThan
- Talep tarihi form açılışında bugün:
- Onay akışı — §4.7'deki
WorkflowDto. Tipik grafik:Start → Compare (tutar eşiği) → Approval (yönetici) / Approval (üst yönetici) → Inform → End.ApprovalIsFilterUserName: trueile herkes yalnızca kendi onayına düşenleri görür,ApprovalIsResetWorkflow: trueile kayıt güncellenince akış başa döner. - Yetki — wizard
App.Hr.AdvanceRequestskökünü ve.Create/.Update/.Delete/.Export/ .Import/.Notealt yetkilerini üretir. Onaylayanların rolüne kök +.Updateverilir; talep sahiplerine kök +.Createyeter. - Dil — menü/başlık/açıklama metinleri wizard dosyasındaki
LanguageText*alanlarından, alan başlıkları herItemsöğesininTurkishCaption/EnglishCaptionalanlarından üretilir. AyrıcaLanguagesData.json'a dokunmaya gerek yoktur. - 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ü
RequiredPermissionNameile 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 gerekiyorsaIsBranch/IsOrganizationUnit) doğru; sorgular tenant güvenli.- Tüm SQL parametreli; string birleştirme yok.
DeleteCommand = DefaultDeleteCommand("{Tablo}").- Seed dosyası oluştu;
InsertedRecordsyalnı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 →
InsertedRecordstemizlenir.
Prompt ile üretim yaptıysan ek olarak:
- Dosyalar doğru kapsam klasöründe (
host/ya datenants/{tenantId}/). - Üretim sırası doğru (§0.2) ve bağımlı wizard'ın zaman damgası daha büyük.
- Tablo/kolon adları uydurulmadı; ya var olan nesneden okundu ya da
sql/object/altında üretildi. KeyFieldDbSourceType, tablodakiIdtipiyle 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.visualDesignerileCodemarker'ı 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 typecheckvenpm run linttemiz. - Ç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.