107 KiB
Low-Code Üretim Referansı (Authoring Reference)
Bu doküman, bir yapay zekâ ajanının platformu bir geliştirici gibi kullanarak ekran ve bileşen üretebilmesi için gereken somut şemaları, alan anlamlarını, enum değerlerini ve çalışan örnekleri içerir.
- Ne yapılacağına karar verme kuralları:
ai.instructions.md 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:
- Tablo/kolon adları —
sql/object/altındaki dosyalar ya da mevcut wizard dosyalarındakiSelectCommand. Kolon adını tahmin etme; yoksa tabloyu da sen üret. - Var olan menü kökü ve izin grubu — aynı modül için ikinci bir kök menü açma. Mevcut
wizard dosyalarındaki
MenuParentCode/PermissionGroupNamedeğerlerini tara. - Dil anahtarı çakışması —
LanguagesData.jsonve diğer wizard dosyaları. Var olan bir anahtarın metnini seed ezmez; yanlış metinle karşılaşırsan anahtarı değiştir.
0.4 Üretim sonrası
- Değişikliklerin devreye girmesi için DB Migrate (SQL Query Manager / Wizard Manager
üzerindeki buton, yetki
App.Setup.Migrate) ya da migrator konteyneri (SEED=true) çalışmalı. - Seeder idempotenttir: ekran zaten varsa dosya atlanır (§4.5). Var olan bir ekranı
değiştirmek için dosyayı düzenlemek yetmez — kayıtları silip yeniden seed etmek ya da
ekranı Wizard'dan
EditFileNameile yeniden çalıştırmak gerekir. - Ürettiğin dosyaları kullanıcıya tek tek listele: hangi dosya, ne üretiyor, hangi menüde görünecek, hangi yetkiyi ister.
0.5 Dosya biçimi kuralları
| Kural | Değer |
|---|---|
| Kodlama | UTF-8 (BOM'suz) |
| JSON alan adları | PascalCase (Wizard, ListFormCode, Groups) — seeder PropertyNameCaseInsensitive okur ama dosyalar PascalCase üretilir |
| Custom component dosyası | Kök alanlar PascalCase; Props içindeki designer dokümanı camelCase (visualDesigner, nodes, sourceMode) |
| Enum'lar | Sayı olarak yazılır (§3) |
GeneratedAt |
ISO-8601 UTC (2026-08-26T10:15:00Z) — yalnızca bilgi amaçlı |
| İç 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ı
- Sayfa iskeleti — kaç bölge var? Üstte KPI şeridi, ortada liste, sağda panel, altta sekme?
- Ana bölge ne? Tablo mu, kart ızgarası mı, pano mu, takvim mi, form mu?
- Araç çubuğu — hangi butonlar var, ne yazıyorlar, ikonları ne?
- Sütunlar — başlıklar, hizalama, biçim (para, tarih, yüzde), rozet/renkli durum hücreleri.
- Filtre/arama — başlık altı filtre satırı, arama kutusu, tarih aralığı seçicileri.
- Form alanları — kontrol tipleri ve kolon düzeni (kaç kolon, hangi alan kaç sütun kaplıyor).
- Satır aksiyonları — satır sonundaki ikonlar.
- Durum göstergeleri — onay adımları, ilerleme çubukları, etiketler.
0.7.2 Görsel öğe → artefakt eşlemesi
| Görselde gördüğün | Karşılığı |
|---|---|
| Başlık satırı + veri satırları olan tablo | Grid layout |
| Kart ızgarası | Card layout |
| Dikey kolonlar + sürüklenebilir kartlar | Todo (Kanban) layout · TodoOptionDto.StatusExpr |
| Ay/hafta takvimi, saat çizelgesi | Scheduler layout |
| Yatay zaman çubukları, bağımlılık okları | Gantt layout |
| Girintili ağaç, açılır düğümler | Tree layout |
| Satır/sütun kesişimli özet tablo, ara toplamlar | Pivot layout |
| Çubuk/çizgi/pasta grafik | Chart layout · SeriesJson |
| Üstte sayı gösteren küçük kutular | Widgets (WidgetEditDto) |
| Kaydın altında sekmeler (Kalemler, Ekler, Geçmiş) | SubForms |
| Araç çubuğundaki özel buton | CommandColumnJson · ButtonPosition: 1 |
| Satır sonundaki ikon | CommandColumnJson · ButtonPosition: 0 |
| Başlık altındaki filtre satırı | FilterRowJson |
| Sağ üstteki arama kutusu | SearchPanelJson |
| "Sürükleyip gruplayın" şeridi | GroupPanelJson |
| Üstte ek filtre çubuğu (tarih aralığı, şube seçimi) | ExtraFilterJson |
| Sayfalama şeridi | PagerOptionJson |
| Renkli durum rozeti | ColumnStylingJson / ColumnCssClass |
| Onayla / Reddet butonları | WorkflowJson (otomatik gelir, elle tanımlama) |
| Yan yana bağımsız bölümler, karışık yerleşim | Custom Component · FlexRow + düğümler |
0.7.3 Görseldeki kontrol → EditorType
| Görselde | EditorType |
Ek |
|---|---|---|
| Tek satır metin kutusu | dxTextBox |
|
| Çok satırlı kutu | dxTextArea |
autoResizeEnabled |
| Sağında ok olan açılır liste | dxSelectBox |
lookup zorunlu |
| İçinde etiket/çip olan açılır liste | dxTagBox |
lookup zorunlu |
| Büyüteçli seçim + tablo açılıyor | dxGridBox / dxLookup |
columns |
| Onay kutusu | dxCheckBox |
|
| Aç/kapa anahtarı | dxSwitch |
switchedOnText |
| Yan yana yuvarlak seçenekler | dxRadioGroup |
layout: horizontal |
| Takvim ikonlu alan | dxDateBox |
|
| İki tarihli aralık alanı | dxDateRangeBox |
|
| Artı/eksi oklu sayı | dxNumberBox |
format.precision |
₺ 1.250,00 biçimli alan |
dxNumberBox |
format.type: currency |
% biçimli alan |
dxNumberBox |
format.type: percent |
| Renk kutucuğu | dxColorBox |
|
| Kalın/italik araç çubuklu alan | dxHtmlEditor |
toolbar.items |
| Kaydırıcı | dxSlider / dxRangeSlider |
min, max |
| Görsel küçük resmi / yükleme alanı | dxImageViewer / dxImageUpload |
accept, maxFileSize |
| Gri, tıklanamaz alan | ilgili editör + EditorOptions: {"readOnly": true} |
|
| Kırmızı yıldızlı etiket | IsRequired: true |
0.7.4 Görselden okunamayacak şeyler
Bunlar bir ekran görüntüsünde görünmez; varsayımını yaz ve bildir:
| Bilgi | Varsayılan davranış |
|---|---|
| Tablo/kolon adları | Görünen başlıklardan PascalCase kolon adı türet, tabloyu sql/object/ altında sen üret |
| Anahtar alan ve tipi | Id / UNIQUEIDENTIFIER (KeyFieldDbSourceType: 9) |
| Lookup'ların kaynağı | Az sayıda sabit seçenek görünüyorsa StaticData, kod/isim çifti gerektiren yerlerde Query |
| Tenant/şube kırılımı | IsTenant: true (§CLAUDE varsayılanı) |
| Yetki grubu ve menü kökü | Ekranın ait olduğu modülden türet, kullanıcıya sor değil bildir |
| Dil metinleri | Görseldeki dilde yaz, diğer dile çevir; ikisi de doldurulur |
| Onaycılar | Yer tutucu bırak (<roleOrUserId>) ve kullanıcıya sor — bu, sorulması gereken bir şeydir |
| İş kuralları (hesap, koşul) | Görselde formül görünmüyorsa EditorScript yazma; sor |
0.7.5 Görsel için de aynı protokol
Görselden çıkardığın tarifi §0.6.1'deki 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)
ComponentKind — 0 List · 1 Custom
SelectCommandType — 1 Table · 2 View · 3 TableValuedFunction · 4 Query ·
5 StoredProcedure
LookupDataSourceType — 1 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: OrderNo → Order No |
CaptionName |
App.Listform.ListformField.{KolonAdı} |
ColSpan |
1 |
IncludeInEditingForm |
true |
ValueExpr / DisplayExpr |
Key / Name (lookup tanımlanırsa değiştirilir) |
EditorOptions / EditorScript |
Boş — ihtiyaç varsa sen yazarsın |
SQL tipi → EditorType çıkarımı (inferEditorType):
| SQL tipi | Üretilen editör |
|---|---|
bit |
dxCheckBox |
int, bigint, smallint, tinyint, decimal, numeric, float, real, money, smallmoney |
dxNumberBox |
date, datetime, datetime2, smalldatetime, datetimeoffset |
dxDateBox |
| diğer hepsi | dxTextBox |
Çıkarım kasıtlı olarak dardır. Foreign key kolonları dxNumberBox/dxTextBox olarak gelir —
bunları dxSelectBox / dxGridBox / dxTagBox yapmak ve lookup tanımlamak senin işindir.
Uzun metin (nvarchar(max)) için dxTextArea, HTML içerik için dxHtmlEditor, durum kolonları
için dxRadioGroup/dxSwitch seçilmelidir; otomatik çıkarım bunları bilemez.
4.3.2 Editör tipine göre özellikler
Her editör kendi EditorOptions sözlüğüne sahiptir. Aşağıdaki tablo, o editöre özgü olan
anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir.
EditorType |
Ne için | Uygun DbSourceType |
Kendine özgü EditorOptions |
|---|---|---|---|
dxTextBox |
Kısa metin | String (16) | — (ortak: maxLength, mask, placeholder) |
dxTextArea |
Uzun metin | String (16) | autoResizeEnabled, minHeight |
dxNumberBox |
Sayı | Int32/Int64/Decimal/Double (7,8,10,11,12) | format.type (fixedPoint/currency/percent), format.precision, format.currency, useMaskBehavior, useLargeSpinButtons, invalidValueMessage |
dxDateBox |
Tarih / tarih-saat | Date/DateTime (5,6,26,27) | useMaskBehavior (+ ortak: type, displayFormat, dateSerializationFormat, pickerType, interval, calendarOptions.*, invalidDateMessage) |
dxDateRangeBox |
Tarih aralığı | Date/DateTime | ortak tarih anahtarları |
dxCalendar |
Gömülü takvim | Date/DateTime | calendarOptions.zoomLevel, calendarOptions.firstDayOfWeek |
dxCheckBox |
Evet/hayır | Boolean (3) | text (kutunun yanındaki etiket) |
dxSwitch |
Evet/hayır (anahtar) | Boolean (3) | switchedOnText, switchedOffText |
dxRadioGroup |
Az sayıda seçenek | String/Int32 | layout (horizontal/vertical) + lookup zorunlu |
dxSelectBox |
Tek seçim (az kayıt) | FK tipi | acceptCustomValue, searchEnabled, minSearchLength, searchExpr, searchMode, searchTimeout, noDataText, showDataBeforeSearch + lookup zorunlu |
dxLookup |
Tek seçim (çok kayıt, mobil dostu) | FK tipi | dropDownOptions.*, searchEnabled, applyValueMode + lookup zorunlu |
dxDropDownBox |
Özel içerikli açılır kutu | FK tipi | dropDownOptions.width/height/hideOnOutsideClick, deferRendering, openOnFieldClick |
dxGridBox |
Çok kolonlu seçim ızgarası | FK tipi | columns*, filterRowVisible*, selectionMode*, acceptCustomValue* + lookup zorunlu |
dxTagBox |
Çok değerli seçim | String (virgüllü) / ilişki tablosu | showSelectionControls*, maxDisplayedTags*, showMultiTagOnly*, applyValueMode*, searchEnabled*, acceptCustomValue*, hideSelectedItems, multiline + lookup zorunlu |
dxAutocomplete |
Serbest metin + öneri | String (16) | minSearchLength, searchExpr, searchTimeout |
dxColorBox |
Renk | String (16) | editAlphaChannel, keyStep |
dxSlider |
Tek değerli kaydırıcı | Sayı | min, max, tooltip.showMode |
dxRangeSlider |
Aralık kaydırıcı | Sayı | min, max, showRange |
dxHtmlEditor |
Zengin metin | String (16) | toolbar.items, toolbar.multiline, valueType (html/markdown), mediaResizing.enabled, imageUpload.uploadUrl, imageUpload.fileUploadMode |
dxImageUpload |
Görsel yükleme (platform) | String (16) | uploadUrl*, accept*, multiple*, maxFileSize*, width*, height* |
dxImageViewer |
Görsel gösterimi (platform) | String (16) | width, height |
\* ile işaretli anahtarlar backend'de tipli DTO'ya çözülür:
GridBoxOptionsDto, TagBoxOptionsDto, ImageUploadOptionsDto. Yanlış tipte yazılan bir değer
hata vermez, sessizce yok sayılır — bu yüzden columns bir dizi, maxDisplayedTags bir
sayı, multiple bir boolean olmalıdır.
Lookup zorunlu yazan editörlerde LookupDataSourceType + LookupQuery + ValueExpr +
DisplayExpr dolu olmalıdır; aksi hâlde alan boş bir liste gösterir:
{
"FieldName": "StatusId", "EditorType": "dxSelectBox", "DbSourceType": 11,
"LookupDataSourceType": 1,
"LookupQuery": "[{\"Key\":1,\"Name\":\"Taslak\"},{\"Key\":2,\"Name\":\"Onayda\"},{\"Key\":3,\"Name\":\"Onaylandı\"}]",
"ValueExpr": "Key", "DisplayExpr": "Name"
}
LookupDataSourceType 1 (StaticData) ise LookupQuery bir JSON dizisi, 2 (Query) ise SQL,
3 (WebService) ise 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 |
| Açılır liste | noDataText, searchEnabled, minSearchLength, searchExpr, searchMode, searchTimeout, showDataBeforeSearch, openOnFieldClick, deferRendering, wrapItemText, dropDownOptions.* |
Elle JSON yazmak yerine C# tarafında EditorOptions akıcı yapısını kullan (§6); hazır
başlangıçlar bu anahtarların doğru bileşimlerini üretir.
4.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": []
}
}
EditorScriptalanındaki// @builderbaşlığı ile onu izleyen kod satırı birebir tutarlı olmalıdır. Elle yazmak yerine C# tarafındaEditorScript.Build(...)kullan ya da Script Builder'da üret; uyuşmazsa dialog script'i "elle düzenlenmiş" sayar.
4.4.1 Seeder'ın senin yerine ürettikleri — bunları dosyaya yazma
WizardDataSeeder, dosyadaki Wizard bloğundan yola çıkarak aşağıdakileri kendisi üretir.
Bunları seed dosyasında aramaya kalkma, orada yokturlar:
| Üretilen | Kural |
|---|---|
| Dil anahtarları | {MenuCode} (menü), {ListFormCode}.Title, {ListFormCode}.Desc, {PermissionGroupName} ve her alan için CaptionName |
| Yetkiler | {MenuCode} kökü + .Create/.Update/.Delete (+ List yolunda .Export/.Import/.Note) |
| Yetkilerin dil anahtarları | Ortak sabitler: App.Platform.Create/Update/Delete/Export/Import/Note |
| Üst menü | MenuParentCode yoksa yaratılır, Order = max(kök Order) + 1 |
| Menü sırası | MenuOrder 0/boş ise max(kardeş Order) + 1 |
DataSource |
DataSourceCode yoksa yaratılır; bağlantı metninde Server geçiyorsa Mssql, aksi hâlde Postgresql |
LayoutJson |
Görünüm bayrakları + DefaultLayout |
PermissionJson / alan PermissionJson |
{MenuCode} kökünden türetilir |
EditingFormJson |
Groups içinden; anahtar alan ve IncludeInEditingForm: false olanlar hariç |
| Form genişlik/yükseklik | Grup kolon sayısı ve satır sayısından hesaplanır (en fazla 3 kolon) |
FilterRowJson, HeaderFilterJson, SearchPanelJson, GroupPanelJson, ColumnOptionJson, PagerOptionJson, ExportJson |
Varsayılanlar |
DeleteCommand |
IsDeletedField: true ise soft delete UPDATE komutu; false ise null |
DefaultFilter |
IsDeletedField: true ise "IsDeleted" = 'false' |
InsertFieldsDefaultValueJson |
IsCreatedField: true ise CreationTime=@NOW, CreatorId=@USERID, IsDeleted=false, Id=@NEWID; değilse yalnızca Id=@NEWID |
DeleteFieldsDefaultValueJson |
IsDeletedField: true ise DeleterId=@USERID, Id=@ID; değilse yalnızca Id=@ID |
ValidationRuleJson |
IsRequired: true olan alanlara required kuralı |
LookupJson |
LookupQuery doluysa LookupDataSourceType + DisplayExpr + ValueExpr ile |
ShowNote |
Alt form veya iş akışı varsa açılır |
SelectionJson |
İş akışı varsa single, yoksa none |
| Görünüm option JSON'ları | İlgili bayrak açık ve zorunlu alanı dolu ise yazılır; eksikse görünüm sessizce kapanır |
Dikkat edilecek tuzaklar:
Tree/GanttiçinParentIdExpr,ScheduleriçinTextExpr,TodoiçinTitleExprveStatusExprboşsa o görünümün JSON'u hiç yazılmaz — bayrağıtrueyapmak yetmez.- Anahtar alanı (
KeyFieldName)Groupsiçine koyarsan sütun olarak gizlenir ve forma girmez; yine de tanımlaman doğrudur, çünkü tip bilgisi oradan okunur. IsDeletedField: falseverirsen ekranda kalıcı silme olmaz (DeleteCommandnull kalır); soft delete kolonları olmayan tabloda silme istiyorsan ListForm editöründen komut yazmalısın.- Dil anahtarı zaten varsa metni ezilmez. Yanlış metin görüyorsan anahtar başka bir yerde tanımlanmıştır.
4.5 Güncelleme, idempotency ve silme semantiği
Seeder idempotenttir — bir ekran zaten varsa dosya atlanır. "Zaten var" ölçütü yola göre değişir:
| Yol | Kontrol |
|---|---|
List |
ListForm kaydında bu ListFormCode var mı |
Custom + menülü |
Bu MenuCode ile menü var mı |
Custom + menüsüz |
Bu MenuCode adında yetki var mı |
Sonuç: var olan bir ekranı seed dosyasını düzenleyerek değiştiremezsin. Değiştirmenin üç yolu vardır:
- Wizard ekranından
EditFileNameile yeniden çalıştır (önce eski dosya veInsertedRecordskayıtları silinir, sonra yenisi üretilir) — tercih edilen yol. - ListForm editöründen ince ayar yap (seed dosyasına yansımaz, §9.6 uyarısı).
- İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır.
Listyolunda seeder ekranı uygularkenListForm,ListFormFieldveListFormWorkflowkayıtlarını silip yeniden yazar; idempotency kapısını geçen dosya için bu güvenlidir.
Silme: yalnızca InsertedRecords içindekiler silinir. Paylaşılan üst menü ve izin grubu
başka bir wizard tarafından yaratıldıysa kalır. Bu yüzden InsertedRecords listesine var olan
bir kaydı yazmak veri kaybettirir.
4.6 Alt formlar ve widget'lar
SubForms (SubFormDto) — ana kaydın altında sekme olarak açılan ekranlar:
"SubForms": [
{
"TabType": "List", // List | 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:
Nile başlayan kısa id'ler seeder tarafından{ListFormCode}-{Id}hâline getirilir; hedef alanlar (NextOn*,CompareOutcomes[].TargetId) da aynı normalizasyondan geçer. Bu yüzden dosyadaN1,N2yazman yeterlidir ve tekil olmaları şarttır. Comparedüğümünde çok dallı karar içinCompareOutcomeskullanılır; iki dallı basit karar içinNextOnTrue/NextOnFalseyeterlidir.ApprovaldüğümüNextOnApprove/NextOnReject,StartveInformdüğümleriNextOnStartalanını kullanır.Enddüğümünün çıkışı yoktur.CompareValuesayısaldır (decimal); metin karşılaştırması bu düğümle yapılmaz.Titletekil olmalıdır; seeder tekrar eden başlıkları ayrıştırır ama okunabilirliği bozar.- Akış varsa ListForm
SelectionMode = singleolur ve not paneli otomatik açılır. - Görsel tasarım
/admin/listform/edit/{kod}→ Workflow sekmesindedir.
4.8 ListForm entity — List component'in tek kaynağı
Bir List component'in bütün davranışı ListForm (ekran) ve ListFormField (alan) kayıtlarında
durur. Wizard bunları üretir; /admin/listform/edit/{kod} düzenler. Prompt ile üretim yaparken
wizard dosyası yazılır, ama hangi ayarın hangi kolona düştüğünü bilmek gerekir — çünkü wizard'ın
kapsamadığı bir istek geldiğinde cevap "kod yazalım" değil, "şu JSON kolonunu şöyle ayarlayalım"
olmalıdır.
Kimlik ve varyant
| Kolon | Ne yapar |
|---|---|
ListFormCode |
Ekranın kodu; kullanıcı/rol/dil özelleştirmeleri buna bağlanır |
CultureName · UserId · RoleId |
Aynı ekranın varyantları. Aynı ListFormCode ile ikinci bir kayıt açıp yalnızca bir rol ya da dil için farklı sütun/düzen verebilirsin |
ListFormType |
List · Form — ekranın türü |
IsSubForm / SubFormsListFormType |
Alt form olarak açıldığında davranış |
Veri
| Kolon | Ne yapar |
|---|---|
DataSourceCode |
Bağlantı |
SelectCommandType + SelectCommand |
Kaynak türü ve kaynağın kendisi |
TableName |
Tablo adı/alias — yazma komutlarının hedefi |
KeyFieldName + KeyFieldDbSourceType |
Anahtar |
SelectFieldsDefaultValueJson |
Select'e geçirilen varsayılan parametreler |
DefaultFilter |
Her sorgunun sonuna eklenen WHERE (soft delete filtresi buradadır) |
IsTenant · IsBranch · IsOrganizationUnit |
Otomatik kırılım filtreleri |
DataSourceJson · CommonJson |
Kaynak ve ortak ayarlar |
Yazma (CRUD)
| Kolon | Ne yapar |
|---|---|
InsertCommand / UpdateCommand / DeleteCommand |
Özel SQL komutları; boşsa platform üretir |
*BeforeCommand / *AfterCommand |
Komut öncesi/sonrası çalışan SQL kancaları — stok düşme, log yazma, durum güncelleme gibi işleri kod yazmadan burada yaparsın |
Insert/Update/DeleteFieldsDefaultValueJson |
Otomatik doldurulan alanlar (@NOW, @USERID, @NEWID, @ID) |
Insert/Update/DeleteServiceAddress |
Varsayılan list-form-data/*; özel endpoint ya da Dynamic Service'e yönlendirilebilir |
FormFieldsDefaultValueJson |
Form açılışındaki varsayılanlar |
Görünüm ve düzen — 8 layout
LayoutJson hangi layout'ların açık olduğunu ve varsayılanı taşır. Her layout'un kendi ayar
kolonu vardır:
| Layout | Ayar kolonu | Zorunlu alan |
|---|---|---|
| Grid | ColumnOptionJson, RowJson, PagerOptionJson, SelectionJson, StateStoringJson |
— |
| Card | ColumnOptionJson (kart alanları sütun tanımlarından türer) |
— |
| Pivot | PivotOptionJson + alan bazında PivotSettingsJson |
— |
| Chart | SeriesJson, LegendJson, ArgumentAxisJson, ValueAxisJson, TooltipJson, PanesJson, AnnotationsJson, CrosshairJson, ScrollBarJson, ZoomAndPanJson, SizeJson, MarginJson, TitleJson, AnimationJson, Common*Json |
SeriesJson |
| Tree | TreeOptionJson |
KeyExpr + ParentIdExpr |
| Gantt | GanttOptionJson |
ParentIdExpr + TitleExpr + StartExpr + EndExpr |
| Scheduler | SchedulerOptionJson |
TextExpr + StartDateExpr + EndDateExpr |
| Todo (Kanban) | TodoOptionJson |
TitleExpr + StatusExpr |
Sekiz layout aynı SelectCommand üzerinden beslenir; ayrı sorgu, ayrı ekran, ayrı menü
gerekmez. Kullanıcı ekranın üstünden geçiş yapar, tercihi kullanıcı bazlı saklanır.
Bu, tek bir metadata kaydından DevExtreme'in DataGrid, CardView, PivotGrid, Chart,
TreeList, Gantt ve Scheduler bileşenlerinin tamamının sürülmesi demektir. Todo layout
Kanban görünümüdür ve diğerleriyle aynı veri hattından beslenir — kolonları StatusExpr
değerlerinden üretir, kart sürüklendiğinde ilgili kolonu günceller. Yani "aynı veriyi pano
olarak da görelim" isteği yeni bir ekran değil, Todo: true + TodoOptionDto demektir.
Filtre, arama, düzenleme
| Kolon | Ne yapar |
|---|---|
FilterRowJson · HeaderFilterJson · FilterPanelJson · SearchPanelJson · GroupPanelJson |
Filtreleme/arama/gruplama panelleri |
ExtraFilterJson |
Ekrana özel ek filtre araç çubuğu |
EditingOptionJson |
Düzenleme modu (row/cell/batch/form/popup), başlık, boyut, izinler |
EditingFormJson |
Düzenleme formunun grup/sekme düzeni |
CommandColumnJson |
Satır aksiyon sütunu |
PermissionJson |
Ekran yetkileri (create/read/update/delete/export/import/note) |
SubFormsJson · WidgetsJson · WorkflowJson |
Alt formlar, KPI kartları, onay akışı |
CustomJsSourcesJson · CustomStyleSourcesJson |
Sayfa yüklenince çalışan JS/CSS — son çare |
Width · Height · FullHeight · AdaptiveLayoutJson |
Boyut ve uyarlanabilir yerleşim |
ExportJson |
xlsx/csv/pdf dışa aktarma |
ListFormField — alan başına ayarlar
| Grup | Kolonlar |
|---|---|
| Bağlama | FieldName, SourceDbType, JoinTableJson (kolon başka tablodan geliyorsa) |
| Görünüm | CaptionName, PlaceHolder, Visible, IsActive, Width, ListOrderNo, Alignment, Format, BandName |
| Sıralama/filtre | SortIndex, SortDirection, AllowSearch, ColumnFilterJson, ColumnHeaderJson, GroupingJson, ColumnCustomizationJson |
| Özet | TotalSummaryJson, GroupSummaryJson |
| Düzenleme | EditorType2, EditorOptions, EditorScript, EditOrderNo, EditGroupOrderNo, ColSpan, AllowEditing, AllowAdding, ValidationRuleJson |
| Lookup | LookupJson |
| Biçimlendirme | ColumnStylingJson, ColumnCssClass, ColumnCssValue |
| Pivot | PivotSettingsJson |
| Yetki | PermissionJson → CanRead, 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;
CustomJsSourcesve 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:
AuthNamezorunlu 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.TextveHintdil anahtarıdır; çeviri::öneki eklenerek çözülür. Düz metin yazma.- Toolbar butonu seçili satırlarla çalışır;
SelectionJsonuygun modda (single/multiple) olmalıdır. İş akışı varsa wizard bunu zatensingleyapar. - Satır bazlı aksiyon istiyorsan
ButtonPosition: 0kullan; buton komut sütununda görünür. - Onay akışının kendi butonları (Onayla/Reddet)
WorkflowJsonüzerinden otomatik gelir; onlarıCommandColumnJsonile tekrar tanımlama.
Tipik süreç kalıbı: Url modunda bir Custom Endpoint'i ya da Dynamic Service'i çağıran adres
verilir, dönüşte liste yenilenir. İş mantığı SQL/serviste kalır; ekranda yalnızca buton tanımı
durur.
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.tsile bu sınıflar aynı çıktıyı üretmek zorundadır. Birinde bir tarif değişirse aynı görevde diğeri de değişir.
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.tsiçindeplatform: trueiş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"
}
]
}
]
}
DataSourceselle yazılmaz: kaydetme sırasındaCustomComponentDataSourceResolvertasarımcı dokümanındakidataSourceslistesinimethod + pathile CRUD endpoint kataloğunda arayarak üretir. EşleşenlerEntityName+SeedFiletaşır; elle yazılmış endpoint'ler listede kalır ama bu alanları boş gelir.Dependencies, kanvasa bırakılan diğer custom component'lerin adlarıdır.
7.1.1 İki mod: hangisini yazacaksın?
Runtime Code alanını her zaman kullanmaz. getComponentRuntimeCode şu kararı verir:
Props.visualDesigner varsa && version === 1 && sourceMode === 'visual' && nodes bir dizi ise
→ kod tasarımcı dokümanından YENİDEN ÜRETİLİR (generateDesignerCode), Code alanı yok sayılır
aksi hâlde
→ Code alanı olduğu gibi Babel ile derlenir
Bu, prompt ile üretim için belirleyicidir:
| Mod | Ne yazarsın | Ne zaman |
|---|---|---|
Visual (sourceMode: "visual") |
Yalnızca Props.visualDesigner dokümanı. Code alanı okunmaz; marker'lı bir yer tutucu bırakılır. |
Ekran, tasarımcının bileşenleriyle ifade edilebiliyorsa. Kullanıcı sonradan kanvastan düzenleyebilir. Varsayılan tercih. |
Code (Props: null) |
Doğrudan JSX. Tasarımcı kanvası olmaz. | Tasarımcı sözlüğünün karşılamadığı yerleşim/etkileşim gerekiyorsa. |
Visual modda Code alanına şunu yaz — hem marker'ı taşır (tasarımcı dosyayı açabilsin) hem de
derlense bile zararsızdır:
/*__SOZSOFT_VISUAL_DESIGNER__<encodeURIComponent(JSON.stringify(document))>__*/
const {Name} = () => {
return (
<>
</>
)
}
export default {Name}
Marker'ın içi Props.visualDesigner ile aynı dokümanın URL-encode edilmiş hâlidir. Tutarsız
kalırsa tasarımcı Props'u esas alır; yine de ikisini eşit tut.
7.1.2 Code modunda çalışma ortamı
Derlenen kod paylaşılan bir fonksiyon kapsamında çalışır. import satırları temizlenir —
ihtiyacın olan her şey kapsamdan gelir:
| Kapsamdaki ad | Ne |
|---|---|
React |
React'in tamamı (React.useState, React.useEffect…) |
components/ui dışa aktarımlarının tamamı |
Button, Input, Select, Dialog, Card, Notification, toast, … (yeni bir UI bileşeni eklendiğinde otomatik gelir) |
UiKit |
Aynı kitin isim uzayı hâli |
PlatformViewHost |
Bir ListForm ekranını gömmek için |
apiService |
Kimlik doğrulamalı HTTP istemcisi — tercih edilen yol |
axios |
Ham HTTP (kimlik doğrulama eklemez) |
DOMPurify |
HTML temizleme |
translate(key) |
Dil anahtarı çözümü |
checkPermission(name) |
Yetki kontrolü |
getCulture() |
Aktif dil |
Kurallar:
export default {ComponentName}zorunludur; ad dosya/kayıt adıyla birebir aynı olmalıdır.- Kullanıcıya görünen metni gömme;
translate('App.…')kullan ve dil anahtarını wizard dosyasında ya daLanguagesData.json'da tanımla. - Veriye erişimde
apiServicekullan; taban adres, token ve tenant başlığı ondan gelir. Örneklerde gördüğünaxios.create({ baseURL: 'https://localhost:44344/' })kalıbı eski bir kayıttır, taklit etme. - Yetkiyle gizlenecek her blok
checkPermission('App.…')ile sarılır. Custom component route'uauthority: []ile üretilir (§7.6 uyarısı) — kontrol bileşenin içindedir. - Başka bir custom component'i JSX olarak kullanabilirsin; adını
Dependencieslistesine ekle.
7.2 Tasarımcı dokümanı (Props.visualDesigner)
DesignerDocument = {
version: 1
sourceMode: 'visual' | 'code' // 'code' tek yönlüdür, kanvasa dönülmez
nodes: DesignerNode[]
canvas: { width: 'responsive' | 'desktop' | 'tablet' | 'mobile' }
lifecycle: { onMount: string } // sayfa açılışında çalışan script
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: truevarsayılandır ve bilinçlidir: değeri henüz gelmemiş bir filtre isteği tutmalıdır; filtresiz koleksiyonu yüklemek "filtre çalışmıyor" gibi okunur.
7.3 Toolbox
| Aile | 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:
idbenzersiz ve kalıcıdır. Bağlamalar (bindings.sourceId) bu id'lere işaret eder; değiştirirsen bağ kopar.cmp_…/source_…öneki bir sözleşmedir, zorunlu değildir ama koru.refscript'lerin adres defteridir; olay script'leri birbirinerefs.inputAmount…ile erişir. Okunur ve tekil tut.- Form içindeki bir alanın kaynağı Form düğümünün
id'sidir (sourceId: "cmp_form"), data source değil. Data source'a bağlanan şeyler listeler/seçim bileşenleridir. - Her düğümde
props,events,bindings,childrenvar olmalıdır (boş olsalar bile). kinddeğeri toolbox ailesiyle uyumlu olmalıdır; yanlışkinddüğümü çizdirmez.::öneki dil anahtarıdır. Metin taşıyan bir prop::ile başlıyorsa üretilen koddatranslate('::…')çağrısına dönüşür; başlamıyorsa düz metin olarak gömülür. Kullanıcıya görünen her metni::App.…biçiminde yaz ("title": "::App.Hr.AdvanceRequest.Title").platformdüğümlerifiltersprop'u alır. Gömülü ListForm ekranını ana kayda ya da URL'ye bağlamak içinDesignerDataSourceFilterile aynı şekli kullanır;source: "record"ikenvalue"{formRef}.{Kolon}"biçimindedir ("form1.EmployeeId").required: trueise ana kayıt gelmeden liste yüklenmez.- Anahtar kolon adında büyük/küçük harf endpoint'in döndürdüğüyle aynı olmalıdır
(
keyFieldName: "id"ile"Id"farklı şeylerdir); CRUD endpoint'in yanıtına bak.
7.4 Form bileşeni
| Prop | Anlam |
|---|---|
selectEndpoint / insertEndpoint / updateEndpoint / deleteEndpoint |
Data source id'si ("source_5w_5ibpv") — URL değil. Endpoint'in kendisi dataSources listesinde tanımlıdır. |
keyFieldName |
Anahtar kolon |
collectionPath |
Select yanıtında liste yolu |
keySource + keyParamName |
Anahtarın nereden geldiği (route/query/sabit) |
previewKeyValue |
Yalnızca tasarımcı önizlemesi |
autoLoad |
Açılışta Select çalışsın mı |
showToolbar |
Kaydet/Sil araç çubuğu |
columnCount / gap |
İç yerleşim |
title |
Dolu ise Card başlığı; boş ise başlık düşer |
İçine bırakılan her bileşen Select sonucunun bir kolonuna bağlanır (bindings.value.sourceId =
Form düğümünün id'si) ve Save/Delete üzerinden geri yazar. Olaylar: onLoad,
onRecordChange, onFieldChange, onNewRecord, onModeChange, onBeforeSave, onAfterSave,
onBeforeDelete, onAfterDelete, onError.
7.5 Yetki modeli (zorunlu)
İki katman:
- Düğüm görünürlüğü —
props.designerPermissiondolu olan düğüm, yetki yoksa hiç render edilmez. Veriyi göremeyecek kullanıcıya boş bir Grid göstermek yerine bileşen çizilmez. - Form komutları —
selectPermission/insertPermission/updatePermission/deletePermission.Otomatikmod: temel, dokümandakipermissionCode'dur (Wizard'ın bu bileşen için ürettiği okuma yetkisi, menü kaydının korunduğu yetkinin aynısı). Komutlar sırasıyla'',.Create,.Update,.Deleteson eklerini alır.Özelmod: yetki adı elle yazılır.- Boş bırakılan komut serbesttir.
Manager ekranı yetkileri: App.DeveloperKit.Components{,.Create,.Update,.Delete}.
7.6 Bileşeni menüye bağlama
Bileşen tek başına bir ekran değildir; menü ve yetki Wizard'ın Custom yolundan gelir:
{
"Wizard": {
"ComponentKind": 1,
"WizardName": "OrderBoard",
"ListFormCode": "App.Mrp.OrderBoard",
"MenuCode": "App.Mrp.OrderBoard",
"CustomComponentName": "OrderBoard",
"MenuUrl": "/admin/order-board",
"CreateMenu": true,
"MenuParentCode": "App.Mrp",
"PermissionGroupName": "App.Mrp",
"PermissionGroupDisplayNameEn": "MRP", "PermissionGroupDisplayNameTr": "MRP",
"LanguageTextMenuEn": "Order Board", "LanguageTextMenuTr": "Sipariş Panosu",
"MenuIcon": "FcKanban", "MenuOrder": 2
},
"IsDeletedField": false, "IsCreatedField": false,
"InsertedRecords": {
"LanguageKeys": ["App.Mrp.OrderBoard"],
"PermissionGroupNames": [],
"PermissionNames": ["App.Mrp.OrderBoard", "App.Mrp.OrderBoard.Create",
"App.Mrp.OrderBoard.Update", "App.Mrp.OrderBoard.Delete"],
"MenuCodes": ["App.Mrp.OrderBoard"], "DataSourceCodes": []
}
}
Custom yolunda ListForm üretilmez; Title/Desc dil anahtarları da üretilmez.
Export/Import/Note yetkileri bu yolda yoktur.
Güvenlik — route yetkisiz üretilir. Custom component route'ları
RoutePathdeğerinden türetilir veauthority: []ile kaydedilir;/admin/ile başlayan yolprotected, diğerleripublicolur. Yani URL'yi bilen her oturum açmış kullanıcı sayfayı açabilir. Menü yetkisi yalnızca menüde görünmeyi engeller. Gerçek koruma bileşenin içindedir:designerPermissionile düğümleri gizle, Form komutlarını*Permissionile kapat, kod modundacheckPermission()ile sar. Yetkisiz kullanıcıya boş bir sayfa gösteren bir bileşen doğru sonuçtur; veri gösteren bileşen açıktır.
RoutePath/admin/ile başlamıyorsa sayfa herkese açıktır — bilinçli değilse başlat.
7.6.1 Depodaki gerçek örnek — RoleComponent + RoleList wizard
Bu çift, custom component'in menüye nasıl bağlandığının çalışan örneğidir. İncelenecek dosyalar:
configs/seeds/host/custom/RoleComponent.json
configs/seeds/host/crud/AbpRoles.json · AbpUsers.json · AbpUserRoles.json
configs/seeds/host/wizard/20260819131500_RoleList.json
Nasıl bağlanıyorlar:
| Bağ | Değer |
|---|---|
Wizard ComponentKind |
1 (Custom) |
Wizard CustomComponentName |
RoleComponent |
Component RoutePath |
/admin/roles |
Wizard MenuUrl |
/admin/roles — component'in RoutePath değeriyle aynı |
Wizard MenuCode / ListFormCode |
App.Wizard.RoleList |
Component Props.visualDesigner.permissionCode |
App.Wizard.RoleList — wizard'ın ürettiği okuma yetkisi |
Wizard InsertedRecords.PermissionNames |
App.Wizard.RoleList + .Create + .Update + .Delete |
Wizard InsertedRecords.LanguageKeys |
App.Wizard.RoleList |
Component DataSources[].SeedFile |
crud/AbpRoles.json vb. |
Okunacak dersler:
permissionCodewizard'ın ürettiği yetkinin aynısıdır. Form komutlarıOtomatikmodda bu koda.Create/.Update/.Deleteekleyerek çözülür. Component'i yazarken bu değeri wizard'dakiMenuCodeile aynı yap.RoutePathile wizard'ınMenuUrldeğeri birebir aynı olmalıdır; farklıysa menü başka bir yere gider.- Component'in
DataSourceslistesicrud/*.jsondosyalarınaSeedFileile işaret eder — export zip'inin bu dosyaları da toplamasını sağlayan şey budur. - Wizard
Customyolunda olsa bileListFormCode,Grid/Card/Pivot/Chartbayrakları gibi List alanlarını taşır; bunlar kullanılmaz, varsayılan değerleriyle dosyada kalır. Yeni dosya yazarken bunları temizlemeye çalışma, şablonu koru. platformdüğümündekilistFormCode(AbpIdentity.Roles) var olan bir ListForm kodudur; component ekranın kendisini yeniden kurmaz, gömer.
7.7 Taşıma
Bir ekranı başka ortama taşımak tek dosya kopyalamak değildir. Wizard File Manager'ın Export butonu zip üretir:
wizard/{dosya}.json
custom/{component}.json (bağımlılıklarıyla)
crud/{entity}.json (component'lerin kullandığı endpoint'ler)
{sql|postgres}/{object|execute}/{nesne}.sql (List yolunda SelectCommand'ın nesnesi)
Import iki adımlıdır: analiz (New/Identical/Conflict, çakışmalar diff editörde çözülür) →
uygulama (dosya dosya yazım, önceki hâl yedeklenir, RollbackImport ile toplu geri alma).
Sınırlar: yalnızca wizard, crud, custom, sql, postgres kök klasörleri (SQL sağlayıcı
klasörlerinin altında yalnızca object/execute); 5 MB/dosya, 50 MB/arşiv, 500 girdi.
Yetkiler: App.Listforms.Wizard.Export / .Import.
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ı açı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 | Açıklama |
|---|---|
| Dosya adı | Nesne adıyla birebir aynı: Hr_T_AdvanceRequest.sql |
| Klasör | object = idempotent nesne tanımı · execute = nesne olarak da oluşturulur, ayrıca tüm migration'lar bittikten sonra çalıştırılır |
| Sağlayıcı | Hedef SQL Server ise sql/, PostgreSQL ise postgres/. İkisini birden destekliyorsan iki dosya da yazılır; içerikler aynı nesnenin iki diyalektidir. |
| İdempotency | SQL Server: CREATE OR ALTER VIEW/PROCEDURE/FUNCTION, tablolar için IF NOT EXISTS koruması. PostgreSQL: CREATE OR REPLACE VIEW/FUNCTION, CREATE TABLE IF NOT EXISTS. |
| Şema | dbo kullanılır; PostgreSQL tarafında tanımlayıcılar çift tırnaklıdır (dbo."Hr_T_AdvanceRequest") çünkü kolon adları PascalCase'tir. |
| Toplu ayraç | SQL Server'da prosedür sonunda GO kullanılabilir; PostgreSQL'de kullanılmaz. |
| Yorum | Dosyanın ilk satırında ne ürettiğini yaz (-- Create View). |
Tablo yazarken §8.1'deki varsayılan kolonlar dahil edilir. Örnek iskelet (SQL Server):
-- Create Table
IF OBJECT_ID(N'[dbo].[Hr_T_AdvanceRequest]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Hr_T_AdvanceRequest] (
[Id] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [PK_Hr_T_AdvanceRequest] PRIMARY KEY,
[TenantId] UNIQUEIDENTIFIER NULL,
-- iş kolonları buraya
[CreationTime] DATETIME2 NOT NULL,
[CreatorId] UNIQUEIDENTIFIER NULL,
[LastModificationTime] DATETIME2 NULL,
[LastModifierId] UNIQUEIDENTIFIER NULL,
[IsDeleted] BIT NOT NULL CONSTRAINT [DF_Hr_T_AdvanceRequest_IsDeleted] DEFAULT (0),
[DeletionTime] DATETIME2 NULL,
[DeleterId] UNIQUEIDENTIFIER NULL
);
END
Idtipini ne seçtiysen wizard dosyasındakiKeyFieldDbSourceTypeile aynı olmalıdır (UNIQUEIDENTIFIER→9,INT IDENTITY→11). Uyumsuzluk sessiz veri hatası üretir.
8.4 Seeder bu dosyaları nasıl çalıştırır
Yeni bir entity/tablo sql/object/ altına yazılır ve veritabanı SqlDataSeeder tarafından bu
dosyalardan kurulur. Mekanik:
- Yalnızca aktif sağlayıcının klasörü çalışır.
DefaultDatabaseProviderSQL Server ise{kapsam}/sql/, PostgreSQL ise{kapsam}/postgres/. İki dosya da yazmak taşınabilirlik içindir; aynı anda ikisi birden çalışmaz. - Sıra:
object/→execute/, her klasörün içinde dosya adına göre alfabetik. Bir tablo başka bir tabloya FK ile bağlıysa dosya adları o sırayı vermelidir (ör.Hr_T_Employee.sqlHr_T_EmployeeAdvance.sql'den önce gelir). - Dosya içeriği
GOsatırlarından batch'lere bölünür ve sırayla çalıştırılır. PostgreSQL'deGOkullanılmaz. - Hata seed'i durdurur. Bir batch patlarsa istisna yukarı fırlatılır ve o kapsamın seed'i
yarım kalır. Bu yüzden her script tekrar tekrar çalıştırılabilir (idempotent) olmalıdır:
CREATE OR ALTER/CREATE OR REPLACE/IF NOT EXISTS. - SQL nesneleri bittikten hemen sonra aynı kapsam için
CrudDataSeederçalışır; böylece CRUD endpoint'leri dayandıkları tablolardan sonra oluşur. Tablo dosyasını yazmadancrud/{Entity}.jsonyazmak anlamsızdır. - Klasör kökünde kalmış
.sqldosyaları geriye dönük uyumluluk için çalıştırılır ama uyarı loglar; yeni dosyayı her zamanobject/ya daexecute/altına koy.
execute/ klasörünün özel sözleşmesi: buradaki dosyalar iki aşamalıdır.
-
- aşama:
SqlDataSeederdosyayı çalıştırır → nesne oluşur/güncellenir.
- aşama:
-
- aşama: tüm migration ve seed işlemleri bittikten sonra
AfterAllMigrationsSqlExecutordosya adından türettiği stored procedure'ü çağırır.
- aşama: tüm migration ve seed işlemleri bittikten sonra
Yani execute/ altındaki bir dosya, adı prosedür adıyla aynı olan bir stored procedure
tanımlamalıdır (Hr_T_AdvanceRequest_Backfill.sql → Hr_T_AdvanceRequest_Backfill
prosedürü). schema.Procedure.sql biçimi de desteklenir. Prosedür adı güvenli tanımlayıcı
deseninden geçer (harf/alt çizgi ile başlar, en fazla 127 karakter). Sadece bir DDL çalıştırmak
istiyorsan dosya object/ altına aittir; execute/ veri dolumu ve migration sonrası düzeltme
içindir.
8.5 Yeni entity/tablo eklerken izlenecek yol
sql/object/{Modul}_T_{Entity}.sql— tabloyu §8.3 iskeletiyle yaz (tenant + audit kolonları varsayılan). PostgreSQL hedefi de destekleniyorsapostgres/object/altına diyalekt eşini yaz.- İndeks ve FK'leri aynı dosyada, varlık kontrolüyle ekle (
IF NOT EXISTS); ayrı dosya sıralamayı kırılgan hâle getirir. - Ekran bu tabloyu doğrudan kullanacaksa CRUD endpoint gerekmez — ListForm
SelectCommandType: 1ile tabloya bağlanır. Custom Component kullanacaksacrud/{Entity}.jsonde yaz (§9). - Wizard dosyasında
SelectCommand= tablo adı,KeyFieldName=Id,KeyFieldDbSourceType= tablodakiIdtipinin sayısı,IsTenant= tablodaTenantIdvarsatrue,IsDeletedField/IsCreatedField= ilgili kolonlar varsatrue. - DB Migrate çalıştır; log'da
Executing: CREATE TABLE …satırını gör.
9. Artefakt F — CRUD Endpoint
Konum: configs/seeds/{kapsam}/crud/{EntityName}.json · Okuyan: CrudDataSeeder
{
"EntityName": "Mrp_T_Order",
"GeneratedAt": "2026-08-20T06:13:28Z",
"Endpoints": [
{ "Method": "GET", "Path": "/api/app/crudendpoint/Mrp_T_Order", "OperationType": "GetList", "IsActive": true },
{ "Method": "GET", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "GetById", "IsActive": true },
{ "Method": "POST", "Path": "/api/app/crudendpoint/Mrp_T_Order", "OperationType": "Create", "IsActive": true },
{ "Method": "PUT", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "Update", "IsActive": true },
{ "Method": "DELETE", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "Delete", "IsActive": true }
]
}
Üretilen C# kodu dosyada tutulmaz; entity adı ve operasyondan yeniden türetilir.
- Üretim/aktivasyon yeri: SQL Query Manager nesne gezgininde tablo satırının CRUD Endpoints
aksiyonu, toolbar'daki toplu üretim, tablo tasarımcısının deploy öncesi adımı ve Wizard'ın
veri ayarları adımı — hepsi aynı
CrudEndpointDialog. - Yetki:
App.SqlQueryManager.CrudEndpoints. İzin yoksa butonlar görünmez vecrud-endpoint-generateuçlarının tamamı reddedilir. - Dispatcher üzerinden çağrı kapıları:
App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove; endpoint bazlı User/Role/Global kuralları bunun üstünde denetlenir. GetListfiltre sözleşmesi §7.2'deki ile aynıdır.
9.2 Custom Endpoint — elle yazılmış SQL'i API olarak açmak
Ekran: /admin/list/App.DeveloperKit.CustomEndpoints (bir ListForm ekranıdır).
Entity: CustomEndpoint.
| 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) → Publish →
DynamicAssemblyRegistrationService 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'ı
EditFileNameile 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 |
Açı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:
- Menüyü elle ekleme. Yeni bir ekranın menüsü wizard seed dosyasından gelir;
MenusData.jsonyalnızca platformun kendi ekranları içindir ve orada daRoutesbölümüne dokunulmaz. - Route kaydı üretme. Dinamik liste ekranları statik
/admin/list/:listFormCoderoute'unu kullanır; custom component'lerin route'uRoutePathalanından türetilir.Routetablosu yalnızca fiziksel React sayfaları içindir ve prompt ile ekran üretirken böyle bir sayfa yazılmaz. - Menü sırası için wizard'ın
MenuOrderalanını kullan; Menu Manager'da elle taşımak seed dosyasına yansımaz ve veritabanı sıfırlandığında kaybolur. - Rol/dil bazlı menü varyantı gerekiyorsa Menu List ekranından ikinci bir kayıt açılır —
aynı
Url, farklıRoleId/CultureName. Wizard bunu üretmez.
9.8 Wizard Manager — layout matrisi
Wizard Manager platformun en yetenekli üretim ekranıdır: tek bir tanımdan hem Custom Component
sayfası hem de yedi farklı görünümü olan List ekranı üretir. Bir List wizard'ında birden fazla
görünüm aynı anda açık olabilir; kullanıcı ekranın üstünden geçiş yapar, tercihi kullanıcı bazlı
saklanır.
| Görünüm | Bayrak | DefaultLayout |
Zorunlu option alanı | Ne zaman aç |
|---|---|---|---|---|
| Grid | Grid |
"grid" |
— | Neredeyse her zaman; varsayılan |
| Card | Card |
"card" |
— | Görsel/özet kayıtlar, mobil kullanım |
| Pivot | Pivot |
"pivot" |
— | Sayısal kırılım/analiz ihtiyacı |
| Chart | Chart |
"chart" |
— (grafik ayarları ListForm editöründe) | Trend/dağılım |
| Tree | Tree |
"tree" |
TreeOptionDto.KeyExpr + ParentIdExpr |
Kendine referanslı hiyerarşi |
| Gantt | Gantt |
"gantt" |
GanttOptionDto.ParentIdExpr + TitleExpr + StartExpr + EndExpr |
Zaman çizelgesi, bağımlılıklı görevler |
| Scheduler | Scheduler |
"scheduler" |
SchedulerOptionDto.TextExpr + StartDateExpr + EndDateExpr |
Takvim/randevu |
| TodoBoard (Kanban) | Todo |
"todo" |
TodoOptionDto.TitleExpr + StatusExpr |
Durum akışı olan işler |
Kurallar:
- Zorunlu option alanı boşsa seeder o görünümün JSON'unu yazmaz; bayrak açık kalsa bile görünüm çalışmaz. Açtığın her görünümün option bloğunu doldur.
DefaultLayout, bayrağıtrueolan bir görünüm olmalıdır.- Aynı veri birden fazla görünümde aynı
SelectCommandüzerinden sunulur; ayrı sorgu yazılmaz. - Bir ekranın hem liste hem pano hem takvim olarak istenmesi tek wizard dosyasıdır — ikinci bir ekran açma.
Wizard Manager'ın kendi aksiyonları: liste/kart görünümü, arama, düzenleme (EditFileName),
silme, DB Migrate, Export (zip), Import (diff + rollback). Yetkiler
App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}.
10. Uçtan uca reçeteler
10.1 "Var olan bir tablo için ekran aç"
- Tablo yoksa SQL Table Designer ile oluştur (varsayılan kolonlarla).
- Ekran birden çok tablodan besleniyorsa View Designer ile view üret.
- Wizard: menü/kimlik → veri ayarları (
SelectCommandType+SelectCommand+ anahtar) → alanlar → (gerekiyorsa) alt form/widget/workflow → yayınla. - Hesaplanan alanlara
EditorScript, biçimli alanlaraEditorOptionsver. - Doğrula: menü görünüyor mu, yetkiler roldeki kullanıcı için doğru mu, tenant filtresi çalışıyor mu, seed dosyası oluştu mu.
10.2 "Ana-detay ekranı"
- Detay ekranını menüsüz wizard ile üret (
CreateMenu: false). - Ana ekranın
SubFormslistesineParentFieldName→ChildFieldNameeşlemesini veDbTypeekle. - İki ekranın yetkileri ayrıdır; detayın yetkisi verilmemişse sekme görünmez.
10.3 "Serbest yerleşimli sayfa / dashboard"
- Gerekli endpoint'ler yoksa CRUD Endpoint üret.
- Component Manager → yeni bileşen → Visual Designer:
PageContainer→FlexRow→ içineplatformdüğümleri (listFormCodeile) ve/veyaForm. - Veri kaynaklarını Data sekmesinden bağla; filtreleri
route/query/recordkaynaklarıyla kur,requiredbayrağını bilinçli seç. - Görünürlük ve komut yetkilerini ver (
designerPermission,*Permission). - Wizard
ComponentKind: 1ile menüye bağla. - Doğrula:
custom/{Name}.jsonve ilgilicrud/*.jsondosyaları oluştu mu.
10.4 "Kanban panosu"
- Tabloda durum kolonu olsun (
Status). - Wizard'da
Todo: true,DefaultLayout: "todo",TodoOptionDtoiçindeTitleExpr,StatusExpr,StatusOrder(kolon sırası),AssigneeExpr,DueDateExprdoldur. - Kolonlar
statusExprdeğerlerinden üretilir; kartlar sürüklenerek durum değiştirir, panodan yeni kolon eklenebilir.
10.5 "Rapor sorgusunu API olarak aç"
- Sorguyu SQL Query Manager'da yaz ve çalıştır, sonucu doğrula.
- Tekrar kullanılacaksa view olarak deploy et (
sql/object/…), tek seferlikse Custom Endpoint içinde bırak. - Custom Endpoints ekranından kaydı oluştur:
Url,Method,DataSourceCode(kullanıcının kendi tenant veritabanı isteniyorsa"!Tenant"),Sql,ParametersJson,PermissionsJson. - Parametreleri
Query/Path/Bodyolarak tanımla; hiçbirini SQL'e string birleştirme ile koyma. - Erişimi
PermissionsJsonile daralt; dispatcher kapısının (…CustomEndpoints.Getvb.) ilgili rolde açık olduğunu doğrula.
10.6 "Konfigürasyonla çözülemeyen iş mantığı"
- Önce gerçekten çözülemediğini göster: ListForm + SQL + Custom Endpoint neden yetmiyor, bir iki cümleyle yaz.
- Dynamic Service oluştur;
PlatformAppServicetüret,[Authorize(...)]ile koru. - TestCompile → hatasız → Publish.
CompilationStatusSuccessolmadan yayınlama. - Swagger'da
ControllerNamealtında göründüğünü doğrula. - Ekran tarafında bu servisi ya ListForm'un
*ServiceAddressalanından ya da Custom Component'in bir data source'undan tüket.
10.7 Şablon senaryo — "onaylı talep formu" (avans, izin, masraf, satın alma…)
Bu tür isteklerin tamamı aynı iskeleti kullanır. Kullanıcı "avans talep formu istiyorum" dediğinde üretilecek dosya seti:
configs/seeds/host/
├── sql/object/Hr_T_AdvanceRequest.sql ← tablo (+ PostgreSQL hedefse postgres/object/…)
├── crud/Hr_T_AdvanceRequest.json ← yalnızca Custom Component yolunda gerekir
├── custom/AdvanceRequestForm.json ← yalnızca serbest yerleşim isteniyorsa
├── wizard/20260826101000_AdvanceRequestApprovals.json ← (varsa) menüsüz alt ekran, önce
└── wizard/20260826101500_AdvanceRequests.json ← ana ekran + menü + yetki + dil + onay akışı
Karar: Talep formu standart bir kayıt listesi + düzenleme formu + onay akışı ise
ComponentKind: 0 (List) yeter ve custom/ + crud/ dosyalarına hiç gerek yoktur. Custom
Component yoluna yalnızca serbest yerleşim (özet kartlar, çok adımlı sihirbaz, gömülü panolar)
isteniyorsa geçilir.
Adım adım:
- Tablo — iş kolonları + §8.1 varsayılanları. Onay akışı için dört kolon şarttır:
durum (
Status), onaylayan (ApproverId), onay tarihi (ApprovalDate), açıklama (ApprovalNote). Talep sahibi için de bir kolon (EmployeeId) bulunmalıdır. - Wizard dosyası — §4.4'teki iskelet:
IsTenant: true(tablodaTenantIdvar),IsDeletedField: true,IsCreatedField: trueMenuParentCode= modül kökü (App.Hr); yoksa wizard yaratır,Order = max + 1PermissionGroupName=App.Hr, EN + TR görünen adlarıyla- Görünümler:
Grid+Cardaçık; durum akışı görsel isteniyorsaTodo: true+TodoOptionDto(§9.8)
- Alanlar —
Groupsiçinde iki grup mantıklıdır: "Talep" (tutar, tarih, açıklama) ve "Onay" (durum, onaylayan, onay tarihi, onay notu). Onay grubundaki alanlarEditorOptions: "{\"readOnly\":true}"ile kilitlenir; kullanıcı doldurmaz, akış doldurur. - Hesap ve varsayılanlar —
EditorScriptile:- Talep tarihi form açılışında bugün:
todaytarifi, tetikleyiciopen - Tutar × oran gibi hesaplar:
multiply/percent - Eşik üstü tutarda uyarı:
notifyya daask, koşulugreaterThan
- Talep tarihi form açılışında bugün:
- Onay akışı — §4.7'deki
WorkflowDto. Tipik grafik:Start → Compare (tutar eşiği) → Approval (yönetici) / Approval (üst yönetici) → Inform → End.ApprovalIsFilterUserName: trueile herkes yalnızca kendi onayına düşenleri görür,ApprovalIsResetWorkflow: trueile kayıt güncellenince akış başa döner. - Yetki — wizard
App.Hr.AdvanceRequestskökünü ve.Create/.Update/.Delete/.Export/ .Import/.Notealt yetkilerini üretir. Onaylayanların rolüne kök +.Updateverilir; talep sahiplerine kök +.Createyeter. - Dil — menü/başlık/açıklama metinleri wizard dosyasındaki
LanguageText*alanlarından, alan başlıkları herItemsöğesininTurkishCaption/EnglishCaptionalanlarından üretilir. AyrıcaLanguagesData.json'a dokunmaya gerek yoktur. - Teslim — dosyaları listele, DB Migrate gerektiğini söyle, §11 kontrol listesini geç.
Bu iskeletin türevleri: izin talebi (tutar yerine gün sayısı, days tarifi), masraf talebi
(alt form olarak masraf kalemleri → menüsüz wizard + SubForms), satın alma talebi (kalemler +
tedarikçi lookup'ı + iki kademeli onay).
11. Teslim öncesi kontrol listesi
- Menü, route ve ekran sözleşmesi birbirini gösteriyor.
- Kök yetki + aksiyon yetkileri var; menü
RequiredPermissionNameile bağlı. - Yetki grubunun EN + TR görünen adı var.
- Kullanıcıya görünen her metnin EN + TR dil anahtarı var; anahtar tekrar edilmemiş.
IsTenant(ve gerekiyorsaIsBranch/IsOrganizationUnit) doğru; sorgular tenant güvenli.- Tüm SQL parametreli; string birleştirme yok.
DeleteCommand = DefaultDeleteCommand("{Tablo}").- Seed dosyası oluştu;
InsertedRecordsyalnızca gerçekten yaratılan kayıtları içeriyor. - Bağımlılıklar (custom component, crud endpoint, sql nesnesi) da seed'li.
- Export zip'i başka bir ortamda açıldığında ekran ayağa kalkıyor.
- Geri alma yolu yazılı: wizard dosyasını sil →
InsertedRecordstemizlenir.
Prompt ile üretim yaptıysan ek olarak:
- Dosyalar doğru kapsam klasöründe (
host/ya datenants/{tenantId}/). - Üretim sırası doğru (§0.2) ve bağımlı wizard'ın zaman damgası daha büyük.
- Tablo/kolon adları uydurulmadı; ya var olan nesneden okundu ya da
sql/object/altında üretildi. KeyFieldDbSourceType, tablodakiIdtipiyle uyumlu.- Açılan her görünümün zorunlu option alanı dolu (§9.8).
- Custom component'te route yetkisiz üretildiği için koruma bileşenin içinde
(
designerPermission/*Permission/checkPermission). - Custom component visual modda ise
Props.visualDesignerileCodemarker'ı aynı dokümanı taşıyor. - Menü ve route elle eklenmedi; wizard dosyasından geliyor.
- Kullanıcıya hangi dosyaların üretildiği ve DB Migrate gerektiği söylendi.
- 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).