sozsoft-platform/.github/instructions/lowcode.instructions.md
2026-08-26 21:44:53 +03:00

107 KiB
Raw Blame History

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
  • api/ kod standardı: dotnet.instructions.md
  • Modül/liste ekleme prosedürü: list.instructions.md
  • Neyin nerede yaşadığı: ../../README.md

Ç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 ile ekran tarifine çevir.
  • Ekran görüntüsü / mockup / fotoğraf → §0.7 ile önce görseli oku, tarifi çıkar.

Tarif hazır olunca §0.6.1 ile yolu seç (Wizard mı, Custom Component mi), sonra §0.2'deki sırayla dosyaları üret.

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:

  1. Tablo/kolon adlarısql/object/ altındaki dosyalar ya da mevcut wizard dosyalarındaki SelectCommand. Kolon adını tahmin etme; yoksa tabloyu da sen üret.
  2. Var olan menü kökü ve izin grubu — aynı modül için ikinci bir kök menü açma. Mevcut wizard dosyalarındaki MenuParentCode / PermissionGroupName değerlerini tara.
  3. Dil anahtarı çakışmasıLanguagesData.json ve diğer wizard dosyaları. Var olan bir anahtarın metnini seed ezmez; yanlış metinle karşılaşırsan anahtarı değiştir.

0.4 Üretim sonrası

  • Değişikliklerin devreye girmesi için DB Migrate (SQL Query Manager / Wizard Manager üzerindeki buton, yetki App.Setup.Migrate) ya da migrator konteyneri (SEED=true) çalışmalı.
  • Seeder idempotenttir: ekran zaten varsa dosya atlanır (§4.5). Var olan bir ekranı değiştirmek için dosyayı düzenlemek yetmez — kayıtları silip yeniden seed etmek ya da ekranı Wizard'dan EditFileName ile yeniden çalıştırmak gerekir.
  • Ürettiğin dosyaları kullanıcıya tek tek listele: hangi dosya, ne üretiyor, hangi menüde görünecek, hangi yetkiyi ister.

0.5 Dosya biçimi kuralları

Kural Değer
Kodlama UTF-8 (BOM'suz)
JSON alan adları PascalCase (Wizard, ListFormCode, Groups) — seeder PropertyNameCaseInsensitive okur ama dosyalar PascalCase üretilir
Custom component dosyası Kök alanlar PascalCase; Props içindeki designer dokümanı camelCase (visualDesigner, nodes, sourceMode)
Enum'lar Sayı olarak yazılır (§3)
GeneratedAt ISO-8601 UTC (2026-08-26T10:15:00Z) — yalnızca bilgi amaçlı
İç içe JSON EditorOptions, EditorScript, LookupQuery → JSON string olarak kaçışlanır
Satır sonu LF

0.6 Talebi ekran tarifine çevir

Dosya yazmadan önce tek bir ekran tarifi çıkar ve kullanıcıya kısaca doğrulat. Tarif, hangi aracın kullanılacağını (Wizard mı, Custom Component mi) ve hangi dosyaların üretileceğini belirler.

Ekran tarifi şablonu — her maddeye bir satır, bilinmiyorsa varsayımını yaz:

Amaç          : Ne işi görüyor, kim kullanıyor
Veri kaynağı  : Tablo/view adı · yoksa "üretilecek"
Anahtar       : Kolon + tip
Sütunlar      : Alan → editör tipi → zorunlu mu → lookup kaynağı
Layout(lar)   : Grid / Card / Pivot / Chart / Tree / Gantt / Scheduler / Todo
Form düzeni   : Gruplar ve kolon sayısı
Aksiyonlar    : Toolbar ve satır butonları, ne yapıyorlar
Alt ekranlar  : Ana-detay sekmeleri
Widget'lar    : Üstteki KPI kartları
Akış          : Onay/koşul adımları
Menü          : Üst menü, sıra, ikon
Yetki         : Grup adı + kimde hangi yetki
Tenant        : IsTenant / IsBranch / IsOrganizationUnit
Dil           : EN + TR metinler

Kural: eksik bilgi için işi durdurma. Varsayımını tarifin içine yaz, üret, sonunda "şu varsayımlarla ürettim" diye bildir. Yalnızca yanlış varsayımın veri kaybettireceği yerlerde (hangi tabloya yazılacağı, kimin onaylayacağı) sor.

0.6.1 Hangi araç? — yol seçimi

Tarifi çıkardıktan sonra tek bir karar kalır:

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.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 önce görseli okuyup §0.6'daki tarifi çıkar, sonra dosyaları üret. Görseli tahmine değil, gördüğün öğelere dayandır.

0.7.1 Görseli okuma sırası

  1. Sayfa iskeleti — kaç bölge var? Üstte KPI şeridi, ortada liste, sağda panel, altta sekme?
  2. Ana bölge ne? Tablo mu, kart ızgarası mı, pano mu, takvim mi, form mu?
  3. Araç çubuğu — hangi butonlar var, ne yazıyorlar, ikonları ne?
  4. Sütunlar — başlıklar, hizalama, biçim (para, tarih, yüzde), rozet/renkli durum hücreleri.
  5. Filtre/arama — başlık altı filtre satırı, arama kutusu, tarih aralığı seçicileri.
  6. Form alanları — kontrol tipleri ve kolon düzeni (kaç kolon, hangi alan kaç sütun kaplıyor).
  7. Satır aksiyonları — satır sonundaki ikonlar.
  8. Durum göstergeleri — onay adımları, ilerleme çubukları, etiketler.

0.7.2 Görsel öğe → artefakt eşlemesi

Görselde gördüğün Karşılığı
Başlık satırı + veri satırları olan tablo Grid layout
Kart ızgarası Card layout
Dikey kolonlar + sürüklenebilir kartlar Todo (Kanban) layout · TodoOptionDto.StatusExpr
Ay/hafta takvimi, saat çizelgesi Scheduler layout
Yatay zaman çubukları, bağımlılık okları Gantt layout
Girintili ağaç, açılır düğümler Tree layout
Satır/sütun kesişimli özet tablo, ara toplamlar Pivot layout
Çubuk/çizgi/pasta grafik Chart layout · SeriesJson
Üstte sayı gösteren küçük kutular Widgets (WidgetEditDto)
Kaydın altında sekmeler (Kalemler, Ekler, Geçmiş) SubForms
Araç çubuğundaki özel buton CommandColumnJson · ButtonPosition: 1
Satır sonundaki ikon CommandColumnJson · ButtonPosition: 0
Başlık altındaki filtre satırı FilterRowJson
Sağ üstteki arama kutusu SearchPanelJson
"Sürükleyip gruplayın" şeridi GroupPanelJson
Üstte ek filtre çubuğu (tarih aralığı, şube seçimi) ExtraFilterJson
Sayfalama şeridi PagerOptionJson
Renkli durum rozeti ColumnStylingJson / ColumnCssClass
Onayla / Reddet butonları WorkflowJson (otomatik gelir, elle tanımlama)
Yan yana bağımsız bölümler, karışık yerleşim Custom Component · FlexRow + düğümler

0.7.3 Görseldeki kontrol → EditorType

Görselde EditorType Ek
Tek satır metin kutusu dxTextBox
Çok satırlı kutu dxTextArea autoResizeEnabled
Sağında ok olan açılır liste dxSelectBox lookup zorunlu
İçinde etiket/çip olan açılır liste dxTagBox lookup zorunlu
Büyüteçli seçim + tablo açılıyor dxGridBox / dxLookup columns
Onay kutusu dxCheckBox
Aç/kapa anahtarı dxSwitch switchedOnText
Yan yana yuvarlak seçenekler dxRadioGroup layout: horizontal
Takvim ikonlu alan dxDateBox
İki tarihli aralık alanı dxDateRangeBox
Artı/eksi oklu sayı dxNumberBox format.precision
₺ 1.250,00 biçimli alan dxNumberBox format.type: currency
% biçimli alan dxNumberBox format.type: percent
Renk kutucuğu dxColorBox
Kalın/italik araç çubuklu alan dxHtmlEditor toolbar.items
Kaydırıcı dxSlider / dxRangeSlider min, max
Görsel küçük resmi / yükleme alanı dxImageViewer / dxImageUpload accept, maxFileSize
Gri, tıklanamaz alan ilgili editör + EditorOptions: {"readOnly": true}
Kırmızı yıldızlı etiket IsRequired: true

0.7.4 Görselden okunamayacak şeyler

Bunlar bir ekran görüntüsünde görünmez; varsayımını yaz ve bildir:

Bilgi Varsayılan davranış
Tablo/kolon adları Görünen başlıklardan PascalCase kolon adı türet, tabloyu sql/object/ altında sen üret
Anahtar alan ve tipi Id / UNIQUEIDENTIFIER (KeyFieldDbSourceType: 9)
Lookup'ların kaynağı Az sayıda sabit seçenek görünüyorsa StaticData, kod/isim çifti gerektiren yerlerde Query
Tenant/şube kırılımı IsTenant: true (§CLAUDE varsayılanı)
Yetki grubu ve menü kökü Ekranın ait olduğu modülden türet, kullanıcıya sor değil bildir
Dil metinleri Görseldeki dilde yaz, diğer dile çevir; ikisi de doldurulur
Onaycılar Yer tutucu bırak (<roleOrUserId>) ve kullanıcıya sor — bu, sorulması gereken bir şeydir
İş kuralları (hesap, koşul) Görselde formül görünmüyorsa EditorScript yazma; sor

0.7.5 Görsel için de aynı protokol

Görselden çıkardığın tarifi §0.6.1'deki tabloya sok, yolu seç, §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.


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
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.

Ş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)

ComponentKind0 List · 1 Custom

SelectCommandType1 Table · 2 View · 3 TableValuedFunction · 4 Query · 5 StoredProcedure

LookupDataSourceType1 StaticData · 2 Query · 3 WebService

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 (EditorType) — 21 tip. 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).

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 / MenuParentShortName / 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 / AllowDetail / ConfirmDelete Toolbar ve satır aksiyonları.
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 (hours/days/weeks/months), Allow* bayrakları
SchedulerOptionDto TextExpr, StartDateExpr, EndDateExpr; AllDayExpr, RecurrenceRuleExpr, StartDayHour, EndDayHour, DefaultView (day/week/month), 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: OrderNoOrder 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 URL'dir. Cascade (ebeveynçocuk) davranışı ListForm editöründeki lookup ayarlarından tanımlanır.

4.3.3 Her editörde geçerli ortak seçenekler

Gruplar hâlinde (optionSpecs.ts sözlüğünün tamamı UI'yı buradan üretir):

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
ı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.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", "MenuParentShortName": "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": []
  }
}

EditorScript alanındaki // @builder başlığı ile onu izleyen kod satırı birebir tutarlı olmalıdır. Elle yazmak yerine C# tarafında EditorScript.Build(...) kullan ya da Script Builder'da üret; uyuşmazsa dialog script'i "elle düzenlenmiş" sayar.

4.4.1 Seeder'ın senin yerine ürettikleri — bunları dosyaya yazma

WizardDataSeeder, dosyadaki Wizard bloğundan yola çıkarak aşağıdakileri kendisi üretir. Bunları seed dosyasında aramaya kalkma, orada yokturlar:

Üretilen Kural
Dil anahtarları {MenuCode} (menü), {ListFormCode}.Title, {ListFormCode}.Desc, {PermissionGroupName} ve her alan için CaptionName
Yetkiler {MenuCode} kökü + .Create/.Update/.Delete (+ List yolunda .Export/.Import/.Note)
Yetkilerin dil anahtarları Ortak sabitler: App.Platform.Create/Update/Delete/Export/Import/Note
Üst menü MenuParentCode yoksa yaratılır, Order = max(kök Order) + 1
Menü sırası MenuOrder 0/boş ise max(kardeş Order) + 1
DataSource DataSourceCode yoksa yaratılır; bağlantı metninde Server geçiyorsa Mssql, aksi hâlde Postgresql
LayoutJson Görünüm bayrakları + DefaultLayout
PermissionJson / alan PermissionJson {MenuCode} kökünden türetilir
EditingFormJson Groups içinden; anahtar alan ve IncludeInEditingForm: false olanlar hariç
Form genişlik/yükseklik Grup kolon sayısı ve satır sayısından hesaplanır (en fazla 3 kolon)
FilterRowJson, HeaderFilterJson, SearchPanelJson, GroupPanelJson, ColumnOptionJson, PagerOptionJson, ExportJson Varsayılanlar
DeleteCommand IsDeletedField: true ise soft delete UPDATE komutu; false ise null
DefaultFilter IsDeletedField: true ise "IsDeleted" = 'false'
InsertFieldsDefaultValueJson IsCreatedField: true ise CreationTime=@NOW, CreatorId=@USERID, IsDeleted=false, Id=@NEWID; değilse yalnızca Id=@NEWID
DeleteFieldsDefaultValueJson IsDeletedField: true ise DeleterId=@USERID, Id=@ID; değilse yalnızca Id=@ID
ValidationRuleJson IsRequired: true olan alanlara required kuralı
LookupJson LookupQuery doluysa LookupDataSourceType + DisplayExpr + ValueExpr ile
ShowNote Alt form veya iş akışı varsa açılır
SelectionJson İş akışı varsa single, yoksa none
Görünüm option JSON'ları İlgili bayrak açık ve zorunlu alanı dolu ise yazılır; eksikse görünüm sessizce kapanır

Dikkat edilecek tuzaklar:

  • Tree/Gantt için ParentIdExpr, Scheduler için TextExpr, Todo için TitleExpr ve StatusExpr boşsa o görünümün JSON'u hiç yazılmaz — bayrağı true yapmak yetmez.
  • Anahtar alanı (KeyFieldName) Groups içine koyarsan sütun olarak gizlenir ve forma girmez; yine de tanımlaman doğrudur, çünkü tip bilgisi oradan okunur.
  • IsDeletedField: false verirsen ekranda kalıcı silme olmaz (DeleteCommand null kalır); soft delete kolonları olmayan tabloda silme istiyorsan ListForm editöründen komut yazmalısın.
  • Dil anahtarı zaten varsa metni ezilmez. Yanlış metin görüyorsan anahtar başka bir yerde tanımlanmıştır.

4.5 Güncelleme, idempotency ve silme semantiği

Seeder idempotenttir — bir ekran zaten varsa dosya atlanır. "Zaten var" ölçütü yola göre değişir:

Yol Kontrol
List ListForm kaydında bu ListFormCode var mı
Custom + menülü Bu MenuCode ile menü var mı
Custom + menüsüz Bu MenuCode adında yetki var mı

Sonuç: var olan bir ekranı seed dosyasını düzenleyerek değiştiremezsin. Değiştirmenin üç yolu vardır:

  1. Wizard ekranından EditFileName ile yeniden çalıştır (önce eski dosya ve InsertedRecords kayıtları silinir, sonra yenisi üretilir) — tercih edilen yol.
  2. ListForm editöründen ince ayar yap (seed dosyasına yansımaz, §9.6 uyarısı).
  3. İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır.

List yolunda seeder ekranı uygularken ListForm, ListFormField ve ListFormWorkflow kayıtlarını silip yeniden yazar; idempotency kapısını geçen dosya için bu güvenlidir.

Silme: yalnızca InsertedRecords içindekiler silinir. Paylaşılan üst menü ve izin grubu başka bir wizard tarafından yaratıldıysa kalır. Bu yüzden InsertedRecords listesine var olan bir kaydı yazmak veri kaybettirir.

4.6 Alt formlar ve widget'lar

SubForms (SubFormDto) — ana kaydın altında sekme olarak açılan ekranlar:

"SubForms": [
  {
    "TabType": "List",              // List | Tree | Gantt | Scheduler | Form | Chart
    "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ı:

"Widgets": [
  {
    "Title": "App.Mrp.Widget.OpenOrders",
    "SubTitle": "App.Mrp.Widget.OpenOrdersSub",
    "SqlQuery": "SELECT COUNT(*) FROM Mrp_T_Order WHERE IsDeleted = 'false' AND Status = 1",
    "Value": "",                    // sabit değer; SqlQuery doluysa boş bırakılır
    "ValueClassName": "text-3xl",
    "Icon": "FcClock",
    "Color": "blue",
    "ColSpan": 1,
    "ColGap": 16,
    "ClassName": "",
    "OnClick": "",                  // tıklanınca açılacak adres
    "IsActive": true
  }
]

SqlQuery tek hücre döndürmelidir; tenant kırılımı gerekiyorsa sorguya TenantId koşulunu kendin yaz — widget sorgusu ListForm'un otomatik tenant filtresine tabi değildir.

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: N ile başlayan kısa id'ler seeder tarafından {ListFormCode}-{Id} hâline getirilir; hedef alanlar (NextOn*, CompareOutcomes[].TargetId) da aynı normalizasyondan geçer. Bu yüzden dosyada N1, N2 yazman yeterlidir ve tekil olmaları şarttır.
  • Compare düğümünde çok dallı karar için CompareOutcomes kullanılır; iki dallı basit karar için NextOnTrue / NextOnFalse yeterlidir.
  • Approval düğümü NextOnApprove / NextOnReject, Start ve Inform düğümleri NextOnStart alanını kullanır. End düğümünün çıkışı yoktur.
  • CompareValue sayısaldır (decimal); metin karşılaştırması bu düğümle yapılmaz.
  • Title tekil olmalıdır; seeder tekrar eden başlıkları ayrıştırır ama okunabilirliği bozar.
  • Akış varsa ListForm SelectionMode = single olur ve not paneli otomatik açılır.
  • Görsel tasarım /admin/listform/edit/{kod} → Workflow sekmesindedir.

4.8 ListForm entity — List component'in tek kaynağı

Bir List component'in bütün davranışı ListForm (ekran) ve ListFormField (alan) kayıtlarında durur. Wizard bunları üretir; /admin/listform/edit/{kod} düzenler. Prompt ile üretim yaparken wizard dosyası yazılır, ama hangi ayarın hangi kolona düştüğünü bilmek gerekir — çünkü wizard'ın kapsamadığı bir istek geldiğinde cevap "kod yazalım" değil, "şu JSON kolonunu şöyle ayarlayalım" olmalıdır.

Kimlik ve varyant

Kolon Ne yapar
ListFormCode Ekranın kodu; kullanıcı/rol/dil özelleştirmeleri buna bağlanır
CultureName · UserId · RoleId Aynı ekranın varyantları. Aynı ListFormCode ile ikinci bir kayıt açıp yalnızca bir rol ya da dil için farklı sütun/düzen verebilirsin
ListFormType List · Form — ekranın türü
IsSubForm / SubFormsListFormType Alt form olarak açıldığında davranış

Veri

Kolon Ne yapar
DataSourceCode Bağlantı
SelectCommandType + SelectCommand Kaynak türü ve kaynağın kendisi
TableName Tablo adı/alias — yazma komutlarının hedefi
KeyFieldName + KeyFieldDbSourceType Anahtar
SelectFieldsDefaultValueJson Select'e geçirilen varsayılan parametreler
DefaultFilter Her sorgunun sonuna eklenen WHERE (soft delete filtresi buradadır)
IsTenant · IsBranch · IsOrganizationUnit Otomatik kırılım filtreleri
DataSourceJson · CommonJson Kaynak ve ortak ayarlar

Yazma (CRUD)

Kolon Ne yapar
InsertCommand / UpdateCommand / DeleteCommand Özel SQL komutları; boşsa platform üretir
*BeforeCommand / *AfterCommand Komut öncesi/sonrası çalışan SQL kancalarıstok düşme, log yazma, durum güncelleme gibi işleri kod yazmadan burada yaparsın
Insert/Update/DeleteFieldsDefaultValueJson Otomatik doldurulan alanlar (@NOW, @USERID, @NEWID, @ID)
Insert/Update/DeleteServiceAddress Varsayılan list-form-data/*; özel endpoint ya da Dynamic Service'e yönlendirilebilir
FormFieldsDefaultValueJson Form açılışındaki varsayılanlar

Görünüm ve düzen — 8 layout

LayoutJson hangi layout'ların açık olduğunu ve varsayılanı taşır. Her layout'un kendi ayar kolonu vardır:

Layout Ayar kolonu Zorunlu alan
Grid ColumnOptionJson, RowJson, PagerOptionJson, SelectionJson, StateStoringJson
Card ColumnOptionJson (kart alanları sütun tanımlarından türer)
Pivot PivotOptionJson + alan bazında PivotSettingsJson
Chart SeriesJson, LegendJson, ArgumentAxisJson, ValueAxisJson, TooltipJson, PanesJson, AnnotationsJson, CrosshairJson, ScrollBarJson, ZoomAndPanJson, SizeJson, MarginJson, TitleJson, AnimationJson, Common*Json SeriesJson
Tree TreeOptionJson KeyExpr + ParentIdExpr
Gantt GanttOptionJson ParentIdExpr + TitleExpr + StartExpr + EndExpr
Scheduler SchedulerOptionJson TextExpr + StartDateExpr + EndDateExpr
Todo (Kanban) TodoOptionJson TitleExpr + StatusExpr

Sekiz layout aynı SelectCommand üzerinden beslenir; ayrı sorgu, ayrı ekran, ayrı menü gerekmez. Kullanıcı ekranın üstünden geçiş yapar, tercihi kullanıcı bazlı saklanır.

Bu, tek bir metadata kaydından DevExtreme'in DataGrid, CardView, PivotGrid, Chart, TreeList, Gantt ve Scheduler bileşenlerinin tamamının sürülmesi demektir. Todo layout Kanban görünümüdür ve diğerleriyle aynı veri hattından beslenir — kolonları StatusExpr değerlerinden üretir, kart sürüklendiğinde ilgili kolonu günceller. Yani "aynı veriyi pano olarak da görelim" isteği yeni bir ekran değil, Todo: true + TodoOptionDto demektir.

Filtre, arama, düzenleme

Kolon Ne yapar
FilterRowJson · HeaderFilterJson · FilterPanelJson · SearchPanelJson · GroupPanelJson Filtreleme/arama/gruplama panelleri
ExtraFilterJson Ekrana özel ek filtre araç çubuğu
EditingOptionJson Düzenleme modu (row/cell/batch/form/popup), başlık, boyut, izinler
EditingFormJson Düzenleme formunun grup/sekme düzeni
CommandColumnJson Satır aksiyon sütunu
PermissionJson Ekran yetkileri (create/read/update/delete/export/import/note)
SubFormsJson · WidgetsJson · WorkflowJson Alt formlar, KPI kartları, onay akışı
CustomJsSourcesJson · CustomStyleSourcesJson Sayfa yüklenince çalışan JS/CSS — son çare
Width · Height · FullHeight · AdaptiveLayoutJson Boyut ve uyarlanabilir yerleşim
ExportJson xlsx/csv/pdf dışa aktarma

ListFormField — alan başına ayarlar

Grup Kolonlar
Bağlama FieldName, SourceDbType, JoinTableJson (kolon başka tablodan geliyorsa)
Görünüm CaptionName, PlaceHolder, Visible, IsActive, Width, ListOrderNo, Alignment, Format, BandName
Sıralama/filtre SortIndex, SortDirection, AllowSearch, ColumnFilterJson, ColumnHeaderJson, GroupingJson, ColumnCustomizationJson
Özet TotalSummaryJson, GroupSummaryJson
Düzenleme EditorType2, EditorOptions, EditorScript, EditOrderNo, EditGroupOrderNo, ColSpan, AllowEditing, AllowAdding, ValidationRuleJson
Lookup LookupJson
Biçimlendirme ColumnStylingJson, ColumnCssClass, ColumnCssValue
Pivot PivotSettingsJson
Yetki PermissionJsonCanRead, CanCreate, CanUpdate, CanExport
Varyant UserId, RoleId, CultureName

Kural: "Şunu da yapabilir miyiz?" sorusuna cevap ararken önce bu iki tabloya bak. Aradığın davranışın bir JSON kolonu varsa çözüm konfigürasyondur; CustomJsSources ve kod yazmak ondan sonra gelir.

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 Kayıtlı bir bileşeni diyalog olarak açar. DialogParameters bir JSON nesnesidir; @Kolon değerleri satırdan doldurulur ({"requestId":"@Id","amount":"@Amount"}).
Script OnClick Serbest JS. Son çaredir; önce diğer iki mod denenir.

Kurallar:

  • AuthName zorunlu sayılmalıdır: yetki kontrolünden geçmeyen buton hiç çizilmez. Yetkisi olmayan kullanıcıya süreç butonu göstermek istemiyorsan tek gereken budur.
  • Text ve Hint dil anahtarıdır; çeviri :: öneki eklenerek çözülür. Düz metin yazma.
  • Toolbar butonu seçili satırlarla çalışır; SelectionJson uygun modda (single/multiple) olmalıdır. İş akışı varsa wizard bunu zaten single yapar.
  • Satır bazlı aksiyon istiyorsan ButtonPosition: 0 kullan; buton komut sütununda görünür.
  • Onay akışının kendi butonları (Onayla/Reddet) WorkflowJson üzerinden otomatik gelir; onları CommandColumnJson ile tekrar tanımlama.

Tipik süreç kalıbı: Url modunda bir Custom Endpoint'i ya da Dynamic Service'i çağıran adres verilir, dönüşte liste yenilenir. İş mantığı SQL/serviste kalır; ekranda yalnızca buton tanımı durur.


5. Artefakt B — EditorScript (alan davranışı)

5.1 Script metninin şekli

// @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 karşılaştırır. Aynı değilse script "elle düzenlenmiş" sayılır ve kural editörü kapanır. Bu yüzden başlık ile gövde asla ayrı ayrı düzenlenmez.

5.2 Tarifler (recipes)

Grup recipe Ürettiği
calc multiply set(T, round(num(A) * num(B), d))
calc subtract set(T, round(num(A) - num(B), d))
calc percent oran alan ya da sabit; mode yok/add/subtract
calc sum set(T, round(sum('A','B',…), d))
calc formula serbest ifade: set(T, num('Gross') * 0.18)
calc today set(T, bugün + offsetDays) (yyyy-MM-dd)
calc days iki tarih arası gün (bitiş dahil)
calc hours iki saat arası fark; bitiş küçükse ertesi güne taşar
data copy seçili lookup kaydının kolonunu alana taşır
data setValue sabit/şablonlu değer ({Alan}, {value}, {selected.Yol})
data clear verilen alanları boşaltır
view readOnly koşula göre kilitler (invert ile tersi)
interaction notify uyarı gösterir
interaction ask onay ister; vazgeçilirse alan eski değerine döner ve script durur
interaction openUrl adres açar
integration apiToField API çağırır, cevabın bir parçasını alana yazar
custom serbest JS satırı (son çare)

Tetikleyici: change (varsayılan) · open (form açılışında varsayılan üretmek için) · both. Koşullar and/or ile birleşir: always, equals, notEquals, contains, empty, notEmpty, greaterThan, lessThan, isTrue, isFalse.

5.3 Runtime API (custom/formula içinde kullanılabilir)

İmza Ne yapar
value / field Değişen alanın değeri ve adı
get('Alan') Form verisinden okur (yol destekler)
num() / str() / bool() / dateOf() Tip dönüşümü
set('Alan', deger) / set({A:1,B:2}) Yazar (toplu yazım tek flush)
clear('A','B') Boşaltır
copy('kolon','Alan') Seçili kayıttan taşır
selected('Yol') / snum() / sstr() Lookup/GridBox seçili kaydı
readOnly('Alan', kosul) Alanı kilitler
round(x, 2) / sum('A','B') Matematik
days('Bas','Bit') / hours('Bas','Bit') Tarih/saat farkı
tpl('{Alan} - {value}') Şablon
notify() / ask() / openUrl() Etkileşim
await api('/api/x','data.name') HTTP
isReady Script form açılışında mı çalışıyor

5.4 Seeder tarafı (C#) — tercih edilen yol

api/src/Sozsoft.Platform.Domain.Shared/Editors/

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.ts ile bu sınıflar aynı çıktıyı üretmek zorundadır. Birinde bir tarif değişirse aynı görevde diğeri de değişir.


6. Artefakt C — EditorOptions

DevExtreme editörüne geçirilen JSON. UI tarafındaki sözlük optionSpecs.ts, hazır kalıplar presets.ts, C# tarafı EditorOptions + EditorOptionsBuilder.

İhtiyaç C# Ürettiği JSON (özet)
Pasif alan EditorOptions.Disabled() {"disabled":true}
Salt okunur EditorOptions.ReadOnly() {"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 + 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()
Görsel yükleme EditorOptions.ImageUpload(multiple: true, 80, 80)
Zengin metin EditorOptions.Html(240) tam araç çubuğu

Akıcı ekleme: .Placeholder(), .MaxLength(), .Height(), .HeightCss("100%"), .Width(), .Format(), .FixedPoint(p), .DisplayFormat(), .SerializationFormat(), .Mask(), .Text(), .Number(), .Flag(), .Json().

EditorOptions = EditorOptions.Number(4).ShowClearButton(),
EditorOptions = EditorOptions.Multiline(60).Placeholder("Açıklama"),
EditorOptions = EditorOptions.New().Flag("acceptCustomValue", true),

Kurallar:

  • Aynı anahtar iki kez verilirse sonuncusu geçerlidir; anahtar sırası korunur.
  • optionSpecs.ts içinde platform: true işaretli ayarlar backend'de tipli DTO'ya (GridBoxOptionsDto, TagBoxOptionsDto, ImageUploadOptionsDto) çözülür; yanlış tip sessizce yok sayılır.
  • Preset'ler mevcut JSON ile birleşir, diğer ayarları silmez.

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"
        }
      ]
    }
  ]
}
  • DataSources elle yazılmaz: kaydetme sırasında CustomComponentDataSourceResolver tasarımcı dokümanındaki dataSources listesini method + path ile CRUD endpoint kataloğunda arayarak üretir. Eşleşenler EntityName + SeedFile taşır; elle yazılmış endpoint'ler listede kalır ama bu alanları boş gelir.
  • Dependencies, kanvasa bırakılan diğer custom component'lerin adlarıdır.

7.1.1 İki mod: hangisini yazacaksın?

Runtime Code alanını her zaman kullanmaz. getComponentRuntimeCode şu kararı verir:

Props.visualDesigner varsa  &&  version === 1  &&  sourceMode === 'visual'  &&  nodes bir dizi ise
    → kod tasarımcı dokümanından YENİDEN ÜRETİLİR (generateDesignerCode), Code alanı yok sayılır
aksi hâlde
    → Code alanı olduğu gibi Babel ile derlenir

Bu, prompt ile üretim için belirleyicidir:

Mod Ne yazarsın Ne zaman
Visual (sourceMode: "visual") Yalnızca Props.visualDesigner dokümanı. Code alanı okunmaz; marker'lı bir yer tutucu bırakılır. Ekran, tasarımcının bileşenleriyle ifade edilebiliyorsa. Kullanıcı sonradan kanvastan düzenleyebilir. Varsayılan tercih.
Code (Props: null) Doğrudan JSX. Tasarımcı kanvası olmaz. Tasarımcı sözlüğünün karşılamadığı yerleşim/etkileşim gerekiyorsa.

Visual modda Code alanına şunu yaz — hem marker'ı taşır (tasarımcı dosyayı açabilsin) hem de derlense bile zararsızdır:

/*__SOZSOFT_VISUAL_DESIGNER__<encodeURIComponent(JSON.stringify(document))>__*/
const {Name} = () => {
  return (
    <>
    </>
  )
}

export default {Name}

Marker'ın içi Props.visualDesigner ile aynı dokümanın URL-encode edilmiş hâlidir. Tutarsız kalırsa tasarımcı Props'u esas alır; yine de ikisini eşit tut.

7.1.2 Code modunda çalışma ortamı

Derlenen kod paylaşılan bir fonksiyon kapsamında çalışır. import satırları temizlenir — ihtiyacın olan her şey kapsamdan gelir:

Kapsamdaki ad Ne
React React'in tamamı (React.useState, React.useEffect…)
components/ui dışa aktarımlarının tamamı Button, Input, Select, Dialog, Card, Notification, toast, … (yeni bir UI bileşeni eklendiğinde otomatik gelir)
UiKit Aynı kitin isim uzayı hâli
PlatformViewHost Bir ListForm ekranını gömmek için
apiService Kimlik doğrulamalı HTTP istemcisi — tercih edilen yol
axios Ham HTTP (kimlik doğrulama eklemez)
DOMPurify HTML temizleme
translate(key) Dil anahtarı çözümü
checkPermission(name) Yetki kontrolü
getCulture() Aktif dil

Kurallar:

  • export default {ComponentName} zorunludur; ad dosya/kayıt adıyla birebir aynı olmalıdır.
  • Kullanıcıya görünen metni gömme; translate('App.…') kullan ve dil anahtarını wizard dosyasında ya da LanguagesData.json'da tanımla.
  • Veriye erişimde apiService kullan; taban adres, token ve tenant başlığı ondan gelir. Örneklerde gördüğün axios.create({ baseURL: 'https://localhost:44344/' }) kalıbı eski bir kayıttır, taklit etme.
  • Yetkiyle gizlenecek her blok checkPermission('App.…') ile sarılır. Custom component route'u authority: [] ile üretilir (§7.6 uyarısı) — kontrol bileşenin içindedir.
  • Başka bir custom component'i JSX olarak kullanabilirsin; adını Dependencies listesine ekle.

7.2 Tasarımcı dokümanı (Props.visualDesigner)

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
  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
  bindings: Record<string, DesignerBinding>
  children: DesignerNode[]
}

DesignerBinding = {
  sourceId: string      // 'source_…' (data source) ya da 'cmp_…' (kapsayıcı Form kaydı)
  path: string          // okunacak kolon
  labelPath?: string
  valuePath?: string
  columns?: string[]    // ekranda görünmeyen ek kolonlar; refs.<ad>.getColumn('x') ile okunur
}

DesignerDataSource = {
  id: string
  name: string
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
  url: string
  responsePath: string  // yanıt içindeki liste yolu; boşsa yanıtın kendisi
  filters?: DesignerDataSourceFilter[]
}

DesignerDataSourceFilter = {
  id: string
  field: string
  operator: 'eq'|'ne'|'contains'|'startswith'|'endswith'|'gt'|'gte'|'lt'|'lte'|'in'|'isnull'|'notnull'
  source: 'static' | 'query' | 'route' | 'record'
  value: string         // static → değer · query/route → URL parametre adı · record → <formRef>.<kolon>
  previewValue?: string // yalnızca tasarımcı önizlemesi; üretilen koda girmez
  required?: boolean    // true ise değer yoksa istek hiç atılmaz
}

Filtre → query string sözleşmesi: eq çıplak parametredir (?RoleId=5), diğerleri adını son ek olarak taşır (?Name.contains=abc). Bu, CrudEndpoint GetList'in okuduğu sözleşmedir.

required: true varsayılandır ve bilinçlidir: değeri henüz gelmemiş bir filtre isteği tutmalıdır; filtresiz koleksiyonu yüklemek "filtre çalışmıyor" gibi okunur.

7.3 Toolbox

Aile Bileşenler
layout PageContainer (maxWidth, padding, gap), FlexRow (columns, firstColumnWidth, gap, wrap, align), Spacer (height)
data Form — dört CRUD endpoint'ini sahiplenen kapsayıcı
platform ListView, DataGridView, TreeView, GanttView, TodoBoard, CardView, SchedulerView, PivotView, ChartView — her biri listFormCode ile bir ListForm ekranını gömer
html / ui Ham HTML etiketleri ve components/ui bileşenleri (sözleşmeleri metadata'dan 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.

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:

  • id benzersiz ve kalıcıdır. Bağlamalar (bindings.sourceId) bu id'lere işaret eder; değiştirirsen bağ kopar. cmp_… / source_… öneki bir sözleşmedir, zorunlu değildir ama koru.
  • ref script'lerin adres defteridir; olay script'leri birbirine refs.inputAmount… ile erişir. Okunur ve tekil tut.
  • Form içindeki bir alanın kaynağı Form düğümünün id'sidir (sourceId: "cmp_form"), data source değil. Data source'a bağlanan şeyler listeler/seçim bileşenleridir.
  • Her düğümde props, events, bindings, children var olmalıdır (boş olsalar bile).
  • kind değeri toolbox ailesiyle uyumlu olmalıdır; yanlış kind düğümü çizdirmez.
  • :: öneki dil anahtarıdır. Metin taşıyan bir prop :: ile başlıyorsa üretilen kodda translate('::…') çağrısına dönüşür; başlamıyorsa düz metin olarak gömülür. Kullanıcıya görünen her metni ::App.… biçiminde yaz ("title": "::App.Hr.AdvanceRequest.Title").
  • platform düğümleri filters prop'u alır. Gömülü ListForm ekranını ana kayda ya da URL'ye bağlamak için DesignerDataSourceFilter ile aynı şekli kullanır; source: "record" iken value "{formRef}.{Kolon}" biçimindedir ("form1.EmployeeId"). required: true ise ana kayıt gelmeden liste yüklenmez.
  • Anahtar kolon adında büyük/küçük harf endpoint'in döndürdüğüyle aynı olmalıdır (keyFieldName: "id" ile "Id" farklı şeylerdir); CRUD endpoint'in yanıtına bak.

7.4 Form bileşeni

Prop Anlam
selectEndpoint / insertEndpoint / updateEndpoint / deleteEndpoint Data source id'si ("source_5w_5ibpv") — URL değil. Endpoint'in kendisi dataSources listesinde tanımlıdır.
keyFieldName Anahtar kolon
collectionPath Select yanıtında liste yolu
keySource + keyParamName Anahtarın nereden geldiği (route/query/sabit)
previewKeyValue Yalnızca tasarımcı önizlemesi
autoLoad ılışta Select çalışsın mı
showToolbar Kaydet/Sil araç çubuğu
columnCount / gap İç yerleşim
title Dolu ise Card başlığı; boş ise başlık düşer

İçine bırakılan her bileşen Select sonucunun bir kolonuna bağlanır (bindings.value.sourceId = Form düğümünün id'si) ve Save/Delete üzerinden geri yazar. Olaylar: onLoad, onRecordChange, onFieldChange, onNewRecord, onModeChange, onBeforeSave, onAfterSave, onBeforeDelete, onAfterDelete, onError.

7.5 Yetki modeli (zorunlu)

İki katman:

  1. Düğüm görünürlüğüprops.designerPermission dolu olan düğüm, yetki yoksa hiç render edilmez. Veriyi göremeyecek kullanıcıya boş bir Grid göstermek yerine bileşen çizilmez.
  2. Form komutlarıselectPermission / insertPermission / updatePermission / deletePermission.
    • Otomatik mod: temel, dokümandaki permissionCode'dur (Wizard'ın bu bileşen için ürettiği okuma yetkisi, menü kaydının korunduğu yetkinin aynısı). Komutlar sırasıyla '', .Create, .Update, .Delete son eklerini alır.
    • Özel mod: yetki adı elle yazılır.
    • Boş bırakılan komut serbesttir.

Manager ekranı yetkileri: App.DeveloperKit.Components{,.Create,.Update,.Delete}.

7.6 Bileşeni menüye bağlama

Bileşen tek başına bir ekran değildir; menü ve yetki Wizard'ın Custom yolundan gelir:

{
  "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ı RoutePath değerinden türetilir ve authority: [] ile kaydedilir; /admin/ ile başlayan yol protected, diğerleri public olur. Yani URL'yi bilen her oturum açmış kullanıcı sayfayı açabilir. Menü yetkisi yalnızca menüde görünmeyi engeller. Gerçek koruma bileşenin içindedir: designerPermission ile düğümleri gizle, Form komutlarını *Permission ile kapat, kod modunda checkPermission() ile sar. Yetkisiz kullanıcıya boş bir sayfa gösteren bir bileşen doğru sonuçtur; veri gösteren bileşen açıktır.

RoutePath /admin/ ile başlamıyorsa sayfa herkese açıktır — bilinçli değilse başlat.

7.6.1 Depodaki gerçek örnek — RoleComponent + RoleList wizard

Bu çift, custom component'in menüye nasıl bağlandığının çalışan örneğidir. İncelenecek dosyalar:

configs/seeds/host/custom/RoleComponent.json
configs/seeds/host/crud/AbpRoles.json · AbpUsers.json · AbpUserRoles.json
configs/seeds/host/wizard/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:

  1. permissionCode wizard'ın ürettiği yetkinin aynısıdır. Form komutları Otomatik modda bu koda .Create/.Update/.Delete ekleyerek çözülür. Component'i yazarken bu değeri wizard'daki MenuCode ile aynı yap.
  2. RoutePath ile wizard'ın MenuUrl değeri birebir aynı olmalıdır; farklıysa menü başka bir yere gider.
  3. Component'in DataSources listesi crud/*.json dosyalarına SeedFile ile işaret eder — export zip'inin bu dosyaları da toplamasını sağlayan şey budur.
  4. Wizard Custom yolunda olsa bile ListFormCode, Grid/Card/Pivot/Chart bayrakları gibi List alanlarını taşır; bunlar kullanılmaz, varsayılan değerleriyle dosyada kalır. Yeni dosya yazarken bunları temizlemeye çalışma, şablonu koru.
  5. platform düğümündeki listFormCode (AbpIdentity.Roles) var olan bir ListForm kodudur; component ekranın kendisini yeniden kurmaz, gömer.

7.7 Taşıma

Bir ekranı başka ortama taşımak tek dosya kopyalamak değildir. Wizard File Manager'ın Export butonu zip üretir:

wizard/{dosya}.json
custom/{component}.json          (bağımlılıklarıyla)
crud/{entity}.json               (component'lerin kullandığı endpoint'ler)
{sql|postgres}/{object|execute}/{nesne}.sql   (List yolunda SelectCommand'ın nesnesi)

Import iki adımlıdır: analiz (New/Identical/Conflict, çakışmalar diff editörde çözülür) → uygulama (dosya dosya yazım, önceki hâl yedeklenir, RollbackImport ile toplu geri alma). Sınırlar: yalnızca wizard, crud, custom, sql, postgres kök klasörleri (SQL sağlayıcı klasörlerinin altında yalnızca object/execute); 5 MB/dosya, 50 MB/arşiv, 500 girdi. Yetkiler: App.Listforms.Wizard.Export / .Import.


8. Artefakt E — SQL nesneleri

8.1 Tablo tasarımcısı — varsayılan kolonlar

Kullanıcı yalnızca iş kolonlarını söylese bile şunlar varsayılan olarak eklenir: Id, TenantId, CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId.

Kullanıcııkça "tenant yok" / "audit yok" demedikçe çıkarma; her seferinde sorma. Deploy öncesi adımda CRUD endpoint üretimi ve hangi operasyonların aktif başlayacağı seçilir; seçilmeyenler pasif kaydedilir, sonradan diyalogdan açılabilir.

8.2 View tasarımcısı

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.

Model: kaynaklar (tablo/view, CROSS/OUTER APPLY, türetilmiş alt sorgu), JOIN'ler (INNER/LEFT/RIGHT/FULL/CROSS; =, <>, >, >=, <, <=) ve criteria satırları (Column / Alias / Output / Group By / Sort / Filter / Or…). Group By sütunu: GroupBy, Where (satır çıktıya girmez, yalnızca filtre taşır), SUM, COUNT, COUNT_DISTINCT, AVG, MIN, MAX. Filtre hücreleri serbest yüklemdir (> 100, LIKE '%abc%', IS NULL).

Tek yönlüdür. Model → T-SQL. Var olan bir view kanonik şekle uymuyorsa geri okunamaz ve dialog ham SQL moduna düşer. Bu yüzden tasarlanmış bir view'ı elle düzenlemek tasarımcıyı kaybettirir.

Nesneler configs/seeds/{kapsam}/{sql|postgres}/{object|execute}/{ad}.sql altına yazılır: object = yeniden oluşturulabilir nesne (tablo/view/fonksiyon/prosedür), execute = bir kez çalıştırılacak script.

8.3 SQL seed dosyası yazım kuralları

Kural ı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

Id tipini ne seçtiysen wizard dosyasındaki KeyFieldDbSourceType ile aynı olmalıdır (UNIQUEIDENTIFIER9, INT IDENTITY11). Uyumsuzluk sessiz veri hatası üretir.

8.4 Seeder bu dosyaları nasıl çalıştırır

Yeni bir entity/tablo sql/object/ altına yazılır ve veritabanı SqlDataSeeder tarafından bu dosyalardan kurulur. Mekanik:

  1. Yalnızca aktif sağlayıcının klasörü çalışır. DefaultDatabaseProvider SQL Server ise {kapsam}/sql/, PostgreSQL ise {kapsam}/postgres/. İki dosya da yazmak taşınabilirlik içindir; aynı anda ikisi birden çalışmaz.
  2. Sıra: object/execute/, her klasörün içinde dosya adına göre alfabetik. Bir tablo başka bir tabloya FK ile bağlıysa dosya adları o sırayı vermelidir (ör. Hr_T_Employee.sql Hr_T_EmployeeAdvance.sql'den önce gelir).
  3. Dosya içeriği GO satırlarından batch'lere bölünür ve sırayla çalıştırılır. PostgreSQL'de GO kullanılmaz.
  4. Hata seed'i durdurur. Bir batch patlarsa istisna yukarı fırlatılır ve o kapsamın seed'i yarım kalır. Bu yüzden her script tekrar tekrar çalıştırılabilir (idempotent) olmalıdır: CREATE OR ALTER / CREATE OR REPLACE / IF NOT EXISTS.
  5. SQL nesneleri bittikten hemen sonra aynı kapsam için CrudDataSeeder çalışır; böylece CRUD endpoint'leri dayandıkları tablolardan sonra oluşur. Tablo dosyasını yazmadan crud/{Entity}.json yazmak anlamsızdır.
  6. Klasör kökünde kalmış .sql dosyaları geriye dönük uyumluluk için çalıştırılır ama uyarı loglar; yeni dosyayı her zaman object/ ya da execute/ altına koy.

execute/ klasörünün özel sözleşmesi: buradaki dosyalar iki aşamalıdır.

    1. aşama: SqlDataSeeder dosyayı çalıştırır → nesne oluşur/güncellenir.
    1. aşama: tüm migration ve seed işlemleri bittikten sonra AfterAllMigrationsSqlExecutor dosya adından türettiği stored procedure'ü çağırır.

Yani execute/ altındaki bir dosya, adı prosedür adıyla aynı olan bir stored procedure tanımlamalıdır (Hr_T_AdvanceRequest_Backfill.sqlHr_T_AdvanceRequest_Backfill prosedürü). schema.Procedure.sql biçimi de desteklenir. Prosedür adı güvenli tanımlayıcı deseninden geçer (harf/alt çizgi ile başlar, en fazla 127 karakter). Sadece bir DDL çalıştırmak istiyorsan dosya object/ altına aittir; execute/ veri dolumu ve migration sonrası düzeltme içindir.

8.5 Yeni entity/tablo eklerken izlenecek yol

  1. sql/object/{Modul}_T_{Entity}.sql — tabloyu §8.3 iskeletiyle yaz (tenant + audit kolonları varsayılan). PostgreSQL hedefi de destekleniyorsa postgres/object/ altına diyalekt eşini yaz.
  2. İndeks ve FK'leri aynı dosyada, varlık kontrolüyle ekle (IF NOT EXISTS); ayrı dosya sıralamayı kırılgan hâle getirir.
  3. Ekran bu tabloyu doğrudan kullanacaksa CRUD endpoint gerekmez — ListForm SelectCommandType: 1 ile tabloya bağlanır. Custom Component kullanacaksa crud/{Entity}.json de yaz (§9).
  4. Wizard dosyasında SelectCommand = tablo adı, KeyFieldName = Id, KeyFieldDbSourceType = tablodaki Id tipinin sayısı, IsTenant = tabloda TenantId varsa true, IsDeletedField/IsCreatedField = ilgili kolonlar varsa true.
  5. DB Migrate çalıştır; log'da Executing: CREATE TABLE … satırını gör.

9. Artefakt F — CRUD Endpoint

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 ve crud-endpoint-generate uçlarının tamamı reddedilir.
  • Dispatcher üzerinden çağrı kapıları: App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove; endpoint bazlı User/Role/Global kuralları bunun üstünde denetlenir.
  • GetList filtre sözleşmesi §7.2'deki ile aynıdır.

9.2 Custom Endpoint — elle yazılmış SQL'i API olarak açmak

Ekran: /admin/list/App.DeveloperKit.CustomEndpoints (bir ListForm ekranıdır). Entity: CustomEndpoint.

Alan Anlam
Name / Description Tanımlayıcı
Url Yayınlanacak yol
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ından okunur.
Sql Parametreli sorgu. String birleştirme yasak.
ParametersJson CustomEndpointParameter[]
PermissionsJson CustomEndpointPermission[]
// ParametersJson
[
  { "Type": "Query", "Name": "customerId", "DefaultValue": "", "Path": "", "IsRequired": true },
  { "Type": "Path",  "Name": "id",         "DefaultValue": "", "Path": "", "IsRequired": true },
  { "Type": "Body",  "Name": "note",       "DefaultValue": "", "Path": "data.note", "IsRequired": false },
  { "Type": "Static","Name": "tenantMode", "DefaultValue": "1", "Path": "", "IsRequired": true }
]

// PermissionsJson — Global / Role / User
[
  { "ResourceType": "Role", "ResourceId": "SalesManager" },
  { "ResourceType": "User", "ResourceId": "<userId>" }
]

Parametre tipleri: Static (sabit), Query (query string), Path (URL segmenti), Body (gövde; Path ile gövde içindeki yol).

Yetki iki katmanlıdır: önce dispatcher kapısı (App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove), sonra endpoint'in kendi PermissionsJson kuralları. Endpoint tanımlarını düzenleme yetkileri ise ListForm ekranının App.DeveloperKit.CustomEndpoints{,.Create,.Update,.Delete} yetkileridir.

Ne zaman CRUD, ne zaman Custom Endpoint? Tek tabloya standart CRUD gerekiyorsa CRUD Endpoint (otomatik üretilir, seed'lenir, filtre sözleşmesi hazırdır). Birleştirilmiş/şekillendirilmiş sorgu, rapor ya da özel bir yol gerekiyorsa Custom Endpoint.

9.3 Dynamic Service — çalışma zamanında derlenen C# servisi

Ekran: /admin/developerkit/dynamic-services. Entity: DynamicService. Bu, karar sırasının 3. adımıdır; konfigürasyon ve SQL yetmediğinde gelir, kod değişikliğinden önce denenir.

Alan Anlam
Name Benzersiz servis adı (DynamicCustomerService)
DisplayName / Description Kullanıcı dostu başlık ve açıklama
Code Tam C# sınıfıusing'ler ve namespace dahil
ControllerName Swagger'da görünecek controller adı
PrimaryEntityType Kullandığı ana entity (opsiyonel)
IsActive Yayında mı
CompilationStatus Pending(0) · Success(1) · Failed(2) · InProgress(3)
LastCompilationError / LastSuccessfulCompilation Son derleme sonucu
Version / CodeHash Kod her değiştiğinde Version artar, hash yenilenir

Yaşam döngüsü: TestCompile (Roslyn ile derle, hata mesajı UI'a döner) → PublishDynamicAssemblyRegistrationService assembly'yi tenant bağlamıyla kaydeder → ActionDescriptorChangeProvider MVC'ye route tablosunun değiştiğini bildirir → DynamicControllerActivator bağımlılıkları enjekte eder. Uygulama yeniden başlatılmaz.

Yetkiler: App.DeveloperKit.DynamicServices{.Create,.Edit,.Delete,.Manage,.TestCompile,.Publish,.ViewCode}.

Yazarken dotnet.instructions.md §5 geçerlidir: PlatformAppService türet, [Authorize(...)] ile koru, tenant bağlamını ICurrentTenant üzerinden kullan, SQL'i parametreli çalıştır, kullanıcıya görünen metni PlatformResource anahtarından ver, sabit/secret gömme.


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 Kolon/anahtar/index tasarımı, varsayılan tenant + audit kolonları, deploy öncesi CRUD endpoint seçimi
View tasarımcısı SqlViewDesignerDialog.tsx + sqlViewDesigner/ Diagram + criteria grid + T-SQL önizleme; bkz. 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.

Kalıcılık kuralı: tasarımcıdan çıkan her nesne configs/seeds/{kapsam}/{sql|postgres}/{object|execute}/{ad}.sql altına düşer. object yeniden oluşturulabilir nesneler (tablo/view/fonksiyon/prosedür), execute bir kez çalışacak script'ler içindir. Editörde elle çalıştırılan ama dosyaya düşmeyen bir DDL, veritabanı yeniden kurulduğunda kaybolur.

9.6 ListForm ekranı ve editörü

Wizard bir ekranı üretir; ince ayar /admin/listform/edit/{listFormCode} üzerinden yapılır. Sekmeler ve karşılık geldikleri JSON kolonları:

Sekme JSON
Veritabanı SelectCommandType, SelectCommand, KeyFieldName, Insert/Update/DeleteCommand (+ Before/After), *ServiceAddress
Sütunlar ListFormField kayıtları, ColumnOptionJson, banded/command sütunlar
Filtreler FilterRowJson, HeaderFilterJson, FilterPanelJson, SearchPanelJson, GroupPanelJson, ExtraFilterJson
Düzenleme EditingOptionJson, EditingFormJson
Yetkiler PermissionJson (ekran ve sütun düzeyi)
Alt Form SubFormsJson
Pivot / Tree / Gantt / Scheduler / Todo PivotOptionJson, TreeOptionJson, GanttOptionJson, SchedulerOptionJson, TodoOptionJson
Widget WidgetsJson
Workflow WorkflowJson
Durum StateStoringJson, PagerOptionJson, SelectionJson
Grafik SeriesJson, LegendJson, ArgumentAxisJson, ValueAxisJson, TooltipJson, ZoomAndPanJson

Ayrıca CustomJsSourcesJson / CustomStyleSourcesJson ekran yüklendiğinde çalışacak JS/CSS taşır — son çaredir, önce EditorScript/EditorOptions denenmelidir.

Uyarı: Bu ekrandan yapılan değişiklikler doğrudan veritabanına yazılır ve wizard seed dosyasına yansımaz. Kalıcı olması gereken bir değişikliği ya wizard'ı EditFileName ile yeniden çalıştırarak ya da seed dosyasını güncelleyerek yap.


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[])
Menu Groups /admin/list/App.Menus.MenuGroup App.Menus.MenuGroup Menü grubu adları (Erp, Kurs gibi üst kümeler)
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 ılacak adres
Icon react-icons adı
Order Kardeşler arası sıra
ShortName Kısa rozet (HR, MRP) — kök menülerde kullanılır
RequiredPermissionName Görünürlük yetkisi
Target / CssClass / ElementId / IsDisabled Bağlantı davranışı ve görünüm
UserId / RoleId / CultureName Kullanıcı, rol ve dil bazlı menü varyantı

Kurallar — prompt ile üretim yaparken:

  1. Menüyü elle ekleme. Yeni bir ekranın menüsü wizard seed dosyasından gelir; MenusData.json yalnızca platformun kendi ekranları içindir ve orada da Routes bölümüne dokunulmaz.
  2. Route kaydı üretme. Dinamik liste ekranları statik /admin/list/:listFormCode route'unu kullanır; custom component'lerin route'u RoutePath alanından türetilir. Route tablosu yalnızca fiziksel React sayfaları içindir ve prompt ile ekran üretirken böyle bir sayfa yazılmaz.
  3. Menü sırası için wizard'ın MenuOrder alanını kullan; Menu Manager'da elle taşımak seed dosyasına yansımaz ve veritabanı sıfırlandığında kaybolur.
  4. Rol/dil bazlı menü varyantı gerekiyorsa Menu List ekranından ikinci bir kayıt açılır — aynı Url, farklı RoleId/CultureName. Wizard bunu üretmez.

9.8 Wizard Manager — layout matrisi

Wizard Manager platformun en yetenekli üretim ekranıdır: tek bir tanımdan hem Custom Component sayfası hem de yedi farklı görünümü olan List ekranı üretir. Bir List wizard'ında birden fazla görünüm aynı anda açık olabilir; kullanıcı ekranın üstünden geçiş yapar, tercihi kullanıcı bazlı saklanır.

Görünüm Bayrak DefaultLayout Zorunlu option alanı Ne zaman aç
Grid Grid "grid" Neredeyse her zaman; varsayılan
Card Card "card" Görsel/özet kayıtlar, mobil kullanım
Pivot Pivot "pivot" Sayısal kırılım/analiz ihtiyacı
Chart Chart "chart" — (grafik ayarları ListForm editöründe) Trend/dağılım
Tree Tree "tree" TreeOptionDto.KeyExpr + ParentIdExpr Kendine referanslı hiyerarşi
Gantt Gantt "gantt" GanttOptionDto.ParentIdExpr + TitleExpr + StartExpr + EndExpr Zaman çizelgesi, bağımlılıklı görevler
Scheduler Scheduler "scheduler" SchedulerOptionDto.TextExpr + StartDateExpr + EndDateExpr Takvim/randevu
TodoBoard (Kanban) Todo "todo" TodoOptionDto.TitleExpr + StatusExpr Durum akışı olan işler

Kurallar:

  • Zorunlu option alanı boşsa seeder o görünümün JSON'unu yazmaz; bayrak açık kalsa bile görünüm çalışmaz. Açtığın her görünümün option bloğunu doldur.
  • DefaultLayout, bayrağı true olan bir görünüm olmalıdır.
  • Aynı veri birden fazla görünümde aynı SelectCommand üzerinden sunulur; ayrı sorgu yazılmaz.
  • Bir ekranın hem liste hem pano hem takvim olarak istenmesi tek wizard dosyasıdır — ikinci bir ekran açma.

Wizard Manager'ın kendi aksiyonları: liste/kart görünümü, arama, düzenleme (EditFileName), silme, DB Migrate, Export (zip), Import (diff + rollback). Yetkiler App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}.


10. Uçtan uca reçeteler

10.1 "Var olan bir tablo için ekran aç"

  1. Tablo yoksa SQL Table Designer ile oluştur (varsayılan kolonlarla).
  2. Ekran birden çok tablodan besleniyorsa View Designer ile view üret.
  3. Wizard: menü/kimlik → veri ayarları (SelectCommandType + SelectCommand + anahtar) → alanlar → (gerekiyorsa) alt form/widget/workflow → yayınla.
  4. Hesaplanan alanlara EditorScript, biçimli alanlara EditorOptions ver.
  5. Doğrula: menü görünüyor mu, yetkiler roldeki kullanıcı için doğru mu, tenant filtresi çalışıyor mu, seed dosyası oluştu mu.

10.2 "Ana-detay ekranı"

  1. Detay ekranını menüsüz wizard ile üret (CreateMenu: false).
  2. Ana ekranın SubForms listesine ParentFieldNameChildFieldName eşlemesini ve DbType ekle.
  3. İki ekranın yetkileri ayrıdır; detayın yetkisi verilmemişse sekme görünmez.

10.3 "Serbest yerleşimli sayfa / dashboard"

  1. Gerekli endpoint'ler yoksa CRUD Endpoint üret.
  2. Component Manager → yeni bileşen → Visual Designer: PageContainerFlexRow → içine platform düğümleri (listFormCode ile) ve/veya Form.
  3. Veri kaynaklarını Data sekmesinden bağla; filtreleri route/query/record kaynaklarıyla kur, required bayrağını bilinçli seç.
  4. Görünürlük ve komut yetkilerini ver (designerPermission, *Permission).
  5. Wizard ComponentKind: 1 ile menüye bağla.
  6. Doğrula: custom/{Name}.json ve ilgili crud/*.json dosyaları oluştu mu.

10.4 "Kanban panosu"

  1. Tabloda durum kolonu olsun (Status).
  2. Wizard'da Todo: true, DefaultLayout: "todo", TodoOptionDto içinde TitleExpr, StatusExpr, StatusOrder (kolon sırası), AssigneeExpr, DueDateExpr doldur.
  3. Kolonlar statusExpr değerlerinden üretilir; kartlar sürüklenerek durum değiştirir, panodan yeni kolon eklenebilir.

10.5 "Rapor sorgusunu API olarak aç"

  1. Sorguyu SQL Query Manager'da yaz ve çalıştır, sonucu doğrula.
  2. Tekrar kullanılacaksa view olarak deploy et (sql/object/…), tek seferlikse Custom Endpoint içinde bırak.
  3. Custom Endpoints ekranından kaydı oluştur: Url, Method, DataSourceCode (kullanıcının kendi tenant veritabanı isteniyorsa "!Tenant"), Sql, ParametersJson, PermissionsJson.
  4. Parametreleri Query/Path/Body olarak tanımla; hiçbirini SQL'e string birleştirme ile koyma.
  5. Erişimi PermissionsJson ile daralt; dispatcher kapısının (…CustomEndpoints.Get vb.) ilgili rolde açık olduğunu doğrula.

10.6 "Konfigürasyonla çözülemeyen iş mantığı"

  1. Önce gerçekten çözülemediğini göster: ListForm + SQL + Custom Endpoint neden yetmiyor, bir iki cümleyle yaz.
  2. Dynamic Service oluştur; PlatformAppService türet, [Authorize(...)] ile koru.
  3. TestCompile → hatasız → Publish. CompilationStatus Success olmadan yayınlama.
  4. Swagger'da ControllerName altında göründüğünü doğrula.
  5. Ekran tarafında bu servisi ya ListForm'un *ServiceAddress alanından ya da Custom Component'in bir data source'undan tüket.

10.7 Şablon senaryo — "onaylı talep formu" (avans, izin, masraf, satın alma…)

Bu tür isteklerin tamamı aynı iskeleti kullanır. Kullanıcı "avans talep formu istiyorum" dediğinde üretilecek dosya seti:

configs/seeds/host/
├── sql/object/Hr_T_AdvanceRequest.sql              ← tablo (+ PostgreSQL hedefse postgres/object/…)
├── crud/Hr_T_AdvanceRequest.json                   ← yalnızca Custom Component yolunda gerekir
├── custom/AdvanceRequestForm.json                  ← yalnızca serbest yerleşim isteniyorsa
├── wizard/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:

  1. Tablo — iş kolonları + §8.1 varsayılanları. Onay akışı için dört kolon şarttır: durum (Status), onaylayan (ApproverId), onay tarihi (ApprovalDate), açıklama (ApprovalNote). Talep sahibi için de bir kolon (EmployeeId) bulunmalıdır.
  2. Wizard dosyası — §4.4'teki iskelet:
    • IsTenant: true (tabloda TenantId var), IsDeletedField: true, IsCreatedField: true
    • MenuParentCode = modül kökü (App.Hr); yoksa wizard yaratır, Order = max + 1
    • PermissionGroupName = App.Hr, EN + TR görünen adlarıyla
    • Görünümler: Grid + Cardık; durum akışı görsel isteniyorsa Todo: true + TodoOptionDto (§9.8)
  3. AlanlarGroups içinde iki grup mantıklıdır: "Talep" (tutar, tarih, açıklama) ve "Onay" (durum, onaylayan, onay tarihi, onay notu). Onay grubundaki alanlar EditorOptions: "{\"readOnly\":true}" ile kilitlenir; kullanıcı doldurmaz, akış doldurur.
  4. Hesap ve varsayılanlarEditorScript ile:
    • Talep tarihi form açılışında bugün: today tarifi, tetikleyici open
    • Tutar × oran gibi hesaplar: multiply / percent
    • Eşik üstü tutarda uyarı: notify ya da ask, koşulu greaterThan
  5. Onay akışı — §4.7'deki WorkflowDto. Tipik grafik: Start → Compare (tutar eşiği) → Approval (yönetici) / Approval (üst yönetici) → Inform → End. ApprovalIsFilterUserName: true ile herkes yalnızca kendi onayına düşenleri görür, ApprovalIsResetWorkflow: true ile kayıt güncellenince akış başa döner.
  6. Yetki — wizard App.Hr.AdvanceRequests kökünü ve .Create/.Update/.Delete/.Export/ .Import/.Note alt yetkilerini üretir. Onaylayanların rolüne kök + .Update verilir; talep sahiplerine kök + .Create yeter.
  7. Dil — menü/başlık/açıklama metinleri wizard dosyasındaki LanguageText* alanlarından, alan başlıkları her Items öğesinin TurkishCaption/EnglishCaption alanlarından üretilir. Ayrıca LanguagesData.json'a dokunmaya gerek yoktur.
  8. Teslim — dosyaları listele, DB Migrate gerektiğini söyle, §11 kontrol listesini geç.

Bu iskeletin türevleri: izin talebi (tutar yerine gün sayısı, days tarifi), masraf talebi (alt form olarak masraf kalemleri → menüsüz wizard + SubForms), satın alma talebi (kalemler + tedarikçi lookup'ı + iki kademeli onay).


11. Teslim öncesi kontrol listesi

  • Menü, route ve ekran sözleşmesi birbirini gösteriyor.
  • Kök yetki + aksiyon yetkileri var; menü RequiredPermissionName ile bağlı.
  • Yetki grubunun EN + TR görünen adı var.
  • Kullanıcıya görünen her metnin EN + TR dil anahtarı var; anahtar tekrar edilmemiş.
  • IsTenant (ve gerekiyorsa IsBranch/IsOrganizationUnit) doğru; sorgular tenant güvenli.
  • Tüm SQL parametreli; string birleştirme yok.
  • DeleteCommand = DefaultDeleteCommand("{Tablo}").
  • Seed dosyası oluştu; InsertedRecords yalnızca gerçekten yaratılan kayıtları içeriyor.
  • Bağımlılıklar (custom component, crud endpoint, sql nesnesi) da seed'li.
  • Export zip'i başka bir ortamda açıldığında ekran ayağa kalkıyor.
  • Geri alma yolu yazılı: wizard dosyasını sil → InsertedRecords temizlenir.

Prompt ile üretim yaptıysan ek olarak:

  • Dosyalar doğru kapsam klasöründe (host/ ya da tenants/{tenantId}/).
  • Üretim sırası doğru (§0.2) ve bağımlı wizard'ın zaman damgası daha büyük.
  • Tablo/kolon adları uydurulmadı; ya var olan nesneden okundu ya da sql/object/ altında üretildi.
  • KeyFieldDbSourceType, tablodaki Id tipiyle uyumlu.
  • ılan her görünümün zorunlu option alanı dolu (§9.8).
  • Custom component'te route yetkisiz üretildiği için koruma bileşenin içinde (designerPermission / *Permission / checkPermission).
  • Custom component visual modda ise Props.visualDesigner ile Code marker'ı aynı dokümanı taşıyor.
  • Menü ve route elle eklenmedi; wizard dosyasından geliyor.
  • Kullanıcıya hangi dosyaların üretildiği ve DB Migrate gerektiği söylendi.
  • Ekran tarifi (§0.6) çıkarıldı ve yol seçimi gerekçelendirildi.
  • Görselden üretildiyse: platformda karşılığı olmayan öğeler ve yapılan varsayımlar (tablo/kolon adları, anahtar tipi, lookup kaynakları) açıkça bildirildi.
  • Onaycı/iş kuralı gibi görselden okunamayan kritik bilgiler için soru soruldu (§0.7.4).