sozsoft-platform/.github/instructions/lowcode.instructions.md
2026-10-01 16:58:07 +03:00

310 KiB
Raw Blame History

Low-Code Üretim Referansı (Authoring Reference)

Bu doküman, bir yapay zekâ ajanının platformu bir geliştirici gibi kullanarak ekran ve bileşen üretebilmesi için gereken somut şemaları, alan anlamlarını, enum değerlerini ve çalışan örnekleri içerir.

  • Ne yapılacağına karar verme kuralları: ai.instructions.md
  • JSON kolon ve modül sözleşmelerinin tam listesi: lowcode-reference.instructions.md
  • api/ kod standardı: dotnet.instructions.md
  • Modül/liste ekleme prosedürü: list.instructions.md
  • Neyin nerede yaşadığı: ../../README.md

Bu dosya nasıl üretileceğini anlatır; lowcode-reference.instructions.md neyin ayarlanabildiğini alan alan listeler (grid/pivot/chart JSON'ları, alan seviyesi JSON'lar, SQL kancaları, tasarımcı envanteri, mail modülü) ve §12'de Saas + Administration + İK menülerindeki hazır gelen bütün ekranları — hangisi yeniden kullanılacak yetenek, hangisi lookup kaynağı, hangisi taklit edilecek canlı örnek. Bir komponent üretmeden önce oradaki §0 yetenek envanterini tara: platformun sunduğu bir katmanı atlamadığından böyle emin olursun.

Çelişki olursa ai.instructions.md kazanır. Buradaki her şey "kod yazmadan üretim" tarafındadır; bu dosya platformun çekirdek kodunu geliştirmek için değil, prompt ile uygulamaya yeni ekran, menü, yetki, dil anahtarı, route ve süreç eklemek içindir.

Bir istek geldiğinde çekirdek koda dokunmak değil, configs/seeds/ altına uygulamanın kendi üreteceği dosyaların aynılarını yazmak esastır — bkz. §0.

Altın kural: Ürettiğin her şey bir seed dosyasına düşmelidir. Veritabanı silinip yeniden oluşturulduğunda geri gelmeyen bir artefakt teslim edilmiş sayılmaz.


0. Üretim protokolü (her prompt için zorunlu)

Kullanıcı "şöyle bir ekran/form/süreç istiyorum" dediğinde uygulamayı kullanıyormuş gibi davran: Wizard'ın, Custom Components ekranının ve SQL Query Manager'ın diske yazacağı dosyaların aynılarını configs/seeds/host/ altına sen yaz. Ekranlara tıklamanın yerine geçen şey budur.

Talep iki biçimde gelir ve ikisi de aynı hatta girer:

  • Yazılı tarif → §0.6'daki keşif turunu işlet, tarifi kur, onaylat.
  • Ekran görüntüsü / mockup / fotoğraf → §0.7 ile önce görseli oku, sonra §0.6 turuyla görselden okunamayan boşlukları sor.

Tarif onaylanınca §0.2'deki sırayla dosyaları üret. Sıra bozulamaz: önce sorular, sonra tarif ve onay, en son dosyalar.

0.1 Yazılacak klasörler

configs/seeds/host/
├── sql/object/{Nesne}.sql        ← tablo / view / fonksiyon / prosedür  (SQL Server)
├── sql/execute/{Ad}.sql          ← bir kez çalışacak script (veri dolumu, migrasyon)
├── postgres/object|execute/…     ← aynısının PostgreSQL diyalekti (hedef PG ise)
├── crud/{EntityName}.json        ← tablonun REST uçları
├── wizard/{WizardName}.json   ← ekran + menü + yetki + dil anahtarları (List yolunda sunucu yazar, §4.1)
├── data/{ListFormCode}.json   ← ekrandan girilen liste verisinin aynası (opsiyonel, §9.15)
│   data/{ListFormCode}/{anahtar}.json  ← aynı aynanın klasör modu (satır başına dosya)
│                                Custom Component'ler klasör modunda yaşar:
│                                data/App.DeveloperKit.CustomComponents/{Id}.json

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ı. MCP'de yeni tablo create_table ile kurulur (script ve dosya tasarımcıyla aynı); view MCP'den kurulamaz
2 CRUD uçları (gerekiyorsa) crud/{Entity}.json Custom Component'in data source'ları buna bakar
3 Custom Component (gerekiyorsa) data/App.DeveloperKit.CustomComponents/{Id}.json Wizard Custom yolunda bileşen adını arar
4 Wizard wizard/{Ad}.json + menü/yetki/dil satırları (data/App.Menus.*, data/Abp.Identity.Permission*, data/App.Languages.LanguageText.json) Deploy (MCP deploy_wizard ya da wizard ekranı, §0.10) yalnızca bu dosyaları yazar; ekran, menü ve yetkiler DB Migrate ile kurulur (§4.0). Dosyaları sunucu yazar. Elle yazılan List dosyası ListForm + Fields taşımak zorundadır, yoksa seeder atlar (§4.1)
5 Alt ekranlar (varsa) ek wizard/… dosyaları Ana wizard'dan önceki SeededAt damgasını alır
6 Liste verisi (isteniyorsa) data/{ListFormCode}.json Ekran ve tablo hazır olmalı; ListForm.SeedFilePath bu yolu gösterir (§9.15)
7 SEO alanları (public yol ürettiysen) Yolu tanımlayan satırın kendisi: data/App.Menus.Routes.json ya da data/App.DeveloperKit.CustomComponents/{Id}.json Yolu üreten dosya yazıldıktan sonra; ziyaretçiye açık hiçbir yol metasız kalmaz (§0.9)

Wizard.SeededAt bir bağımlılık aracıdır. Dosya adı tarih taşımaz ({WizardName}.json); WizardDataSeeder wizard/ klasörünü Wizard.SeededAt damgasına göre sıralı işler (eşitlikte dosya adı). Bir wizard başka bir wizard'ın ürettiği menüye/ListForm'a dayanıyorsa, bağımlı olanın damgası daha büyük olmalıdır. Alt form olarak kullanılacak menüsüz ekranı her zaman önce damgala.

0.3 Yazmadan önce zorunlu keşif

Uydurma. Şu üçünü mutlaka kontrol et:

  1. Tablo/kolon adları — sql/object/ altındaki dosyalar, mevcut wizard dosyalarındaki SelectCommand ya da (MCP varsa) describe_list/search_schema. Kolon adını tahmin etme; yoksa tabloyu da sen üret.
  2. Var olan menü kökü ve izin grubu — aynı modül için ikinci bir kök menü açma. data/App.Menus.Menu.json (platform menüleri), data/Abp.Identity.PermissionGroups.json ve mevcut wizard dosyalarındaki MenuParentCode / PermissionGroupName değerlerini tara (MCP'de list_wizards).
  3. Dil anahtarı çakışması — data/App.Languages.LanguageText.json ve diğer wizard dosyaları. Var olan bir anahtarın metnini seed ezmez; yanlış metinle karşılaşırsan anahtarı değiştir.

0.4 Üretim sonrası

  • Değişikliklerin devreye girmesi için DB Migrate (SQL Query Manager / Wizard Manager üzerindeki buton, yetki App.Setup.Migrate) ya da migrator konteyneri (SEED=true) çalışmalı.
  • Seeder idempotenttir: ekran zaten varsa dosya atlanır, yalnızca eksik dil metinleri eklenir (§4.5). Kurulu bir ekranı değiştirmek için dosyayı düzenlemek yetmez — ListForm editöründen değiştir (dosyaya geri yazılır) ya da wizard'ı EditFileName ile yeniden deploy et.
  • Ü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 crud/ ve data/ dosyalarında vardır; wizard seed dosyasında karşılığı Wizard.SeededAt alanıdır. data/: dosyanın oluşturulma anı, yalnızca bilgi amaçlı — ilk yazımda dolar, satır ekleme/silme onu değiştirmez (ekleyip geri kaldıran işlem git'te iz bırakmaz). crud/: üretim damgası, migrate kararında kullanılır (§9.1)
İç içe JSON EditorOptions, EditorScript, LookupQuery → JSON string olarak kaçışlanır
Satır sonu LF

0.6 Zorunlu keşif turu — sormadan üretme

Kullanıcı bir ekran/komponent istediğinde ilk cevabın dosya değil, sorudur. Talep ne kadar kısa gelirse gelsin ("stok listesi olsun", "şöyle bir sayfa istiyorum", bir ekran görüntüsü), üretimden önce keşif turunu işlet. Tek satırlık bir talepten üretilen ekran çalışır ama kullanılabilir olmaz; eksik olan şey kodda değil, tarifte olur.

Bu, dosyanın geri kalanındaki "varsayımını yaz ve devam et" tavrının istisnasıdır ve yalnızca yeni ekran/komponent üretimi için geçerlidir. Var olan bir ekranı düzeltme, alan ekleme, hata ayıklama, açıklama isteği gibi taleplerde soru turu yoktur — doğrudan yap.

Turun kuralları

  1. Soruları toplu sor. Tek tek değil, AskUserQuestion ile turda en fazla 4 soru; her sorunun 2–4 seçeneği ve önerilen bir varsayılanı olsun ((Önerilen)). Kullanıcı okumadan onaylayabilmelidir.
  2. En fazla üç tur. Tur 1 iskeleti kurar (§0.6.2), Tur 2 seçilen yolun ayrıntısını alır (§0.6.3 / §0.6.4), Tur 3 yalnızca ilk iki turda ortaya çıkan açık uçları kapatır. Üçüncü turdan sonra kalan boşluk varsayımla doldurulur ve tarifte belirtilir.
  3. Cevabı zaten bilinen şeyi sorma (§0.6.5). Depoda okunabilen (tablo/kolon adları, mevcut menü kökleri, yetki grupları) her şey önce aranır; soru ancak arama sonuçsuz kalırsa ya da iki makul seçenek varsa sorulur.
  4. Her soru bir üretim kararını değiştirmelidir. Cevabı ne olursa olsun aynı dosyayı üreteceksen o soruyu sorma.
  5. Kaçış yolu her zaman açık: kullanıcı "sen bilirsin / varsayılanlarla üret / hepsini sen seç" derse turu bitir, varsayımlarla üret ve sonunda listele. Kullanıcı baştan "soru sorma" dediyse hiç başlatma.
  6. Sonunda tarifi onaylat (§0.6.6). Onay gelmeden configs/seeds/ altına dosya yazma.

Turun çıktısı kapsamı belli bir ekran tarifidir; tarifin doldurulacak katmanları lowcode-reference.instructions.md §0'daki 25 satırlık yetenek envanteridir. Turu o listeyi tarayarak kur: her katman için ya cevabı al, ya varsayılanı uygula, ya da "bu ekranda yok" de.

0.6.1 Hangi araç? — yol seçimi

Tur 1'in birinci sorusunun karşılığı budur; kullanıcı türü net söylemediyse tarife bakıp öner, tek başına karar verme:

Tarifte varsa Yol
Kayıt listesi + ekle/düzenle/sil + filtre/arama SQL Query Manager + Wizard (ComponentKind: 0)
Aynı verinin birden çok görünümü (pano, takvim, ağaç, grafik) Wizard — tek dosya, birden çok layout (§9.8)
Ana kayıt + detay sekmeleri Wizard + SubForms
Onay süreci Wizard + WorkflowDto
Üstte KPI kartları Wizard + Widgets
Serbest yerleşim: yan yana kartlar, özel bölümler, sihirbaz adımları, karışık bileşenler Custom Component (ComponentKind: 1)
Var olan ekranların bir sayfada toplanması (dashboard) Custom Component + platform düğümleri (listFormCode)
Tek kayıt üzerinde çalışan özel form (liste yok) Custom Component + Form düğümü

Şüphede kalırsan Wizard'ı seç. Grid'e benzeyen her şey Wizard'la daha ucuz, daha yetkin ve daha bakımlıdır; Custom Component'e yalnızca yerleşim gerçekten grid/form kalıbına sığmıyorsa geç.

İki yol birlikte de kullanılır: veriyi Wizard ile ekran yap, sonra Custom Component içine platform düğümü olarak göm.


0.6.2 Tur 1 — her talepte sorulacak dört şey

Bu dört soru, talebin türünden bağımsız olarak sorulur; dördü de üretilecek dosya kümesini doğrudan değiştirir.

# Soru Neden belirleyici Seçenekler
1 Ekran türü Üretilecek artefaktı seçer Liste ekranı (Wizard) · Serbest yerleşimli sayfa (Custom Component) · Tek kayıtlık form · Karma (Wizard + Custom)
2 Veri kaynağı Tablo yoksa sql/object/ de üretilecek Var olan tablo/view (adı?) · Yeni tablo üretilsin · Birden çok tablo → view üretilsin · Var olan bir API/endpoint
3 Kim kullanacak / hangi modül Menü kökü, yetki grubu ve isimlendirme buradan türer Mevcut modüllerden biri (listele) · Yeni modül
4 Kapsam IsTenant/IsBranch/IsOrganizationUnit ve tablo ön eki Kiracı geneli (varsayılan) · Şube bazlı · Birim ağacına göre · Host (tüm kiracılar ortak)
  1. sorunun cevabı yolu belirler; belirsizse §0.6.1'deki yol tablosuna bak ve şüphede Wizard'ı öner. 2. sorunun cevabı "var olan tablo" ise adını sormadan önce sql/object/ ve mevcut wizard dosyalarını tara — bulursan sorma, doğrulat.

0.6.3 Tur 2 — List (Wizard) yolu soru seti

Sırayla tara; ekranın gerçekten ihtiyaç duyduğu satırları sor, gerisini varsayılanla geç. Sütun listesi en kritik olanıdır — onsuz üretilen ekran her zaman yeniden yazılır.

Konu Sorulacak Varsayılan (sorulmazsa)
Sütunlar Hangi alanlar görünecek, başlıkları ne? Tablo varsa "şu kolonların hepsi mi, bir kısmı mı?" Tablodaki tüm kolonlar, audit kolonları gizli
Alan tipleri Her alan için editör: metin / sayı / tarih / onay kutusu / açılır liste / çoklu seçim / zengin metin / görsel (§0.7.3, §4.3.2) Kolon DbType'ından türetilir
Zorunluluk ve doğrulama Hangileri zorunlu? Aralık/desen/uzunluk kuralı olan var mı? NOT NULL kolonlar zorunlu, başka kural yok
Lookup'lar Açılır listeler nereden beslenecek: sabit liste mi, başka tablo mu, var olan tanım tablosu mu (ülke/para birimi/departman…)? Bağlı liste (il→ilçe) var mı? Sabit liste; FK varsa hedef tabloya Query
Anahtar Anahtar kolon ve tipi Id / UNIQUEIDENTIFIER (KeyFieldDbSourceType: 9)
Görünümler Varsayılan dörtlüden (Grid · Card · Pivot · Chart) kapatılacak olan var mı? Ağaç, gantt, takvim, kanban gerekiyor mu? Açılışta hangisi? Grid + Card + Pivot + Chart, DefaultLayout: "grid" (§9.8)
Görünüm eşlemeleri Açılan her görünümün zorunlu alanları: ağaçta ParentId, takvimde başlangıç/bitiş, kanbanda durum kolonu (§9.8) Görünüm açılmaz
Form düzeni Alanlar hangi gruplara ayrılsın, grup başına kaç kolon? Popup mu satır içi mi? Tek grup, 2 kolon, popup
Otomatik dolan alanlar Kullanıcı/tarih/tenant dışında otomatik dolacak alan var mı? Belge numarası gerekiyor mu (§9.9)? Audit alanları + tenant
Hesap ve koşul Alanlar arası hesap (miktar×fiyat), koşullu kilit, seçimle dolan alan var mı (§5)? Yok
Filtre/arama Filtre satırı, arama kutusu, ekran üstü sabit filtre (tarih aralığı/şube/durum)? Filtre satırı + arama açık
Özet Altta toplam/adet satırı gerekiyor mu, hangi kolonlarda? Yok
Aksiyonlar Ekle/düzenle/sil dışında buton: onaya gönder, çıktı al, dış sisteme aktar, başka ekran aç? (§4.9) Yalnız CRUD
Alt ekranlar Kaydın altında sekme (kalemler, ekler, geçmiş) olacak mı? İlişki alanı ne? Yok
KPI kartları Üstte sayaç/kart şeridi olacak mı, hangi sayılar? Yok
Onay süreci Kayıt onaya düşecek mi? Kim onaylayacak (rol/kullanıcı), kaç adım? Yok — onaycı asla varsayılmaz, sorulur
Koşullu renklendirme Duruma göre renklenecek satır/hücre var mı? Yok
Yetki Ayrı rol kırılımı gerekiyor mu, yoksa tek yetki ailesi yeterli mi? {MenuCode} + standart son ekler
Menü Hangi kök menünün altına, hangi sıraya, hangi ikonla? Modülün kök menüsü, max(Order)+1
Dil Menü/başlık/alan metinlerinin TR ve EN karşılıkları Türkçeden çevrilir

0.6.4 Tur 2 — Custom Component yolu soru seti

Serbest yerleşimde tarif, sayfanın bölgeleri üzerinden çıkarılır. Ekran görüntüsü varsa önce §0.7 ile oku, sonra buradaki boşlukları sor.

Konu Sorulacak Varsayılan (sorulmazsa)
Sayfa iskeleti Kaç bölge var, nasıl dizilmiş? (üst şerit / tek kolon / iki kolon + kenar panel / sekmeli) Tek kolon PageContainer
Bölge içerikleri Her bölgede ne var: kart, form, liste, grafik, pano, metin, buton grubu? — (sorulmadan geçilemez)
Hazır ekran gömme Bölgelerden biri var olan bir ListForm ekranı mı? (öyleyse platform düğümü + listFormCode, primitiflerden kurma) Sorulur
Form bölgesi Tek kayıt üzerinde çalışan bir form var mı? Hangi alanlar, hangi uçlar (select/insert/update/delete), anahtar nereden gelir (query/route)? Form yok
Veri kaynakları Her bölge veriyi nereden alır: CRUD endpoint mi, Custom Endpoint mi, var olan bir API mi? CRUD endpoint üretilir
Filtreler Bölge verisi neye göre süzülür: sabit değer, URL parametresi, başka bölgedeki seçili kayıt? (§10.4 · reference) Süzme yok
Etkileşim Tıklama/seçim/kaydetme sonrası ne olsun: başka bölge yenilensin, sayfa açılsın, uyarı çıksın? (§7.8) Yok
Durum ve boşluk Veri yokken, yüklenirken, hata alındığında ne görünsün? Standart boş/yükleniyor durumu
Yetki Bölge bazlı gizleme gerekiyor mu? Hangi bölge hangi yetkiyle? Sayfa yetkisi
Route ve menü Sayfanın adresi ne olsun, menüde nerede dursun? /admin/{kebab-ad}, modül kök menüsü
Bağımlılık Sayfa başka bir custom component'i kullanacak mı? Yok
Mod Tasarımcıda düzenlenebilir kalsın mı (Visual), yoksa yerleşim tasarımcıya sığmıyor mu (Code)? (§7.1.1) Visual

Yeniden kullanım sorusu her Custom talebinde sorulur: istenen bölgelerden biri Wizard'la daha ucuza çözülüyorsa bunu söyle ve karma yolu öner (veriyi Wizard ile ekran yap, Custom Component içine platform düğümü olarak göm). Custom Component'i, yerleşim gerçekten grid/form kalıbına sığmadığı için seç — "daha esnek olur" diye değil.

0.6.5 Sormadan geçilecekler

Soru turu bir anket değildir. Aşağıdakiler sorulmaz, uygulanır ve tarifte tek satırla bildirilir:

Konu Neden sorulmaz
Tenant + audit kolonları (Id, TenantId, CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId) Yeni tabloda varsayılan; kullanıcı "audit yok" demedikçe eklenir
Tanım tablosunda IsActive Yeni tanım tablosunda varsayılan (aşağıda); kullanıcı "aktif/pasif yok" demedikçe eklenir
Metinlerin EN + TR olması, dil anahtarı üzerinden verilmesi Platform sözleşmesi
İsimlendirme (ListFormCode, menü kodu, yetki adları, dosya adları) §2'den türer
Seed dosyası yazılıp yazılmayacağı Her artefakt seed'lenir (§0.2)
Route kaydı açılıp açılmayacağı List statik route kullanır, Custom RoutePath'ten türer (§9.7)
Parametreli SQL kullanımı, yetki kontrolü, tenant izolasyonu Tartışmaya açık değil
Depoda okunabilen tablo/kolon/menü/yetki adları Önce ara, bulduğunu doğrulat, sıfırdan sorma
Var olan bir tanım tablosunun (ülke, para birimi, departman, ünvan, birim) yeniden üretilip üretilmeyeceği Üretilmez; mevcut tabloya lookup bağlanır (reference §12.6)
Liste ekranının üstündeki KPI kartları (Widgets) List ekranı en az 3 kartla gelir, kartlar tablonun durum/bayrak/tarih/tutar kolonlarından seçilir; alt form olarak üretilen ekrana widget yazılmaz (§4.6.1)

Buna karşılık her zaman sorulacak üç şey vardır, çünkü yanlış varsayım geri dönülmez sonuç üretir: hangi tabloya yazılacağı, kimin onaylayacağı (onay süreci varsa) ve hesap/iş kuralının formülü.

0.6.6 Ekran tarifi ve onay

Turlar bitince tarifi tek blok hâlinde göster ve onay iste. Kullanıcının cevapladığı satırlar olduğu gibi, varsayılanla doldurulanlar (varsayılan) işaretiyle yazılır — böylece kullanıcı neyi onayladığını görür.

Amaç          : Ne işi görüyor, kim kullanıyor
Yol           : Wizard (ComponentKind 0) | Custom Component (ComponentKind 1) | karma
Veri kaynağı  : Tablo/view adı · yoksa "üretilecek" + hangi kolonlarla
Anahtar       : Kolon + tip (DbType numarası)
Sütunlar      : Alan → editör tipi → zorunlu mu → lookup kaynağı        (List yolu)
Bölgeler      : Bölge → bileşen → veri kaynağı → filtre                 (Custom yolu)
Layout(lar)   : Grid + Card + Pivot + Chart (varsayılan dörtlü) [+ Tree / Gantt / Scheduler / Todo] + açılış görünümü
Form düzeni   : Gruplar ve kolon sayısı, düzenleme modu
Otomatikler   : Varsayılan değerler, numaratör, hesaplar (EditorScript)
Doğrulama     : Alan kuralları + sunucu tarafı kontrol
Aksiyonlar    : Toolbar ve satır butonları, ne yapıyorlar
Alt ekranlar  : Ana-detay sekmeleri + ilişki alanları
Widget'lar    : Üstteki KPI kartları ve sorguları
Akış          : Onay adımları ve onaycılar
Otomasyon     : Zamanlanmış iş / bildirim / mail gerekiyor mu
Menü          : Üst menü, sıra, ikon
Yetki         : Grup adı + kimde hangi yetki
Eklemeler     : Hangi ui bileşenine hangi olay/prop/küçük yetenek eklenecek (§7.3) — yeni bileşen yok
Kapsam        : IsTenant / IsBranch / IsOrganizationUnit
Dil           : EN + TR metinler
Üretilecek    : Dosya listesi (§0.1 klasörlerine göre)

Onaydan sonra §0.2'deki sırayla üret. Üretim bittiğinde dosyaları tek tek listele: hangi dosya, ne üretiyor, hangi menüde görünecek, hangi yetkiyi ister — ve tarifte (varsayılan) kalan satırları "şunları ben seçtim, değiştirmek ister misin?" diye ayrıca hatırlat.

0.7 Görselden üretim (ekran görüntüsü / mockup)

Kullanıcı tarif yerine bir fotoğraf, ekran görüntüsü ya da mockup gönderdiğinde keşif turu iptal olmaz, kısalır: görsel, Tur 2 sorularının çoğunu cevaplar. Önce görseli oku ve tarifi doldur; sonra §0.6 turunu yalnızca görselden okunamayan boşluklar (§0.7.4) için işlet. Görseli tahmine değil, gördüğün öğelere dayandır.

0.7.1 Görseli okuma sırası

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

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

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

Görselde yalnız tablo görünmesi Card/Pivot/Chart'ı kapatmak değildir; liste ekranı varsayılan dörtlüyle üretilir (§9.8), görseldeki görünüm DefaultLayout olur.

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
Fotoğraf / görsel alanı (seçilen, çekilen ya da başka kayıttan kopyalanan) dxImageUpload, kolon nvarchar(MAX) (base64); salt okunur gösterilecekse EditorOptions: {"readOnly": true} — dxImageViewer değil accept, maxFileSize, capture
Gri, tıklanamaz alan ilgili editör + EditorOptions: {"readOnly": true}
Kırmızı yıldızlı etiket IsRequired: true

0.7.4 Görselden okunamayacak şeyler

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

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

0.7.5 Görsel için de aynı protokol

Görselden çıkardığın tarifi §0.6.1'deki yol tablosuna sok, görselden okunamayan boşlukları §0.7.4 + §0.6 turuyla sor, tarifi §0.6.6 ile onaylat, sonra §0.2'deki sırayla dosyaları üret. Görsel bir "tasarım sözleşmesi" değildir: platformun kendi bileşenleriyle en yakın karşılığı üretilir, piksel taklidi yapılmaz. Görseldeki bir öğenin platformda karşılığı yoksa bunu üretim sonunda açıkça yaz.


0.8 Çerçeve sınırı — uygulama kendini geliştirir, dışına çıkmaz

Platform kendi mekanizmalarıyla büyür; çerçevenin dışına çıkan her talep üretimden önce kullanıcıya sorulur ve yalnızca onayla yapılır. "Yapılamaz" cevabı yoktur, "sormadan çerçeve dışına çık" da yoktur.

Çerçeve içi — sormadan yapılır (keşif turu tarifinde gösterilir, onay tarifle gelir):

Alan Örnek
Seed artefaktları Wizard, custom component (visual/code modu), crud/, SQL nesneleri (tablo/view/SP/fonksiyon), Custom Endpoint, Dynamic Service, BackgroundWorker, bildirim türü/kuralı, dil anahtarı, yetki kaydı, SEO kaydı (§0.9)
Mevcut bileşeni genişletme Kullanılan bir ui bileşenine eksik olay / prop / küçük yetenek ekleme (§7.3 notu) — komponentin %70'i hazır araçlarla kuruluyorsa kalanı bu eklemelerle tamamlanır; componentProps.json girdisini tamamlama
Mevcut sözlüğü genişletme Yeni EditorScript tarifi (scriptRecipes.ts ↔ Domain.Shared/Editors birlikte), yeni EditorOptions anahtarı, yeni DefaultValue token'ı — geriye dönük uyumlu olmak şartıyla
Tasarımcı sözleşmesi Var olan düğüme prop/varsayılan ekleme, yeni Script Builder tarifi

Çerçeve dışı — önce sor, onay olmadan dokunma:

Alan Örnek
Yeni React bileşeni / sayfası Yeni ui bileşeni, yeni toolbox ailesi, EXCLUDED_COMPONENTS değişikliği, views/ altına elle sayfa, Developer Kit menüsüne yeni ekran, var olan bileşeni yeniden yazmak ya da mevcut davranışını değiştirmek
Çekirdek motor ListForm runtime, seeder'lar, codeGenerator.ts, yetki/kimlik altyapısı, DataSourceManager, dispatcher/derleyici (CustomEndpointAppService, DynamicServiceCompiler)
Backend şeması Yeni ABP modülü/entity/migration, platform tablolarında ({Önek}_{H|T|B}_{Ad} — reference §12.1) kolon değişikliği, TableNameEnum/TableNameResolver değişikliği, DTO/JSON kolon sözleşmesini bozan değişiklik
Bağımlılık Yeni NuGet/npm paketi, sürüm yükseltme
Sabit yapılar data/App.Menus.Routes.json satırlarının eklenmesi/silinmesi/yolunun değişmesi (Seo* alanları hariç, §0.9), PlatformConsts içindeki mevcut değerlerin değişmesi
Dış dünya Yeni harici entegrasyon, yeni bağlantı tipi, üçüncü taraf servis

Sorma biçimi: çerçeve içinde yapılabilen her şeyi önce bitir, sonra tek soruda AskUserQuestion ile sor — neden konfigürasyon/seed yolu yetmiyor (bir iki cümle), en küçük hangi değişiklik yeter, hangi dosyalara dokunacak, geri alma nasıl olacak. Seçenekler arasında daima "çerçeve içinde daraltılmış çözüm" alternatifi de bulunsun. Onay gelmeden api/ ya da ui/ altında çerçeve dışı bir satır yazılmaz; onay bir talebe özeldir, sonraki talebe taşınmaz.


0.9 SEO alanları — ziyaretçiye açık her yol için zorunlu

Ziyaretçiye açık bir yol üreten her iş, o yolun SEO alanlarını da doldurur. Bu kullanıcıdan istenen bir bilgi değildir, üretimin parçasıdır: kullanıcı SEO bilgisi girmez, sen yazarsın. Metasız yayınlanan bir sayfa arama sonucunda başlıksız ve açıklamasız görünür; paylaşıldığında da boş bir kart çıkar.

Ayrı bir SEO tablosu yoktur. Meta, yolu tanımlayan kaydın üzerinde yaşar. Bu platformda rota iki yerden doğar, ikisinde de aynı alan kümesi vardır:

Yolu tanımlayan Seed dosyası Örnek yollar
Route satırı (fiziksel React sayfası) data/App.Menus.Routes.json /products, /blog, /demo, /checkout
Custom Component RoutePath data/App.DeveloperKit.CustomComponents/{Id}.json /home, /about, /contact

Aynı yolu ikisi de tanımlıyorsa bileşen kazanır — çalışma zamanında da bileşen fiziksel rotayı gölgeler (SeoAppService.CollectSeoRoutesAsync). /admin/ ile başlayan hiçbir yol için alan doldurulmaz; yönetim yüzeyi zaten noindex gelir.

Ne zaman: public bir RoutePath taşıyan Custom Component, Pub modülüne eklenen bir menü, ya da ziyaretçiye açık yeni bir fiziksel sayfa ürettiğinde.

// data/App.Menus.Routes.json ya da data/App.DeveloperKit.CustomComponents/{Id}.json
// içindeki satırın üzerine, var olan alanların yanına:
{
  "Path": "/kurslar",                            // Custom Component'te: "RoutePath"
  "SeoTitle": "::App.PublicSeo.Courses.Title",   // dil anahtarı ya da düz metin
  "SeoDescription": "::App.PublicSeo.Courses.Description",
  "SeoKeywords": "::App.PublicSeo.Courses.Keywords",
  "SeoOgImage": null,                            // boşsa ayarlardaki varsayılan paylaşım görseli
  "SeoCanonicalUrl": null,                       // boşsa site adresi + yol
  "SeoNoIndex": false,
  "SeoIncludeInSitemap": true,
  "SeoChangeFrequency": "weekly",
  "SeoPriority": 0.7,
  "SeoStructuredData": null                      // ham JSON-LD; sayfaya olduğu gibi eklenir
}

Kurallar:

Kural Neden
Metin alanları dil anahtarı taşır (::App.PublicSeo.{Sayfa}.{Alan}), anahtarlar data/App.Languages.LanguageText.json'a EN + TR yazılır Kayıt kültür başına çoğalmaz; çeviri dil dosyasında, tek yerde durur (standing default 4)
Title 60, Description 160 karakteri aşmaz Arama sonucunda kırpılır
Metni uydurma — sayfanın kendi içeriğinden türet Sayfada yazmayan bir vaat meta'da da yazmamalı
Kişiye özel / tek kullanımlık akış (/checkout, /payment, /success) → SeoNoIndex: true, SeoIncludeInSitemap: false İçeriği olmayan, tekrar ziyaret edilemeyen adres
Parametreli rota (/blog/:id) alan taşımaz Bir adres değil kalıptır; tek tek adresler sitemap'e veriden üretilir (SeoAppService)
Yönlendiren adres → SeoCanonicalUrl hedefi gösterir, SeoIncludeInSitemap: false Aynı içerik iki adreste görünmesin
Her satır SeoNoIndex ve SeoIncludeInSitemap taşır — ziyaretçiye açık olmayanlar da (true/false) Alanlar ekranın alan listesinde; NOT NULL bit kolonu satırda yoksa insert düşer. Diğer non-nullable alanlarla (IsActive, IsDisabled) aynı kural

Genel varsayılanlar kayıtta değil ayardadır: site adresi, site adı, varsayılan başlık ve açıklama, paylaşım görseli, Twitter hesabı, diller, robots yönergesi, Google doğrulaması, sosyal hesaplar, varsayılan anahtar kelimeler (DefaultKeywords), sitemap anahtarı (SitemapEnabled) ve sitemap dışı yollar (SitemapExcludePaths) App.SiteManagement.Seo.* ayarlarındadır (configs/seeds/host/data/App.SettingDefinitions.json). Bir değer bütün site için geçerliyse oraya yazılır, her yola tekrarlanmaz. Site kökü (/) hiçbir kayıt taşımaz; doğrudan bu varsayılanlardan beslenir.

Akış: ayarlar + iki kaynaktan toplanan yol kayıtları uygulama konfigürasyonunun seo alanıyla ziyaretçiye taşınır (PlatformApplicationConfigurationContributor), istemcide SeoHead sayfanın başlığını, açıklamasını, kanonik adresini, og:/twitter: kartını ve JSON-LD belgelerini kurar; :: ile başlayan değerler orada çevrilir (resolveSeoText). sitemap.xml ve robots.txt aynı kaynaktan SeoController tarafından üretilir.

0.10 MCP ile üretim — geliştirme araçları

/mcp ucuna bağlı bir istemci ekranı dosya yazarak değil, uygulamanın kendi servislerinden geçen araçlarla üretir; sonuç ve seed dosyaları wizard ekranıyla birebir aynıdır. Araç adları McpToolNames'tedir; okuyanlar App.DeveloperKit.McpServers.Invoke, değiştirenler .Write ister ve kiracı kullanıcısına hiçbiri açılmaz.

Araç Ne yapar Not
get_dev_guide Bu dosyanın (lowcode) ya da reference'ın başlık listesini / bir bölümünü döner Bölüm numarayla istenir (4.3); aynı numarayı iki başlık taşımamalıdır
create_table Yeni SQL Server tablosu kurar, sql/object/{Tablo}.sql dosyasını yazar Ek olarak App.SqlQueryManager ister; Id/TenantId/audit kolonlarını kendi ekler (fullAudited, multiTenant varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz
list_wizards · get_wizard Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner Var olan menü kökünü/izin grubunu bulmak için önce list_wizards
deploy_wizard Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) Girdi §4.4'teki düz cevap ya da get_wizard çıktısıyla aynı dosya biçimi; enum'lar sayı. replaceFileName = EditFileName (§4.5). List ekranında Widgets dolu gelir — alt form olarak üretilen ekran hariç (Widgets: []) — en az 3 kart, kiracı tablosunda "TenantId" = '@TENANTID' (§4.6.1); kartlardaki her dil anahtarı var olmalı ya da Language.Texts'te EN + TR verilmeli, yoksa deploy reddeder. MenuOrder yazılmaz — sunucu yeni ekranı kardeşlerinin sonuna koyar, güncellemede yerini korur. Talep bağı (DevRequest) da yazılmaz — sunucu etkin talepten doldurur (§4.0). İstek demo veri taşıyorsa SeedData: true + seedRows (§0.10.1). Satır/toolbar butonları CommandColumns (§4.9). Bağımlı lookup öğedeki Cascade* alanları (§4.3.4). Çıkıştaki isPending: true ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır; model migrate'i beklemez
delete_wizard Wizard'ı ve yalnızca onun ürettiği kayıtları siler; dropTables: true ekranın tablosunu da düşürür Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; App.SqlQueryManager ister (§4.5)
start_dev_request App.DevRequests talebini üstlenir: Ready to Start durumundaysa In Progress durumuna alır, değilse reddeder Ek olarak App.DevRequests.Update ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11)
get_dev_request Talebi durumuna dokunmadan okur (aynı metin + analiz + resim içerikleri) Analizden önce ya da yarım kalan talebe dönmek için; App.DevRequests ister
save_dev_request_analysis Backlog/Analysis durumundaki talebin analizini Analysis kolonuna yazar (var olanı değiştirir), talebi Analysis durumuna alır; diğer durumları reddeder Ek olarak App.DevRequests.Update ister. Standart §0.12; Description'a dokunmaz
append_dev_request_note Talebin Analysis kolonunun sonuna HTML ekler Üretim sonuç notu için; Description ve Analysis update_row ile yazılmaz (§0.11)
add_dev_request_subtasks SubTasks listesine tamamlanmış satırlar ekler Atanan = bağlanan kullanıcı, bitiş = bugün (sunucu yazar); SubTasks'ı update_row ile yazma (§0.11)
verify_wizard DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada install kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. passed yalnızca engelleyici bir kontrol kalırsa false
list_components · get_component · save_component · delete_component Custom component okuma/yazma (ada göre upsert) save_component visual designer bileşenini reddeder — yalnız Code modu (§7.1.1)
list_report_templates · get_report_template · save_report_category · save_report_template · delete_report_template Rapor kategorisi ve DynamicReport şablonu okuma/yazma (ada göre upsert) Yazma App.Reports.* ekranlarının C/U/D sözleşmesinden geçer, seed dosyasına yansır. listFormCode ile yer tutucuları doğrular ve satır butonunu döner (§9.12)

Yetkiler. MCP'nin iki yetkisi vardır: .Invoke (bağlantı + okuma) ve .Write (her değişiklik; kiracıda yalnızca satır yazma). İkisi de kapıdır, işlemin kendi uygulama yetkisi ayrıca aranır. Araç başına tam tablo: README.md §13.9 › "Yetki modeli". Bir araç yetki yüzünden reddedilirse cevap eksik yetkiyi adıyla verir ("'deploy_wizard' için gereken yetkiniz yok: Wizard (App.Listforms.Wizard)…"): bu durumda başka bir yoldan deneme — aynı işi farklı bir araçla yapmaya çalışma, eksik yetkiyi kullanıcıya olduğu gibi bildir ve dur.

0.10.1 Demo veri — SeedData

İstek örnek/demo satır taşıyorsa (prompt'taki tablo ya da liste, ekran görüntüsündeki satırlar, eklenen dosya) bu satırlar ekranla birlikte seed edilir; migrate çalıştığında liste boş gelmez.

  1. deploy_wizard cevabına SeedData: true yaz, satırların hepsini aynı çağrıda seedRows ile gönder (alan adından değere nesne dizisi). Ekranın SeedFilePath'i data/{ListFormCode}.json olur, SeedSyncInsert/Update/Delete açılır (§9.15); satırlar deploy anında bu dosyaya yazılır. Guid anahtarı sunucu üretir; TenantId ve audit alanlarını migrate doldurur — yazma. Aynı içerikli satır dosyada varsa (tekrar deploy) yeniden eklenmez.
  2. DB Migrate'i bekleme. Migrate wizard seeder ile ekranı kurar, ardından ListFormSeedDataApplier dosyadaki satırları ekler (anahtarı olmayanı ekler, var olana dokunmaz).
  3. Migrate'ten sonra ekrandan girilen satırlar ListFormSeedDataSynchronizer ile aynı dosyaya yazılır (kiracı bağlamında tenants/{TenantId}/data/).

Kurallar:

  • Dosyayı elle yazma; seedRows alan adlarını ekranın alanlarına göre doğrular, bilinmeyen alanı reddeder.
  • Görsel yalnızca ekranın şablonu değildir: içinde görünen her satır okunur ve demo veri olarak eklenir (standing default 6'daki "piksel sözleşmesi değildir" kuralı düzen içindir, veri için değil).
  • Görselden okunamayan değer uydurulmaz: zorunlu alan boşsa kullanıcıya sor.
  • Lookup alanının değeri kaynak tablodaki anahtardır; kaynakta yoksa önce o ekrana satır eklenir (o ekranın SeedFilePath'i yoksa satır yalnızca bu veritabanında kalır — raporda belirt).
  • İstek demo veri taşımıyorsa SeedData ve seedRows yazılmaz; test için satır eklenmez.

Okuma araçları (list_lists, describe_list, search_schema, query_list, run_select, list_queries, run_query) keşif turunda (§0.3) tablo/kolon/lookup kaynağını bulmak içindir; kullanıcının uygulamadaki yetkisini aşmaz.

0.11 Geliştirme döngüsü — App.DevRequests kuyruğu

Talepler /admin/list/App.DevRequests ekranında (Sas_H_DevRequest) toplanır; kiracılar da talep girer (App.DevRequest). Döngüyü /mcp'ye bağlı bir host kullanıcısı yürütür — kiracı bağlamında geliştirme araçları kapalıdır. Gereken yetkiler: App.DevRequests.Update, App.DeveloperKit.McpServers.Invoke + .Write ve üretimin kendi yetkileri (§0.10).

Talep yaşam döngüsü: Backlog → Analysis (MCP analiz eder, §0.12) → Ready to Start (analizi beğenen kullanıcı taşır; beğenmezse analiz yeniden yazılır ya da elle düzeltilir) → In Progress → Testing → Staging. Üretim Ready to Start'tan başlar ve talebin Description'ı ile onaylanmış Analysis'i birlikte tarif sayılır.

Girişler istemcide hazır prompt (komut) olarak görünür (McpPromptNames):

Prompt Kapsam
analyze_dev_request (request: Id ya da tam başlık) Yalnızca o talebi analiz eder (§0.12); hiçbir şey üretmez
analyze_backlog_requests Backlog durumundaki bütün talepler, SortOrder sırasıyla analiz edilir
process_dev_request (request: Id ya da tam başlık) Yalnızca o talep. Talep başlatılamazsa hiçbir şey üretilmez, başka talebe geçilmez
process_ready_requests Ready to Start durumundaki bütün talepler, SortOrder sırasıyla; başlatılamayan talep atlanır

Kullanıcı prompt yerine düz metin de yazabilir; aynı anlama gelir:

Kullanıcı yazar Karşılığı
<Id ya da "başlık"> talebini analiz et analyze_dev_request — yalnızca o talep
Analiz et · Backlog'daki talepleri analiz et analyze_backlog_requests
<Id ya da "başlık"> talebini işle process_dev_request — yalnızca o talep
Ready to Start durumundaki talepleri sırayla işle · Ready olanları işle process_ready_requests
Ready to Start durumundaki talepleri Id'leriyle listele Yalnızca adım 1 (query_list); hiçbir şey üstlenilmez, üretilmez

İstemciye göre komut yazımı (Claude Code /mcp__sozsoft__process_dev_request <Id> vb.): README.md › MCP › "Geliştirme taleplerini çalıştırma".

Durum kuralı sunucudadır: talep yalnızca start_dev_request ile üstlenilir. Araç talebi Ready to Start durumunda değilse reddeder, uygunsa aynı işlemde In Progress durumuna alır ve talebin metnini döndürür. Başlık birden fazla talebe uyuyorsa Id ister. Status hiçbir zaman update_row ile In Progress yapılmaz. Adımlar:

Adım Araç Durum
1. Kuyruğu oku (yalnızca toplu) query_list — ListFormCode: App.DevRequests, filtre ["Status","=","Ready to Start"], sıralama SortOrder —
2. Talebi üstlen start_dev_request — Id ya da tam başlık. Yarım kalmış talebe dönüşte get_dev_request In Progress
3. Üret Analizin Üretim planı sırayla, aynen uygulanır (aşağıdaki kurallar) → create_table / deploy_wizard / save_component; demo veri varsa SeedData: true + seedRows (§0.10.1); bu dosyanın kuralları aynen geçerlidir —
4. Teslim Sonuç kaydı + update_row → Testing. DB Migrate beklenmez; talep burada biter Testing
5. Test Kullanıcı DB Migrate çalıştırır, ekranı elle test eder ve talebi kendisi Staging'e alır (geri dönüşte In Progress). MCP bu adımı yapmaz Staging

Sonuç kaydı şema değiştirmeden mevcut alanlara yazılır:

  • SubTasks — her üretilen artefakt bir satır, add_dev_request_subtasks ile yalnızca başlık verilerek ("deploy_wizard: Mrp.Orders.json"). Araç satırı tamamlanmış yazar, atanan kişiyi bağlanan kullanıcı, bitiş tarihini bugün yapar ve var olan satırları korur. SubTasks update_row ile yazılmaz.
  • Timesheets — sunucu yazar, model hiçbir şey yapmaz. get_dev_request / start_dev_request talebi kullanıcının etkin talebi yapar; ardından yapılan her MCP yazma işlemi (create_table, deploy_wizard, delete_wizard, save_component, delete_component, insert_row / update_row / delete_row, save_dev_request_analysis, append_dev_request_note, add_dev_request_subtasks) o talebe bir satır ekler (IMcpDevRequestTimesheetRecorder). Etkin talep deploy_wizard'ın yazdığı wizard dosyasına da DevRequest olarak girer (§4.0); Wizard Manager talebi gösterir ve talebin tamamını export eder. Açıklama App.Mcp.Timesheet.* dil anahtarından Türkçe üretilir ("Hr_T_Doctor tablosu oluşturuldu"), süre önceki satırdan ya da talebin açıldığı andan bu yana ölçülür (en az 1 dakika, saat olarak), çalışan bağlanan kullanıcı, tarih bugündür. Timesheets update_row ile yazılmaz.
  • Analysis — append_dev_request_note ile sonuna <h4>MCP</h4> başlığıyla kısa özet: tablo, ekran, alan, demo veri, menü/yetki, analize eklenen varsayılanlar ve DB Migrate bekleyen wizard'lar. Tekrar üretimde yeni bir <h4>MCP</h4> bloğu eklenir, eskisi silinmez.
  • Description talebin yazıldığı hâliyle kalır; MCP ona hiçbir şey yazmaz. Description ve Analysis hiçbir zaman update_row ile yazılmaz — açıklama base64 görselleri taşır ve yeniden yazmak onları siler.

Görseller. Talebe yapıştırılan ekran görüntüleri açıklamada base64 durur; araç onları metinden ayıklar ve ayrı resim içeriği olarak verir, açıklamada yerlerinde [image-N] kalır. Kullanıcıdan görseli yeniden istemezsin. Görsel talebin parçasıdır: ekran düzenini (§0.7 — piksel sözleşmesi değildir) ve içindeki satırları taşır; satırlar demo veridir (§0.10.1).

Kurallar

  • Onaylanmış analiz tarifin kendisidir; üretim karar vermez. Soru sorulmaz, seçim yapılmaz, analizin yazdığı hiçbir şey değiştirilmez ya da daraltılmaz — tek istisna platform kuralıyla çelişen kısımdır (aşağıda). Bu dosyadaki "kullanıcıya sor" kuralları (onaycı, görselden okunamayan zorunlu alan, çerçeve dışı ihtiyaç) bu döngüde analizin (onay) kararlarıyla çözülmüştür. Analizde karşılığı olmayan küçük bir ayrıntı (§0.6 varsayılanı olan türden) varsayılanla tamamlanır ve sonuç notuna yazılır.
  • Platform kuralı analizden önce gelir. Analiz, bu dosyadaki bağlayıcı bir kuralla çelişiyorsa (görsel kolonu nvarchar(MAX) + dxImageUpload, tanım tablosunda IsActive, parametreli SQL, kiracı filtresi…) üretim kuralı uygular, çelişkiyi sonuç notuna yazar; durmaz, soru sormaz. Bu yüzden her create_table öncesi kolon listesi ekranın editörleriyle karşılaştırılır: editörü dxImageUpload olan kolon nvarchar(MAX) açılır — tablo kurulduktan sonra kolon değiştirilemez. Kural analizi talebin istediğinden uzaklaştırmaz; yalnızca platformun kurallarına uydurur.
  • Alt form ağacı analizdeki sırayla deploy edilir: en derindeki alt form önce, onu taşıyan alt form sonra, ana ekran en son (§0.12 madde 6); ana ekranın SubForms'u deploy anında var olan ekranlara işaret eder ve alt form yetkileri onun altına bu sırada taşınır. Alt form olarak üretilen her ekran (menüsüz ya da menülü) boş Widgets ile, menüsüz olan CreateMenu: false ile, Form sekmesinin hedefi AllowAdding + AllowUpdating açık gider (§4.6).
  • Üretim tek seferde biter: In Progress → Testing tek geçiştir; talep durdurulmaz, Analysis'e geri alınmaz, kısmi ekran üretilmez. Üretilebilirlik analizde doğrulanmıştır (§0.12). Döngü DB Migrate'i beklemez.
  • Test için ekrana satır ekleme — SeedSync açık ekranda seed dosyasına iz bırakır. Talebin kendi demo verisi test satırı değildir, §0.10.1 ile eklenir.
  • MCP talebi Staging'e almaz: deploy yalnızca dosya yazar, ekran migrate'ten sonra vardır ve testi kullanıcı yapar.

Migrate döngünün parçası değildir ve MCP'den tetiklenmez. Parti bitince yetkili host kullanıcısı Wizard Manager'daki DB Migrate'i (/api/setup/migrate, Setup.Migrate) çalıştırır; migrate ekranları, menü/yetki/dil satırlarını ve demo verisini kurar, Redis'i temizler ve bütün kiracıları dolaşır. Rapor bu adımı tek satırla belirtir.

0.12 Talep analizi — Backlog → Analysis

"Analiz et" dendiğinde Backlog talepleri üretimden önce analiz edilir; kullanıcı analizi okuyup beğenirse talebi Ready to Start'a kendisi taşır, beğenmezse analiz değişir (MCP'ye yeniden analiz ettirir ya da Analysis sekmesinde elle düzeltir). Analiz üretim yapmaz: tablo, wizard, bileşen, seed dosyası yazılmaz; yalnızca okuma araçları ve save_dev_request_analysis kullanılır.

Adım Araç Durum
1. Kuyruğu oku (yalnızca toplu) query_list — ListFormCode: App.DevRequests, filtre ["Status","=","Backlog"], sıralama SortOrder —
2. Talebi oku get_dev_request — metin + görseller —
3. Keşif list_wizards (menü kökleri, modüller, izin grupları), list_lists / describe_list / search_schema (var olan ekran, tablo, lookup kaynağı), list_components, get_dev_guide (§0.6, §0.7, §0.8, §1) —
4. Yaz save_dev_request_analysis — analizin tamamı, HTML (Timesheets satırını sunucu yazar, §0.11) Analysis

Durum kuralı sunucudadır: araç yalnızca Backlog ya da Analysis durumundaki talebi kabul eder; Analysis'teki talebe yeniden çağrılırsa analiz değiştirilir. Ready to Start ve sonrası reddedilir. Description'a hiçbir koşulda yazılmaz.

Analiz standardı. HTML, aşağıdaki başlıklar bu sırayla (<h4>), boş kalan başlık yazılmaz. Kısa ve karar odaklı yaz; talebi tekrar anlatma.

Analiz kapalı uçludur ve üretilebilirdir. Üretim analizi tek seferde, karar vermeden uygular (§0.11); bu yüzden:

  • Her konu tek bir kararla yazılır. Açık soru, seçenek listesi ("(a) … (b) …"), "istenir mi?", "gerekirse", "ileride" bırakılmaz.
  • Talebin açıkça yazdığı şey (menü yolu, alan, kapsam) girdidir: değiştirilmez, daraltılmaz, alternatifi sunulmaz.
  • Talepte olmayan bilgi için analiz karar verir (Kararlar, madde 9). Tabloya yazma hedefi, onaycı ve hesap formülü de boş bırakılmaz: somut karar (onay) etiketiyle yazılır; talebi Ready to Start'a alan kullanıcı onları onaylamış olur.
  • Planın her adımının mevcut araçlarla (create_table, deploy_wizard, save_component ve sözleşmeleri, §4.4) yapılabildiği keşifte doğrulanır (madde 11).
  1. Amaç — talep neden istenmiş, kim kullanacak, hangi işi çözecek (bir-iki cümle).

  2. Görsel analizi — talepte görsel varsa her biri için ([image-N]): ekran türü (liste, form, kanban, grafik, public sayfa…), görünen alanlar ve tipleri, gruplar/sekmeler, butonlar/aksiyonlar, filtreler, görünen satırlar (demo veri adayı, §0.10.1). Görsel yoksa başlık yazılmaz.

  3. Çözüm yolu — karar sırasındaki adım (Wizard List / Form / Custom component / SQL + Custom Endpoint / Dynamic Service) ve neden.

  4. Menü yeri — ekranın menüde nerede duracağı, tek bakışta okunur biçimde. Önce tam yol tek satırda (Kök › Ara › Ekran), sonra kökten ekrana her seviye bir satır:

    Seviye Kod Ad (TR / EN) İkon Durum
    Kök … … / … … Var / Yeni
    Ara (yalnızca talep istiyorsa, her biri) … … / … … Var / Yeni
    Ekran ListFormCode … / … … Yeni

    Altında ModuleId, izin grubu ve route yazılır. Var olan seviyeler list_wizards ve menü seed'inden okunur, kodları oradaki gibi yazılır; sıra (Order) deploy'da hesaplanır, analize yazılmaz. Seviye sayısı talepten gelir, sabit değildir: Kök › Ekran iki, Kök › Ara › Ekran üç seviyedir; analiz talebin istemediği bir ara seviye eklemez. Talep menü yolunu yazıyorsa (başlıkta ya da açıklamada) o yol girdidir ve seviye seviye aynen alınır. Wizard karşılığı: ekranın bir üstü MenuParentCode, daha üstündekiler MenuParentPath (iki seviyede boş, §4.4). Yetki ağacı menü ağacıyla aynıdır: deploy, izin grubu dışındaki her menü seviyesini aynı kodlu ara yetki yapar ve ekranın okuma yetkisini onun altına yazar. Analiz bunu menü tablosunun altında tek satırda gösterir — Yetki: {İzin grubu} › {Ara yetki} › {ListFormCode} — ve her ara seviyenin yetkisi için Var / Yeni yazar (var olan Abp.Identity.Permissions.json'dan okunur). Yetkinin yerini menüden ayrı seçen bir wizard alanı yoktur; yetki dalı menü yoluyla birlikte değişir. Menüsüz alt formun yetkisi listenin yetkisinin altındadır: alt formun okuma yetkisinin ParentName değeri onu SubForms içinde taşıyan listenin yetki kodudur (grubun kökü değil); analiz satırı Yetki: {İzin grubu} › {Ara yetki} › {Liste} › {AltForm} olur. Deploy bunu kendisi kurar — alt form önce deploy edilir, liste deploy edildiğinde alt formun yetkisi listenin altına, listenin alt yetkilerinin (Create/Update/…) ardına taşınır; ayrıca bir alan yazılmaz. Zincir iç içedir: alt formun alt formu bağlı olduğu alt formun altındadır (… › HealthCard › HealthCardGeneral › HealthCardWorkHistory). Kendi menüsü olan ekran başka bir ekranın sekmesinde de açılıyorsa yetkisi kendi menü dalında kalır; analiz onu alt form ağacında "menülü" diye işaretler. Aynı üst menü altındaki ekranlar bağımlılık sırasıyla yazılır: başka bir ekranın lookup kaynağı olan (tanım) ekran, onu kullanan ekranın üstündedir. Menüdeki sıra deploy sırasından gelir; madde 10'daki üretim planı da aynı sırayla yazılır.

  5. Veri modeli — tablo adı (onek kuralı §2), yeni mi var olan mı; kolonlar (ad, tip, zorunluluk, uzunluk), lookup kaynakları, kiracı + audit (standing default 1). Yeni her tablonun ilk satırı Tanım tablosu: Evet / Hayır kararıdır, talepteki dayanağıyla (§8.1); atlanmaz. Evet ise kolon tablosunda IsActive (bit, varsayılan 1, zorunlu) yer alır — talepte ya da görselde olmasa da — ve madde 8 (form alanı + Aktif/Pasif widget'ı) ile madde 10 (create_table kolonu, demo veride IsActive: true) bunu taşır. Görsel kolonu: görsel tutan her kolon (fotoğraf, resim, logo, avatar kopyası, belge görüntüsü) nvarchar(MAX)'tir ve editörü dxImageUpload'dır — değer başka bir kayıttan kopyalanıyor olsa da. Görsel base64 metin olarak saklanır; nvarchar(512) gibi sınırlı boy yazılmaz, dxImageViewer kullanılmaz (§4.3 editör tablosu). Çok satırlı serbest metin (dxTextArea, dxHtmlEditor) de nvarchar(MAX)'tir. Kullanıcı (çalışan) bilgisi AbpUsers'tan okunur. Kullanıcıya ait her bilgi kullanıcı kaydında durur: IdentityNumber (TC), BirthDate, BirthPlace, Gender (Male/Female), HomeAddress, EducationLevel, GraduationSchool, BloodType, sağlık kartı alanları ({Mother,Father,Sibling,Child}HealthStatus/…HealthNote, ChronicDisease, …Vaccination, Lab*, Exam*, Complaint*, Diagnosis*, anamnez soruları, FitForWork*, ShiftWorkFit, NightWorkFit, *DoctorId), Nationality, MaritalStatus, SskNo, HireDate, DepartmentId, JobPositionId, WorkHour, kimlik kartı alanları (SerialNo, Province, District, FatherName, MotherName…). Kullanıcı seçen bir lookup bu kolonları doğrudan u. ile seçer; başka bir ekranın tablosundaki son kayıttan (OUTER APPLY (SELECT TOP 1 …), COALESCE(önceki kayıt…)) türetmez — o tablolar kullanıcıyı değil kendi kaydını tutar. Ekranın tablosu bu bilginin kopyasını tutacaksa kolon aynı ad, aynı tip/boy ve aynı değer kümesiyle açılır (IdentityNumber nvarchar(15), Gender nvarchar(16) + Male/Female ve App.StaticLookup.Male/Female anahtarları, EducationLevel değerleri); IdentityNo, E/K gibi farklı ad ya da değer açılmaz, sorguda AS ile yeniden adlandırma ya da CASE çevirisi gerekmez. Bu kolonların hepsi AbpIdentity.Users ekranında alan olarak tanımlıdır (kişisel olanlar gizli alan) ve describe_list AbpIdentity.Users ile görünür. MCP şema sözlüğü tablo kataloğunu değil ekranların alan listesini döker: bir kolon sözlükte yoksa bu, tabloda olmadığı anlamına gelmez — kullanıcı bilgisi için başka tabloya gidilmez. Query kaynağı (ekran birden çok tabloyu tek sorguda birleştiriyorsa ve view tercih edilmediyse, §8.2): SelectCommandType: 4, TableName: "Q", KeyFieldName + KeyFieldDbSourceType zorunlu (§3). Ana tablo (en dıştaki FROM'un ilk tablosu) IsDeleted taşıyorsa sorgu ana tablonun IsDeleted kolonunu seçer; deploy DefaultFilter'ı kendisi yazar. Query ekranında DeleteCommand üretilmez — silme gerekiyorsa hedef tablo kararı (onay) ile yazılır. Join'lerdeki soft delete koşulu (hc."IsDeleted" = 'false') sorgunun içinde durur.

  6. Alt form ağacı — ekran sekme taşıyorsa her sekme bir satır, kökten iç içe:

    Sekme Hedef ListFormCode TabType Menü İlişki Ekle / Düzenle / Sil Yetki yolu
    … … Form (tek kayıt) / List (çok kayıt) Menüsüz / Menülü Id → …Id ✓ / ✓ / – … › Liste › AltForm

    Form sekmesinin hedefi AllowAdding + AllowUpdating açık yazılır (kayıt yoksa sekme ilk kaydı açar, varsa ikinci kayıt açılmaz); List sekmesinde üçü açıktır; silme yalnızca talep istiyorsa. Alt form olarak üretilen ekranda (menüsüz ya da menülü) widget yoktur, düzenleme formu tek gruplu ise grup başlığı yazılmaz (§4.3). Ayrıntı: §4.6.

  7. Etkilenen kısımlar — uygulamada dokunulacak her yer: yeni/değişen ekranlar, alt formlar, menü, yetkiler (C/R/U/D + varsa özel), dil anahtarları, widget'lar, iş akışı/bildirim, SEO (public yol varsa), var olan ekranlara etkiler.

  8. Görünümler ve davranış — Grid/Card/Pivot/Chart (standing default 12), widget kartları (§4.6.1), EditorScript / EditorOptions ihtiyaçları, hesaplar ve doğrulamalar. Görsel alanı her zaman dxImageUpload'dır (madde 5). Widget kartları yalnızca ana ekrana yazılır; alt form olarak üretilen ekrana yazılmaz (§4.6.1).

  9. Kararlar — talepten okunamayıp analizde verilen her karar, tek satır gerekçesiyle (§0.6); varsayılamayan konular (onay) etiketi taşır. "Açık sorular" başlığı yoktur.

  10. Üretim planı — bağımlılık sırasıyla (tablo → lookup kaynağı ekran → en derindeki alt form → onu taşıyan alt form → ana ekran) sıralı artefakt listesi (create_table: …, deploy_wizard: ….json, save_component: …); üretimdeki add_dev_request_subtasks başlıkları bununla aynı olur.

  11. Üretime hazır — tek satır: Evet, ya da Hayır — eksik yetenek: … (çerçeve dışı ihtiyaç, §0.8, adıyla). Talebi daraltan geçici çözüm önerilmez; "Hayır" taşıyan talep yetenek eklenene kadar Ready to Start'a alınmaz.

1. Hangi istek → hangi artefakt

İstek Üretilecek artefakt Bölüm
"X tablosunun listesi/ekranı olsun" Wizard seed dosyası (ComponentKind: 0) 4
"Şu alanı otomatik hesaplasın / şarta göre kilitlensin" EditorScript (Script Builder tarifi) 5
"Alan şu formatta / maskeyle görünsün" EditorOptions (Options Builder) 6
"Ekran bir tablodan değil, birleştirilmiş veriden beslensin" SQL View Designer → View + Wizard 8
"Serbest yerleşimli bir sayfa / dashboard / özel form" Custom Component + Wizard (ComponentKind: 1) 7
"Bir tabloya REST API açalım" CRUD Endpoint 9
"Elle yazdığım bir SQL'i API olarak açalım" Custom Endpoint 9.2
"API'de iş mantığı/hesap/entegrasyon lazım" Dynamic Service 9.3
"Onay süreci olsun" WorkflowDto + workflow kriterleri 4.7
"Ana-detay ekranı" SubForms 4.6
"Üstte KPI kartları" Widgets 4.6
"Alan otomatik dolsun (kullanıcı, tarih, tenant, sorgu sonucu)" *FieldsDefaultValueJson 4.3.5
"Belge/sipariş numarası otomatik üretilsin" Sequence kaydı + varsayılan değer 9.9 · 4.3.5
"Bağlı açılır listeler (il → ilçe)" LookupJson cascade alanları 4.3.4
"Şu koşuldaki satır renkli olsun" ColumnStylingJson 4.3.7
"Ekranın üstünde tarih/şube/durum filtresi" ExtraFilterJson 4.3.8
"Kayıt kurallara uymadan kaydedilmesin" ValidationRuleJson (+ sunucu tarafı SQL kontrolü) 4.3.6
"Kaydederken/silerken ek SQL çalışsın (stok, log, durum)" *BeforeCommand / *AfterCommand 4.8
"Şu iş her gece / her 5 dakikada çalışsın" BackgroundWorker kaydı + stored procedure 9.10
"Şu olayda bildirim gitsin" Notification Type + Rule 9.11
"Fatura/teklif/iş emri/sertifika çıktısı" Raporlar kategorisinde DynamicReport şablonu + satır butonu (ya da hazır DynamicGrid) 9.12
"Ayar ekranından açılıp kapansın" Setting Definition 9.14
Hiçbiri yetmiyorsa Dynamic Service → en son çare kod ai.instructions.md §4

2. İsimlendirme sözleşmesi

Tutarlılık zorunludur; menü, route, yetki ve dil anahtarı aynı kökten türer.

Kapsam: Buradaki isimlendirme ve dosyanın genelindeki kod standartları (parametreli SQL, yetki sözleşmesi, EN+TR dil anahtarı, seed zorunluluğu, tenant izolasyonu) artefakt türünden bağımsızdır — Menu, Route, Wizard, ListForm, ListFormField, Custom Component, CRUD/Custom Endpoint, Dynamic Service, SQL nesnesi, Sequence, BackgroundWorker, Notification… hangisini üretirsen üret aynı kurallar geçerlidir. "Bu küçük bir yardımcı kayıt" diye standart dışına çıkma.

Şey Kalıp Örnek
Tablo {Modul}_T_{Entity} Mrp_T_Order
View {Modul}_V_{Ad} Mrp_V_OrderSummary
ListFormCode App.{Modul}.{Liste} App.Mrp.Orders
MenuCode ListFormCode ile aynı App.Mrp.Orders
Üst menü kodu App.{Modul} App.Mrp
Yetki grubu App.{Modul} App.Mrp
Yetkiler {MenuCode} + .Create/.Update/.Delete/.Export/.Import/.Note App.Mrp.Orders.Create
Dil anahtarı (menü/başlık) {MenuCode} App.Mrp.Orders
Dil anahtarı (alan) App.Listform.ListformField.{Alan} App.Listform.ListformField.OrderNo
Route (List) /admin/list/{ListFormCode} — statik :listFormCode route'u karşılar, DB'ye kayıt yazılmaz /admin/list/App.Mrp.Orders
Route (Custom) Bileşenin RoutePath değeri; route doğrudan bundan üretilir /admin/order-board
Wizard seed dosyası {WizardName}.json Orders.json
Custom component seed data/App.DeveloperKit.CustomComponents/{Id}.json — dosya adı satırın Id'si, bileşen adı satırın Name alanı …/8389ccbc-3e7b-….json
CRUD endpoint seed crud/{EntityName}.json Mrp_T_Order.json
Liste verisi seed data/{ListFormCode}.json (tek dosya) ya da data/{ListFormCode}/{anahtar}.json (klasör) — §9.15 App.Definitions.Sequence.json

Kapsam klasörü: host bağlamında configs/seeds/host/…, tenant bağlamında configs/seeds/tenants/{tenantId}/…. Çözüm her zaman SeedPathResolver üzerinden yapılır; yol elle kurulmaz.


3. Ortak enum değerleri (seed dosyalarına sayı olarak yazılır)

ComponentKind (WizardComponentKindEnum) — 0 List · 1 Custom

SelectCommandType — 1 Table · 2 View · 3 TableValuedFunction · 4 Query · 5 StoredProcedure. 4 Query'de TableName zorunludur (sorgunun alias'ı, FROM (…) AS "Q"); wizard/deploy_wizard "Q" yazar, elle kurulan Query ekranında da doldurulur. KeyFieldName + KeyFieldDbSourceType da zorunludur; wizard anahtarsız Query ekranını reddeder, tip 0 gelirse seçilen alanlardaki karşılığından tamamlar. Sorgunun ana tablosu (en dıştaki FROM'un ilk tablosu) IsDeleted taşıyorsa DefaultFilter = "IsDeleted" = 'false' yazılır; filtre dış sorguda çalıştığı için sorgu ana tablonun IsDeleted kolonunu seçmelidir, seçmiyorsa deploy reddedilir. DeleteCommand Query'de üretilmez (kaynak tablo adı değildir), gerekiyorsa elle yazılır.

LookupDataSourceType — 1 StaticData · 2 Query · 3 WebService

CustomValueType (FieldCustomValueTypeEnum) — 1 Value · 2 CustomKey · 3 DbQuery · 4 QueryParams · 5 Sequence (§4.3.5)

SortMode (UiGridSortModeEnum) — 1 Single · 2 Multiple · 3 None

ButtonPosition (UiCommandButtonPositionTypeEnum) — 0 CommandColumn (satır sonu) · 1 Toolbar (§4.9)

ValidationRule.Type (UiColumnValidationRuleTypeEnum) — seed'e metin olarak yazılır: required numeric range stringLength custom compare pattern email async (§4.3.6)

WorkerType (WorkerTypeEnum) — 1 MailQueueWorker · 2 SqlWorker · 3 NotificationWorker · 4 SessionCleanupWorker · 5 BackupWorker (§9.10)

Scope (WorkerScopeEnum) — 1 Host · 2 Tenant · 3 All (önce host, sonra her kiracı) (§9.10)

ResetPeriod (SequenceResetPeriod) — 0 None · 1 Yearly · 2 Monthly · 3 Daily · 4 Hourly · 5 Minutely (§9.9)

DataSourceType (DataSourceTypeEnum) — 1 Mssql · 2 Postgresql

AuthorizationType (AuthorizationTypeEnum) — 0 Deny · 1 Read · 2 Create · 3 Update · 4 Delete · 5 Export · 6 Import

ListFormCustomizationType (ListFormCustomizationTypeEnum) — 1 UserUiFilter · 2 GridState · 3 ServerJoin · 4 ServerWhere. Kullanıcının kaydettiği filtre ve grid tercihinin nerede durduğunu belirler; seed dosyasına yazılmaz.

Metin sabiti olan "enum"lar — bunlar enum değil const string'tir, seed'e metin yazılır:

Sabit Değerler
ListFormType (ListFormTypeEnum) List · Form · Chart
SubForms[].TabType (ListFormTabTypeEnum) List · Card · Tree · Gantt · Scheduler · Todo · Form · Chart · Pivot
DefaultLayout grid · card · pivot · chart · tree · gantt · scheduler · todo (küçük harf)
Bildirim kanalı (NotificationChannels) Mail · Sms · WhatsApp · Telegram · Rocket · Desktop · UiToast · UiActivity — etiketleri App.NotificationChannel.{Kanal} dil anahtarlarıdır
Bildirim alıcı tipi (NotificationRecipientTypes) User · Role · OrganizationUnit · All · Custom

DbSourceType / KeyFieldDbSourceType (System.Data.DbType):

# Ad SQL karşılığı
1 Binary binary, varbinary, image
2 Byte tinyint
3 Boolean bit
5 Date date
6 DateTime datetime, datetime2, smalldatetime
7 Decimal decimal, numeric, money
8 Double float, real
9 Guid uniqueidentifier
10 Int16 smallint
11 Int32 int
12 Int64 bigint
16 String nvarchar, varchar, nchar, char, text
17 Time time
25 Xml xml
27 DateTimeOffset datetimeoffset

Editör tipleri (EditorType2) — 21 tip, alana metin olarak yazılır. DevExtreme'den gelenler: dxAutocomplete, dxCalendar, dxCheckBox, dxColorBox, dxDateBox, dxDateRangeBox, dxDropDownBox, dxHtmlEditor, dxLookup, dxNumberBox, dxRadioGroup, dxRangeSlider, dxSelectBox, dxSlider, dxSwitch, dxTagBox, dxTextArea, dxTextBox. Platformun kendi editörleri (PlatformEditorTypes): dxGridBox, dxImageViewer, dxImageUpload (+ dxTagBox platform tarafında da özel işlenir).

Tuzak: PlatformConsts.EditorTypes.dxLookup sabiti bozuk bir metin taşır ("dxHtmlEdidxLookuptor"). Hiçbir yerde kullanılmadığı için etkisi yoktur, ama C# seeder yazarken bu sabiti kullanma; EditorType2 = "dxLookup" diye düz metin yaz. Diğer 20 sabit doğrudur.

Tip seçimi kuralı: Boolean → dxCheckBox/dxSwitch, tarih → dxDateBox, sayı → dxNumberBox, FK/lookup → dxSelectBox (az kayıt) veya dxGridBox/dxLookup (çok kayıt/çok kolon), çok değerli FK → dxTagBox, uzun metin → dxTextArea, HTML → dxHtmlEditor. Her editörün kendine özgü seçenekleri için §4.3.2.


4. Artefakt A — ListForm ekranı (Wizard seed dosyası)

Konum: configs/seeds/{kapsam}/wizard/{WizardName}.json (dosya adında tarih yoktur) Okuyan: WizardDataSeeder (migrate) · Yazan: ListFormWizardAppService (deploy) ve WizardSeedSynchronizer (ListForm editöründen kayıt) · Okuma/yazma her zaman WizardSeedFileDto.FromSeedJson / ToSeedJson ile — düz JsonSerializer bloklu bölümleri düşürür.

İki biçim vardır, karıştırma: wizard cevabı (düz ListFormWizardDto, §4.4) deploy'un girdisidir — wizard ekranı ve MCP deploy_wizard bunu alır; seed dosyası (§4.1) deploy'un çıktısıdır. List yolunda dosyayı deploy'a ürettir; ListForm bölümü olmayan List dosyası seeder tarafından uyarıyla atlanır.

4.0 Deploy dosya yazar, migrate kurar

Deploy (wizard ekranı, MCP deploy_wizard, düzenleme dahil) veritabanına hiçbir şey yazmaz ve hiçbir şey silmez. Tek çıktısı seed dosyalarıdır:

Dosya İçerik
wizard/{WizardName}.json Ekranın ListForm/Fields sözleşmesi, wizard cevapları, InsertedRecords, Pending, DevRequest
data/App.Menus.Module.json, data/App.Menus.Menu.json Modül, üst menü, ekranın menüsü
data/Abp.Identity.PermissionGroups.json, data/Abp.Identity.Permissions.json İzin grubu, okuma yetkisi ve alt yetkileri
data/App.Languages.LanguageText.json Menü, başlık, açıklama, grup, alan başlıkları ve Language.Texts anahtarlarının (widget başlıkları) EN + TR metinleri (Id iş anahtarından v5 GUID, SeedRowIds.LanguageText)

Satırları WizardSeedRowStore yazar; anahtarlar WizardSeedRowKeys'ten gelir (export aynı listeyle toplar). Sahiplik: bir satır ne veritabanında ne dosyada varsa ya da önceki sürüm ona sahipse wizard'ındır (InsertedRecords) ve cevaplarla yazılır; başkasına ait satıra (var olan üst menü, izin grubu, paylaşılan dil anahtarı) dokunulmaz — metni kendi ekranından değişir.

Kurulum DB Migrate'tedir (Wizard Manager'daki buton, POST /api/setup/migrate). Sıra: WizardDataSeeder ekrana özgü kayıtları kurar (veri kaynağı, ListForm, alanlar, iş akışı), ardından ListFormSeedDataApplier menü/yetki/dil satırlarını ekler (eksik olanı ekler, var olana dokunmaz). Yetkiler hiçbir role otomatik atanmaz — admin dahil; ekranı hangi rolün göreceği rol ekranından seçilir.

Deploy'dan migrate'e kadar wizard'ın hiçbir kaydı veritabanında yoktur ve bu beklenen durumdur. Yeni modül, kök/üst menü, izin grubu, yetki ya da dil anahtarı veritabanında bulunmuyorsa deploy onu dosyaya yazar, migrate Order sırasıyla (route → modül → menü, grup → yetki) ekler. Bunları uyarı olarak raporlama, kullanıcıdan başka bir ekrandan eklemesini isteme. Deploy sonrası kullanıcıya söylenecek tek adım DB Migrate'tir.

Bekleyen kurulum (Pending). Deploy dosyaya Pending bölümü yazar; Wizard Manager dosyayı "Migrate bekliyor" olarak gösterir (WizardFileInfoDto.IsPending = Pending dolu ya da ekran bu veritabanında kurulu değil). Düzenleme önceki sürümün sahip olduğu kayıtları Pending.Replaced'a, kodu değişen eski ekranı Pending.ListFormCodes'a ekler ve artık üretilmeyen satırları dosyalardan çıkarır. Migrate bekleyen dosyada önce bu kayıtları ve wizard'ın güncel kayıtlarını veritabanından kaldırır, ekranı yeniden kurar, Pending'i siler; kaldırılan satırları applier dosyadaki güncel hâlleriyle geri ekler. "Yalnızca eksiği ekle" kuralı böylece düzenlenmiş ekranı da güncel hâline getirir. Kaldırılan yetkinin rol atamaları yalnızca yetki kalıcı olarak gidiyorsa silinir. WizardSeedSynchronizer bekleyen dosyaya yazmaz — dosya ekranın bir sonraki sürümünü taşır.

Talep bağı (DevRequest). MCP deploy_wizard dosyaya kullanıcının etkin talebini (get_dev_request / start_dev_request ile açılan, IMcpDevRequestTimesheetRecorder.GetActiveRequestIdAsync) { "Id", "Title" } olarak yazar. Bağ modelin girdisinden gelmez: ListFormWizardDto.DevRequestId [JsonIgnore]'dur, yalnızca sunucu içinden doldurulur. Aynı wizard'ı sonra başka bir talep deploy ederse bağ o talebe geçer; Wizard Manager'dan elle yapılan kayıt önceki bağı korur, hiç talep yoksa bölüm dosyaya yazılmaz. Migrate, senkronizasyon ve import bölümü olduğu gibi taşır.

4.1 Dosya iskeleti

{
  "Wizard": {                  // yalnızca ekrandan üretilemeyen bilgiler — 4.2
    "ComponentKind": 0, "WizardName": "Tags", "WizardCode": "App.Prj.Tags",
    "SeededAt": "2026-09-01T09:03:00", "CustomComponentName": null,
    "PermissionGroupName": "App.Prj", "ModuleId": "Prj",   // boş bağlantı metni yazılmaz
    "Menu":     { "MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…",
                  "MenuParentPath": [ { "Code": "…", "Icon": "…", "En": "…", "Tr": "…" } ],   // yalnızca derin yolda
                  "MenuParentCode": "…", "MenuParentIcon": "…" },   // "CreateMenu": false ise eklenir
    "Language": { "MenuEn": "…", /* Menu/Title/Desc/MenuParent/PermissionGroup · EN + TR */
                  "Texts": [ { "Key": "…", "En": "…", "Tr": "…" } ] },   // Texts yalnızca doluysa
    "Groups":   [ { "Items": [ { "FieldName": "…", "CaptionName": "…",
                                 "En": "…", "Tr": "…" } ] } ]   // grup Caption/ColCount yok
  },
  "ListForm": { /* GridOptionsEditDto — ekranın kendisi, ListForm detayıyla aynı sözleşme */ },
  "Fields": [ /* ColumnFormatEditDto — alanlar, alan detayıyla aynı sözleşme */ ],
  "IsDeletedField": true,      // tabloda IsDeleted var mı (soft delete)
  "IsCreatedField": true,      // tabloda CreatorId var mı (audit)
  "InsertedRecords": {         // bu çalıştırmanın GERÇEKTEN yarattığı kayıtlar
    "LanguageKeys": [], "PermissionGroupNames": [], "PermissionNames": [],
    "MenuCodes": [], "DataSourceCodes": [], "ModuleIds": []
  },
  "DevRequest": { "Id": "…", "Title": "…" }   // yalnızca MCP talebiyle deploy edildiyse
}

Wizard bölümü yalnızca ekrandan üretilemeyen bilgileri taşır (menü, yetki, dil metinleri, bileşen yolu, alan başlıklarının dil karşılıkları); ListForm/Fields içinde karşılığı olan hiçbir alan orada tekrar edilmez (§4.2). Anahtar sırası sabittir: ComponentKind, WizardName, WizardCode, SeededAt, CustomComponentName, PermissionGroupName, ModuleId, DataSourceConnectionString, Menu, Language, Groups. ListForm/Fields içindeki Id/CreationTime/EditType gibi çalışma zamanı alanları sıfırlanmış yazılır.

Custom yolunda (ComponentKind: 1) ListForm null, Fields [] yazılır ve kırpma yapılmaz: Wizard bloğu List'e ait anahtarları (Grid/Card/…, SelectCommand, option blokları) varsayılan değerleriyle taşımaya devam eder; bunlar kullanılmaz, temizlemeye çalışma. Bu yolun dosyası elle yazılabilir — seeder yalnızca menü, yetki ve dil kaydı üretir.

ListForm + Fields ekranın tek gerçeğidir. Seeder ekranı bu iki bölümden kurar; Wizard bölümünden yalnızca menü, yetki, dil anahtarları ve veri kaynağı üretilir. Ekran ListForm detayından (liste ayarları, alan ayarları, JSON satır editörleri) düzenlendiğinde sunucu aynı dosyanın ListForm/Fields bölümlerini ve Wizard bölümündeki karşılıklarını geri yazar (WizardSeedSynchronizer); böylece migrate ya da import ekranı dosyadan kurarken tasarımcıdaki iş kaybolmaz (EditFileName ile yeniden deploy ise ekranı cevaptan üretir, §4.5). Elle bu dosyayı yazarken ListForm/Fields bölümlerini uydurma — wizard'ı deploy et, dosyayı sunucu üretsin.

InsertedRecords silme sözleşmesidir: wizard silindiğinde yalnızca burada listelenen kayıtlar silinmeye aday olur, önceden var olan kayıtlara (var olan izin grubu, var olan üst menü) dokunulmaz. Aday kayıt da yalnızca başka bir yerde kullanılmıyorsa silinir (§4.5 Silme). Elle dosya yazarken buraya var olan bir kaydı koyma — kullanılmıyorsa silme onu da götürür.

4.2 Wizard bloğu — alan alan

Wizard bloğu yalnızca ekranın sözleşmesinden üretilemeyen bilgileri taşır: menü, yetki, dil metinleri, bileşen yolu ve alan başlıklarının iki dilli karşılıkları. Ekrana ait olan her şey (veri kaynağı, select komutu, anahtar alan, kırılım bayrakları, düzen/görünüm bayrakları, düzenleme izinleri, görünüm option blokları, alt formlar, widget'lar, iş akışı, alan tanımları) ListForm/Fields bölümlerindedir ve dosyaya ikinci kez yazılmaz.

Dosyada wizard alanları üç başlık altında toplanır — Menu (menü kaydı), Language (dil metinleri), Groups (alan başlıklarının dil karşılıkları); geri kalanı kök seviyededir. Gruplama ile ListFormCode → WizardCode, MenuParentModuleId → ModuleId ve LanguageTexts → Language.Texts adlandırmaları yalnızca dosya biçimidir; C# sözleşmesi (ListFormWizardDto) düzdür ve dosya okunurken düzleştirilir (WizardSeedFileDto.FromSeedJson ↔ ToSeedJson). Aşağıdaki tablolar C# alan adlarını kullanır.

Aşağıdaki tablolarda [ListForm] işaretli alanlar istek girdisidir: sihirbaz bunları POST eder, sunucu ekranı bunlardan üretir, ama seed dosyasında ListForm bölümündeki karşılıkları saklanır. Dosya okunurken bu alanlar ListForm/Fields üzerinden geri doldurulur (WizardSeedFileDto.FromSeedJson), dolayısıyla wizard'ı okuyan kod tam nesneyi görür.

Kimlik ve yol

Alan Anlam
ComponentKind 0 List (varsayılan) · 1 Custom. Dosyadaki ilk alan olmalı.
WizardName Dosya adının ({WizardName}.json) ve export zip adının kaynağı.
SeededAt İlk deploy anı; seeder dosyaları bu damgaya göre sıralar. Sunucu üretir, düzenlemede korunur.
ListFormCode Ekranın kodu; route, yetki adları ve dil anahtarları bu koddan türer. Dosyada WizardCode adıyla, üçüncü sırada yazılır.
MenuCode Menü kaydının kodu; yetki adlarının kökü de budur.
MenuUrl List'te /admin/list/{MenuCode} hesaplanır; Custom'da bileşenin RoutePath'i yazılır.
CustomComponentName Yalnızca Custom yolunda; bağlanacak bileşenin adı.
EditFileName Yalnızca istek girdisi (MCP'de replaceFileName): doluysa güncellemedir — .Update yetkisi ister, SeededAt korunur, dosyalar cevaptan yeniden yazılır ve ekran migrate'te cevaptan yeniden kurulur (§4.0); ListForm editöründe yapılmış, wizard'da karşılığı olmayan ayarlar kaybolur. Seed dosyasına yazılmaz.

Menü

Dosyada bu bloğun tamamı Menu başlığı altındadır; MenuParentModuleId ise kök seviyede ModuleId adıyla yazılır. Menüsüz wizard'da da blok korunur: MenuCode yetkilerin kökü, Custom yolunda MenuUrl rotanın kaynağıdır.

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 olduğu için dosyaya yalnızca false iken yazılır.
MenuParentCode / MenuParentModuleId / MenuParentIcon Ekranın doğrudan üst menüsü; yoksa oluşturulur.
MenuParentPath MenuParentCode'un üstündeki seviyeler, kökten aşağı; her biri { Code, Icon, En, Tr }. Derinlik sınırsızdır: deploy zinciri kökten yürür, var olan seviyeye bağlanır, eksik seviyeyi bir üstünün altına kardeşlerinin sonuna kurar (menü + dil satırı, InsertedRecords'a girer). Boşsa üst menü kök seviyededir; boş liste dosyaya yazılmaz. Örnekler: İnsan Kaynakları › Yeterlilik Belgeleri (iki seviye) → MenuParentCode: "App.Hr", MenuParentPath yok; İnsan Kaynakları › Tanımlamalar › Yeterlilik Belgeleri (üç seviye) → MenuParentPath: [{ Code: "App.Hr", … }], MenuParentCode: "App.Hr.Definitions".
MenuIcon React-icons adı (FcBiohazard, FaBox…).
MenuOrder Kardeşler arası sıra; 0/boş ise kardeşlerin sonu. MCP deploy'unda yok sayılır (yeni ekran sona, güncelleme yerinde kalır).

Yetki ve dil

Dil metinlerinin tamamı dosyada Language başlığı altındadır; başlık zaten bölümü işaretlediği için anahtarlar ortak LanguageText öneki olmadan yazılır (C# sözleşmesinde önek durur). ListForm yalnızca dil anahtarını taşır (Title = "{ListFormCode}.Title"), metinlerin tek kaynağı burasıdır.

Alan Anlam
PermissionGroupName Var olan grup ya da yeni grup adı.
Language.PermissionGroupEn / Tr Grup adıyla aynı dil anahtarına yazılır. Var olan grup seçilip boş bırakılırsa sunucu veritabanındaki değeri geri yazar.
Language.MenuEn / Tr Menü etiketi.
Language.TitleEn / Tr Ekran başlığı (yalnızca List yolunda üretilir).
Language.DescEn / Tr Ekran açıklaması (yalnızca List yolunda).
Language.MenuParentEn / Tr Üst menü etiketi.
Language.Texts Başka bir cevaptan üretilmeyen dil anahtarları: [{ "Key", "En", "Tr" }] (widget başlığı gibi). Anahtar yoksa deploy satırı yazar ve wizard'a bağlar (silmede gider). Deploy, widget sorgularındaki tek tırnaklı noktalı her tanımlayıcıyı ('App.Widget.ThisMonth') dil anahtarı sayar; veritabanında, bekleyen dil satırlarında, bu wizard'ın ürettiklerinde ya da burada yoksa deploy'u adlarıyla reddeder.

Üretilen yetkiler: {MenuCode} (okuma, kök) + List yolunda .Create .Update .Delete .Export .Import .Note, Custom yolunda yalnızca .Create .Update .Delete. Hiçbiri bir role otomatik atanmaz (admin dahil); ekranı görecek roller yetkiyi rol ekranından alır.

Veri

Alan Anlam
DataSourceCode [ListForm] Bağlantı kodu; varsayılan "Default".
DataSourceConnectionString Yalnızca yeni bir bağlantı tanımlanıyorsa doldurulur; boşsa dosyaya hiç yazılmaz. Asla gerçek kimlik bilgisi yazma.
SelectCommandType [ListForm] Bkz. §3.
SelectCommand [ListForm] Tablo/view adı, fonksiyon çağrısı ya da sorgu metni.
KeyFieldName / KeyFieldDbSourceType [ListForm] Birincil anahtar ve tipi. Wizard ekranının başlangıç değeri 11 (Int32), C# DTO'nun varsayılanı yok — her zaman açıkça yaz; Guid anahtarda 9.

Davranış

Alan Anlam
IsTenant / IsBranch / IsOrganizationUnit [ListForm] Otomatik kırılım filtresi. Tabloda TenantId varsa IsTenant: true olmalı.
AllowAdding / AllowUpdating / AllowDeleting / ConfirmDelete [ListForm] Toolbar ve satır aksiyonları (EditingOptionDto).
SeedData [ListForm] true ise SeedFilePath = data/{ListFormCode}.json ve SeedSyncInsert/Update/Delete açık üretilir; ekrandan girilen satır dosyaya yazılır, migrate geri yükler. Wizard ekranında sorulmaz; demo veri taşıyan MCP üretimi için (§0.10.1). Varsayılan false.
AllowDetail [ListForm] Satırda Detay düğmesi çıkar; /admin/form/{ListFormCode}/{anahtar} adresini yeni sekmede açar — aynı ekranın tek kayıtlık form görünümüdür. KeyFieldName boşsa çalışmaz.
DefaultLayout [ListForm] "grid", "card", "pivot", "chart", "tree", "gantt", "scheduler", "todo".
Grid Card Pivot Chart Tree Gantt Scheduler Todo [ListForm] Hangi görünümlerin açık olduğu (LayoutDto). Liste ekranında Grid/Card/Pivot/Chart varsayılan true, diğer dördü false (§9.8). Açtığın her görünümün option bloğunu da doldur.

Görünüm option blokları — [ListForm]; yalnızca ilgili bayrak true ise anlamlıdır:

Blok Zorunlu alanlar
TreeOptionDto KeyExpr, ParentIdExpr; opsiyonel HasItemsExpr, RootValue, AutoExpandAll, RecursiveSelection
GanttOptionDto KeyExpr, ParentIdExpr, TitleExpr, StartExpr, EndExpr; ProgressExpr, ScaleType (auto/minutes/hours/sixHours/days/weeks/months/quarters/years, varsayılan weeks), Allow* bayrakları
SchedulerOptionDto TextExpr, StartDateExpr, EndDateExpr; AllDayExpr, RecurrenceRuleExpr, RecurrenceExceptionExpr, StartDayHour, EndDayHour, DefaultView (day/week/workWeek/month/timelineDay/timelineWeek/timelineMonth/agenda, varsayılan week), CellDuration, FirstDayOfWeek, Allow*
TodoOptionDto TitleExpr, StatusExpr; DescriptionExpr, AnalysisExpr, 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 */ ]
  }
]

Grup başlığı yalnızca düzenleme formunda birden fazla grup varsa yazılır; tek gruplu formda başlık popup başlığını tekrarlar ve boş bırakılır. MCP deploy_wizard bunu kendisi uygular: düzenleme formuna giren tek grup varsa Caption boşaltılır (McpDevelopAppService.DeployWizardAsync).

Seed dosyasında Groups kırpılmış hâlde durur: grup başlığı/kolon sayısı ve alanların editör bilgileri ListForm.EditingFormDto ve Fields içinde olduğu için tekrarlanmaz; dosyada her öğe yalnızca FieldName, CaptionName ve En/Tr taşır — çünkü alan başlıklarının dil metinlerinin başka kaynağı yoktur. Okurken tümü geri doldurulur.

Her Items öğesi:

Alan Anlam
FieldName Veri kolonunun adı (SQL'deki adıyla birebir).
CaptionName Dil anahtarı ya da düz başlık.
En / Tr Alan başlığının dil metinleri; ikisi de doldurulur.
EditorType [Fields] Bkz. §3.
EditorOptions [Fields] DevExtreme editör JSON'u — §6.
EditorScript [Fields] Alan davranış script'i — §5.
DbSourceType [Fields] Bkz. §3.
IsRequired [Fields] Zorunluluk (ValidationRuleDto).
IncludeInEditingForm [Fields] false ise yalnızca grid sütunu olur, forma girmez (EditGroupOrderNo).
ColSpan [Fields] Formda kaç kolon kaplar (grubun ColCount değeri içinde).
LookupDataSourceType [Fields] 1/2/3 — bkz. §3. 0 = lookup yok.
LookupQuery [Fields] Query tipinde SQL; StaticData tipinde JSON dizisi; WebService tipinde URL.
ValueExpr / DisplayExpr [Fields] Lookup'ın değer ve etiket kolonları.

4.3.1 Sütunlar nasıl otomatik üretilir

Gösterilecek tüm sütunlar ListFormField kayıtlarında durur. Wizard bu kayıtları veritabanı kolon metadata'sından (GetTableColumnsAsync) otomatik üretir; sen yalnızca farklı olmasını istediğin şeyi belirtirsin.

Bir kolon Groups[].Items[] içine eklendiğinde varsayılanlar şöyle türetilir:

Alan Nereden gelir
EditorType SQL tipinden çıkarılır (aşağıdaki tablo)
DbSourceType SQL tipinden System.Data.DbType karşılığına eşlenir (§3)
IsRequired Kolon NOT NULL ise true
Anahtar alan KeyFieldName ilk grubun ilk alanı olarak IncludeInEditingForm: false ile eklenir (DbSourceType = KeyFieldDbSourceType); listede gizli sütun olur, forma girmez
En / Tr Kolon adı PascalCase'ten kelimelere ayrılır: OrderNo → Order No
CaptionName App.Listform.ListformField.{KolonAdı}
ColSpan 1
IncludeInEditingForm true
ValueExpr / DisplayExpr Key / Name (lookup tanımlanırsa değiştirilir)
EditorOptions / EditorScript Boş — ihtiyaç varsa sen yazarsın

SQL tipi → EditorType çıkarımı (inferEditorType):

SQL tipi Üretilen editör
bit dxCheckBox
int, bigint, smallint, tinyint, decimal, numeric, float, real, money, smallmoney dxNumberBox
date, datetime, datetime2, smalldatetime, datetimeoffset dxDateBox
diğer hepsi dxTextBox

Çıkarım kasıtlı olarak dardır. Foreign key kolonları dxNumberBox/dxTextBox olarak gelir — bunları dxSelectBox / dxGridBox / dxTagBox yapmak ve lookup tanımlamak senin işindir. Uzun metin (nvarchar(max)) için dxTextArea, HTML içerik için dxHtmlEditor, durum kolonları için dxRadioGroup/dxSwitch seçilmelidir; otomatik çıkarım bunları bilemez.

4.3.2 Editör tipine göre özellikler

Her editör kendi EditorOptions sözlüğüne sahiptir. Aşağıdaki tablo, o editöre özgü olan anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir.

EditorType Ne için Uygun DbSourceType Kendine özgü EditorOptions
dxTextBox Kısa metin String (16) — (ortak: maxLength, mask, placeholder)
dxTextArea Uzun metin String (16) autoResizeEnabled, minHeight
dxNumberBox Sayı Int32/Int64/Decimal/Double (7,8,10,11,12) format.type (fixedPoint/currency/percent), format.precision, format.currency, useMaskBehavior, useLargeSpinButtons, invalidValueMessage
dxDateBox Tarih / tarih-saat Date/DateTime (5,6,26,27) useMaskBehavior (+ ortak: type, displayFormat, dateSerializationFormat, pickerType, interval, calendarOptions.*, invalidDateMessage)
dxDateRangeBox Tarih aralığı Date/DateTime ortak tarih anahtarları
dxCalendar Gömülü takvim Date/DateTime calendarOptions.zoomLevel, calendarOptions.firstDayOfWeek
dxCheckBox Evet/hayır Boolean (3) text (kutunun yanındaki etiket)
dxSwitch Evet/hayır (anahtar) Boolean (3) switchedOnText, switchedOffText
dxRadioGroup Az sayıda seçenek String/Int32 layout (horizontal/vertical) + lookup zorunlu
dxSelectBox Tek seçim (az kayıt) FK tipi acceptCustomValue, searchEnabled, minSearchLength, searchExpr, searchMode, searchTimeout, noDataText, showDataBeforeSearch + lookup zorunlu
dxLookup Tek seçim (çok kayıt, mobil dostu) FK tipi dropDownOptions.*, searchEnabled, applyValueMode + lookup zorunlu
dxDropDownBox Özel içerikli açılır kutu FK tipi dropDownOptions.width/height/hideOnOutsideClick, deferRendering, openOnFieldClick
dxGridBox Çok kolonlu seçim ızgarası FK tipi columns*, filterRowVisible*, selectionMode*, acceptCustomValue* + lookup zorunlu
dxTagBox Çok değerli seçim String (| ile ayrılmış — §4.3.2.1) showSelectionControls*, maxDisplayedTags*, showMultiTagOnly*, applyValueMode*, searchEnabled*, acceptCustomValue*, hideSelectedItems, multiline + lookup zorunlu
dxAutocomplete Serbest metin + öneri String (16) minSearchLength, searchExpr, searchTimeout
dxColorBox Renk String (16) editAlphaChannel, keyStep
dxSlider Tek değerli kaydırıcı Sayı min, max, tooltip.showMode
dxRangeSlider Aralık kaydırıcı Sayı min, max, showRange
dxHtmlEditor Zengin metin String (16) toolbar.items, toolbar.multiline, valueType (html/markdown), mediaResizing.enabled, imageUpload.tabs, imageUpload.uploadUrl, imageUpload.fileUploadMode, tableResizing.enabled, tableContextMenu.enabled. Boş editorOptions da tam araç çubuğu, dosyadan/URL'den resim, resim boyutlandırma ve tablo araçlarıyla gelir (ui/src/views/shared/htmlEditorOptions.ts); yazılan değer varsayılanın üstüne tek seviye birleştirilir, yalnızca farkı yaz.
dxImageUpload Görsel yükleme (platform); görsel tutan her kolonun editörü. uploadUrl yoksa dosya base64 metin olarak kolona yazılır → kolon nvarchar(MAX); sınırlı boylu kolonla deploy reddedilir. uploadUrl varsa kolona dönen adres yazılır String (16) uploadUrl*, accept*, capture*, multiple*, maxFileSize*, width*, height*
dxImageViewer Görsel gösterimi (platform), yükleme yapmaz. Tablo kolonuna bağlı alanda kullanılmaz — görsel kolonu dxImageUpload'dır, deploy dxImageViewer'ı reddeder 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 kolonu hücre metnini açılışta yüklenen lookup listesinden bulur. Lookup'ı ekranın kendi kayıt kaynağından beslenen kolonda (ör. SELECT DISTINCT CompanyName … FROM Hr_T_Visitor ile Autocomplete) kaydedilen yeni değer bu listede yoktur. Sunucu bu kolonları ekran açılırken işaretler (ColumnFormatDto.IsLookupSelfSourced: Query lookup'ının FROM/JOIN nesnesi ekranın SelectCommand nesnesiyle aynı; yalnızca true yazılır, seed dosyasına girmez). Grid, Tree ve Card görünümünde satır eklenince/güncellenince yalnızca işaretli kolonlar ve yalnızca kaydedilen değer listede yoksa lookup yeniden yüklenir (refreshLookupsForRow) — hücre, editörün açılır listesi ve filtre satırı sayfa yenilenmeden güncellenir. Başka kaynaktan beslenen lookup, statik veri, WebService ve cascade lookup için kayıtta hiç lookup isteği atılmaz.

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"
}

İstemci ValueExpr/DisplayExpr'i küçük harfe çevirerek okur; StaticData dizisi JSON'dan olduğu gibi yüklendiği için öğe anahtarları küçük harf (key, name, cascade'de group) yazılır. name değeri :: öneki eklenerek çevrilir — dil anahtarı da yazılabilir. Query sonucunda ise istemci Key/Name/Group kolonlarını key/name/group'a eşler; sorgu kolonları bu adlarla döndürür (SELECT Id AS [Key], Name AS [Name] …) ve ValueExpr/DisplayExpr Key/Name kalır.

LookupDataSourceType 1 (StaticData) ise LookupQuery bir JSON dizisi, 2 (Query) ise SQL, 3 (WebService) ise noktalı virgülle ayrılmış bir çağrı tarifidir. Tam sözleşme ve cascade (ebeveyn–çocuk) kurulumu için §4.3.4.

4.3.2.1 Çok değerli alanların depolama sözleşmesi

dxTagBox (ve selectionMode: "multiple" verilmiş dxGridBox) tek bir metin kolonunda, değerleri | ile ayırarak saklar (PlatformConsts.MultiValueDelimiter). İlişki tablosu açmaya gerek yoktur; kolon NVARCHAR(n) olarak tanımlanır ve DbSourceType: 16 verilir.

Zincirin iki yönü farklı yerlerde kapanır — yeni ekran üretirken üçünü birden kontrol et:

Yön Nerede olur Not
Okuma ("A|B" → ["A","B"]) UI, useListFormCustomDataSource Alanın EditorType2 değeri dxTagBox ise kolon extras.multiValue alır; ayrıştırma buna bakar
Yazma, SQL yolu (["A","B"] → "A|B") Backend, QueryHelper.GetFormattedValue Yalnızca alanın DbSourceType'ı String (16) ise birleştirir — sayısal tip verirsen dizi sessizce bozulur
Yazma, tipli DTO yolu Yok — elle kurulur Aşağıya bak

Tuzak: ekranın *ServiceAddress alanı varsayılan list-form-data/* yerine tipli bir DTO alan bir uca (list-form-dynamic-api/..., Custom Endpoint, Dynamic Service) bakıyorsa QueryHelper devreye girmez; gelen dizi string property'ye bağlanamaz ve istek 400 Bad Request + "The JSON value could not be converted to System.String" ile düşer. Bu durumda DTO'daki alana [JsonConverter(typeof(MultiValueStringJsonConverter))] eklenir (Application.Contracts/ListForms/DynamicApi/); converter hem diziyi hem düz metni kabul eder ve | ile birleştirilmiş tek string üretir. Salt okuma DTO'larına eklenmez.

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

Aşağıda en sık kullanılanlar özetlenir; tam sözlük ve grup grup liste §6.3'tedir — ikisi çeliştiğinde optionSpecs.ts esastır.

Grup Anahtarlar
Genel disabled, readOnly, visible, hint, tabIndex, showClearButton, valueChangeEvent, validationMessageMode, validationMessagePosition
Görünüm width, height, stylingMode, label, labelMode, elementAttr.class, inputAttr.style, inputAttr.aria-label, buttons
Metin placeholder, maxLength, spellcheck, mode, mask, maskChar, maskRules.X, maskInvalidMessage, showMaskMode, useMaskedValue, encodeHtml
Sayı min, max, format, showSpinButtons
Tarih type, displayFormat, dateSerializationFormat, pickerType, interval, invalidDateMessage
Açılır liste noDataText, searchEnabled, minSearchLength, searchExpr, searchMode, searchTimeout, showDataBeforeSearch, openOnFieldClick, deferRendering, wrapItemText, dropDownOptions.*

Elle JSON yazmak yerine C# tarafında EditorOptions akıcı yapısını kullan (§6); hazır başlangıçlar bu anahtarların doğru bileşimlerini üretir.

4.3.4 Lookup ve cascade (LookupJson)

Wizard'daki LookupDataSourceType + LookupQuery + ValueExpr + DisplayExpr alanları alanın LookupJson kolonuna dönüşür. Cascade de wizard girdisidir: öğedeki CascadeParentFields, CascadeRelationField, CascadeFilterOperator, CascadeEmptyFields aynı adla LookupJson'a yazılır (WizardConsts.DefaultLookupJson; yalnızca dolu olanlar). Wizard ekranında bu alanların adımı yoktur, düzenlemede korunur. MCP deploy_wizard ile cascade'li ekran doğrudan üretilir; cascade eksik yetenek değildir ve analizde Ready to Start'ı engellemez. Tam sözleşme:

Alan Anlam
DataSourceType 1 StaticData · 2 Query · 3 WebService
ValueExpr / DisplayExpr Varsayılan Key / Name
LookupQuery Kaynağın kendisi (tipe göre biçim değişir, aşağıda)
CascadeParentFields Çocuk alanda doldurulur; değerleri sorguya taşınacak ebeveyn alan(lar), virgülle. Sırayla @param0, @param1… olur (Query/WebService); boş ya da gelmeyen ebeveyn NULL bağlanır. Değer her zaman metin parametre olarak bağlanır (ListFormSelectAppService.ToLookupParameter), sayı anahtarlı ebeveyn de çalışır; dönüşümü kolon tipine göre veritabanı yapar. WebService gövdesine değer JSON kaçışlı yazılır
CascadeRelationField Çocuk alanda, yalnızca StaticData'da anlamlıdır: öğede ilk ebeveynin değeriyle karşılaştırılan alan; boşsa group. Ebeveyn boşken liste süzülmez
CascadeFilterOperator StaticData cascade filtresinin operatörü; varsayılan =
CascadeEmptyFields Ebeveyn alanda doldurulur; bu alan değişince boşaltılacak çocuk alanlar, virgülle. Ara halka (İl) hem çocuk hem ebeveyn olduğu için iki tarafı birden taşır

LookupQuery biçimleri:

Tip Biçim Not
1 StaticData [{"key":1,"name":"Taslak"},{"key":2,"name":"Onayda"}] Kolon sayı tipindeyse key sayı, metin tipindeyse key metin olmalıdır; aksi hâlde seçim eşleşmez. Cascade gerekiyorsa her öğeye group eklenir.
2 Query SELECT Id AS [Key], Name AS [Name] FROM ... WHERE IsDeleted = 0 Cascade'de ebeveyn değerleri @param0… ile gelir; boş ebeveyn tüm listeyi göstersin diye (Kolon = @param0 OR @param0 IS NULL) kalıbı kullanılır. Sorgu ekranın veri kaynağında/tenant bağlamında çalışır, @USERID gibi token'lar çözülür.
3 WebService METHOD;url;bodyJson;keySelector;nameSelector;groupSelector Noktalı virgülle altı parça. Selector'lar her satır a üzerinde çalışan JS ifadeleridir (a.id, a.name, null). Gövdedeki @param0, @param1… cascade filtre değerleriyle doldurulur. Örnek: GET;notification-rule/notification-types;{};a.id;a.name;null

Cascade örneği — Ülke → İl → İlçe (aşağıdaki nesneler LookupJson kolonuna tek satır, kaçışlanmış JSON string olarak yazılır; canlı örnek Tenants ekranı, LookupQueryValues.CityValues / DistrictValues):

// Country alanı (yalnız ebeveyn)
{ "DataSourceType": 2, "ValueExpr": "Key", "DisplayExpr": "Name",
  "LookupQuery": "SELECT Id AS [Key], Name AS [Name] FROM Sas_H_Country WHERE IsDeleted = 'false' ORDER BY Name",
  "CascadeEmptyFields": "City,District" }

// City alanı (Country'nin çocuğu, District'in ebeveyni)
{ "DataSourceType": 2, "ValueExpr": "Key", "DisplayExpr": "Name",
  "LookupQuery": "SELECT Name AS [Key], Name AS [Name] FROM Sas_H_City WHERE (Country = @param0 OR @param0 IS NULL) AND IsDeleted = 'false' ORDER BY Name",
  "CascadeParentFields": "Country", "CascadeRelationField": "Country", "CascadeFilterOperator": "=",
  "CascadeEmptyFields": "District" }

// District alanı (yalnız çocuk) — iki ebeveyn → @param0, @param1
{ "DataSourceType": 2, "ValueExpr": "Key", "DisplayExpr": "Name",
  "LookupQuery": "SELECT Name AS [Key], Name AS [Name] FROM Sas_H_District WHERE (Country = @param0 OR @param0 IS NULL) AND (City = @param1 OR @param1 IS NULL) AND IsDeleted = 'false' GROUP BY Name ORDER BY Name",
  "CascadeParentFields": "Country,City", "CascadeRelationField": "City", "CascadeFilterOperator": "=" }

Tablo adlarını tahmin etme: platform tablolarının adı TableNameResolver'dan gelir (reference §12.1); yukarıdakiler kalıbı gösterir, üretimden önce sql/object/ ya da mevcut sorgudan doğrula.

Wizard öğesi olarak (Eğitim Tipi → Eğitim Konusu; ebeveyn StaticData, çocuk Query):

{ "FieldName": "TrainingType", "EditorType": "dxSelectBox", "LookupDataSourceType": 1,
  "LookupQuery": "[{\"key\":\"Orientation\",\"name\":\"Oryantasyon Eğitimi\"}]",
  "ValueExpr": "key", "DisplayExpr": "name",
  "CascadeEmptyFields": "TrainingTopicId" },
{ "FieldName": "TrainingTopicId", "EditorType": "dxSelectBox", "LookupDataSourceType": 2,
  "LookupQuery": "SELECT Id AS [Key], Name AS [Name] FROM Hr_T_TrainingTopic WHERE (TrainingType = @param0 OR @param0 IS NULL) AND IsDeleted = 'false' ORDER BY Name",
  "ValueExpr": "Key", "DisplayExpr": "Name",
  "CascadeParentFields": "TrainingType" }

Lookup'ta Id görünmesin. Açılır listede metin yerine Id görünüyorsa iki nedeni vardır: DisplayExpr sorgunun döndürdüğü kolon adıyla birebir (büyük/küçük harf dahil) eşleşmiyordur, ya da EditorOptions'ta grouped: true vardır ve öğeler group alanı taşımaz. Query lookup'ı [Key] + [Name] takma adlarını döndürür ve ValueExpr: "Key", DisplayExpr: "Name" yazılır. Bağımlı liste grouped ile değil cascade ile kurulur.

Lookup verisi önbelleklenir; aynı sorgu aynı filtrelerle bir kez yüklenir. Grid, Tree, Card ve Form aynı veri kaynağını kullanır (views/form/useLookupDataSource.ts); cascade kuralı tek yerdedir.

4.3.5 Varsayılan değerler — *FieldsDefaultValueJson

Kullanıcının doldurmayacağı alanlar (audit, tenant, durum, belge numarası) script ile değil, bu JSON'larla doldurulur. Dört ayrı liste vardır ve hepsi aynı öğe şeklini taşır:

Kolon Ne zaman çalışır
InsertFieldsDefaultValueJson Insert ve Duplicate
UpdateFieldsDefaultValueJson Update
DeleteFieldsDefaultValueJson Delete (soft delete alanları)
FormFieldsDefaultValueJson Form açılışı (Select) — ekrana gelen varsayılanlar
[
  { "FieldName": "Id",           "FieldDbType": 9,  "Value": "@NEWID",  "CustomValueType": 2 },
  { "FieldName": "CreationTime", "FieldDbType": 6,  "Value": "@NOW",    "CustomValueType": 2 },
  { "FieldName": "CreatorId",    "FieldDbType": 9,  "Value": "@USERID", "CustomValueType": 2 },
  { "FieldName": "IsDeleted",    "FieldDbType": 3,  "Value": "false",   "CustomValueType": 1 },
  { "FieldName": "OrderNo",      "FieldDbType": 16, "Value": "SiparisNo","CustomValueType": 5 },
  { "FieldName": "ExchangeRate", "FieldDbType": 7,  "Value": "",        "CustomValueType": 3,
    "SqlQuery": "SELECT TOP 1 Rate FROM Fin_T_ExchangeRate ORDER BY Date DESC" }
]

CustomValueType (FieldCustomValueTypeEnum)

# Tip Value ne demek
1 Value Sabit değer (false, 3, Taslak, GETDATE())
2 CustomKey Aşağıdaki token'lardan biri
3 DbQuery Değer SqlQuery alanındaki tek hücre döndüren sorgudan gelir (ekranın DataSourceCode/tenant bağlamında çalışır)
4 QueryParams Değer, adresteki query string parametresinden okunur; Value parametre adıdır
5 Sequence Value bir Sequence adıdır (§9.9); yazma anında sıradaki numara alınır, form açılışında (Select) yalnızca ad döner ve UI numarayı ayrıca ister

CustomKey token'ları (PlatformConsts.DefaultValues) — DefaultValueManager'ın tanıdığı tam küme:

Token Değer
@USERID · @USERNAME · @ROLES Oturumdaki kullanıcının id'si, adı, rol listesi (dizi)
@DATE · @NOW Bugünün tarihi (saat 00:00) · tarih + saat
@YEAR · @MONTH · @DAY Sayı olarak yıl/ay/gün
@NEWID Yeni Guid
@ID Kaydın anahtarı; Delete'te seçili anahtarların tamamı
@SELECTED_IDS Seçili anahtarların tamamı (her operasyonda)
@TENANTID Aktif kiracı

Listedeki tokenlar dışında bir CustomKey değeri null üretir ve o alan yazılmaz. Bu yüzden @AUTONUMBER, CustomKey değil CustomValueType: 1 (Value) ile yazılır:

{ "FieldName": "SiraNo", "FieldDbType": 11, "Value": "@AUTONUMBER", "CustomValueType": 1 }

FormFieldsDefaultValueJson'a böyle yazılan alan istemciye column.defaultValue olarak iner ve yeni satır tarayıcıda artan bir numara alır — geçici, oturum içi bir numaradır; kalıcı belge numarası için Sequence (CustomValueType: 5, §9.9) kullan.

DefaultValue / DefaultValueType bir ListFormField kolonu değildir. İstemcinin gördüğü bu iki alan, ListFormSelectAppService'in her metadata isteğinde FormFieldsDefaultValueJson'ı çözerek ürettiği hesaplanmış değerlerdir. Yani "alanın varsayılanı" her zaman ekran seviyesindeki dört listenin birine yazılır.

Boş/null üretilen değer yazılmaz. FieldDbType yanlış verilirse değer QueryHelper.GetFormattedValue tarafından sessizce yanlış biçimlenir — kolon tipiyle aynı DbType numarasını yaz (§3).

4.3.6 Doğrulama — ValidationRuleJson

Alan başına DevExtreme doğrulama kuralları dizisi. Wizard IsRequired: true alanlara required kuralını kendi yazar; gerisi elle eklenir.

Type Kullanılan alanlar
required Message, Trim
numeric Message, IgnoreEmptyValue
range Min, Max (sayı ya da tarih), Reevaluate
stringLength Min, Max, Trim
pattern Pattern (regex)
email Message, IgnoreEmptyValue
compare ComparisonType (==, ===, !=, !==, <, <=, >, >=)
custom JS ile doğrulama; Reevaluate
async Sunucuya sorarak doğrulama; Reevaluate
"ValidationRuleJson": "[{\"Type\":\"required\",\"Message\":\"App.Validation.Required\"},
  {\"Type\":\"range\",\"Min\":0,\"Max\":1000000,\"Message\":\"App.Validation.Amount\"}]"

4.3.7 Koşullu biçimlendirme — ColumnStylingJson

"Gecikmiş satır kırmızı olsun", "onaylı kayıt yeşil rozetli olsun" istekleri buradan çözülür; script gerekmez.

Alan Anlam
RowType data (varsayılan) · group · header · filter · detail · groupFooter · totalFooter
UseRow true ise stil hücreye değil tüm satıra uygulanır
Condition =, <>, <, <=, >, >=, contains, notcontains, startswith, endswith, isblank, isnotblank, between, anyof, noneof
ConditionValue Boş bırakılırsa koşul aranmaz, stil her zaman uygulanır
CssStyles anahtar:değer çiftleri, aralarında `
CssClassName Hazır bir CSS sınıfı kullanılacaksa

Alan üzerindeki ColumnCssClass / ColumnCssValue kolonları da aynı işi koşulsuz yapar.

4.3.8 Ekran üstü ek filtre — ExtraFilterJson

Grid'in üstünde duran, kullanıcının seçtiği değere göre listeyi süzen filtre çubuğu (tarih aralığı, şube, durum). Filtre satırından (FilterRowJson) farkı: her zaman görünür, varsayılan değeri olur ve yeni kayıtta o alanı otomatik doldurur.

[
  { "FieldName": "Status", "Caption": "Durum", "Operator": "=", "ControlType": "Select",
    "DefaultValue": "1",
    "Items": [ { "Key": "1", "Value": "Taslak" }, { "Key": "2", "Value": "Onayda" } ] },
  { "FieldName": "OrderNo", "Caption": "Sipariş No", "Operator": "contains", "ControlType": "Text" }
]
  • ControlType: Select · diğer her değer metin kutusudur (Enter ya da odak kaybında uygulanır).
  • Select seçenekleri ya Items dizisinden gelir ya da SqlQuery alanına yazılan sorgudan; sorgu Key ve Value adlı iki kolon döndürmelidir ve ekranın veri kaynağında/tenant bağlamında çalışır.
  • Seçilen değerler [FieldName, Operator, Value] üçlüsüne çevrilir, URL'deki filter parametresiyle and ile birleştirilir. Böylece filtreli ekran adresi paylaşılabilir.
  • DefaultValue dolu ise seçim temizlenemez ve liste ilk açılışta o değerle gelir.
  • Ek filtre ile yönetilen alan, yeni kayıt varsayılanlarında atlanır (§4.3.5) — değeri filtreden gelir.

4.4 Çalışan örnek — wizard cevabı (deploy girdisi)

Aşağıdaki düz biçim wizard ekranının POST ettiği ve MCP deploy_wizard'ın aldığı cevaptır (ListFormWizardDto; Groups[].Items[] = WizardColumnItemInputDto, En/Tr = C#'taki EnglishCaption/TurkishCaption). Okuyucu FromSeedJson olduğu için Menu/Language blokları ya da WizardCode/ModuleId adları da kabul edilir. Deploy bunu okuyup ekranı kurar ve §4.1 biçimindeki dosyayı yazar; IsDeletedField/IsCreatedField tablodaki IsDeleted/CreatorId kolonlarından, InsertedRecords gerçekten yaratılan kayıtlardan sunucu tarafından doldurulur — cevaba yazılmaz.

{
  "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,

    "MenuEn": "Orders",       "MenuTr": "Siparişler",
    "TitleEn": "Orders",      "TitleTr": "Siparişler",
    "DescEn": "Order list",   "DescTr": "Sipariş listesi",
    "MenuParentEn": "MRP",    "MenuParentTr": "MRP",

    "PermissionGroupName": "App.Mrp",
    "PermissionGroupEn": "MRP", "PermissionGroupTr": "MRP",
    "MenuParentCode": "App.Mrp", "MenuParentModuleId": "MRP", "MenuParentIcon": "FcFactory",
    "MenuIcon": "FcShipped",

    "DataSourceCode": "Default", "DataSourceConnectionString": "",
    "SelectCommandType": 1,
    "SelectCommand": "Mrp_T_Order",
    "KeyFieldName": "Id",
    "KeyFieldDbSourceType": 9,

    "Groups": [
      {
        "Caption": "Genel", "ColCount": 2,
        "Items": [
          {
            "FieldName": "Id", "CaptionName": "App.Listform.ListformField.Id",
            "Tr": "Kimlik", "En": "Id",
            "EditorType": "dxTextBox", "DbSourceType": 9,
            "IsRequired": false, "IncludeInEditingForm": false, "ColSpan": 1
          },
          {
            "FieldName": "OrderNo", "CaptionName": "App.Listform.ListformField.OrderNo",
            "Tr": "Sipariş No", "En": "Order No",
            "EditorType": "dxTextBox", "DbSourceType": 16,
            "IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
            "EditorOptions": "{\"showClearButton\":true,\"maxLength\":20}"
          },
          {
            "FieldName": "CustomerId", "CaptionName": "App.Listform.ListformField.Customer",
            "Tr": "Cari", "En": "Customer",
            "EditorType": "dxSelectBox", "DbSourceType": 9,
            "IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
            "LookupDataSourceType": 2,
            "LookupQuery": "SELECT Id AS [Key], Name AS [Name] FROM Crm_T_Customer WHERE IsDeleted = 0 ORDER BY Name",
            "ValueExpr": "Key", "DisplayExpr": "Name"
          },
          {
            "FieldName": "Quantity", "CaptionName": "App.Listform.ListformField.Quantity",
            "Tr": "Miktar", "En": "Quantity",
            "EditorType": "dxNumberBox", "DbSourceType": 7,
            "IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
            "EditorOptions": "{\"format\":{\"type\":\"fixedPoint\",\"precision\":2},\"showSpinButtons\":true}"
          },
          {
            "FieldName": "UnitPrice", "CaptionName": "App.Listform.ListformField.UnitPrice",
            "Tr": "Birim Fiyat", "En": "Unit Price",
            "EditorType": "dxNumberBox", "DbSourceType": 7,
            "IsRequired": true, "IncludeInEditingForm": true, "ColSpan": 1,
            "EditorOptions": "{\"format\":{\"type\":\"fixedPoint\",\"precision\":2}}"
          },
          {
            "FieldName": "Total", "CaptionName": "App.Listform.ListformField.Total",
            "Tr": "Tutar", "En": "Total",
            "EditorType": "dxNumberBox", "DbSourceType": 7,
            "IsRequired": false, "IncludeInEditingForm": true, "ColSpan": 2,
            "EditorOptions": "{\"readOnly\":true,\"format\":{\"type\":\"fixedPoint\",\"precision\":2}}",
            "EditorScript": "// @builder {\"v\":1,\"rules\":[{\"id\":\"r1\",\"recipe\":\"multiply\",\"condition\":{\"operator\":\"always\",\"source\":\"\",\"value\":\"\"},\"params\":{\"left\":\"Quantity\",\"right\":\"UnitPrice\",\"target\":\"Total\",\"digits\":\"2\"}}]}\n// @runOnOpen\nset(\"Total\", round(num(\"Quantity\") * num(\"UnitPrice\"), 2))"
          }
        ]
      }
    ],
    "SubForms": [],
    // Satır/toolbar butonları (§4.9) — örneğin rapor şablonu açan satır butonu (§9.12).
    "CommandColumns": [],
    // List ekranı widget'la gelir (§4.6.1): kiracı tablosu → her kartta TenantId koşulu.
    "Widgets": [
      {
        "SqlQuery": "SELECT 'App.Widget.Total' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'blue' AS \"Color\", 'App.Mrp.Orders' AS \"SubTitle\", 'FaShoppingCart' AS \"Icon\" FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID' UNION ALL SELECT 'App.Widget.Today', CAST(COUNT(*) AS VARCHAR(32)), 'green', 'App.Mrp.Orders', 'FaCalendarDay' FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID' AND CAST(\"CreationTime\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE) UNION ALL SELECT 'App.Widget.TotalRevenue', CAST(COALESCE(SUM(\"Total\"), 0) AS VARCHAR(32)), 'purple', 'App.Mrp.Orders', 'FaMoneyBillWave' FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID'",
        "Title": "Title", "Value": "Value", "Color": "Color", "Icon": "Icon", "SubTitle": "SubTitle",
        "ValueClassName": "text-3xl", "ColSpan": 4, "ColGap": 3, "ClassName": "mb-3", "IsActive": true
      }
    ],
    "WorkflowDto": { "ApprovalUserFieldName": "", "ApprovalDateFieldName": "",
      "ApprovalStatusFieldName": "", "ApprovalDescriptionFieldName": "",
      "ApprovalIsFilterUserName": false, "ApprovalIsResetWorkflow": false, "Criteria": [] }
  }
}

Deploy sonrası dosyada Wizard bölümü Menu/Language/Groups başlıklarına toplanır ve yalnızca FieldName/CaptionName/En/Tr kalır; ekranın kendisi ListForm + Fields bölümlerine düşer (§4.1). SeededAt ilk deploy anında sunucu tarafından yazılır.

EditorScript alanındaki // @builder başlığı ile onu izleyen satırlar birebir tutarlı olmalıdır: açılışta çalışan bir kural varsa (hesap tarifleri varsayılan both) // @runOnOpen satırı gelir ve metin literalleri çift tırnaklıdır (JSON.stringify). Elle yazmak yerine C# tarafında EditorScript.Build(...) kullan ya da Script Builder'da üret; uyuşmazsa dialog script'i "elle düzenlenmiş" sayar.

4.4.1 Deploy'un ve seeder'ın senin yerine ürettikleri — cevaba yazma

Aşağıdakileri deploy wizard cevabından kendisi üretir (ListFormWizardDto.BuildListForm / BuildFields; menü, yetki ve dil satırları ListFormWizardAppService → WizardSeedRowStore). Ekrana ait olanlar dosyanın ListForm/Fields bölümlerine düşer; WizardDataSeeder migrate'te ekranı oradan olduğu gibi kurar (GridOptionsEditDto.ApplyTo), yeniden türetmez. Menü, yetki ve dil kayıtları platform seed dosyalarına satır olarak yazılır, migrate ListFormSeedDataApplier ile ekler (§4.0). Sıra değerleri veritabanı ile dosyadaki satırların birleşiminden hesaplanır:

Ü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ü zinciri MenuParentPath + MenuParentCode kökten yürünür; eksik seviye bir üstünün altına Order = max(kardeş Order) + 1 ile yaratılır (kökte kök menülerin sonu)
Menü sırası MenuOrder 0/boş ise max(kardeş Order) + 1
DataSource DataSourceCode yoksa migrate yaratı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 WizardConsts.DefaultDeleteCommand(SelectCommand) (soft delete UPDATE); false ise null
DefaultFilter IsDeletedField: true ise "IsDeleted" = 'false'
InsertFieldsDefaultValueJson IsCreatedField: true ise CreationTime=@NOW, CreatorId=@USERID, IsDeleted=false, Id=@NEWID; değilse yalnızca Id=@NEWID
DeleteFieldsDefaultValueJson IsDeletedField: true ise DeleterId=@USERID, Id=@ID; değilse yalnızca Id=@ID
ValidationRuleJson IsRequired: true olan alanlara required kuralı
LookupJson LookupQuery doluysa LookupDataSourceType + DisplayExpr + ValueExpr ile
ShowNote Alt form veya iş akışı varsa açılır
SelectionJson İş akışı varsa single, yoksa none
Görünüm option JSON'ları İlgili bayrak açık ve zorunlu alanı dolu ise yazılır; eksikse görünüm sessizce kapanır

Dikkat edilecek tuzaklar:

  • Tree/Gantt için ParentIdExpr, Scheduler için TextExpr, Todo için TitleExpr ve StatusExpr boşsa o görünümün JSON'u hiç yazılmaz — bayrağı true yapmak yetmez.
  • Anahtar alanı (KeyFieldName) Groups içine her zaman koyulur — ilk grubun ilk alanı olarak, IncludeInEditingForm: false ile. Deploy onu kendiliğinden eklemez; yoksa listede anahtar kolonun ListFormField kaydı hiç oluşmaz ve satır anahtarı okunamadığı için düzenleme, silme, detay ve alt form ilişkileri çalışmaz. Yazdığında sütun otomatik gizlenir (Visible = false) ve forma girmez; tip bilgisi de oradan okunur.
  • IsDeletedField: false verirsen ekranda kalıcı silme olmaz (DeleteCommand null kalır); soft delete kolonları olmayan tabloda silme istiyorsan ListForm editöründen komut yazmalısın.
  • Dil anahtarı zaten varsa metni ezilmez. Yanlış metin görüyorsan anahtar başka bir yerde tanımlanmıştır.

4.5 Güncelleme, idempotency ve silme semantiği

Seeder idempotenttir — bir ekran zaten kuruluysa ve bekleyen kurulumu (Pending) yoksa dosya atlanır. "Kurulu" ölçütü yola göre değişir (Wizard Manager'daki durum da aynı karardır):

Yol Kontrol
List ListForm kaydında bu ListFormCode var mı
Custom Bu MenuCode adında yetki var mı

Sonuç: kurulu bir ekranı seed dosyasını elle düzenleyerek değiştiremezsin (Pending yazan deploy'dur). Değiştirmenin yolları:

  1. ListForm editörü (/admin/listform/edit/{kod}) — alan ekleme, JSON kolonları, komutlar, görünümler. Her kayıt WizardSeedSynchronizer ile aynı dosyanın ListForm/Fields/Wizard bölümlerine geri yazılır; ince ayar için tercih edilen yol budur.
  2. EditFileName ile yeniden deploy (wizard ekranı ya da MCP replaceFileName) — menü, yetki grubu, veri kaynağı gibi wizard cevabına ait değişiklikler için. Dosyalar yeniden yazılır, ekran DB Migrate'te cevaptan yeniden kurulur (§4.0); editörde yapılmış, wizard'da karşılığı olmayan ayarlar kaybolur.
  3. İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır.

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

Silme iki yoldur ve ikisinde de başka bir yerde kullanılan kayıt/satır silinmez:

  • Migrate bekleyen wizard (IsPending) — veritabanına dokunulmaz. Yalnızca wizard'ın json dosyalarına eklediği satırlar (InsertedRecords + Pending.Replaced: menü, modül, yetki, izin grubu, dil metni) çıkarılır (WizardSeedRowStore.RemoveOwned). Satır, seed dosyalarında ona bağlanan başka bir satır (alt menü, alt yetki, menünün istediği yetki, gruptaki yetki, modülü kullanan menü/yetki, anahtarı görünen ad olarak kullanan satır) ya da veritabanında onu kullanan bir kayıt varsa (WizardRecordCleaner.FindUsagesAsync, aşağıdaki tablo) kalır ve raporda kullananlarıyla görünür.
  • Kurulu wizard — önce veritabanı kayıtları kendi işleminde silinir (WizardRecordCleaner); işlem başarıyla tamamlanırsa silinen (ya da veritabanında bulunmayan) kayıtların json satırları çıkarılır (RemoveSeedRowsAsync). Veritabanı adımı hata verirse dosyalara dokunulmaz. Migrate yalnızca eksik kaydı ekler, silemez; bu yüzden silme veritabanına doğrudan yansır.

Kurulu wizard'da ekran (ListForm + alanlar + iş akışı) ve InsertedRecords içindekiler, başka bir yerde kullanılmıyorsa silinir. Sıra bağımlılık yönündedir — önce kullanan, sonra kullanılan — ve her kontrol o ana kadar kalan kayıtlara karşı yapılır:

Kayıt Silinmez, çünkü kullanan var
ListForm (+ alanlar, iş akışı) başka bir ekranın SubFormsJson'u
Menü altında menü kalmış (üst menü, son alt menüsü gidince silinir)
Yetki alt yetki, menü RequiredPermissionName, ekran/alan PermissionJson, intranet widget'ı
Yetki ataması — yetkiyle birlikte gider (tüm roller/kullanıcılar)
Yetki grubu içinde yetki kalmış
Modül menü ModuleId, yetkinin modül listesi, kiracının modül listesi
Veri kaynağı ekran, Custom Endpoint, zamanlanmış iş
Dil anahtarı menü/yetki/yetki grubu adı, ekran başlığı/açıklaması, alan başlığı

Silme yolunda wizard'ın bağlandığı kaplar da boşaldıysa gider, InsertedRecords'ta olmasalar bile: kap başka bir wizard'la oluşmuş ve o wizard silinirken bu wizard yüzünden bırakılmış olabilir. Üst menü zinciri yukarı doğru yürünür — alt menüsü kalmayan ve kendisi adres açmayan (Url boş) menü silinir, adresi olan ya da altında menü kalan kayıtta zincir durur. Wizard'ın PermissionGroupName'i içinde yetki kalmamışsa, silinen menülerin ve MenuParentModuleId'nin modülü yukarıdaki tabloda kullananı yoksa silinir; adlarının dil anahtarları da aynı kontrolden geçer. Bu kaplar rapora yalnızca silindiklerinde girer.

Kurulu wizard'da silinen ya da veritabanında bulunmayan menü, modül, yetki, yetki grubu ve dil metni satırları, veritabanı işlemi tamamlandıktan sonra seed dosyalarından da çıkarılır (App.Menus.{Menu,Module}.json, Abp.Identity.Permission{s,Groups}.json, App.Languages.LanguageText.json; IListFormSeedDataSynchronizer.RemoveRowsByFieldAsync, iş anahtarıyla, SeedSyncDelete'e bağlı); dosyada kalırsa migrate kaydı geri getirir. Kullanımda olduğu için bırakılan kaydın satırı kalır.

Bağlı wizard'ı olan (alt formu ya da component bağımlılığı wizard olarak duran) bir wizard silinemez; önce bağlı olanlar silinir. Kural listedeki gruplamayla aynı kaynaktandır (WizardFileInfoDto.Dependencies), sunucu DeleteFile'da reddeder, istemci butonu kapatır. Silme ucu (DeleteFile) her kaydın akıbetini (Deleted / Kept + kullananlar / NotFound) WizardDeleteResultDto olarak döner ve loglar; Wizard Manager bunu rapor olarak gösterir. Düzenleme (EditFileName) bu sınıfı çağırmaz; önceki sürümün kayıtlarını migrate kaldırır (§4.0). Wizard'ın yarattığı modül InsertedRecords.ModuleIds ile izlenir.

Kurulu wizard silinirken ekranın veri aynası (ListForm.SeedFilePath → data/{ListFormCode}.json ya da klasörü) ekranla birlikte silinir; ekransız uygulanamaz. Migrate bekleyen wizard'da ayna yalnızca ekran hiç kurulmamışsa silinir (MCP demo satırları); kurulu ekranın aynası veritabanındaki satırların kaydıdır, kalır.

Ekranın tablosu wizard'ın ürettiği bir kayıt değildir (tasarımcı ya da create_table ile ayrıca kurulur, InsertedRecords'ta yoktur) ve kendiliğinden silinmez. Silme onayı önce DeleteAnalysis ile adayı sorar (WizardTableCleaner): yalnızca Table tipli ekranın tablosu aday olur ve iki koşulla seçilebilir — kapsamdaki {sql|postgres}/object/{Tablo}.sql bir CREATE TABLE scriptidir (platform tabloları bu klasörde durmaz; scriptsiz tablo NotManaged) ve tabloyu başka bir ekran (sorgu/C/U/D komutu ya da bu ekranı alt form olarak kullanan üst ekran), Custom Endpoint SQL'i ya da custom component veri kaynağı kullanmaz (InUse). Kullanıcı onay diyaloğunda tabloyu seçerse (WizardDeleteInput.DropTables; birden çok seçilebilir tablo varsa başlıktaki Tümünü seç hepsini işaretler) kayıt temizliğinden sonra DROP TABLE çalışır, ardından tablonun CRUD endpoint'leri ve seed dosyaları ({sql|postgres}/object/{Tablo}.sql, crud/{Tablo}.json) silinir. Düşürme hatası (ör. yabancı anahtar) silmeyi durdurmaz: tablo ve scripti yerinde kalır, rapor Failed + mesaj gösterir. Tablo düşürmek App.SqlQueryManager yetkisi ister; wizard silme yetkisi tek başına yetmez. Düzenleme tabloya hiç dokunmaz.

Silme sırası. Tek tek silmede sıra sonucu belirler; yanlış sıra kayıtları sahipsiz bırakır: bırakılan (Kept) satırın sahibi silinen wizard'dı ve sonradan hiçbir silme onu kaldırmaz.

  1. Alt formlar önce, onları taşıyan ekran sonra — en derindeki alt form ilk gider (HealthCardWorkHistory → HealthCardGeneral → … → HealthCard). Bağlı wizard'ı olan wizard zaten silinemez (yukarıda).
  2. Aynı seviyede en son deploy edilen önce — paylaşılan menü seviyesi, ara yetki, izin grubu ve dil anahtarı onu ilk üreten wizard'a aittir; o wizard, diğerleri gidip satır kullanımsız kalınca en son silinirse satır da gider. Önce silinirse satır "kullanımda" diye bırakılır ve sahipsiz kalır.
  3. Tek tek silmenin sınırı: alt formun ekranı, onu SubForms'unda taşıyan ekran veritabanında durdukça kullanımda sayılır ve korunur (Kept); tablosu da aynı nedenle (InUse) düşürülemez. Kural 1 bu yüzden tek tek silmede alt formun ekranını ve tablosunu temizleyemez — birlikte üretilmiş bir ekran ağacı talep olarak silinir.

Talep bazlı toplu silme (Wizard Manager'da talep başlığındaki çöp kutusu; DeleteDevRequest, DeleteDevRequestAnalysis, yetki App.Listforms.Wizard.Delete) talebin bütün wizard'larını tek işlemde siler. Onay diyaloğu sırayı ve tabloları gösterir:

  • Sıra yukarıdaki kural 1 + 2'dir (BuildDevRequestDeletePlanAsync); döngüde kalan dosyalar yalnızca deploy zamanına göre sıralanır. Talebin dışındaki bir alt forma bağlı olmak silmeyi durdurmaz — o alt form başka bir talebindir ve yerinde kalır.
  • Talebin ekranları birbirini kullanım saymaz: alt formun ekranı, aynı işte silinecek üst ekran yüzünden korunmaz (WizardRecordCleaner.CleanAsync(seed, batchListForms)); tablo kullanımında da talebin ekranları sayılmaz (WizardTableCleaner.AnalyzeAsync(seed, batchListForms)).
  • Tablolar en sonda düşürülür: bütün ekranlar gittikten sonra kullanım yeniden ölçülür; aynı tabloyu paylaşan alt formlar (Hr_T_HealthCard gibi) tabloyu bir kez listeler. Bağlantılar veri kaynağı kaydı silinmeden önce çözülür. Tablo seçmek yine App.SqlQueryManager ister.
  • Rapor tek wizard silmeyle aynı sözleşmedir (WizardDeleteResultDto, bütün wizard'ların kayıtları tek listede).

MCP delete_wizard tek dosya siler ve kural 1 + 2'ye uyar; ekran ağacını temizlemek için kullanıcı Wizard Manager'daki talep silmesini kullanır.

4.6 Alt formlar ve widget'lar

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

"SubForms": [
  {
    "TabType": "List",              // List|Card|Tree|Gantt|Scheduler|Todo|Form|Chart|Pivot
    "TabTitle": "App.Mrp.OrderItems",   // dil anahtarı
    "Code": "App.Mrp.OrderItems",       // hedef ListFormCode
    "IsRefresh": true,                  // ana kayıt değişince yenilensin
    "Relation": [
      { "ParentFieldName": "Id", "ChildFieldName": "OrderId", "DbType": 11 }
    ]
  }
]

Relation bir dizidir — bileşik anahtarda birden fazla eşleme verilir.

Alt form ekranının kuralları (sekmede açılan hedef ekran; MCP ile üretimde de geçerli):

  • Menü: Yalnızca sekmede açılan ekran menüsüz (CreateMenu: false) üretilir. Kendi sayfası da olan ekran (Muayene Listesi gibi) menülü kalır ve ana ekranın SubForms'una yalnızca eklenir.
  • Yetki: Menüsüz alt formun okuma yetkisi ana ekranın yetkisinin altındadır (ParentName = ana ekranın yetki kodu). Zincir iç içedir: alt formun alt formu, bağlı olduğu alt formun altına yazılır (App.Hr.HealthCard › App.Hr.HealthCardGeneral › App.Hr.HealthCardWorkHistory). Alt formlar ana ekrandan önce deploy edilir; ana ekran deploy edildiğinde yetkiler altına taşınır. Deploy'dan sonra Abp.Identity.Permissions.json'da bu bağ doğrulanır — kökte kalan alt form yetkisi rol ekranında grubun köküne düşer. Menülü ekranın yetkisi kendi menü dalında kalır.
  • TabType: "Form" — ana kayda bağlı tek kayıt: Sekme ilişkiye uyan tek kaydı gösterir (1:1, ör. çalışan başına bir sağlık kartı bölümü). Hedef ekran AllowAdding: true ve AllowUpdating: true ile üretilir: kayıt yokken sekme "Yeni" düğmesi gösterir, kayıt varken yalnızca düzenleme açıktır ve ikinci kayıt açılmaz (FormView + FormButtons, isSubForm). AllowAdding kapalıysa sekme boş "kayıt bulunamadı" mesajında kalır ve kayıt girilemez. AllowDeleting istek açıkça istemedikçe kapalıdır.
  • TabType: "List" — ana kayda bağlı çok kayıt: AllowAdding/AllowUpdating/AllowDeleting açık gelir; ilişki kolonu (ChildFieldName) yeni satıra ana kayıttan yazılır.
  • Widget: Alt form oluşturulurken widget oluşturulmaz — menüsüz ya da menülü, alt form olarak üretilen her ekran Widgets: [] ile gider; sekmede şerit ayrıca çizilmez (§4.6.1).

Widgets (WidgetEditDto) — ekranın üstündeki KPI kartları. Title, SubTitle, Value, Icon, Color, OnClick sabit metin değil, SqlQuery sonucundaki kolon adlarıdır; widget, sorgunun döndürdüğü her satır için bir kart çizer:

"Widgets": [
  {
    "SqlQuery": "SELECT 'App.Mrp.Widget.OpenOrders' AS Baslik, COUNT(*) AS Deger, 'blue' AS Renk, 'FcClock' AS Ikon FROM Mrp_T_Order WHERE TenantId = '@TENANTID' AND Status = 1",
    "Title": "Baslik",              // sorgudaki kolon adı
    "Value": "Deger",               // sorgudaki kolon adı
    "Color": "Renk",
    "Icon": "Ikon",
    "SubTitle": "",                 // karşılığı olan kolon yoksa boş bırak
    "OnClick": "",                  // tıklanınca açılacak adresi taşıyan kolonun adı
    "ValueClassName": "text-3xl",
    "ColSpan": 1,
    "ColGap": 16,
    "ClassName": "",
    "IsActive": true
  }
]
  • SqlQuery boşsa widget hiç çizilmez; IsActive: false olan da çalıştırılmaz.
  • Title/SubTitle kolonunun döndürdüğü değer dil anahtarıdır; ekran :: öneki ekleyip çevirir, karşılığı yoksa metni aynen basar. Sorguya düz metin değil anahtar yaz.
  • Tek sorguyla birden çok kart üretmek için UNION ALL kullan — üç durum için üç widget kaydı açmana gerek yok.
  • Widget sorgusu ekranın otomatik tenant/şube filtresine tabi değildir; kırılımı sorgu kendisi yazar. Sorgu DefaultFilter ile aynı token çözümünden geçer (DefaultValueHelper): kiracı tablosunda "TenantId" = '@TENANTID' yaz — host'ta IS NULL'a çevrilir, liste ekranıyla aynı kırılımı verir. @USERID, @DATE vb. de çözülür. Ayrıntı: lowcode-reference.instructions.md §5.3.

4.6.1 Otomatik widget — Wizard ve MCP ile üretilen her liste ekranı

Wizard ekranından ya da MCP deploy_wizard ile üretilen her List ekranı Widgets ile gelir; kullanıcı "widget/KPI" demese de, sorulmadan eklenir ve tarifte tek satırla bildirilir. İstek widget'ı açıkça dışarıda bırakırsa ya da ekran aşağıdaki istisnalardan biriyse eklenmez.

  • En az 3 kart, tek widget kaydında UNION ALL ile. ColSpan = 12 / kartSayısı (3 kart → 4, 4 kart → 3, 6 kart → 2), ColGap: 3, ClassName: "mb-3", ValueClassName: "text-3xl", IsActive: true.
  • Kartları tablonun anlamlı kolonlarından seç, sırayla:
    1. Durum/statü kolonu (Status, ApprovalStatus, lookup'lı dxSelectBox) → değer başına bir kart; başlık lookup'taki dil anahtarı.
    2. Boolean bayraklar (IsActive, IsPublished, IsPinned, IsSent…) → Aktif/Pasif, Yayında/Taslak gibi çift.
    3. Tarih kolonu → "Bugün" (CAST("Kolon" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE)), "Yaklaşan" ("Kolon" >= CURRENT_TIMESTAMP), "Süresi dolmuş".
    4. Sayısal/tutar kolonu → COALESCE(SUM("Kolon"), 0) (Toplam tutar, toplam görüntülenme).
    5. Hiçbiri 3 kartı doldurmuyorsa App.Widget.Total ile başla.
  • Sorgu kuralları:
    • Tablo TenantId taşıyorsa (IsTenant: true) her karta "TenantId" = '@TENANTID' yazılır (§4.6, reference §5.3). Unutulursa ortak veritabanında kartlar bütün kiracıları sayar.
    • IsDeleted koşulu widget'a yazılmaz.
    • Değer CAST(... AS VARCHAR(32)) ile metne çevrilir: sayı ve tutar kartları aynı UNION ALL'da tip çakışmasına düşmez.
    • Tanımlayıcılar çift tırnaklı yazılır, sağlayıcıya özgü fonksiyon (DATEADD, GETDATE, NOW()) kullanılmaz; sorgu hem SQL Server hem PostgreSQL'de çalışmalıdır.
  • Metin: Title bir dil anahtarıdır. Önce var olan anahtarlar kullanılır: genel App.Widget.* (Total, Active, Passive, Published, Draft, Today, Upcoming, Pending, Sent, TotalViews, TotalRevenue…), lookup'ın App.StaticLookup.* anahtarları, alanın App.Listform.ListformField.* başlığı. Karşılığı yoksa yeni anahtar wizard'ın Language.Texts alanında EN + TR verilir (§4.4); tanımsız anahtarla deploy reddedilir. SubTitle ekranın kendi başlık anahtarıdır (ListFormCode).
  • Renk ve ikon: blue toplam, green olumlu/aktif, gray pasif/taslak, red hata/iptal, yellow/orange bekleyen, purple tutar/özet. İkon react-icons/fa adıdır (FaCheckCircle, FaTimesCircle, FaCalendarDay, FaEye, FaMoneyBillWave…).
  • İstisna — widget eklenmez: Form ve Chart ekranları; alt form olarak üretilen her ekran — başka bir ekranın SubForms sekmesi olan, menüsüz (CreateMenu: false, beğeni/yorum/seçenek gibi detay tabloları) ya da kendi menüsü de olan (Muayene Listesi gibi) — Widgets: [] ile deploy edilir; MCP bu ekranlara widget üretmez, menüsüz olanda deploy gönderileni de düşürür; IsActive de taşımayan, yalnızca kod + ad tutan eski tanım listeleri. Bunlarda anlamlı bir kırılım yoktur, şerit yalnızca kayıt sayısını tekrarlar. Yeni tanım tablosu IsActive ile gelir (§8.1) ve Toplam · Aktif · Pasif kartlarını alır.
  • Alt form sekmesinde widget çizilmez. Widget sorgusu sekmede Relation ile süzülmez; kartlar sekmedeki satırlarla değil bütün tabloyla ilgili sayılar gösterirdi. Liste görünümleri alt form olarak açıldığında şeridi çizmez ve yenilemez (useListFormWidgets(…, isSubForm), Pivot'ta isSubForm); kendi menüsü olan ekranın şeridi kendi sayfasında kalır. Bu bir güvencedir, üretim kuralının yerine geçmez: alt form olarak üretilen ekrana yine widget yazılmaz. Ana kayda göre sayı isteniyorsa kart ana ekranın widget'ına yazılır.
  • Kartların sorgusunu seed dosyasına yazmadan önce tablo kolonları doğrulanır (MCP'de search_schema, ekranda tablo tasarımcısı). Var olmayan kolona yazılmış kart ekranı hatayla açar.

Seeder'da (ListFormSeeder_*) aynı kural DefaultWidgetsJson(tablo, listFormName, tenant, params WidgetCard[]) ile uygulanır.

Alt form olarak kullanılacak ekranı menüsüz wizard (CreateMenu: false) ile üret; menüde görünmesin ama kendi yetkileri ve alan tanımları olsun. Wizard.SeededAt damgası ana wizard'ınkinden küçük olmalıdır (§0.2).

4.7 İş akışı (onay süreci)

WorkflowDto onay alanlarını tabloda karşılığı olan kolonlara bağlar:

Alan Tabloda karşılığı
ApprovalUserFieldName Onaylayan kullanıcı
ApprovalDateFieldName Onay tarihi
ApprovalStatusFieldName Durum
ApprovalDescriptionFieldName Onay/ret açıklaması
ApprovalIsFilterUserName Liste yalnızca kullanıcının onayına düşenleri göstersin
ApprovalIsResetWorkflow Kayıt güncellenince akış başa dönsün

Criteria düğüm grafiğidir. Kind değerleri: Start, Compare, Approval, Inform, End.

"WorkflowDto": {
  "ApprovalUserFieldName": "ApproverId",
  "ApprovalDateFieldName": "ApprovalDate",
  "ApprovalStatusFieldName": "Status",
  "ApprovalDescriptionFieldName": "ApprovalNote",
  "ApprovalIsFilterUserName": true,
  "ApprovalIsResetWorkflow": true,
  "Criteria": [
    { "Id": "N1", "Kind": "Start",    "Title": "Başlangıç",
      "NextOnStart": "N2", "PositionX": 40,  "PositionY": 40 },

    { "Id": "N2", "Kind": "Compare",  "Title": "Tutar kontrolü",
      "CompareColumn": "Amount", "CompareOperator": ">", "CompareValue": 5000,
      "CompareOutcomes": [
        { "Label": "> 5000", "TargetId": "N3",
          "Conditions": [ { "CompareColumn": "Amount", "CompareOperator": ">", "CompareValue": 5000 } ] },
        { "Label": "<= 5000", "TargetId": "N4",
          "Conditions": [ { "CompareColumn": "Amount", "CompareOperator": "<=", "CompareValue": 5000 } ] }
      ],
      "PositionX": 40, "PositionY": 160 },

    { "Id": "N3", "Kind": "Approval", "Title": "Genel Müdür onayı",
      "Approver": "<roleOrUserId>", "NextOnApprove": "N5", "NextOnReject": "N6",
      "PositionX": 300, "PositionY": 260 },

    { "Id": "N4", "Kind": "Approval", "Title": "Yönetici onayı",
      "Approver": "<roleOrUserId>", "NextOnApprove": "N5", "NextOnReject": "N6",
      "PositionX": 40,  "PositionY": 260 },

    { "Id": "N5", "Kind": "Inform",   "Title": "Talep sahibine bilgi",
      "Approver": "<roleOrUserId>", "NextOnStart": "N7", "PositionX": 170, "PositionY": 380 },

    { "Id": "N6", "Kind": "End",      "Title": "Reddedildi", "PositionX": 340, "PositionY": 480 },
    { "Id": "N7", "Kind": "End",      "Title": "Onaylandı",  "PositionX": 170, "PositionY": 480 }
  ]
}

Kurallar:

  • Id sözleşmesi: N ile başlayan kısa id'ler seeder tarafından {ListFormCode}-{Id} hâline getirilir; hedef alanlar (NextOn*, CompareOutcomes[].TargetId) da aynı normalizasyondan geçer. Bu yüzden dosyada N1, N2 yazman yeterlidir ve tekil olmaları şarttır.
  • Compare düğümünde çok dallı karar için CompareOutcomes kullanılır; iki dallı basit karar için NextOnTrue / NextOnFalse yeterlidir.
  • Approval düğümü NextOnApprove / NextOnReject, Start ve Inform düğümleri NextOnStart alanını kullanır. End düğümünün çıkışı yoktur.
  • Inform düğümü e-posta gönderir (MailQueue üzerinden, Approver alanındaki kullanıcı/rol adreslerine, önceki adımların notlarıyla birlikte). Uygulama içi bildirim ya da SMS isteniyorsa ayrıca bir bildirim kuralı tanımlanır (§9.11).
  • CompareValue sayısaldır (decimal); metin karşılaştırması bu düğümle yapılmaz.
  • Title tekil olmalıdır; seeder tekrar eden başlıkları ayrıştırır ama okunabilirliği bozar.
  • Akış varsa ListForm SelectionMode = single olur ve not paneli otomatik açılır.
  • Görsel tasarım /admin/listform/edit/{kod} → Workflow sekmesindedir.

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

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

Kimlik ve varyant

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

Veri

Kolon Ne yapar
DataSourceCode Bağlantı
SelectCommandType + SelectCommand Kaynak türü ve kaynağın kendisi
TableName Tablo adı/alias — yazma komutlarının hedefi; 4 Query'de zorunlu alias ("Q")
KeyFieldName + KeyFieldDbSourceType Anahtar; 4 Query'de ikisi de zorunlu
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 boşsa seri ekranda kurulur; grafik tarif edildiyse doldur, §9.8)
Tree TreeOptionJson KeyExpr + ParentIdExpr
Gantt GanttOptionJson ParentIdExpr + TitleExpr + StartExpr + EndExpr
Scheduler SchedulerOptionJson TextExpr + StartDateExpr + EndDateExpr
Todo (Kanban) TodoOptionJson TitleExpr + StatusExpr

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

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

Filtre, arama, düzenleme

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

ListFormField — alan başına ayarlar

Grup Kolonlar
Bağlama FieldName, SourceDbType, JoinTableJson (kolon başka tablodan geliyorsa)
Görünüm CaptionName, PlaceHolder, Visible, IsActive, Width, ListOrderNo, Alignment, Format, BandName
Sıralama/filtre SortIndex, SortDirection, AllowSearch, ColumnFilterJson, ColumnHeaderJson, GroupingJson, ColumnCustomizationJson
Özet TotalSummaryJson, GroupSummaryJson
Düzenleme EditorType2, EditorOptions, EditorScript, EditOrderNo, EditGroupOrderNo, ColSpan, AllowEditing, AllowAdding, ValidationRuleJson
Lookup LookupJson
Biçimlendirme ColumnStylingJson, ColumnCssClass, ColumnCssValue
Pivot PivotSettingsJson
Yetki PermissionJson — Deny (bool) + C/R/U (yetki adları) + E/I (bool). Ekran seviyesindeki yedi harfli sözleşmeyle karıştırma; CanRead/CanCreate/CanUpdate/CanExport istek başına hesaplanan [NotMapped] sonuçlardır (lowcode-reference §5.1)
Varyant UserId, RoleId, CultureName

Kural: "Şunu da yapabilir miyiz?" sorusuna cevap ararken önce bu iki tabloya bak. Aradığın davranışın bir JSON kolonu varsa çözüm konfigürasyondur; CustomJsSources ve kod yazmak ondan sonra gelir. Bu kolonların alan alan sözleşmesi lowcode-reference.instructions.md içindedir: grid/pivot/chart blokları, düzenleme ve popup ayarları, SQL kancaları ve token'ları, sütun filtresi/özeti/join'i, yetki ve varyant kolonları.

4.9 Toolbar ve satır butonları — CommandColumnJson

Grid'in araç çubuğuna ve satır aksiyon sütununa özel butonlar eklenir. Süreç yönetiminin (onaya gönder, iptal et, belge üret, dış sisteme aktar) kod yazmadan yapıldığı yer burasıdır.

ListForm.CommandColumnJson bir CommandColumnDto[] taşır. Wizard cevabında (§4.4) ve MCP deploy_wizard girdisinde aynı dizi CommandColumns alanıdır; deploy onu CommandColumnJson'a yazar, seed dosyasında ListForm bölümünde durur ve düzenlemede korunur. Wizard ekranında bu alanın adımı yoktur; buton ekranın detayındaki Komutlar sekmesinden ya da deploy_wizard ile yazılır.

[
  {
    "ButtonPosition": 1,                       // 0 = satır aksiyon sütunu · 1 = toolbar
    "AuthName": "App.Hr.AdvanceRequests.Update",   // yetki yoksa buton hiç çizilmez
    "Text": "App.Hr.AdvanceRequests.SendToApproval",   // dil anahtarı (başına :: eklenir)
    "Hint": "App.Hr.AdvanceRequests.SendToApprovalHint",
    "Icon": "check",
    "IsVisible": true,
    "VisibleExpression": "",
    "Url": "",
    "UrlTarget": "_blank",
    "DialogName": "",
    "DialogParameters": "",
    "OnClick": ""
  }
]

Bir butonun davranışı üç moddan biridir ve bu sırayla değerlendirilir:

Mod Dolu alan Ne yapar
Adres Url (+ UrlTarget) Adresi açar. @FieldName token'ları seçili ilk satırın kolon değerleriyle değiştirilir (/admin/form/App.Hr.AdvanceRequests/@Id). PWA modunda hedef _self olur.
Diyalog DialogName + DialogParameters Platformun sabit diyalog listesinden birini açar (aşağıya bak). DialogParameters bir JSON nesnesidir; @Kolon değerleri satırdan doldurulur ({"requestId":"@Id","amount":"@Amount"}).
Script OnClick Serbest JS (eval). Son çaredir; önce diğer iki mod denenir. Platform aksiyonları için §9.13.

Kurallar:

  • AuthName boş bırakılırsa buton herkese görünür. Süreç butonlarında bu alanı boş bırakma: yetki kontrolünden geçmeyen buton hiç çizilmez, gizlemenin tek doğru yolu budur.
  • Text ve Hint dil anahtarıdır; çeviri :: öneki eklenerek çözülür. Düz metin yazma.
  • Icon bir DevExtreme ikon adıdır (check, trash, doc, preferences …); tema CSS'i dx-icon-<ad> sınıfıyla çizer. Komut diyalogundaki alan hazır ikon listesini önerir, listede olmayan bir ad da yazılabilir. Form ekranının araç çubuğu da aynı ikon adlarını kullanır.
  • Toolbar butonu seçili satırlarla çalışır; SelectionJson uygun modda (single/multiple) olmalıdır. İş akışı varsa wizard bunu zaten single yapar.
  • Toolbar butonu Grid, Card ve Tree'nin yanında Todo panosunda da çizilir; pano satır seçimi taşımadığı için orada yalnızca satırdan bağımsız Url ve OnClick butonları görünür (DialogName görünmez).
  • Satır bazlı aksiyon istiyorsan ButtonPosition: 0 kullan; buton komut sütununda görünür.
  • IsVisible ve VisibleExpression yalnızca satır butonlarında (ButtonPosition: 0) çalışır; toolbar butonu her zaman görünür (görünürlüğü AuthName belirler). VisibleExpression bir ok fonksiyonudur ve eval edilir: "(e) => e.row.data.Status === 'Taslak'".
  • URL token'ları: satır butonunda @LISTFORMCODE ve grid sütunu olarak tanımlı her alan için @FieldName; toolbar butonunda seçili ilk satırın tüm kolonları. Sütun listesinde olmayan bir alan adı satır butonunda değiştirilmez.
  • Diyalog modunda @ parametreleri satır butonuna aittir: değerleri satır verisinden okunur, toolbar'da satır bağlamı olmadığı için orada yalnızca sabit parametreler çalışır (yeni kayıt diyalogu için {"id":""} gibi). DialogParameters boşsa diyalog hiç açılmaz — bu yüzden parametresiz diyaloglarda da en az bir sabit anahtar yazılır. Her iki konumda da diyalog kapanınca grid otomatik yenilenir.
  • DialogName kayıtlı bir custom component adı değildir; platformda tanımlı şu diyalogları açar: RolesPermission, UsersPermission, TenantsConnectionString, CreateTenantFromOrderDialog, CreateNotification, AuditLogDetail, BranchSeed, DynamicServiceEditor. Kayıt yeri views/shared/DialogContext/DialogShowComponent.tsx'tir; her diyalog open + onDialogClose prop'larını alır. Kendi ekranını açmak istiyorsan Adres modunu kullan (custom component route'u ya da /admin/list/{ListFormCode}).
  • DialogName: "BarcodeScanDialog" toolbar butonuna özeldir (ButtonPosition: 1) ve sürekli barkod taramasını açar: okunan her kod listeye yeni bir satır olarak eklenir. DialogParameters'ta field zorunludur (barkodun yazılacağı kolon); kalan anahtarlar her satıra sabit değer olarak yazılır ({"field":"Barcode","WarehouseId":"1"}). Satır ekleme ekranın kendi InsertServiceAddress ucundan geçer — yetki ve kiracı izolasyonu normal satır eklemeyle aynıdır. Kamera HTTPS ister.
  • Onay akışının kendi butonları (Onayla/Reddet) WorkflowJson üzerinden otomatik gelir; onları CommandColumnJson ile tekrar tanımlama.

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

Kalıp — yazma tarafı ayrı bir sayfada olan liste. Kayıt grid'in popup formuyla değil, var olan bir React sayfasıyla (Monaco editörü, sihirbaz, tasarımcı) düzenleniyorsa liste yine bir ListForm'dur; üç aksiyon şöyle bağlanır:

Aksiyon Nereye yazılır
Ekle EditingOptionJson: AllowAdding: true + AddDialogName (+ sabit AddDialogParameters) — toolbar'ın hazır Add butonu grid popup'i yerine diyaloğu açar. Ayrı sayfaya gidilecekse AddDialogName yerine AddPageUrl. Her iki durumda da ayrı bir toolbar butonu tanımlama: hazır buton doğru konumda (ilk aksiyon) ve doğru görünümdedir, yetkisi PermissionJson.C'den gelir
Düzenle Satır butonu (ButtonPosition: 0), DialogName + {"id":"@Id"}; ayrı sayfa yolunda Url modu (/admin/list/{Kod}/edit/@Id)
Sil Kayıt yalnızca tablodan silinmiyorsa (assembly/dosya/dış kayıt da düşecekse) satır butonu + OnClick ile UiEvalService aksiyonu; düz tabloda AllowDeleting + DeleteCommand yeterlidir

AllowUpdating/AllowDeleting false bırakılır, böylece grid içinde ikinci bir düzenleme yolu açılmaz; AllowAdding yalnızca hazır Add butonunun çizilmesi için true'dur ve AddDialogName olduğu sürece popup form açmaz. Diyalog kapanışı listeyi yenilediği için kaydettikten sonra ayrıca bir yenileme çağrısı gerekmez. Canlı örnek: Dynamic Services (App.DeveloperKit.DynamicServices) — liste, KPI şeridi ve Swagger butonu ListForm'da; kod yazma DynamicServiceEditor diyalogunda (§9.3).

Adres değişiminde veritabanı uyarısı: data/App.Menus.Menu.json ve App.Menus.Routes.json kayıtları yalnızca yoksa eklenir (ListFormSeedDataApplier). Var olan bir menünün Url'ini ya da bir route'un Path'ini değiştirdiğinde kurulu veritabanları eski adreste kalır; düzeltme {sql|postgres}/execute/ altında bir prosedürle (§8.3) ya da elle yapılır. Yeni kurulumda sorun çıkmadığı için bu adım kolayca atlanır.

4.10 Wizard adım haritası — ekrandaki her adımın seed karşılığı

/admin/listform/wizard sihirbazı (kaynak: wizard/Wizard.tsx + WizardStep*.tsx) aşağıdaki adımlardan geçer. Seed dosyası yazarken de aynı sırayla düşün: her adım Wizard bloğunun bir bölümünü doldurur. Keşif turu (§0.6) soruları da bu adımlarla bire bir eşleşir.

# Adım Görünme koşulu Doldurduğu seed alanları
0 Komponent Tipi her zaman ComponentKind (0 List · 1 Custom)
1 Custom Component yalnız Custom yolu CustomComponentName, MenuUrl (bileşenin RoutePath'i)
2 Menü her zaman WizardName, CreateMenu, MenuParentCode (ekranda eksik seviyeler menü diyaloğuyla kurulur; MCP MenuParentPath yazar), MenuCode, Language.MenuEn/Tr, PermissionGroupName (+ görünen adlar), MenuIcon, MenuOrder
3 Ayarlar yalnız List yolu ListFormCode, DataSourceCode (+DataSourceConnectionString), SelectCommandType, SelectCommand, KeyFieldName, KeyFieldDbSourceType, IsTenant/IsBranch/IsOrganizationUnit, AllowAdding/Updating/Deleting, ConfirmDelete, AllowDetail, DefaultLayout + 8 görünüm bayrağı (SeedData adımda sorulmaz, düzenlemede korunur), Language.TitleEn/Tr, Language.DescEn/Tr
4 Alanlar yalnız List Groups[] (aşağıda)
5 Alt Formlar yalnız List SubForms[]
6 Widget'lar yalnız List Widgets[]
7 İş Akışı yalnız List WorkflowDto
8–11 Todo / Tree / Gantt / Scheduler List + ilgili bayrak açıksa TodoOptionDto / TreeOptionDto / GanttOptionDto / SchedulerOptionDto
12 Deploy her zaman — (özet + üretim; dosyaya alan yazmaz)

Custom yolunda 3–11 adımları hiç görünmez; menü + yetki + dil + bileşen bağlama üretilir. Bir görünüm bayrağı sonradan kapatılırsa aktif adım listeden düşer, sihirbaz en yakın geçerli adıma döner.

Adım 2 — Menü. WizardName her yolda zorunludur. CreateMenu anahtarı kapatılırsa menü alanlarının tamamı gizlenir ve menü/üst menü kaydı üretilmez (alt form ekranı kalıbı, §4.6). Açıkken zorunlular: üst menü (ağaçtan seçilir ya da yeni kök tanımlanır), MenuCode, EN+TR menü etiketi, izin grubu (listeden seçilir ya da yeni ad; görünen adları EN+TR yazılır), MenuIcon (react-icons adı) ve MenuOrder ≥ 1. Custom yolunda ayrıca MenuUrl gösterilir — bileşenin RoutePath'inden gelir, elle route açılmaz.

Adım 3 — Ayarlar. SelectCommand üç yoldan doldurulur: var olan tablo/view seçimi, SQL Table Designer diyaloğu (yeni tablo; §8.1 varsayılan kolonlarıyla) ya da serbest sorgu/SP (SelectCommandType'a göre). Aynı adımda CRUD Endpoints diyaloğu açılabilir (yetki: App.SqlQueryManager.CrudEndpoints) — Custom Component'in veri kaynağı olacak tabloysa endpoint'leri burada üret. AllowDetail için KeyFieldName şarttır.

Adım 4 — Alanlar. Sütunlar GetTableColumnsAsync ile tablodan gelir; "tümünü ekle" ya da tek tek sürükle-bırak. Her grup Caption + ColCount, her alan WizardColumnItemInputDto taşır. Sihirbazın kendi otomatikleri — dosya yazarken aynısını uygula:

Otomatik Kural
EditorType çıkarımı bit → dxCheckBox · sayısal tipler → dxNumberBox · tarih tipleri → dxDateBox · gerisi → dxTextBox
Başlık Kolon adı CamelCase'ten kelimelere ayrılır (OrderNo → "Order No"); TR/EN başlangıç değeri budur, dil anahtarı App.Listform.ListformField.{Alan}
IsRequired Kolon NOT NULL ise true
Lookup sorgusu üreticisi Tablo + anahtar kolon + ad kolonu seçilir → SELECT "K" AS "Key", "N" AS "Name" FROM "Tablo" [WHERE "IsDeleted" = 0] ORDER BY "N"; — tabloda IsDeleted varsa filtre kendiliğinden eklenir
ValueExpr/DisplayExpr Key / Name

Alan kartındaki Seçenekler düğmesi Editor Options Builder'ı (§6), Script düğmesi Script Builder'ı (§5) açar — çıktıları EditorOptions/EditorScript string'leridir.

Adım 7 — İş Akışı. Zorunlu ikili: ApprovalUserFieldName + ApprovalStatusFieldName; opsiyonel ApprovalDateFieldName, ApprovalDescriptionFieldName, ApprovalIsFilterUserName (liste yalnız sıradaki onaycıya düşsün), ApprovalIsResetWorkflow (kayıt değişince akış başa dönsün). Onay düğümleri (Criteria) görsel akış tasarımcısında kurulur ve ListFormWorkflow kayıtlarına gider (§4.7).

Adım 12 — Deploy. Sol tarafta tüm adımların özeti, sağda üretim log'u. Log, deploy'un seed dosyalarına yazdığı bölümleri sırayla gösterir; migrate'te WizardDataSeeder ekrana özgü kayıtları, ardından ListFormSeedDataApplier menü/yetki/dil satırlarını kurar (§4.0):

  1. Konfigürasyon doğrulama
  2. Menü kaydı (CreateMenu: false ise atlanır)
  3. Dil metinleri
  4. İzin grubu + yetkiler (okuma yetkisi menü dalındaki ara yetkinin altına; menüsüz alt formda listenin yetkisinin altına, §0.12 Menü yeri)
  5. List yolunda: veri kaynağı → ListForm → alan grupları · Custom yolunda: bileşen bağlama
  6. Deploy + tamamlandı

Başarıda sihirbaz "Migrate bekliyor" bildirimiyle Wizard Manager'a döner (ekran henüz kurulu değildir; DB Migrate ile kurulur) ve seed dosyası configs/seeds/{kapsam}/wizard/ altına yazılmış olur. Düzenleme modu Wizard Manager'dan EditFileName ile açılır: aynı adımlar dolu gelir, kaydetmede dosyalar cevaptan yeniden yazılır; önceki sürümün kayıtları Pending.Replaced'a alınır, veritabanına dokunulmaz — kaldırma ve yeniden kurulum DB Migrate'tedir (§4.0).


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

Bir alanın davranışı (hesapla, doldur, kilitle, sor, çağır) EditorScript; görünümü ve editör özellikleri (§6) EditorOptions'tır. İkisi de ListForm alan kaydının birer metin kolonudur ve ikisi de aynı kutudan düzenlenir:

Dialog Nerede açılır Ne düzenler Lehçe/sözlük
Script Builder (ScriptBuilderDialog) Wizard → Alanlar adımı · ListForm editörü → Alan → Seçenekler sekmesi EditorScript formScriptDialect.ts + scriptRecipes.ts
Editor Options Builder (EditorOptionsBuilderDialog) Wizard → Alanlar adımı · ListForm editörü → Alan → Detay sekmesi EditorOptions optionSpecs.ts + presets.ts
Event Script Builder (DesignerScriptBuilderDialog) Visual Designer → JavaScript sekmesi Custom component düğüm events script'i designerScriptDialect.ts (§7.8)

Script Builder'ın iki görünümü vardır ve ikisi de aynı metni üretir:

  • Kod (varsayılan): Monaco editörü + lehçeye bağlı IntelliSense (alan adları yalnızca string literali içinde önerilir), sağda arama yapılabilen Snippet ve Runtime API paneli. TypeScript teşhisleri kapalıdır — script bir handler gövdesidir, tek başına return/await geçerli görünmez.
  • Sihirbaz: kural kartları. Her kart = bir tetikleyici + koşullar + bir tarif (recipe). Kart altında o kuralın üreteceği satır canlı gösterilir; sağ panelde script'in tamamı.

5.1 Script metninin şekli ve gidiş-dönüş sözleşmesi

// @builder {"v":1,"rules":[ … ]}      ← ilk satır: kuralların JSON'u
// @runOnOpen                          ← yalnızca bir kural open/both ise
<her kural için tek satır runtime çağrısı>

Dialog script'i regex ile çözmez; başlıktaki kurallardan script'i yeniden üretip metinle karakter karakter karşılaştırır:

Durum Sonuç
Metin, kurallardan üretilenle birebir aynı Sihirbaz kuralları açar (inSync)
Tetikleyici kavramından önce yazılmış script (trigger: change ile yeniden üretim tutuyorsa) Kurallar geçerli sayılır, yeni varsayılanlarla yükseltilir
Metin farklı (elle düzenlenmiş) Sihirbaz kuralları ezmez; sarı "kod ayrıştı" bandı çıkar. Kullanıcı bir kurala dokunana kadar kod olduğu gibi kalır; Yeniden üret düğmesi kodu kurallardan tazeler
// @builder başlığı yok Sihirbaz boş açılır; kural eklenirse elle yazılmış kod tamamen değişir

Bunun pratik karşılığı: başlık ile gövdeyi ayrı ayrı düzenleme. Seeder ile basılan script'in dialogda açılabilir kalması, C# tarafının (§5.9) TS tarifleriyle birebir aynı metni üretmesine bağlıdır.

5.2 Bir kuralın anatomisi

[tetikleyici] + [koşul 1 (and|or) koşul 2 …] + [tarif + parametreleri]
  • Koşullar and ya da or ile birleşir (tek bağlaç, kural genelinde). Her koşul operatör + kaynak alan + değer üçlüsüdür; birden fazlaysa parçalar parantezlenir.
  • Koşul normalde if (...) sarmalayıcısına dönüşür. readOnly tarifinde ise conditionIsArgument işaretlidir: koşul if olmaz, doğrudan aksiyonun argümanı olur — koşul bozulduğunda alan yeniden açılabilsin diye (aksi halde bir kez kilitlenir, kalırdı).
  • Eksik kural üretilmez. Zorunlu parametresi ya da koşul değeri boş kural script'e hiç girmez; kartta "Eksik" rozeti çıkar. Bu, C# tarafında da aynıdır.
  • Kural sırası çıktı sırasıdır; kartlardaki ok düğmeleri sırayı değiştirir. Aynı alana yazan iki kural varsa son kural kazanır.

5.3 Tarifler (recipes) — üretilen satırın tamamı

Grup recipe Parametreler Ürettiği satır
calc multiply left, right, target, digits? set('T', round(num('A') * num('B'), 2))
calc subtract left, right, target, digits? set('T', round(num('A') - num('B'), 2))
calc percent amount, rate, target, digits?, mode? set('T', round(num('A') * num('R') / 100, 2)) · mode=add → num('A') + … · mode=subtract → num('A') - …
calc sum target, sources[], digits? set('T', round(sum('A', 'B'), 2))
calc formula target, expression set('T', <ifade>) — ifade ham JS'tir, runtime yardımcıları kullanılabilir
calc today target, offset? set('T', new Date().toISOString().slice(0, 10)) · offset varsa new Date(Date.now() + n * 86400000)
calc days start, end, target set('T', days('Bas', 'Bit')) — bitiş günü dahil
calc hours start, end, target set('T', hours('Bas', 'Bit')) — bitiş küçükse ertesi güne taşar
data copy source (seçili kayıttaki kolon/yol), target copy('unitPrice', 'UnitPrice')
data setValue target, text set('T', tpl('{Musteri} - {value}'))
data clear sources[] clear('Il', 'Ilce')
view readOnly target, invert? readOnly('Alan', <koşul>) · invert → readOnly('Alan', !(<koşul>)) · koşulsuz → readOnly('Alan', true)
interaction notify message notify('Limit aşıldı') — mesajda {Alan} şablonu geçerli
interaction ask message if (!ask('Emin misiniz?')) return — vazgeçilirse alan eski değerine döner ve script durur
interaction openUrl url, target? openUrl('/report?id={Id}') · _self verilirse ikinci argüman eklenir
integration apiToField target, url, path? set('T', await api('/api/x/{value}', 'data.name'))
integration location lat, lng, accuracy? await geoTo('Enlem', 'Boylam') — cihaz konumunu alanlara yazar; HTTPS ve kullanıcı izni ister
integration custom code Satırın kendisi. Son çare; sondaki ; atılır

Tablodaki satırlar okunsun diye tek tırnakla yazıldı; üretilen metinde her literal JSON.stringify ile çift tırnaklıdır (set("Total", round(num("Quantity") * num("UnitPrice"), 2))). Elle yazarken de böyle yaz, yoksa gidiş-dönüş karşılaştırması tutmaz (§5.1).

sum ve clear çoklu alan alır (fields listesi); diğerlerinde her parametre tek alandır. Sayı bekleyen parametrelere sabit yazılabilir: 20 olduğu gibi, KdvOrani ise num('KdvOrani') olarak geçer.

5.4 Koşul operatörleri

Operatör Kaynak Değer Üretilen ifade
always — — (koşul yok)
equals ✔ ✔ str('A') === 'x'
notEquals ✔ ✔ str('A') !== 'x'
contains ✔ ✔ str('A').includes('x')
empty ✔ — !get('A')
notEmpty ✔ — !!get('A')
greaterThan ✔ ✔ num('A') > 100 (değer alan adıysa tırnaklanır)
lessThan ✔ ✔ num('A') < 100
isTrue ✔ — bool('A')
isFalse ✔ — !bool('A')

Karşılaştırmalar metin üzerindendir (equals/notEquals/contains); sayısal karşılaştırma istiyorsan greaterThan/lessThan kullan ya da formula/custom yaz.

5.5 Tetikleyiciler ve isReady guard'ları

Tetikleyici Ne zaman çalışır
change Yalnızca alanın değeri değiştiğinde
open Yalnızca form açılırken (varsayılan değer üretmek için)
both İkisinde de

Belirtilmezse grup varsayılanı geçerlidir: calc, data, view → both; interaction, integration → change. Gerekçe: değer üreten kurallar form açılışında da çalışmalıdır (türetilmiş alan boş kalmasın); uyarı gösteren ya da dış çağrı yapan kurallar yalnızca değişimde çalışmalıdır (form her açıldığında uyarı çıkmasın, boşuna istek atılmasın).

Script'in herhangi bir kuralı açılışta çalışıyorsa metnin başına // @runOnOpen düşer ve her satır kendi tetikleyicisine göre sarmalanır: change → if (!isReady) …, open → if (isReady) …. Hiçbir kural açılışta çalışmıyorsa guard üretilmez, kod kısa kalır.

Açılışta çalışma güvenliği runtime tarafında korunur: değişmeyen değer yazılmaz (Object.is kontrolü), yazmalar bir tick sonraya alınır ve aynı turda tekrar girilmez — yoksa contentReady kendini besleyip sonsuz döngü olurdu.

5.6 Runtime API — script'in içinde ne varsa

Script AsyncFunction ile derlenir, aşağıdaki adlar parametre olarak enjekte edilir. await serbesttir.

İmza Anlamı / davranışı
value · field Değişen alanın değeri ve adı
data Script'in üzerinde çalıştığı canlı form verisi (kopya)
isReady true ise script form açılışında çalışıyor
get('Alan') Form verisinden okur. Nokta yolu (Musteri.Unvan) ve büyük/küçük harf duyarsız eşleşme destekler (SQL kolon adları tutarsız gelebiliyor)
num() · str() · bool() Tip dönüşümü. bool için true/'true'/1/'1' doğrudur
dateOf('Alan') Date ya da null
pick(nesne, 'a.b') Herhangi bir nesneden yol okuma
set('Alan', deger) · set({A:1, B:2}) Yazar. Değişmeyen değer yazılmaz; yazmalar biriktirilip tek flush ile forma ve düzenlenen grid hücresine uygulanır
clear('A','B') Verilen alanları null yapar
copy('kolon','Alan') Lookup/GridBox'ta seçili kayıttan taşır; hedef verilmezse yolun son parçası hedef olur
selected('Yol') · snum() · sstr() Seçili kaydın tamamı / sayı / metin hâli
readOnly('Alan', kosul) Formdaki herhangi bir editörü kilitler/açar (yalnızca script'in bağlı olduğu alanı değil)
round(x, 2) · sum('A','B') Matematik
days('Bas','Bit') · hours('Bas','Bit') Gün farkı (bitiş dahil) · saat farkı (negatifse +24)
tpl('{Alan} - {value}') Şablon: {Alan}, {value}, {selected.Yol}
notify('mesaj') window.alert (şablon çözülür)
ask('mesaj') window.confirm; hayır denirse revert() çalışır ve false döner
revert() Alanı değişiklik öncesi değerine döndürür (editöre de yazar)
openUrl('/adres', '_blank') Yeni sekme/pencere
await api(url, 'data.name', options?) Ham fetch. Kimlik doğrulama başlığı eklemez; korumalı bir uç için Custom Endpoint tarafında erişim açık olmalıdır
await geoTo('Lat', 'Lng', 'Acc'?) Cihaz konumunu alanlara yazar. İzin yoksa/okunamazsa alanlara dokunmaz ve uyarı gösterir, false döner
$ Tüm yardımcıların tek nesne hâli

Eski (uzun) script'ler bozulmasın diye geriye dönük uyum vardır: script içinde yardımcı adlarıyla çakışan bir tanım varsa (const data = … gibi) script yalnızca eski beş parametreyle (formData, e, editor, runtimeSetEditorReadOnly, setFormData) derlenir ve yardımcılar tanımsız kalır. Yardımcı adlarını yeniden tanımlama.

5.7 Yetenek sınırları — script ile yapılamayanlar

  • Alan gizleme yok. Runtime'da visible yardımcısı yoktur; koşullu görünürlük yerine readOnly kullan, kalıcı gizleme EditorOptions.visible ya da IncludeInEditingForm'dur.
  • Lookup sorgusu değiştirilemez; bağımlı liste wizard öğesinin Cascade* alanlarıyla kurulur (§4.3.4).
  • Sunucu tarafı doğrulama değildir. ask/notify yalnızca tarayıcıdadır; iş kuralı zorunluysa SQL/endpoint tarafında da uygula.
  • Satırlar arası hesap yok: script yalnızca düzenlenen kaydı ve seçili lookup kaydını görür.
  • Script bir alana bağlıdır: X alanının script'i yalnızca X değiştiğinde (ve açılışta) çalışır. Üç alandan beslenen bir toplam için ya her birine kural yaz ya da toplamı üreten kuralı üç alanın script'ine de koy.
  • Hata yönetimi: script hatası konsola yazılır, kullanıcıya gösterilmez ve form akışını kesmez.

5.8 Kod modunda çalışmak

Sihirbazın karşılamadığı bir şey için önce formula, sonra custom tarifini dene — bunlar sihirbazı korur. Tamamen elle yazılan script sihirbazı kapatır. Yardım panelindeki hazır kalıplar (koşullu yazma, açılış/değişim ayrımı, toplu set, seçili kayıttan doldurma, onay, try/catch'li API çağrısı, alan kilitleme) doğrudan editöre eklenir.

// Açılışta ve değişimde farklı davranış
if (isReady) {
  set('Durum', str('Durum') || 'Taslak')
} else if (!ask('Fiyatı değiştirmek istediğinize emin misiniz?')) {
  return
}

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

api/src/Sozsoft.Platform.Domain.Shared/Editors/ — scriptRecipes.ts dosyasının portudur.

Tarif C#
multiply / subtract EditorScript.Multiply(left, right, target, digits) · Subtract(...)
percent EditorScript.Percent(amount, rate, target, digits, EditorScriptPercentMode.Add|Subtract|Only)
sum EditorScript.Sum(target, "A", "B")
formula EditorScript.Formula(target, "num('Gross') * 0.18")
today / days / hours Today(target, offsetDays) · Days(start, end, target) · Hours(start, end, target)
copy / setValue / clear Copy(sourcePath, target) · SetValue(target, text) · Clear("Il", "Ilce")
readOnly ReadOnly(target, invert)
notify / ask / openUrl Notify(message) · Ask(message) · OpenUrl(url, target)
apiToField / location / custom ApiToField(target, url, responsePath) · Location(latField, lngField, accuracyField) · Custom(code)

Akıcı ekler: .When(...) (hepsi sağlanmalı), .WhenAny(...) (biri yeterli), .OnChange() / .OnOpen() / .OnOpenAndChange() / .WithTrigger(EditorScriptTrigger.X), .WithId("multiply_1") (verilmezse tarif adı + sıra numarasından üretilir).

Koşullar: EditorScriptCondition.Is / IsNot / Contains / IsEmpty / IsNotEmpty / GreaterThan / LessThan / IsTrue / IsFalse / Always.

EditorScript = EditorScript.Build(
    EditorScript.Multiply("Quantity", "UnitPrice", "Total", digits: 2),
    EditorScript.Percent("Total", "VatRate", "VatAmount", 2, EditorScriptPercentMode.Add)
        .When(EditorScriptCondition.IsNotEmpty("VatRate")),
    EditorScript.ReadOnly("Total"),
    EditorScript.Today("OrderDate").OnOpen(),
    EditorScript.Copy("Customer.TaxNumber", "TaxNumber"),
    EditorScript.Ask("Fiyatı değiştirmek istediğinize emin misiniz?")
        .When(EditorScriptCondition.GreaterThan("UnitPrice", "1000")));

Tek kurallı script için EditorScriptRule doğrudan string'e dönüşür:

EditorScript = EditorScript.Days("StartDate", "EndDate", "DayCount");

Eşleme kuralı: scriptRecipes.ts ile bu sınıflar aynı çıktıyı üretmek zorundadır. Birinde bir tarif değişirse aynı görevde diğeri de değişir; aksi hâlde seeder ile basılan script dialogda "elle düzenlenmiş" sayılır ve kural editörü kapanır.

5.10 Senaryo → kural dizisi

İstek Kurallar (hangi alana)
"Miktar × fiyat toplamı, KDV dahil" Quantity+UnitPrice → multiply(Total); Total → percent(mode=add); Total → readOnly
"Müşteri seçilince vergi no ve adres dolsun" CustomerId → copy('taxNumber','TaxNumber'), copy('address','Address')
"İl değişince ilçe boşalsın" City → clear(District)
"Kapalı kayıtta hiçbir şey düzenlenemesin" Her alanda readOnly + koşul Status equals Kapali — ya da tek alanda birden çok readOnly kuralı
"Talep tarihi bugün gelsin, geçmiş tarih seçilemesin" RequestDate → today (OnOpen) + EditorOptions min (§6)
"Tutar 1000'i geçerse onay istensin" Amount → ask + koşul greaterThan 1000
"Vergi no girilince unvan servisten gelsin" TaxNumber → apiToField(target=Title, path=data.name) + koşul notEmpty
"İzin gün sayısı otomatik" EndDate → days(StartDate, EndDate, DayCount); DayCount → readOnly

6. Artefakt C — EditorOptions

DevExtreme editörüne (ve platformun kendi editörlerine) geçirilen JSON. UI sözlüğü optionSpecs.ts, hazır kalıplar presets.ts, C# tarafı EditorOptions + EditorOptionsBuilder.

6.1 Dialog nasıl çalışır

  • Ayarlar sekmesi tamamen optionSpecs.ts sözlüğünden üretilir; alanlar gruplara ayrılır, arama kutusu yol ve etiket üzerinde çalışır.
  • Alanlar kolonun EditorType2 değerine göre süzülür. "Tüm ayarlar" kutusu süzgeci kapatır; "Yalnızca dolu olanlar" yalnızca yazılmış ayarları gösterir. Zaten değer yazılmış bir ayar, editöre uymasa bile gizlenmez ("kaydettim ama göremiyorum" durumu oluşmasın diye).
  • Boolean alanlar üç durumludur: tanımsız / true / false. Temizlemek anahtarı JSON'dan tamamen siler; geride kalan boş ara nesneler ({"format":{}}) de temizlenir.
  • Bir ayar sözlükteki tipiyle uyuşmayan bir değer taşıyorsa alan kilitlenir ve uyarı gösterir; düzeltme Ham JSON sekmesinde yapılır.
  • Sözlük dışı ayarlar bölümü: sözlükte olmayan her yol burada listelenir ve path.to.option + tip (string / number / boolean / json) + değer ile yeni ayar eklenebilir. Yani DevExtreme'in sözlükte olmayan herhangi bir seçeneği de buradan yazılır — sözlük bir sınır değil, bir kısayoldur.
  • Ham JSON sekmesi metnin tamamını düzenler. JSON bozukken Ayarlar sekmesi ve Kaydet düğmesi kilitlidir; bozuk metin sessizce silinmez.
  • Kaydedilen değer tek satır JSON'dur; hiç ayar yoksa boş metindir.

6.2 Yol yazımı ve tipler

Tip Girdi JSON'a yazılan
boolean üç durumlu seçim true / false / (yok)
number sayı 12
text metin "dd/MM/yyyy"
select listeden seçim (bilinmeyen mevcut değer korunur) seçilen değer
size sayı ya da CSS 200 veya "100%"
stringList virgülle ayrılmış ["key","name"]
json ham JSON nesne/dizi

İç içe ayar noktayla yazılır: format.precision → {"format":{"precision":2}}. Dizi elemanları yaprak sayılmaz; toolbar.items tek bir json alanıdır.

6.3 Ayar sözlüğü — gruplar

Grup Anahtarlar Hangi editörlerde
Genel readOnly, disabled, visible, hint, tabIndex, valueChangeEvent, validationMessageMode, validationMessagePosition, elementAttr.class, inputAttr.style, inputAttr.aria-label hepsi
Görünüm width, height, placeholder, label, labelMode, stylingMode, showClearButton hepsi (giriş kutusu olmayanlarda placeholder/showClearButton anlamsız)
Metin mode, maxLength, mask, maskChar, maskRules.X, maskInvalidMessage, showMaskMode, useMaskedValue, spellcheck, autoResizeEnabled, minHeight, maxHeight dxTextBox, dxTextArea, dxAutocomplete
Sayı min, max, step, showSpinButtons, useLargeSpinButtons, format.type, format.precision, format.currency, invalidValueMessage, useMaskBehavior dxNumberBox
Tarih type, pickerType, displayFormat, dateSerializationFormat, interval, min, max, openOnFieldClick, applyValueMode, invalidDateMessage, calendarOptions.firstDayOfWeek, calendarOptions.zoomLevel dxDateBox, dxCalendar, dxDateRangeBox
Açılır liste searchEnabled*, searchMode, searchExpr, searchTimeout, minSearchLength, showDataBeforeSearch, acceptCustomValue*, noDataText, deferRendering, wrapItemText, dropDownOptions.width/height/hideOnOutsideClick dxSelectBox, dxLookup, dxTagBox, dxDropDownBox, dxGridBox, dxAutocomplete
TagBox showSelectionControls*, maxDisplayedTags*, showMultiTagOnly*, applyValueMode*, multiline, hideSelectedItems dxTagBox
GridBox columns*, selectionMode*, filterRowVisible* dxGridBox
Görsel uploadUrl*, fileFieldName, accept*, capture*, multiple*, maxFileSize* dxImageUpload, dxImageViewer
Seçim text (dxCheckBox), switchedOnText/switchedOffText (dxSwitch), layout (dxRadioGroup), editAlphaChannel/keyStep (dxColorBox) ilgili editör
Kaydırıcı min, max, step, tooltip.enabled, tooltip.showMode, showRange dxSlider, dxRangeSlider
HTML valueType, toolbar.multiline, toolbar.items, mediaResizing.enabled, imageUpload.tabs, imageUpload.fileUploadMode, imageUpload.uploadUrl, tableResizing.enabled, tableContextMenu.enabled dxHtmlEditor
Liste sütunu format (metin hâli), encodeHtml, buttons hepsi — bu üçü grid sütununu da etkiler

\* işaretli anahtarlar backend'de tipli DTO'ya (GridBoxOptionsDto, TagBoxOptionsDto, ImageUploadOptionsDto) çözülür; yanlış tipte yazılan değer hata vermez, sessizce yok sayılır. columns dizi, maxDisplayedTags sayı, multiple boolean olmalıdır.

6.4 Hazır kalıplar (preset)

Preset mevcut JSON ile birleşir, diğer ayarları silmez; aynı anahtar varsa üzerine yazar.

Preset Ürettiği
Salt okunur / Pasif {"readOnly":true} · {"disabled":true}
Sağa hizalı {"inputAttr":{"style":"text-align: right"}}
2 ondalık · Artırmalı sayı · Para format.fixedPoint(2) · + useMaskBehavior + showSpinButtons · format.currency TRY
Tarih · Tarih-saat · Saat type+displayFormat+dateSerializationFormat üçlüsü (saatte ayrıca pickerType:list, interval, width:100%)
Telefon maskesi mask, maskRules.X, maskInvalidMessage, placeholder
Otomatik büyüyen metin autoResizeEnabled, minHeight, maxHeight
Aranabilir liste searchEnabled, searchMode:contains, searchTimeout, showClearButton
TagBox anında seçim showSelectionControls, applyValueMode:instantly, searchEnabled
GridBox tek/çok seçim columns, selectionMode, filterRowVisible, height, width
Çoklu görsel width, height, multiple, accept:image/*
HTML tam araç çubuğu htmlEditorOptions.ts varsayılanı: toolbar.items (tam liste), toolbar.multiline, mediaResizing, imageUpload (tabs: file/url, base64), tableResizing, tableContextMenu — boş editorOptions da aynı varsayılanı alır
Yükseklik 100 / 200 {"height":100} · {"height":200}
Barkod okutma butonu buttons[] — kamera ikonlu buton; onClick = "@SCAN_BARCODE" token'ı. Yalnız dxTextBox. Token'ı editingForm.ts kamera diyaloğuna bağlar, okunan kod alana yazılır. HTTPS şart.
Kamerayla foto çek {"accept":"image/*","capture":"environment"} — yalnız dxImageUpload. Mobilde dosya seçici yerine yalnızca kamera açılır (user = ön kamera); masaüstünde yok sayılır. Ayarlanmazsa dokunmatik cihazda dosya seçicinin yanına ayrı bir kamera butonu gelir, yani ikisi birden kullanılabilir — bu kalıp kamerayı zorunlu kılmak içindir.

6.5 Seeder tarafı (C#)

İhtiyaç C# Ürettiği JSON (özet)
Pasif / salt okunur EditorOptions.Disabled() · ReadOnly() {"disabled":true} · {"readOnly":true}
Temizle düğmesi EditorOptions.ShowClearButton() {"showClearButton":true}
Çok satırlı EditorOptions.Multiline(80) {"height":80}
Sabit ondalık EditorOptions.Number(2) format + maske davranışı + spin
Yüzde EditorOptions.Percent()
Tarih / tarih-saat / saat EditorOptions.Date() · DateTime() · Time(15)
Telefon EditorOptions.Phone() global format + maske + örnek
Kaydırıcı EditorOptions.Slider() {"tooltip":{"enabled":true}}
Görsel yükleme EditorOptions.ImageUpload(multiple: true, 80, 80)
Zengin metin EditorOptions.Html(240) htmlEditorOptions.ts varsayılanıyla aynı bloklar (tam araç çubuğu, imageUpload, mediaResizing, tableResizing, tableContextMenu; biri değişirse diğeri de değişir) + encodeHtml + elementAttr min-height:240px (sabit yükseklik değil; içerikle büyür)

Akıcı ekler: .Placeholder(), .MaxLength(), .Height(int\|string), .Width(int\|string), .Format(), .FixedPointFormat(p), .DisplayFormat(), .DateSerializationFormat(), .Mask(mask, invalidMessage, useMaskedValue), .ShowSpinButtons(), .UseMaskBehavior(), .Disabled(), .ReadOnly(), .ShowClearButton() ve tip bazlı genel yazıcılar: .Text(key, value), .Number(key, value), .Flag(key, value), .Raw(key, json).

EditorOptions = EditorOptions.Number(4).ShowClearButton(),
EditorOptions = EditorOptions.Multiline(60).Placeholder("Açıklama"),
EditorOptions = EditorOptions.New().Flag("acceptCustomValue", true),
EditorOptions = EditorOptions.New().Raw("columns", "[\"key\",\"name\"]").Text("selectionMode", "single"),

Kurallar:

  • Aynı anahtar iki kez verilirse sonuncusu geçerlidir; anahtar sırası korunur.
  • .Raw değeri geçerli JSON olmalıdır; doğrulanmaz.
  • Hiç seçenek yoksa Build() boş metin döner — alan null kalır, {} yazılmaz.

6.6 EditorScript mi EditorOptions mı?

İhtiyaç Yer
Alan her koşulda kilitli / gizli EditorOptions: readOnly, visible
Alan koşula göre kilitli EditorScript: readOnly tarifi
Biçim, maske, ondalık, tarih formatı, arama, açılır liste davranışı EditorOptions
Değer üretme, kopyalama, temizleme, uyarı, servis çağrısı EditorScript
Alt/üst sınır (tarih/sayı) EditorOptions min/max — script ile doğrulama yerine editörle engelle

7. Artefakt D — Custom Component (Visual Designer)

ListForm ile ifade edilemeyen ekranın yolu fiziksel React sayfası değil, tasarımcıda üretilen bir custom component'tir.

7.1 Seed dosyası

configs/seeds/{kapsam}/data/App.DeveloperKit.CustomComponents/{Id}.json — Custom Components ekranının veri aynası, klasör modunda (§9.15): her bileşen kendi dosyasındadır, dosya adı satırın Id'sidir. Dosya yine liste verisi zarfını taşır; Rows tek satırdır. Listeden ve Visual Designer'dan (CustomComponentAppService) yapılan her kayıt bu klasöre yansır.

{
  "ListFormCode": "App.DeveloperKit.CustomComponents",
  "KeyFieldName": "Id",
  "GeneratedAt": "2026-08-20T11:23:45Z",
  "Order": 0,
  "Rows": [
    {
      "Id": "8389ccbc-3e7b-738d-35aa-3a237eea9b9d",
      "Name": "OrderBoard",
      "RoutePath": "/admin/order-board",
      "Description": "",
      "IsActive": true,
      "Dependencies": ["OrderCard"],
      "Code": "/*__SOZSOFT_VISUAL_DESIGNER__<url-encoded designer doc>__*/\nconst OrderBoard = () => { … }\n\nexport default OrderBoard",
      "Props": "{\"visualDesigner\":{ … }}",
      "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"
        }
      ],
      "SeoTitle": null, "SeoDescription": null, "SeoKeywords": null, "SeoOgImage": null,
      "SeoCanonicalUrl": null, "SeoNoIndex": true, "SeoIncludeInSitemap": false,
      "SeoChangeFrequency": null, "SeoPriority": null, "SeoStructuredData": null
    }
  ]
}
  • Id satırın anahtarı ve dosyanın adıdır. Yeni bir bileşen yazarken sabit bir GUID üret; import ve seed eşleşmesi bu değer üzerinden yapılır. Yeni bileşen = klasöre yeni dosya; diğer dosyalara dokunulmaz.
  • Name tekil ve export default adıyla aynıdır; Wizard CustomComponentName bu ada bakar.
  • Dependencies bir dizidir ([]); eski satırlarda ""/"[]" görülür, taklit etme.
  • Seo* alanları her satırda bulunur (§0.9): /admin/ rotalı ya da rotasız bileşende SeoNoIndex: true, SeoIncludeInSitemap: false; public rotada doldurulur. Rotasız bileşende RoutePath null yazılır, boş metin değil.
  • Code modunda Props null'dır; Visual modda tasarımcı dokümanını taşır (§7.1.1).
  • DataSources elle yazılmaz: kaydetme sırasında CustomComponentDataSourceResolver tasarımcı dokümanındaki dataSources listesini method + path ile CRUD endpoint kataloğunda arayarak üretir. Eşleşenler EntityName + SeedFile taşır; elle yazılmış endpoint'ler listede kalır ama bu alanları boş gelir.
  • Dependencies, kanvasa bırakılan diğer custom component'lerin adlarıdır.
  • OperationType değerleri: GetList, GetById, Create, Update, Delete (§9.1 ile aynı küme).

7.1.1 İki mod: hangisini yazacaksın?

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

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

Bu, prompt ile üretim için belirleyicidir:

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

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

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

export default {Name}

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

7.1.2 Code modunda çalışma ortamı

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

Kapsamdaki ad Ne
React React'in tamamı; useState, useEffect, useCallback, useMemo, useRef, useReducer, useContext, createContext, memo, Fragment ayrıca çıplak ad olarak da gelir
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
Kanvas düğümleri (componentEditor/runtimeNodes.ts) PlatformIcon (ikon — react-icons kapsamda yoktur), BarcodeScanField, BarcodeViewer, Public* site düğümleri
apiService Kimlik doğrulamalı HTTP istemcisi — tercih edilen yol
axios Ham HTTP (kimlik doğrulama eklemez)
DOMPurify HTML temizleme
translate(key) Dil anahtarı çözümü
checkPermission(name) Yetki kontrolü
getCulture() Aktif dil

Kurallar:

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

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

DesignerDocument = {
  version: 1
  sourceMode: 'visual' | 'code'   // 'code' tek yönlüdür, kanvasa dönülmez
  nodes: DesignerNode[]
  canvas: { width: 'responsive' | 'desktop' | 'tablet' | 'mobile' }
  lifecycle: { onMount: string }  // sayfa açılışında çalışan script (§7.8)
  dataSources: DesignerDataSource[]
  permissionCode?: string         // Wizard'ın bu bileşen için ürettiği okuma yetkisi
}

DesignerNode = {
  id: string            // 'cmp_…' — opak
  type: string          // 'Form', 'Grid', 'Input', 'ListView', 'FlexRow' …
  kind: 'html' | 'ui' | 'layout' | 'platform' | 'custom'
  slot?: string
  ref?: string          // 'btnSave' — script'ler birbirine bununla erişir
  props: Record<string, unknown>
  events: Record<string, string>          // olay adı → script (§7.8)
  bindings: Record<string, DesignerBinding>
  children: DesignerNode[]
}

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

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

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

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

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

7.3 Toolbox

Aile (toolboxGroup) Bileşenler
layout PageContainer (maxWidth, padding, gap, className), FlexRow (columns, firstColumnWidth, gap, wrap, align, className), Table
data Form (dört CRUD endpoint'ini sahiplenen kapsayıcı) + veri bağlanabilen bileşenler: Select, AutoComplete, Dropdown, Menu, Pagination, Radio.Group, Tabs, Grid
platform ListView, DataGridView, TreeView, GanttView, TodoBoard, CardView, SchedulerView, PivotView, ChartView — her biri listFormCode ile bir ListForm ekranını gömer. Aynı ailede public site düğümleri: PublicHeaderBar, PublicNav, PublicMobileMenu, PublicLogo, PublicThemeToggle, PublicLanguageSelector, PublicDemoButton
html a, div, p, span, h1–h5, img — yalnız bu on etiket (input/button/textarea/select/checkbox kutuda yok, ui karşılıkları kullanılır)
ui components/ui bileşenleri — sözleşmeleri componentProps.json metadata'sından okunur — ve catalog.ts'teki elle tanımlı ekler: PlatformIcon, Spacer, BarcodeScanField (kamerayla okut, onScan), BarcodeViewer (value, format — varsayılan QRCode; Code128, EAN13, DataMatrix…, label, showText, scale, fitWidth)
custom Diğer custom component'ler

Önce yeniden kullan: istenen şey zaten bir ListForm ekranının verdiği liste/ağaç/grafikse, onu primitiflerden yeniden kurma — ilgili platform düğümünü listFormCode ile bırak.

Önemli — eksik olan eklenir, yeni bileşen yazılmaz. İstenen komponent Developer Kit araçlarıyla (toolbox + konfigürasyon) büyük ölçüde — diyelim %70 — kuruluyorsa geri kalanı "yapılamaz" ya da sessizce atlanacak bir şey değildir; ama çözüm yeni bir bileşen / aile / Developer Kit ekranı üretmek de değildir. Kullanılan var olan bileşene küçük eklemeler yapılır ve komponent tamamlanır. Eklenebilecekler: eksik bir olay (onBlur, onKeyDown, onDoubleClick…), eksik bir prop (maxLength, prefix, readOnly, variant değeri, size…) ya da bileşenin var olan davranışını bozmadan eklenen küçük bir yetenek (ikon slotu, ek görünüm seçeneği, ref API'sine yeni metot). Geliştirme zinciri:

Durum Yapılacak (hepsi mevcut dosyalarda, yeni bileşen dosyası yok)
components/ui/{Name} olayı/prop'u zaten taşıyor, panel göstermiyor Olaysa: bileşen DESIGNER_PRIMARY_EVENTS kaydındaysa panel yalnız o dizideki olayları gösterir → catalog.ts'te diziye olay adını ekle (isteğe bağlı DESIGNER_EVENT_SNIPPETS başlangıç script'i). Prop'sa önce filtrelere bak: HIDDEN_GENERATED_PROPERTIES (field, form) ve catalog.ts'teki bileşene özel elemeler (Select.items, Menu.variant, Table.columns/data, Checkbox.defaultChecked); filtrede değilse componentProps.json girdisi eksiktir, tamamla
components/ui/{Name} olayı/prop'u hiç taşımıyor Aynı bileşene on* prop'unu ya da yeni prop'u ekle (varsayılanı mevcut davranışı koruyacak şekilde) → generated/componentProps.json'daki bileşen girdisine elle ekle (type: "function" olay için; dosya otomatik üretilmez, README) → olaysa adını DESIGNER_PRIMARY_EVENTS'e; değer kümesi metadata'da tip adıyla geliyorsa NAMED_UNION_OPTIONS (anahtar tsType), tek bileşene özel varsayılan/seçenek gerekiyorsa PROPERTY_OVERRIDES (anahtar bileşen → prop). Kod üreticisi (codeGenerator.ts) prop'u JSX niteliği olarak basar, olay için handle_{düğüm}_{olay} handler'ını node.events'ten kendisi üretir
Bileşen catalog.ts'teki elle tanımlı eklerden (DESIGNER_EXTRAS: PlatformIcon, BarcodeViewer…) Prop/olay sözleşmesi componentProps.json'da değil, girdinin kendisindedir; oraya ekle. Kanvasın doğrudan çizdiği düğümse componentEditor/runtimeNodes.ts sözlüğü de güncellenir
Script Builder'da da kullanılacak designerScriptDialect.ts / designerScriptRecipes.ts'te olay cancelable mı, hangi tarifler listelenecek — ekle; ref API'sine yeni metot eklediysen §7.8 tablosu
Sonrasında npm run typecheck + npm run lint; lowcode-reference.instructions.md §10.9 prop tablosu / §10.10 olay tablosu güncellenir

Sınır — bunlar yapılmaz: yeni ui/data/layout bileşeni, yeni toolbox ailesi, EXCLUDED_COMPONENTS'ten bileşen çıkarma, Developer Kit menüsüne yeni ekran/komponent, bileşenin mevcut davranışını/varsayılanını değiştiren ekleme (geriye dönük uyumluluk şart — var olan custom component'ler aynı çıktıyı vermeye devam etmeli). Ekleme "küçük" değilse (bileşeni yeniden yazmak, ikinci bir bileşen gibi davranmasını sağlamak) bu çerçeve dışıdır ve §0.8'e göre sorulur.

İhtiyaç görsel davranış değil de veri/aksiyonsa Custom Endpoint (§9.2), iş mantığıysa Dynamic Service (§9.3, §10.6) her zaman açıktır.

Keşif turu tarifinde (§0.6.6) hangi bileşene ne ekleneceği açıkça yazılır ve tarifle birlikte onaylatılır; bu çerçeve içi bir genişletmedir (§0.8).

7.3.1 Çalışan minimal doküman — bir Form + gömülü liste

Aşağıdaki Props değeri (JSON string olarak yazılır) tek başına çalışır: üstte bir CRUD formu, altında var olan bir ListForm ekranı.

{
  "visualDesigner": {
    "version": 1,
    "sourceMode": "visual",
    "canvas": { "width": "responsive" },
    "lifecycle": { "onMount": "" },
    "permissionCode": "App.Hr.AdvanceRequest",
    "dataSources": [
      { "id": "source_list",   "name": "Hr_T_AdvanceRequest — GetList",
        "method": "GET",    "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest",      "responsePath": "" },
      { "id": "source_byid",   "name": "Hr_T_AdvanceRequest — GetById",
        "method": "GET",    "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest/{id}", "responsePath": "" },
      { "id": "source_create", "name": "Hr_T_AdvanceRequest — Create",
        "method": "POST",   "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest",      "responsePath": "" },
      { "id": "source_update", "name": "Hr_T_AdvanceRequest — Update",
        "method": "PUT",    "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest/{id}", "responsePath": "" },
      { "id": "source_delete", "name": "Hr_T_AdvanceRequest — Delete",
        "method": "DELETE", "url": "/api/app/crudendpoint/Hr_T_AdvanceRequest/{id}", "responsePath": "" }
    ],
    "nodes": [
      {
        "id": "cmp_page", "type": "PageContainer", "kind": "layout", "ref": "page",
        "props": { "maxWidth": "1280px", "padding": 24, "gap": 16 },
        "events": {}, "bindings": {},
        "children": [
          {
            "id": "cmp_form", "type": "Form", "kind": "layout", "ref": "form1",
            "props": {
              "title": "::App.Hr.AdvanceRequest.Title",
              "selectEndpoint": "source_byid",
              "insertEndpoint": "source_create",
              "updateEndpoint": "source_update",
              "deleteEndpoint": "source_delete",
              "keyFieldName": "Id",
              "collectionPath": "",
              "keySource": "route",
              "keyParamName": "id",
              "autoLoad": true,
              "showToolbar": true,
              "columnCount": 2,
              "gap": 16,
              "selectPermission": "",
              "insertPermission": "",
              "updatePermission": "",
              "deletePermission": ""
            },
            "events": {}, "bindings": {},
            "children": [
              {
                "id": "cmp_amount", "type": "Input", "kind": "ui", "ref": "inputAmount",
                "props": { "size": "md" },
                "events": {},
                "bindings": { "value": { "sourceId": "cmp_form", "path": "Amount" } },
                "children": []
              },
              {
                "id": "cmp_note", "type": "Input", "kind": "ui", "ref": "inputNote",
                "props": { "textArea": true, "rows": 3 },
                "events": {},
                "bindings": { "value": { "sourceId": "cmp_form", "path": "Description" } },
                "children": []
              }
            ]
          },
          {
            "id": "cmp_list", "type": "ListView", "kind": "platform", "ref": "listView1",
            "props": {
              "listFormCode": "App.Hr.AdvanceRequests",
              "height": "520px",
              "className": "mt-4",
              "designerPermission": "App.Hr.AdvanceRequests",
              "filters": [
                {
                  "id": "filter_1", "field": "EmployeeId", "operator": "eq",
                  "source": "record", "value": "form1.EmployeeId",
                  "previewValue": "", "required": true
                }
              ]
            },
            "events": {}, "bindings": {}, "children": []
          }
        ]
      }
    ]
  }
}

Doküman yazarken uyulacak kurallar:

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

7.4 Form bileşeni

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

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

7.5 Yetki modeli (zorunlu)

İki katman:

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

Liste ekranı yetkileri: App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}.

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",
    "PermissionGroupEn": "MRP", "PermissionGroupTr": "MRP",
    "MenuEn": "Order Board", "MenuTr": "Sipariş Panosu",
    "MenuIcon": "FcKanban", "MenuOrder": 2
  },
  "IsDeletedField": false, "IsCreatedField": false,
  "InsertedRecords": {
    "LanguageKeys": ["App.Mrp.OrderBoard"],
    "PermissionGroupNames": [],
    "PermissionNames": ["App.Mrp.OrderBoard", "App.Mrp.OrderBoard.Create",
      "App.Mrp.OrderBoard.Update", "App.Mrp.OrderBoard.Delete"],
    "MenuCodes": ["App.Mrp.OrderBoard"], "DataSourceCodes": []
  }
}

Yukarıdaki düz cevap deploy girdisidir (§4.4); dosyada Menu/Language başlıklarına toplanır, ListForm: null, Fields: [] yazılır. Custom yolunda ListForm üretilmez; Title/Desc dil anahtarları da üretilmez. Export/Import/Note yetkileri bu yolda yoktur.

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

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

7.6.1 Depodaki örnek — RoleComponent + Custom wizard bağı

data/App.DeveloperKit.CustomComponents/932daf62-….json (RoleComponent, Visual mod) ile crud/AbpRoles.json · AbpUsers.json · AbpUserRoles.json custom component tarafının çalışan örneğidir: Form + Input/Checkbox düğümleri CRUD data source'larına bağlıdır, altta ListView düğümü var olan AbpIdentity.Roles ekranını gömer. Onu menüye bağlayan wizard dosyası (RoleList.json) depodan silinmiştir; bileşen şu an menüsüz, yalnızca RoutePath ile açılır ve permissionCode boştur — yetki kalıbı için örnek alma. Bağ kurulurken şu değerler eşleşir:

Bağ Kural
Wizard ComponentKind 1 (Custom)
Wizard CustomComponentName Bileşen satırının Name'i (RoleComponent)
Wizard MenuUrl Bileşenin RoutePath'i — birebir aynı; farklıysa menü başka yere gider
Wizard MenuCode / ListFormCode Yetkilerin kökü (App.Wizard.RoleList)
Bileşen Props.visualDesigner.permissionCode Wizard'ın ürettiği okuma yetkisi = MenuCode; Form komutları Otomatik modda buna .Create/.Update/.Delete ekler
Wizard InsertedRecords.PermissionNames kök + .Create + .Update + .Delete
Bileşen DataSources[].SeedFile crud/AbpRoles.json vb. — export zip'inin bu dosyaları da toplamasını sağlar

Ek dersler: Custom wizard dosyası List alanlarını (Grid/Card/…, SelectCommand) varsayılan değerleriyle taşır, kullanılmaz (§4.1); platform düğümündeki listFormCode var olan bir ekran kodudur — bileşen ekranı 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, ekranın özyinelemeli bağımlılık kapanışını (WizardExportCollector) zip'e koyar. Talep hücresindeki export düğmesi aynısını bir talebin bütün wizard'ları için tek arşivde yapar (§9.8):

wizard/{dosya}.json                           (ekranın kendisi + SubFormsDto[].Code ile bağlı
                                               alt formların wizard dosyaları, özyinelemeli)
data/App.DeveloperKit.CustomComponents/{Id}.json   (component + bağımlılıklarına süzülmüş kopya)
data/{ListFormCode}.json                      (ekranın SeedFilePath'i doluysa kendi veri aynası)
crud/{entity}.json                            (component'lerin/nesnenin kullandığı endpoint'ler)
{sql|postgres}/{object|execute}/{nesne}.sql   (SelectCommand'ın nesnesi + lookup sorgularının
                                               FROM/JOIN nesneleri + Custom yolunda CRUD
                                               endpoint'lerinin dayandığı tablolar)

Script dosyaları sabit kombinasyondan tahmin edilmez; kapsam kökündeki script klasörleri diskten taranır. Script bulunamaması uyarı üretmez — her tablo seed ile yönetilmez (ABP tabloları EF migration'larıyla kurulur); "hedefte var mı" kararını import analizi verir. Çözülemeyen bir kayıtlı referans (alt form, SeedFilePath, DataSources[].SeedFile, component satırı) indirmeyi engellemez ama sessiz de kalmaz: ExportAnalysis ucu arşivin tam içeriğini ve eksikleri döndürür, eksik varsa File Manager indirmeden önce listeyi gösterir.

Import ekranı dört adımlı bir sihirbazdır (Steps): Dosyalar (çakışma çözümü) → Kontroller (bulgular; tabloya dokunan bulguda SqlTableDesignerDialog açılır — şema farkında düzenleme, eksik tabloda initialTableName ile oluşturma modunda; deploy sonrası analiz yenilenir) → Yapılacaklar (onaydan önce ne olacağının tam listesi) → Uygulama. Geriye dönüş serbest, ileri geçiş koşulludur: çözülmemiş çakışma varken Kontroller'e, engelleyici bulgu varken Yapılacaklar'a geçilmez.

Engelleyici bulgular kaynağına göre ikiye ayrılır (WizardImportIssueDto.RequiresReexport): arşivin kendi eksikliği/bozukluğu — bozuk JSON, eksik Wizard/ListForm bölümü, karşılığı olmayan alt form / bağımlılık / component / script — hedef ortamda giderilemez, eksik olan veri arşivde yoktur; tek çözüm kaynak ortamda export'u tekrarlamaktır ve UI bunları ayrı başlıkta, düzeltme düğmesi olmadan gösterir. WizardImportValidator.Add üzerinden geçen her Error bu sınıftadır; ortamda giderilebilen engeller (eksik tablo, şema farkı) bulgularını kendileri kurar. Export önizlemesi de aynı dili konuşur: çözülemeyen bir kayıtlı referans varsa arşivin import'ta reddedileceğini söyler.

Import toplu bir işlemdir; engelleme oturum genelidir, dosya bazlı değildir. Tek bir engelleyici bulgu tüm zip'i durdurur ve dosya seçimini kaldırarak aşılamaz — yarım kurulumu önleyen budur. Bu yüzden tabloya dair her engelin ekran içinde bir çıkış yolu olmak zorundadır: yeni bir engelleyici bulgu türü eklerken bulguya TableName (+ tablo hedefte yoksa TableMissing) yazılır, yoksa kullanıcı diyalogdan çıkmadan ilerleyemez.

Sunucu tarafı üç işlemdir: analiz (New/Identical/Conflict, çakışmalar diff editörde çözülür; data/*.json satır bazlı karşılaştırılır — yalnızca zip'in taşıdığı anahtarlar, dolayısıyla ilgisiz component'ler fark olarak görünmez) → uygulama (dosya dosya yazım, önceki hâl yedeklenir, RollbackImport ile toplu geri alma) → kapanış (CompleteImport).

Kapanış bağımlılık sırasıyla çalışır ve sıra sabittir: (a) {sql|postgres}/{object,execute}/*.sql (object önce) — tablo yoksa burada oluşur, aksi halde sonraki adımlar eksik tablo yüzünden düşerdi; (b) hedefte eksik kolonlar eklenir; (c) crud/*.json → ICrudEndpointSeedApplier; (d) data/*.json → ListFormSeedDataApplier (tabloda olmayan satır eklenir, var olan satır dosyadaki değerlerle güncellenir, soft delete edilmiş satırın IsDeleted/DeletionTime/DeleterId izleri temizlenir). Wizard'ın menü/yetki/dil satırlarını taşıyan platform dosyaları (WizardSeedRowStore.ListFormCodes) bu adımda uygulanmaz. (e) wizard/*.json → ekran kurulmaz: dosyaya Pending yazılır, Wizard Manager onu "Migrate bekliyor" gösterir ve ekran, menü ve yetkiler bir sonraki DB Migrate'te gelir (§4.0) — hedefte kurulu olan ekran da dosyadaki hâliyle yeniden kurulur. Bu adım rollback kapsamında değildir. Export zip'i wizard'ın platform satırlarını (WizardSeedRowKeys.All) ilgili dosyalardan süzerek taşır; import onları hedef dosyayla anahtar üzerinden birleştirir.

Analiz doğrulama da yapar (WizardImportValidator) — dosyalar yazılmadan önce. En az bir Error varken import başlatılamaz; sunucu oturuma blocked işaret dosyası bırakır, dolayısıyla ApplyImport doğrudan çağrılsa bile reddeder. Error: bozuk JSON; Wizard/ListForm bölümü eksik wizard dosyası; ekranın ya da bir crud/{Entity}.json'un tablosu hedefte yok ve zip onu oluşturan scripti taşımıyor; data/{code}.json'un ekranı ne zip'te ne hedefte var; custom component'in Dependencies/DataSources[].SeedFile bağımlılığı bulunamıyor; Custom wizard'da bileşen adı ya da rota boş; alt form referansı karşılıksız. Warning: nesne varlığı sorgulanamadı, sağlayıcı uyuşmazlığı (sql ↔ postgres), SeedFilePath veri dosyası yok, boş dosya. Eksik tablo ölçütü "veritabanında var ya da zip onu oluşturan scripti taşıyor"dur.

Şema karşılaştırması. Tablo script'leri IF OBJECT_ID(...) IS NULL BEGIN CREATE TABLE ... END ile sarılı olduğu için hedefte tablo varsa CREATE bloğu atlanır ve yeni kolon kendiliğinden gelmez; ekran açılışta "geçersiz kolon" hatası verir. Analiz bunu yakalar: script'teki CREATE TABLE bildirimi (SqlCreateTableParser) hedefin INFORMATION_SCHEMA.COLUMNS şemasıyla karşılaştırılır. Ayrım kayıp riskine göredir: eksik kolon eklenir (kayıpsız) — Warning olarak bildirilir, çalıştırılacak cümle gösterilir (WizardImportIssueDto.Statement) ve kapanışta uygulanır; tek istisna varsayılanı olmayan NOT NULL kolonun dolu tabloya eklenmesidir, o durumda durulur. Var olan kolon değiştirilmez — uzunluk/kesinlik/ondalık/nullability farkı Error'dur ve import'u durdurur; daraltma veri keser, tip dönüşümü başarısız olabilir. Hedefteki fazla kolon Warning'dir, asla düşürülmez; hiçbir yol DROP TABLE/DROP COLUMN/ALTER COLUMN üretmez. Tip adı karşılaştırılmaz: sağlayıcı kataloğu kanonik adı raporlar (PG'de VARCHAR → character varying) ve metin karşılaştırması sahte fark üretirdi. Karşılaştırma tek yerdedir (SqlTableSchemaComparer); analiz ve kapanış aynı sonucu kullanır.

data/*.json dosyaları kapsamdaki tüm satırları taşıdığı için üzerine yazılmaz: satırlar anahtar alan üzerinden birleştirilir (gelen satır varsa güncellenir, yoksa eklenir, hedefteki diğer satırlar korunur). Sınırlar: yalnızca wizard, crud, data, 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, 200 MB açılmış toplam (zip bomb sınırı okuma sırasında da uygulanır), 500 girdi. Yetkiler: App.Listforms.Wizard.Export / .Import.

7.8 Event Script — düğüm olay script'leri

DesignerNode.events ve lifecycle.onMount içindeki her metin bir olay script'idir ve tasarımcının JavaScript sekmesinde aynı Script Builder ile düzenlenir (lehçe: designerScriptDialect.ts + designerScriptRecipes.ts). §5'teki bütün mekanizma aynen geçerlidir: // @builder başlığı, kuralı yeniden üretip metinle karşılaştırma, kod/sihirbaz geçişi, snippet ve Runtime API paneli. Fark, başlıkta ayrıca sqlRef saklanmasıdır — kurallar hangi Form ref'iyle üretildiyse geri okuma da onunla yapılır.

Script'ler Props.visualDesigner içinde saklanır ve kod üreticisi tarafından tek argümanlı (event) handler gövdesine gömülür. Metin gömüldüğü için her artefakt gibi seed dosyasına da yazılır; Custom Components ekranının seed verisi güncellenmeden bırakılan bir script veritabanı sıfırlandığında geri gelmez.

Olaylar

Olay Ne zaman event içeriği İptal edilebilir
onLoad Select cevabı forma yerleştiğinde rows, count, record, index —
onRecordChange Aktif kayıt değiştiğinde record, index, rows, count —
onFieldChange Kayda değer yazıldığında field, value, previous, record —
onNewRecord Yeni kayıt açıldığında record —
onModeChange Mod değiştiğinde mode (new/edit), record —
onBeforeSave Kaydetmeden önce record, original, payload, mode, isNew return false → kaydetmeyi durdurur
onAfterSave Kaydettikten sonra record, response, mode, isNew —
onBeforeDelete Silmeden önce record, key return false → silmeyi durdurur
onAfterDelete Sildikten sonra record —
onError Hata oluştuğunda message, action (reload/save/delete), error, record —

Bunlar Form düğümünün kayıt yaşam döngüsüdür. Diğer bileşenlerin kendi olayları vardır: Button.onClick, Input.onChange, Select.onChange, Checkbox.onChange, Tabs.onChange, Menu.onSelect, Dropdown.onSelect, Pagination.onChange, AutoComplete.onInputChange/onSelect. Bu olaylarda event bileşenin kendi yükünü taşır (Input → DOM olayı, Select → seçilen option nesnesi, Menu/Dropdown → seçilen değer).

"İşlemi iptal et" tarifi yalnızca cancelable olaylarda listelenir; diğerlerinde üretilen return false sessizce hiçbir şey yapardı.

Koşul kaynakları

Form script'inden farklı olarak koşulun kaynağı dört türden biri olabilir:

Tür Okunan ifade
Kayıt kolonu refs.<sqlRef>.getField('Kolon')
Komponent değeri refs.<ad>.getValue()
Event verisi event?.a?.b
Serbest ifade Yazdığın kodun kendisi (refs.form1.getMode())

Operatörler §5.4 ile aynıdır; üretilen ifade String((...) ?? "") === "x", Number((...) || 0) > 100 biçiminde null-güvenlidir.

Tarifler

Grup recipe Ürettiği
record setField refs.form1.setField('Status', 'Taslak') — değer modu: metin / sayı / boolean / ifade / null
record multiply · sumFields · formula · dayDiff kayıt kolonlarından hesaplayıp setField ile yazar
record copyFromEvent refs.form1.setField('OldValue', event?.previous)
record clearFields refs.form1.clearFields('City', 'District')
record save · remove · reload · newRecord await refs.form1.save() …
record goToRow nextRow() / prevRow() / firstRow() / lastRow() / goToRow(n)
record selectColumnToField refs.form1.setField('UnitPrice', refs.select1.getColumn('unitPrice'))
component selectColumnToRef refs.input1.setValue(refs.select1.getColumn('unitPrice'))
component refSetValue · refSetText · refSetProp setValue / setText / setProp('placeholder', …)
component refVisible · refEnabled · refReadOnly koşul argüman olur: refs.card1.setVisible(<koşul>) (invert ile tersi)
interaction notify notify('Kayıt güncellendi', 'success') — info/success/warning/danger
interaction confirm if (!window.confirm('Emin misiniz?')) return (ya da return false)
interaction openUrl window.open('/report?id=1', '_blank')
integration apiToField refs.form1.setField('Title', (await api.get(url))?.name)
integration apiPost await api.post(url, refs.form1.getRecord()) — gövde: kayıt / event / yok
integration custom Serbest satır
flow cancel · stop · log return false · return · console.log(...)

Kayıt tarifleri bir Form ref'i gerektirir; sayfada Form yoksa kart "eksik" kalır ve script'e girmez.

Runtime — script'in kapsamındakiler

Her düğüm ref adıyla adreslenir; bir script başka bir bileşeni prop geçirmeden değiştirebilir.

Herhangi bir refs.<ad> Ne yapar
getValue() / setValue(x) Değeri okur/yazar (Form içindeyse kayda, değilse bileşenin kendi state'ine)
show() / hide() / setVisible(b) / isVisible() Görünürlük
setEnabled(b) / isEnabled() / setReadOnly(b) Etkinlik ve salt okunurluk
getProps() / setProps(patch) / setProp(ad, deger) / setText(metin) Prop'ları çalışma zamanında ezer
reset() Bu ref üzerindeki tüm ezmeleri kaldırır
Select/AutoComplete/Menu ref'i Ne yapar
getSelectedOption() / getOptions() / getLabel() Seçili option nesnesi, tüm seçenekler, etiket
getColumn('yol') / getColumnNames() Seçili satırın ekranda görünmeyen kolonlarını okur (binding'deki columns)
Form ref'i Ne yapar
getRecord() / setRecord(o) / getOriginal() Aktif kayıt ve yüklendiği hâli
getField(yol) / setField(yol, x) / setFields({...}) / clearFields(...) Kolon yazma — her yazma onFieldChange tetikler
getChanges() / hasChanges() Değişen kolonlar (anahtar hariç)
getKey() / getKeyField() Anahtar değeri / adı
getRows() / getRowCount() / getIndex() Yüklenen satırlar ve konum
goToRow(i) / nextRow() / prevRow() / firstRow() / lastRow() Gezinme
getMode() / isNew() / isBusy() / getError() / setError(m) Durum
await save() / remove() / reload() / newRecord() CRUD komutları (Form'un endpoint slotlarını kullanır)
Genel Ne yapar
event Olayın yükü
await api.get/post/put/patch/delete/request apiService üzerinden kimlik doğrulamalı HTTP; api.errorMessage(error) kullanıcıya gösterilecek mesajı çıkarır
notify(mesaj, tip) Toast (info varsayılan)
React, components/ui bileşenleri, apiService, translate, checkPermission §7.1.2'deki kapsam burada da geçerlidir

Sınırlar: script'ler tasarımcı dokümanına gömülüdür, aralarında paylaşılan modül yoktur — tekrar eden mantık ya bir Custom Endpoint'e ya da Dynamic Service'e taşınır. Kullanıcıya görünen metin translate('App.…') ile verilir; yetki ile gizlenecek bir davranış checkPermission ile sarılır.


8. Artefakt E — SQL nesneleri

8.1 Tablo tasarımcısı (SqlTableDesignerDialog) — çalışma mantığı

Diyalog altı adımlı bir mini sihirbazdır: Kolonlar → Ayarlar → Index'ler → İlişkiler → CRUD (yalnız App.SqlQueryManager.CrudEndpoints yetkisi varsa) → SQL önizleme. Prompt ile tablo üretirken bu adımların çıktısını sen yazarsın; kurallar aynıdır.

Kolonlar adımı. Her kolon: ad, tip, uzunluk, null izin, varsayılan değer, açıklama. Tip listesi (SQL Server): nvarchar, nvarchar(MAX), int, bigint, decimal (18,4), float, bit, datetime, datetime2, date, uniqueidentifier, money.

Sağ üstteki kolon şablonları hazır kolon setlerini ekler (var olan adla çakışan atlanır) — seed dosyası yazarken aynı setleri kullan:

Şablon Eklediği kolonlar
Multi-Tenant TenantId (uniqueidentifier, null)
Full Audited Id (uniqueidentifier, PK, NEWID()), CreationTime (datetime2, GETUTCDATE()), CreatorId, LastModificationTime, LastModifierId, IsDeleted (bit, 0), DeletionTime, DeleterId
Workflow UserName (nvarchar(256)), Status (nvarchar(50)), Date (datetime), Description (nvarchar(200)) — WorkflowDto'nun dört alan adının karşılığı
Todo Title (nvarchar(300)), Status (nvarchar(50), N'Backlog'), Description, DueDate, Tags, Assignees, Priority, Completed (bit, 0), SortOrder (int, 0), Timesheets, SubTasks — Kanban ekranının veri tabanı
Gantt Title, ParentId (uniqueidentifier, null), StartDate, EndDate (datetime2), Progress (int, 0)
Scheduler UserName, Text, Description, StartDate, EndDate, AllDay (bit, 0), RecurrenceRule, RecurrenceException

Yani "onaylı talep formu" ya da "kanban panosu" isteğinde tablo kolonlarını uydurma: ilgili şablon setini Full Audited + Multi-Tenant ile birleştir.

Ayarlar adımı. Modül (menü ağacından seçilir) + entity adı girilir; tablo adı otomatik üretilir: {ModülÖnEki}_{T|D}_{EntityName} — T tabloda TenantId kolonu varsa, yoksa D. (Platformun kendi H/B harfleri TableNameResolver'a aittir; tasarımcı proje tabloları için yalnız T/D üretir.)

Index'ler: PrimaryKey / UniqueKey / Index, clustered seçimi, çok kolonlu (ASC/DESC). İlişkiler (SqlTableRelation): OneToOne/OneToMany, FK kolonu → hedef tablo.kolon, ON DELETE/ON UPDATE davranışı (NoAction/Cascade/SetNull/Restrict), zorunluluk.

Deploy zinciri — sırayla:

  1. Üretilen SQL hedef veri kaynağında çalıştırılır (yeni tabloda CREATE, düzenleme modunda ALTER diff'i).
  2. Seed dosyası kaydedilir: her zaman tam CREATE TABLE script'i (diff değil), save-table-script ucu ile configs/seeds/{kapsam}/{sql|postgres}/object/{TabloAdı}.sql altına (SeedPathResolver). Dosya kaydı başarısız olsa deploy geri alınmaz — log'a düşer.
  3. CRUD adımında işaretlendiyse endpoint'ler üretilir (crud/{Entity}.json); onay kutusu varsayılan kapalıdır, Custom Component'in kullanacağı tabloda açılır.

Kullanıcı açıkça "tenant yok / audit yok" demedikçe Multi-Tenant + Full Audited setleri dahil edilir; her seferinde sorma.

Tanım tablosu → IsActive otomatik. Tanım tablosu, başka ekranların seçtiği kod/ad/parametre listesidir. Bir ekranın tanım olup olmadığına sabit bir ad listesiyle değil talebin içeriğine bakılarak karar verilir: talep ekranı tanımlamaların altına koyuyorsa (başlıktaki ya da açıklamadaki menü yolu, "tanım/parametre listesi" ifadesi) ya da amaç ekranın başka ekranlara seçim kaynağı olmasıysa ekran tanımdır. Karar analizde verilir ve gerekçesiyle yazılır; üretim kararı yeniden vermez. Tanım olan yeni tabloya iş kolonlarının sonuna, audit kolonlarından önce sorulmadan eklenir:

Katman IsActive
Tablo bit NOT NULL DEFAULT 1 (create_table'da iş kolonu olarak, defaultValue: 1)
Liste Görünür kolon, dxCheckBox
Form dxCheckBox, yeni kayıtta true (InsertFieldsDefaultValueJson)
Widget Toplam · Aktif · Pasif kartları (§4.6.1 madde 2)
Demo veri Satırlar IsActive: true ile yazılır
Dil App.Listform.ListformField.IsActive (var olan anahtar)

Pasife alma silmenin yerine geçmez; silme açık kalır. Analiz bunu Kararlar'a değil Veri modeli'ne yazar (§0.12 madde 5); tanım tablosu olduğunu da orada belirtir. Var olan tabloya kolon eklenmez (şema değişikliği ayrı iştir).

8.2 View tasarımcısı (SqlViewDesignerDialog) — çalışma mantığı

Bir ekran birden fazla tablodan besleniyorsa, SelectCommandType: 4 (Query) yerine tasarlanmış bir view (SelectCommandType: 2) tercih edilir: yeniden kullanılabilir, seed'lenir ve tasarımcıda görsel olarak düzenlenebilir kalır.

Diyalog SSMS'in view designer'ının karşılığıdır: üstte diagram paneli, altta criteria grid, sağda üretilen T-SQL önizlemesi. Model (viewModel.ts):

Parça Alanlar
Kaynak (ViewSource) şema + nesne adı + takma ad; JoinKind (INNER/LEFT/RIGHT/FULL/CROSS); join koşulları (sol.kolon <op> sağ.kolon, op: = <> > >= < <=); türetilmiş alt sorgu (derivedSql) ve CROSS/OUTER APPLY
Criteria satırı (CriteriaRow) kolon ya da serbest ifade (expression — alias zorunlu), Alias, Output (SELECT'e girsin mi), GroupMode, SortType + SortOrder, filters[] (index = OR grubu; hücre serbest yüklem: > 100, LIKE '%abc%', IN (1,2), IS NULL)
Ayarlar (ViewSettings) şema + view adı, DISTINCT, Group By anahtarı (sigma), TOP n / TOP n PERCENT

GroupMode değerleri: GroupBy, Where (satır çıktıya girmez, yalnızca filtre taşır), SUM, COUNT, COUNT_DISTINCT, AVG, MIN, MAX. Sigma kapalıyken Group By sütunu ve GROUP BY üretimi yoktur.

Deploy, üretilen CREATE OR ALTER VIEW'ı hedef veri kaynağında çalıştırır — seed dosyası yazmaz (aşağıdaki kalıcılık uyarısı). Var olan bir view açılırken tanım parseViewSql ile geri okunmaya çalışılır; kanonik şekle uymayan tanımda diyalog ham SQL moduna düşer ve tasarımcı devre dışı kalır — tasarlanmış bir view'ı elle düzenlemek tasarımcıyı kaybettirir.

Kalıcılık — kritik ayrım: diske otomatik seed yazan tek tasarımcı tablo tasarımcısıdır. View Designer'ın deploy'u, sorgu editöründen çalıştırılan CREATE PROCEDURE/CREATE FUNCTION ve her türlü elle DDL yalnızca veritabanına gider. Bu nesnelerin configs/seeds/{kapsam}/{sql|postgres}/object/{Ad}.sql dosyasını sen yazarsın — prompt ile üretimde zaten dosyayı doğrudan yazdığın için bu senin doğal yolun; uygulama içinden üretim yapıldıysa dosyaya alınmamış nesne veritabanı sıfırlanınca kaybolur.

Klasör sözleşmesi: object = yeniden oluşturulabilir nesne (tablo/view/fonksiyon/prosedür), execute = bir kez çalıştırılacak script (§8.4).

8.2.1 SP / Function üretimi — ne zaman, nasıl

List ve Custom komponent üretirken SQL nesnesi ihtiyacı dört kalıpta çıkar:

İhtiyaç Nesne Nereye bağlanır
Çok tablolu okuma View SelectCommandType: 2, SelectCommand: "{Modul}_V_{Ad}"
Parametreli okuma (kullanıcıya/duruma göre değişen küme) Table-Valued Function SelectCommandType: 3, SelectCommand: "dbo.Fn(@USERID)" — parametre token'ları çözülür
Yazma sonrası iş mantığı (stok, log, durum, kuyruk) Stored Procedure *BeforeCommand/*AfterCommand → EXEC {Modul}_S_{Ad} @ID, @USERID (§4.8)
Zamanlanmış iş gövdesi Stored Procedure BackgroundWorker.BeforeSp (§9.10)
Veri dolumu / migration sonrası düzeltme Prosedür olarak execute/ dosyası FinalSeeder dosya adındaki prosedürü çağırır (§8.4)

Sorgu editörünün hazır şablonları (select, insert, update, delete, create-view, create-procedure, create-scalar-function, create-table-function) her iki diyalektte de vardır; seed dosyası yazarken aynı iskeleti kullan ve §8.3'ün idempotency kuralına çevir (CREATE OR ALTER / CREATE OR REPLACE). Nesne editörde açıldığında baştaki CREATE/ALTER her zaman CREATE OR ALTER'a normalize edilir — dosyada da böyle yaz.

8.3 SQL seed dosyası yazım kuralları

Kural Açıklama
Dosya adı Nesne adıyla birebir aynı: Hr_T_AdvanceRequest.sql
Klasör object = idempotent nesne tanımı · execute = nesne olarak da oluşturulur, ayrıca tüm migration'lar bittikten sonra çalıştırılır
Sağlayıcı Hedef SQL Server ise sql/, PostgreSQL ise postgres/. İkisini birden destekliyorsan iki dosya da yazılır; içerikler aynı nesnenin iki diyalektidir.
İdempotency SQL Server: CREATE OR ALTER VIEW/PROCEDURE/FUNCTION, tablolar için IF NOT EXISTS koruması. PostgreSQL: CREATE OR REPLACE VIEW/FUNCTION, CREATE TABLE IF NOT EXISTS.
Şema dbo kullanılır; PostgreSQL tarafında tanımlayıcılar çift tırnaklıdır (dbo."Hr_T_AdvanceRequest") çünkü kolon adları PascalCase'tir.
Toplu ayraç SQL Server'da prosedür sonunda GO kullanılabilir; PostgreSQL'de kullanılmaz.
Yorum Dosyanın ilk satırında ne ürettiğini yaz (-- Create View).

Tablo yazarken §8.1'deki varsayılan kolonlar dahil edilir. Örnek iskelet (SQL Server):

-- Create Table
IF OBJECT_ID(N'[dbo].[Hr_T_AdvanceRequest]', N'U') IS NULL
BEGIN
    CREATE TABLE [dbo].[Hr_T_AdvanceRequest] (
        [Id]                   UNIQUEIDENTIFIER NOT NULL CONSTRAINT [PK_Hr_T_AdvanceRequest] PRIMARY KEY,
        [TenantId]             UNIQUEIDENTIFIER NULL,
        -- iş kolonları buraya
        [CreationTime]         DATETIME2        NOT NULL,
        [CreatorId]            UNIQUEIDENTIFIER NULL,
        [LastModificationTime] DATETIME2        NULL,
        [LastModifierId]       UNIQUEIDENTIFIER NULL,
        [IsDeleted]            BIT              NOT NULL CONSTRAINT [DF_Hr_T_AdvanceRequest_IsDeleted] DEFAULT (0),
        [DeletionTime]         DATETIME2        NULL,
        [DeleterId]            UNIQUEIDENTIFIER NULL
    );
END

Id tipini ne seçtiysen wizard dosyasındaki KeyFieldDbSourceType ile aynı olmalıdır (UNIQUEIDENTIFIER → 9, INT IDENTITY → 11). Uyumsuzluk sessiz veri hatası üretir.

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

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

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

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

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

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

8.5 Yeni entity/tablo eklerken izlenecek yol

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

9. Artefakt F — CRUD Endpoint ve Developer Kit

Bu bölüm CRUD Endpoint (9.1), elle yazılmış SQL'i API'ye çeviren Custom Endpoint (9.2), runtime derlenen Dynamic Service (9.3), Developer Kit ekranları (9.4–9.8), platformun hazır gelen konfigürasyon ekranlarını (9.9–9.14) ve liste verisi seed dosyasını (9.15) kapsar.

9.1 CRUD Endpoint seed dosyası

Konum: configs/seeds/{kapsam}/crud/{EntityName}.json · Okuyan: CrudEndpointSeedApplier (migrate'te CrudDataSeeder, import kapanışında CompleteImport üzerinden — kural tek yerdedir)

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

Uygulama politikası damga karşılaştırmalıdır. Eksik endpoint her zaman eklenir. Mevcut bir kayıt yalnızca dosyanın GeneratedAt damgası kaydın LastModificationTime/CreationTime değerinden yeniyse güncellenir; dosyada artık yer almayan bir operasyon yalnızca aynı koşulda silinir. Aksi halde kayıt korunur ve log'a drift uyarısı düşer. Amaç: CRUD Endpoint Manager'dan yapılan bir revizyon, depodan gelen eski bir dosya yüzünden migrate sırasında geri alınmasın; gerçekten yeni bir dosya (başka ortamda üretilmiş, import edilmiş) ise değişiklik yayılsın. GeneratedAt taşımayan bir dosya hiçbir zaman yeni sayılmaz.

  • Üretim/aktivasyon yeri: SQL Query Manager nesne gezgininde tablo satırının CRUD Endpoints aksiyonu, toolbar'daki toplu üretim, tablo tasarımcısının deploy öncesi adımı ve Wizard'ın veri ayarları adımı — hepsi aynı CrudEndpointDialog.
  • Yetki: App.SqlQueryManager.CrudEndpoints. İzin yoksa butonlar görünmez ve crud-endpoint-generate uçlarının tamamı reddedilir.
  • Dispatcher üzerinden çağrı kapıları: App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove; endpoint bazlı User/Role/Global kuralları bunun üstünde denetlenir.
  • GetList filtre sözleşmesi §7.2'deki ile aynıdır.

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

Ekran: /admin/list/App.DeveloperKit.CustomEndpoints (bir ListForm ekranıdır). Entity: CustomEndpoint (FullAuditedEntity + IMultiTenant — kayıtlar kiracı bazlıdır).

Alan Anlam
Name / Description Tanımlayıcı
Url Yayınlanacak yol — dispatcher önekinin arkasına eklenir (aşağıya bak)
Method GET / POST / PUT / DELETE
DataSourceCode Hangi bağlantıdan okunacağı. "!Tenant" özel değeridir: isteği yapan kullanıcının kendi tenant veritabanı kullanılır; başka her değer Sas_H_DataSource kaydına gider
Sql Parametreli sorgu — @paramAdi yer tutucuları ParametersJson'daki adlarla eşleşir. String birleştirme yasak
ParametersJson CustomEndpointParameter[]
PermissionsJson CustomEndpointPermission[]

9.2.1 Dispatcher — istek nasıl çözülür

Dört sabit uç tüm custom endpoint trafiğini karşılar (CustomEndpointAppService):

GET|POST|PUT|DELETE  /api/app/custom-endpoint/{**path}

Çözüm sırası:

  1. Verb kapısı: App.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove — kullanıcıda bu yetki yoksa istek endpoint'e hiç ulaşmaz.

  2. Eşleme: istek yolu URL-decode edilir, /api/app/custom-endpoint öneki atılır, başına ve sonuna / garanti edilir; kayıt path.StartsWith(Url) && Method == verb ile bulunur. Bu bir önek eşlemesidir — Url: "/orders" hem /orders/ hem /orders/123/ isteklerini yakalar; ilk eşleşen kayıt kazanır. Çakışmayan, ayırt edici yollar seç.

  3. Endpoint yetkisi: PermissionsJson kurallarından en az biri tutmalıdır:

    ResourceType ResourceId Kim geçer
    Global — Verb kapısını geçen herkes
    Role rol adı O roldeki kullanıcılar
    User kullanıcı adı (UserName, id değil) O kullanıcı

    Boş PermissionsJson = kimse çağıramaz (401). Endpoint'i açmak bilinçli bir karardır; en azından bir Role kuralı yaz, herkese açıksa Global yaz.

  4. Parametre çözümü (sırayla; hepsi tek param sözlüğünde toplanır ve SQL'e adıyla bağlanır):

    Type Değer nereden Kurallar
    Static DefaultValue Token'lar çözülür (aşağıda)
    Query ?name=… Yoksa: IsRequired → hata; değilse DefaultValue (token'lı)
    Path URL segmenti Path alanı bir desendir: /orders/:id/ — :name hangi segmentteyse gerçek yoldan aynı sıradaki segment okunur
    Body JSON gövde ya da form (yalnız POST/PUT) Yalnızca kök seviye özellikler okunur, ad eşleşmesi büyük/küçük harf duyarsızdır. Path alanı Body'de kullanılmaz — iç içe yol çözülmez, gövdeyi düz tut

    Static/varsayılan değerlerde çözülen token'lar (DefaultValueHelper): @USERID, @USERNAME, @ROLES, @DATE, @NOW, @DAY, @MONTH, @YEAR, @TENANTID (host bağlamında = '@TENANTID' deseni IS NULL'a çevrilir).

  5. Çalıştırma: sorgu seçilen bağlantıda QueryAsync(Sql, conn, param) ile koşar; sonuç satır dizisi olarak JSON döner. Hata → 500 + { Message, CorrelationId } (iç hata mesajı kullanıcıya sızdırılmaz). Sorgu, dönüşsüz bir UPDATE/EXEC de olabilir — Method'u anlamına uygun seç.

// ParametersJson
[
  { "Type": "Query", "Name": "customerId", "DefaultValue": "", "Path": "", "IsRequired": true },
  { "Type": "Path",  "Name": "id",         "DefaultValue": "", "Path": "/orders/:id/", "IsRequired": true },
  { "Type": "Body",  "Name": "note",       "DefaultValue": "", "Path": "", "IsRequired": false },
  { "Type": "Static","Name": "userId",     "DefaultValue": "@USERID", "Path": "", "IsRequired": true }
]

// PermissionsJson
[ { "ResourceType": "Role", "ResourceId": "SalesManager" },
  { "ResourceType": "User", "ResourceId": "ali.akman" } ]

Endpoint tanımlarını düzenleme yetkileri ListForm ekranının App.DeveloperKit.CustomEndpoints{,.Create,.Update,.Delete} yetkileridir — çağırma yetkilerinden (.Get/.Post/.Put/.Remove + PermissionsJson) ayrıdır.

Seed: configs/seeds/{kapsam}/data/App.DeveloperKit.CustomEndpoints.json (§9.15). Ekranın SeedSyncInsert/Update/Delete bayrakları açıktır: ekrandan eklenen/değiştirilen/silinen kayıt dosyaya yansır, migrate'te eksikse geri eklenir. Prompt ile üretimde satırı bu dosyaya yaz (Id sabit GUID; ParametersJson/PermissionsJson kaçışlanmış string).

Ne zaman CRUD, ne zaman Custom Endpoint? Tek tabloya standart CRUD gerekiyorsa CRUD Endpoint (otomatik üretilir, crud/{Entity}.json ile seed'lenir, filtre sözleşmesi hazırdır). Birleştirilmiş/şekillendirilmiş sorgu, rapor, farklı kaynaktan okuma ya da tek atımlık yazma gerekiyorsa Custom Endpoint. EditorScript'in api() çağrısı ve Visual Designer data source'ları için de tercih edilen hedef budur.

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

Ekran: liste /admin/list/App.DeveloperKit.DynamicServices (ListForm); kod editörü aynı listenin toolbar/satır butonlarından açılan DynamicServiceEditor diyalogu (Monaco). Entity: DynamicService (FullAuditedEntity + IMultiTenant). Karar sırasının 3. adımıdır: konfigürasyon ve SQL yetmediğinde gelir, çekirdek kod değişikliğinden önce denenir.

Alan Anlam
Name Benzersiz servis adı (DynamicCustomerAppService)
DisplayName / Description Kullanıcı dostu başlık ve açıklama
Code Tam C# kaynak dosyası — using'ler ve namespace dahil
ControllerName Swagger'da görünen ad (sınıf adından türetilir)
PrimaryEntityType Kullandığı ana entity (bilgi amaçlı)
IsActive Pasifse route'lanmaz; açılışta da yüklenmez
CompilationStatus 0 Pending · 1 Success · 2 Failed · 3 InProgress
LastCompilationError / LastSuccessfulCompilation Son derleme sonucu
Version / CodeHash Kod her değiştiğinde Version artar, SHA-256 hash yenilenir, durum Pending'e döner

9.3.1 Kod sözleşmesi

Editörün başlangıç şablonu geçerli asgari iskelettir:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Microsoft.AspNetCore.Authorization;

namespace DynamicServices
{
    [Authorize]
    public class DynamicCustomerAppService : ApplicationService
    {
        public virtual async Task<string> GetHelloWorldAsync()
            => await Task.FromResult("Hello World from Dynamic AppService!");
    }
}
  • Sınıf AppService ya da ApplicationService ile bitmeli ve ApplicationService tabanından türemelidir; aksi hâlde derlense bile controller olarak keşfedilmez.
  • Bağımlılıklar constructor injection ile gelir (DynamicControllerActivator çözer): IRepository<TEntity, TKey>, ICurrentTenant, ICurrentUser, platform servisleri — derleme referansları yüklü tüm Volo.Abp, Sozsoft.Platform ve Microsoft.AspNetCore assembly'leridir, yani platform entity ve servislerinin tamamı görünürdür.
  • Route türetimi (ABP konvansiyonu): sınıf adından AppService eki atılır → kebab-case; metot adından Async eki atılır → kebab-case. DynamicCustomerAppService.GetCustomersAsync → GET /api/app/dynamic-customer/get-customers.
  • HTTP verb sezgisi: Get/Find/List* → GET · Create/Insert/Add* → POST · Update/Edit* → PUT · Delete/Remove* → DELETE · diğerleri: parametresiz/basit parametreli → GET, karmaşık parametreli → POST. Metodu buna göre adlandır.

Güvenlik sınırları — derleme öncesi metin taraması yapılır ve şunlar reddedilir: System.IO, System.Diagnostics, System.Environment, System.Net.Sockets, System.Reflection.Emit, System.Runtime.InteropServices, Microsoft.Win32, System.Security.Cryptography, System.Net.NetworkInformation namespace'leri; Process, File, Directory, FileStream, StreamWriter/Reader, Socket, TcpClient, UdpClient, HttpWebRequest, WebClient, Assembly.Load, ThreadStart tipleri; unsafe kod. Tarama düz Contains ile çalışır — yasaklı bir ad yorum satırında ya da başka bir tanımlayıcının içinde geçse bile (Profile içindeki File gibi) derleme reddedilir; adlandırmanı buna göre seç. Dosya işi gerekiyorsa platformun dosya altyapısını, HTTP çağrısı gerekiyorsa ABP'nin IHttpClientFactory soyutlamalarını kullan.

9.3.2 Yaşam döngüsü

TestCompile ──► Publish ──► kuyruğa kayıt ──► assembly tenant bağlamıyla yüklenir
   (Roslyn,        (önce yine derler;          (DynamicAssemblyRegistrationService,
    hata satır/     başarısızsa yayınlamaz)     arka plan servisi)
    kolonuyla                                        │
    UI'a döner)                              ActionDescriptorChangeProvider
                                             MVC route tablosunu tazeler —
                                             ► uygulama YENİDEN BAŞLATILMAZ
  • Uygulama açılışında IsActive && CompilationStatus == Success olan tüm servisler yeniden derlenip yüklenir — kod veritabanında yaşar, assembly diske yazılmaz.
  • Assembly'ler tenant başına izlenir; bir kiracının servisi diğerinde route'lanmaz. Yeniden publish eski assembly kaydını düşürür, yenisini yükler.
  • Derleme Debug seviyesinde, allowUnsafe: false ile yapılır; hatalar satır/kolon bilgisiyle LastCompilationError'a ve UI'a düşer.

Yönetim yetkileri: App.DeveloperKit.DynamicServices{,.Create,.Update,.Delete,.Manage,.TestCompile,.Publish,.ViewCode} — Manage aktif/pasif ve toplu işlemler, ViewCode kaynak görüntüleme kapısıdır. Servisin kendi uçlarının yetkisi ise koddaki [Authorize(...)] özniteliğidir; sınıfı yetkisiz bırakma, gerekiyorsa data/Abp.Identity.Permissions.json'a yeni yetki tanımla (CLAUDE.md kesişen değişiklik kuralı).

Seed: data/App.DeveloperKit.DynamicServices.json (§9.15; seed bayrakları açık — dosya ilk kayıtta oluşur). Veritabanı sıfırlanınca kayıt bu dosyadan döner; açılışta yalnızca IsActive && CompilationStatus == Success olanlar derlendiği için dosyadaki satır bu iki değeri taşımalıdır. Code tam C# kaynağıdır, JSON string olarak kaçışlanır.

Yazarken dotnet.instructions.md §5 geçerlidir: tenant bağlamını ICurrentTenant üzerinden kullan, SQL'i parametreli çalıştır, kullanıcıya görünen metni dil anahtarından ver, sabit/secret gömme.

9.3.3 Ortak altyapı — Data Source çözümü (host + tenant bağlantıları)

ListForm'un DataSourceCode'u, Custom Endpoint'in "!Tenant"'ı, worker'ların DataSourceCode'u ve widget sorguları — hepsi aynı kayıt kümesinden beslenir: Sas_H_DataSource (ekran: /admin/list/App.DataSource). Kayıt üç alandır: Code, DataSourceType (1 Mssql · 2 Postgresql), ConnectionString.

Tabloda iki tür kayıt yan yana durur:

Tür Code Nereden gelir
Host / harici bağlantı Elle verilen ad ("Default", "ErpReadOnly"…) Ekrandan ya da wizard'ın DataSourceConnectionString alanından
Tenant bağlantısı Kiracının adı Otomatik: Tenants ekranından bir kiracıya bağlantı dizesi tanımlanınca TenantConnectionStringEventHandler Code = tenant adı olan kaydı ekler/günceller/siler. Elle dokunma

Çözüm kuralı (DataSourceManager.GetDataSourceAsync(isTenant, code)):

isTenant && CurrentTenant.Name != null  →  önce Code == {tenant adı} aranır
bulunamazsa / isTenant değilse          →  Code == {dataSourceCode} aranır

Pratik sonuçları:

  • IsTenant: true bir ekran, kiracının kendi veritabanı kaydı varsa oradan okur; yoksa ekranın DataSourceCode'una (çoğunlukla "Default") düşer. Yani kiracıya özel veritabanına geçiş, ekran tanımına dokunmadan Tenants ekranından yapılır.
  • Custom Endpoint'te DataSourceCode: "!Tenant" aynı mekanizmanın kısa yoludur: çağıran kullanıcının tenant bağlantısı kullanılır.
  • DataSourceType, bağlantı metninde Server geçiyorsa Mssql, aksi hâlde Postgresql olarak türetilir (wizard ve tenant event handler aynı kuralı kullanır).
  • Bağlantı dizesi hiçbir seed dosyasına, koda ya da örneğe yazılmaz — yalnızca bu tabloda ve kiracı kaydında durur; dokümana/örneğe yer tutucu yaz.

9.4 Developer Kit menüsü — tam kapsam

Menü: App.DeveloperKit (üst) → aşağıdaki alt ekranlar. Her satır bu dokümandaki bölümüne bağlıdır; hiçbiri "kod yaz" adımının yerine geçmez, hepsi ondan öncedir.

Ekran Route Yetki Ne üretir Bölüm
SQL Query Manager /admin/sqlQueryManager App.SqlQueryManager SQL nesneleri, CRUD endpoint'ler 9.5
Custom Endpoints /admin/list/App.DeveloperKit.CustomEndpoints App.DeveloperKit.CustomEndpoints Elle yazılmış SQL'den REST endpoint 9.2
Dynamic Services /admin/list/App.DeveloperKit.DynamicServices App.DeveloperKit.DynamicServices Runtime derlenen C# AppService 9.3
Components /admin/list/App.DeveloperKit.CustomComponents App.DeveloperKit.CustomComponents 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 (menü yolu, bağımlılık ağacı, silme), export/import 4, 7.7
MCP Servers /admin/list/App.DeveloperKit.McpServers App.DeveloperKit.McpServers (+ .Invoke, .Write) Dış yapay zekâ istemcisinin /mcp erişimi ve açık araçlar (AllowedTools) 0.10

9.5 SQL Query Manager — bileşen bileşen

Tek ekranda toplanmış araç seti (SqlQueryManager.tsx):

Panel / araç Dosya Yapabildikleri
Veri kaynağı seçici — DataSource kayıtları arasında geçiş; SQL Server ve PostgreSQL
Nesne gezgini SqlObjectExplorer.tsx Tablolar, view'lar, stored procedure'ler, fonksiyonlar ve hazır şablonlar (SqlObjectExplorerDto); arama, nesne tanımını açma, kopyalama, silme, satır bazında CRUD Endpoints aksiyonu, çoklu seçimle toplu endpoint üretimi
Sorgu editörü SqlEditor.tsx Monaco tabanlı; çalıştırma (ExecuteQueryAsync), çoklu sekme, nesne tanımını editöre yükleme (GetNativeObjectDefinitionAsync), tablo create script'i üretme (GetTableCreateScriptAsync)
Sonuç grid'i SqlResultsGrid.tsx Sonuç kümesi görüntüleme ve dışa aktarma
Tablo tasarımcısı SqlTableDesignerDialog.tsx 6 adımlı diyalog: kolonlar (+ hazır şablon setleri) → ayarlar (ad üretimi) → index'ler → FK ilişkileri → CRUD seçimi → SQL önizleme; deploy'da seed dosyasını da yazar — tam mekanik §8.1
View tasarımcısı SqlViewDesignerDialog.tsx + sqlViewDesigner/ Diagram + criteria grid + T-SQL önizleme; tek yönlü parse, ham SQL moduna düşme — tam mekanik §8.2
CRUD endpoint diyaloğu CrudEndpointDialog.tsx Endpoint üretme, aktif/pasif etme, test, silme; aynı diyalog Wizard'ın veri adımında da açılır
DB Migrate DbMigrateButton Migration + seed tetikleme (App.Setup.Migrate)
File Manager kısayolu /admin/files configs/seeds altındaki seed dosyalarının yönetimi
Kolon bilgisi GetTableColumnsAsync Wizard alan adımının ve tasarımcıların kolon kaynağı

Servis yüzeyi (SqlObjectManagerAppService): GetAllObjectsAsync, ExecuteQueryAsync, GetNativeObjectDefinitionAsync, GetTableColumnsAsync, GetTableCreateScriptAsync, SaveTableScriptAsync (tablo seed'i), DeleteSqlDataFilesAsync (nesne silinince seed dosyası da silinir).

Editör davranışları:

  • Nesne gezgininden bir tanım editöre yüklendiğinde baştaki CREATE/ALTER her zaman CREATE OR ALTER'a normalize edilir — aynı metin tekrar tekrar çalıştırılabilir kalsın diye.
  • Hazır şablonlar: select / insert / update / delete / create-view / create-procedure / create-scalar-function / create-table-function; aktif veri kaynağının diyalektine göre (SQL Server / PostgreSQL) gelir.
  • Sorgu, seçili DataSource üzerinde çalışır — tenant bağlamı otomatik değildir; yazdığın DDL/DML hangi kaynağı seçtiysen oraya gider.

Kalıcılık kuralı (§8.2'deki uyarının özeti): seed dosyasını otomatik yazan tek akış tablo tasarımcısının deploy'udur. View Designer deploy'u ve editörden çalıştırılan her DDL (view/SP/function) yalnızca veritabanına gider; kalıcı olması için configs/seeds/{kapsam}/{sql|postgres}/{object|execute}/{ad}.sql dosyası ayrıca yazılır. Prompt ile üretimde bu ayrım seni etkilemez — dosyayı zaten sen yazarsın (§0.2 sırası) — ama "uygulamadan ürettim, seed'i var mı?" sorusunun cevabı budur.

9.6 ListForm editörü — tam sekme envanteri

Wizard bir ekranı üretir; ince ayar /admin/listform/edit/{listFormCode} üzerinden yapılır (kaynak: edit/FormEdit.tsx + FormTab*.tsx). Görünen sekmeler ekranın ListFormType'ına bağlıdır (tabVisibilityConfig): Form tipinde yalnızca Detay, Veritabanı, Yetkiler, Komutlar, Düzenleme, Alanlar, Form, Widget ve Alt Form sekmeleri açılır; List tipinde tamamı.

Her sekmenin düzenlediği kolonlar — alan alan sözleşmeleri lowcode-reference §1–§9'dadır:

Sekme Düzenlediği Sözleşme
Detay CultureName, ListFormType, Title, Name, Description, ShowNote, Width/Height/FullHeight, IsSubForm, SubFormsListFormType, LayoutJson (varsayılan + bayraklar) ref §5.4 · §5.6
Veritabanı beş alt sekme, aşağıda ref §1 · §4
Yetkiler PermissionJson — C/R/U/D/E/I/N yetki adları ref §5.1
Komutlar CommandColumnJson satır ekle/düzenle diyaloğu §4.9
Sütunlar ColumnOptionJson (grid genel görünümü: kenarlık, satır rengi, kolon seçici, sabitleme, RTL…) ref §2.1
Düzenleme EditingOptionJson + Popup alt nesnesi ref §3.1
Sıralama SortMode (Single/Multiple/None) §3
Filtreleme FilterRowJson, HeaderFilterJson, FilterPanelJson ref §2.6
Satır RowJson (yükseklik, metin kaydırma) ref §2.2
Arama SearchPanelJson ref §2.6
Gruplama GroupPanelJson ref §2.6
Seçim SelectionJson ref §2.4
Sayfalama PagerOptionJson ref §2.3
Durum StateStoringJson ref §2.5
Ek Filtre ExtraFilterJson satır diyaloğu §4.3.8
Form EditingFormJson — düzenleme formunun grup satırları ref §3.2
Alanlar ListFormField kayıtları — alan başına 15 alt sekme, aşağıda ref §9
Kişiselleştirme ListFormCustomization kayıtları: tür (UserUiFilter/GridState/ServerJoin/ServerWhere), ad, rol/kullanıcı, veri ref §2.5
Pivot / Tree / Gantt / Scheduler / Todo PivotOptionJson / TreeOptionJson / GanttOptionJson / SchedulerOptionJson / TodoOptionJson ref §6–§7
Alt Form SubFormsJson ref §5.2
Widget WidgetsJson ref §5.3
Workflow WorkflowJson + ListFormWorkflow düğümleri §4.7
Grafik sekmeleri (Ortak, Eksen, Bölme, Seri, Animasyon, Açıklama, Zoom, Legend, Crosshair, Export) 21 grafik JSON kolonu ref §8

Veritabanı alt sekmeleri:

Alt sekme Alanlar
Veri Kaynağı IsTenant/IsBranch/IsOrganizationUnit, DataSourceCode, SelectCommandType + SelectCommand, TableName (alias), KeyFieldName + tipi
Select DefaultFilter, SelectFieldsDefaultValueJson satır diyaloğu
Insert / Update / Delete ilgili *ServiceAddress, *Command, *BeforeCommand, *AfterCommand, *FieldsDefaultValueJson

Alan editörü (Alanlar sekmesinde bir satır açılınca; form-fields/FormFieldEdit.tsx) — 15 alt sekme; her biri ListFormField'ın bir kolonunu düzenler:

Alt sekme Kolon Sözleşme
Detay FieldName, CaptionName, PlaceHolder, BandName, AllowSearch, IsActive, Visible, SourceDbType, Alignment, Format, EditorOptions (ham) ref §9.7
Seçenekler ListOrderNo, Width, SortIndex/SortDirection, AllowEditing/AllowAdding, EditorType2, EditOrderNo/EditGroupOrderNo/ColSpan + Script Builder (EditorScript, §5) ve Options Builder (EditorOptions, §6) ref §9.7
Yetkiler PermissionJson (Deny + C/R/U + E/I) ref §5.1
Lookup LookupJson (tip, cascade, sorgu) §4.3.4
Kişiselleştirme ColumnCustomizationJson (sabitleme) ref §9.4
Filtreleme ColumnFilterJson ref §9.1
Başlık Filtresi ColumnHeaderJson ref §9.2
Gruplama GroupingJson ref §9.3
Grup Özeti / Toplam GroupSummaryJson / TotalSummaryJson ref §9.5
Join JoinTableJson ref §9.6
CSS ColumnCssClass / ColumnCssValue ref §9.7
Doğrulama ValidationRuleJson §4.3.6
Koşullu Biçim ColumnStylingJson §4.3.7
Pivot PivotSettingsJson ref §6.2

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

Editörün üstündeki dil/rol/kullanıcı seçicileri varyant düzenler (ref §5.7): seçim yapınca aynı ekranın o bağlama özgü kopyası üzerinde çalışırsın.

Seed'e yansıma: Bu ekrandan yapılan her kayıt (liste ayarları, alanlar, JSON satır editörleri — ListFormsAppService, ListFormFieldsAppService, ListFormJsonRowAppService) ekranın wizard seed dosyasına WizardSeedSynchronizer ile geri yazılır; wizard'dan üretilmemiş (C# seeder'la gelen platform) ekranlarda karşılık gelen dosya yoktur ve değişiklik yalnızca veritabanında kalır. Prompt ile üretimde bu editör senin "hangi anahtar nerede" haritandır: kullanıcı "ekranda şunu değiştir" dediğinde önce bu tablodan hangi JSON kolonuna dokunacağını bul, sonra seed dosyasında o kolonu güncelle.


9.7 Menü Yönetimi ekranları

Menü: App.Menus (üst) → dört ekran. Wizard bunların çoğunu senin yerine yapar; bu ekranlar elle düzeltme ve wizard'ın kapsamadığı düzenlemeler içindir.

Ekran Route Yetki Ne yapar
Routes /admin/list/App.Menus.Routes App.Menus.Routes Fiziksel React sayfalarının route kayıtları (Key, Path, ComponentType, ComponentPath, RouteType, Authority[])
Modules /admin/list/App.Menus.Module App.Menus.Module Modül adları (Sas, Adm, Hr, Pub); kök menü ModuleId ile buraya bağlanır. Yetkilerin Modules değeri kiracıdaki görünürlüğü belirler (Sas host, Adm/Hr kiracı)
Menu List /admin/list/App.Menus.Menu App.Menus.Menu Menü kayıtlarının ham listesi
Menu Manager /admin/menuManager App.Menus.Manager Sürükle-bırak menü ağacı; sıra ve hiyerarşi düzenleme

Menu kaydının alanları (wizard bunları üretir; elle eklemen gerekirse):

Alan Anlam
Code Benzersiz kod; yetki adının kökü
DisplayName Dil anahtarı (düz metin değil)
ParentCode Üst menü; boş ise kök menü
Url Açılacak adres
Icon react-icons adı
Order Kardeşler arası sıra
ModuleId Kısa rozet (HR, MRP) — kök menülerde kullanılır
RequiredPermissionName Görünürlük yetkisi
Target / CssClass / ElementId / IsDisabled Bağlantı davranışı ve görünüm
UserId / RoleId / CultureName Kullanıcı, rol ve dil bazlı menü varyantı

Kurallar — prompt ile üretim yaparken:

  1. Menüyü elle ekleme. Yeni bir ekranın menüsü wizard seed dosyasından gelir; data/App.Menus.Menu.json (anahtar Code) platformun kendi ekranları ve Pub modülü (public site, §0.9) içindir. App.Menus.Routes.json'da yalnızca var olan satırın Seo* alanlarına dokunulur; rota eklemek/silmek/yolunu değiştirmek çerçeve dışıdır (§0.8).
  2. Route kaydı üretme. Dinamik liste ekranları statik /admin/list/:listFormCode route'unu kullanır; custom component'lerin route'u RoutePath alanından türetilir. Route tablosu yalnızca fiziksel React sayfaları içindir ve prompt ile ekran üretirken böyle bir sayfa yazılmaz.
  3. Menü sırası için wizard'ın MenuOrder alanını kullan. Menu Manager'daki taşıma/düzenleme de App.Menus.Menu ekranının seed dosyasına (data/App.Menus.Menu.json) ve menü adı App.Languages.LanguageText.json'a yansır (MenuAppService → IListFormSeedDataSynchronizer).
  4. Rol/dil bazlı menü varyantı gerekiyorsa Menu List ekranından ikinci bir kayıt açılır — aynı Url, farklı RoleId/CultureName. Wizard bunu üretmez.

9.8 Wizard Manager — layout matrisi

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

Görünüm Bayrak DefaultLayout Zorunlu option alanı Ne zaman aç
Grid Grid "grid" — Varsayılan açık; açılış görünümü
Card Card "card" — Varsayılan açık
Pivot Pivot "pivot" — (alanın PivotSettingsJson.IsPivot) Varsayılan açık
Chart Chart "chart" — (SeriesJson boşsa kullanıcı seriyi ekranda kurar) Varsayılan açık
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:

  • Varsayılan dörtlü: prompt, görsel ya da tarifle üretilen her liste ekranı (Wizard List yolu, MCP deploy_wizard, alt form olarak kullanılan liste dahil) Grid, Card, Pivot ve Chart bayrakları true ve DefaultLayout: "grid" ile yazılır. Bir görünüm yalnızca istek onu açıkça dışarıda bıraktığında ("pivot olmasın", "sadece liste") false yazılır; "liste/tablo/grid" demek diğer üçünü kapatmak değildir. Tree/Gantt/Scheduler/Todo varsayılan kapalıdır, zorunlu alanları olduğu için yalnızca istenince açılır. Kod tarafı aynı varsayılanı taşır: UI wizard başlangıç değerleri, ListFormWizardDto ve LayoutDto — bayrak yazılmayan cevap dörtlüyü açık alır.
  • Prompt bir grafik tarif ediyorsa ("aylara göre ciro") SeriesJson da doldurulur; tarif yoksa Chart açık kalır, seri kullanıcı tarafından ekranda kurulur.
  • Zorunlu option alanı boşsa seeder o görünümün JSON'unu yazmaz; bayrak açık kalsa bile görünüm çalışmaz. Açtığın her görünümün option bloğunu doldur.
  • DefaultLayout, bayrağı true olan bir görünüm olmalıdır.
  • Aynı veri birden fazla görünümde aynı SelectCommand üzerinden sunulur; ayrı sorgu yazılmaz.
  • Bir ekranın hem liste hem pano hem takvim olarak istenmesi tek wizard dosyasıdır — ikinci bir ekran açma.

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

Her wizard'ı üreten talep listede görünür (WizardFileInfoDto.DevRequestId/Title/Url; başlık talepten güncel okunur). Başlık talebin kaydını açar (/admin/form/App.DevRequests/{Id}); talep bu ortamda yoksa (import edilmiş dosya) dosyadaki başlık bağlantısız gösterilir, elle eklenen wizard'da hücre boştur. Yanındaki düğme talebin bütün wizard'larını tek arşivde indirir (api/app/list-form-wizard/export-dev-request{,-analysis}, .Export yetkisi): kapanış tek WizardExportCollector çağrısında kurulur, paylaşılan dosyalar arşive bir kez ve birleşik satırlarla girer. Import çok wizard'lı arşivi zaten tek oturumda uygular.

Listede her ekranın üst menü zinciri (WizardFileInfoDto.MenuPath, Menu tablosundan; kayıt yoksa wizard'ın MenuParentPath + MenuParentCode cevabı) görünür. Bağlı wizard'lar üst ekranın altında gruplanır (Dependencies): List yolunda alt formlar, Custom yolunda component Dependencies zinciri — export kapanışıyla aynı kural; wizard'ı olmayan ara component atlanır. Kök, hiçbir wizard'ın bağımlılığı olmayan dosyadır; ortak bir alt form her üst ekranın altında tekrar görünür. Gruplar +/− ile açılıp kapanır (varsayılan açık); arama sırasında eşleşme gizli kalmasın diye hepsi açık gösterilir.

9.9 Numaratör — Sequence (App.Definitions.Sequence)

"Sipariş numarası otomatik gelsin", "talep numarası her yıl 1'den başlasın" istekleri için kod yazılmaz; bir Sequence kaydı açılır ve alanın varsayılan değeri CustomValueType: 5 ile o kayda bağlanır (§4.3.5).

Ekran: /admin/list/App.Definitions.Sequence · Yetki: App.Definitions.Sequence{,.Create,.Update,.Delete}

Alan Anlam
Name Varsayılan değerde referans verilen ad (SiparisNo)
Prefix / Suffix Sabit ön/son ek
PaddingSize Sayının soldan sıfırla doldurulacağı basamak (varsayılan 5)
StartNumber / NextNumber / IncrementStep Başlangıç, sıradaki değer, artış adımı
ResetPeriod 0 None · 1 Yearly · 2 Monthly · 3 Daily · 4 Hourly · 5 Minutely
LastPeriodKey Sıfırlamanın hangi dönemde yapıldığını tutar (2026, 202608…). Platform yönetir, elle yazma
FormatTemplate Varsayılan {Prefix}{Number}{Suffix}
IsActive Pasif kayıt numara üretmez (istek hata verir)

FormatTemplate token'ları — tarih token'ları .NET biçim harfleridir, M ay / m dakikadır:

Token Karşılığı
{Prefix} · {Suffix} Sabit ön/son ek
{Number} PaddingSize kadar soldan sıfırlanmış sıra numarası
{yyyy} · {yy} Yıl (4/2 hane)
{MM} · {M} Ay (2 hane / sıfırsız)
{dd} · {d} Gün
{HH} · {H} Saat
{mm} · {m} Dakika
{ss} · {s} Saniye

Şablonda geçmeyen token yok sayılır; {Number} yazmayı unutursan numaratör sabit bir metin üretir ve bunu kimse fark etmez — şablonu her zaman {Number} ile yaz.

Örnek — SIP-2026-00042: Prefix = "SIP-", PaddingSize = 5, ResetPeriod = 1 (Yearly), FormatTemplate = "{Prefix}{yyyy}-{Number}".

Seed: data/App.Definitions.Sequence.json (seed bayrakları kapalı — yeni numaratörü satır olarak bu dosyaya yaz; NextNumber başlangıç değeridir, LastPeriodKey yazılmaz).

Numara yazma anında (Insert) alınır ve NextNumber ilerler; form açılışında yalnızca önizleme istenir. Bu yüzden iptal edilen bir kayıt numarayı tüketebilir — boşluksuz numara gerekiyorsa numarayı InsertAfterCommand ile SQL tarafında üret.

9.10 Zamanlanmış işler — BackgroundWorker (App.BackgroundWorkers.RecurringJobs)

"Her gece şu prosedür çalışsın", "her 5 dakikada bildirim kuyruğu boşalsın" istekleri. Ekranlar: App.BackgroundWorkers.RecurringJobs (tanımlar) ve App.BackgroundWorkers.Jobs (Hangfire çalıştırma kayıtları).

Alan Anlam
Name Benzersiz ad; dağıtık kilit (DistributedLock) bu adla alınır — aynı iş iki sunucuda aynı anda çalışmaz
Cron Cron ifadesi (*/5 * * * *, 0 3 * * *)
WorkerType 1 MailQueueWorker · 2 SqlWorker · 3 NotificationWorker · 4 SessionCleanupWorker · 5 BackupWorker
Scope 1 Host (varsayılan) · 2 Tenant · 3 All — aşağıda Kapsam
DataSourceCode BeforeSp / AfterSp hangi bağlantıda çalışacak
BeforeSp / AfterSp İş gövdesinden önce ve sonra çağrılan stored procedure adları
Options Tipe özel JSON (MailQueueWorker için kuyruk ayarları)
IsActive Pasifse zamanlanmaz

Hangfire mekaniği (BackgroundWorkerInitializer + PlatformBackgroundWorker) — tanımlar Hangfire'a şöyle çevrilir:

  • Aktif her kayıt kapsam başına bir recurring job olur: host için id {WorkerType}:{Name}, her kiracı için {WorkerType}:{Name}#{TenantId}; kuyruk platform, zaman dilimi Europe/Istanbul — cron ifadeleri Türkiye saatine göre yazılır. Kiracı eklenip silindiğinde liste yeniden üretilir (BackgroundWorkerTenantHandler).
  • Yükleme sırasında platform kuyruğunda kalan ama artık bir kayda denk gelmeyen job'lar silinir; yani iş listesinin tek kaynağı bu tablodur, Hangfire panelinden elle job açma.
  • MailQueueWorker tanımı yüklenirken Options.MailTemplate Razor şablonu worker adıyla şablon deposuna da kaydedilir.

Her tetiklemede çalışma sırası:

  1. Dağıtık kilit alınır — host kapsamında Name, kiracı kapsamında Name#{TenantId} ile; alınamazsa (başka instance aynı kapsamı çalıştırıyor) sessizce çıkılır.
  2. BeforeSp doluysa DataSourceCode bağlantısında çağrılır.
  3. Tipe göre gövde: MailQueueWorker → kuyruğu gönderir (§11 · reference) · SqlWorker/BackupWorker → SQL gövdesi (backup hedefi App:BackupPath ayarı) · NotificationWorker → bildirim kuyruğunu kanallara dağıtır · SessionCleanupWorker → süresi geçen oturumları düşürür.
  4. AfterSp doluysa çağrılır.
  5. Hata IExceptionNotifier'a bildirilir ve loglanır — iş sessizce yutulmaz; çalıştırma geçmişi App.BackgroundWorkers.Jobs ekranındadır (Tenant, ExecutionDate, Duration, Status, FailureReason, ExceptionDetails) ve her satır tek bir kapsamın çalışmasıdır.

Kapsam: Scope alanı işin gövdesinin hangi bağlamda çalışacağını belirler — Host (1) yalnızca host, Tenant (2) her kiracı için bir kez, All (3, ekranda "Host ve Kiracılar") önce host sonra kiracılar — her biri ayrı bir iş kaydı üretir, Jobs ekranındaki Tenant kolonu hangi kiracıda çalıştığını gösterir. Kiracı tablosuna dokunan bir iş Host kapsamında çalışırsa kiracı satırlarını göremez; kiracı verisi işleyen her işe kapsamını ver.

Önemli: SqlWorker ve BackupWorker tiplerinde asıl iş BeforeSp/AfterSp prosedürlerindedir. Yani "her gece stok kapanışı" demek = WorkerType: 2 + BeforeSp: "Wms_S_NightlyClose". Prosedürü sql/object/ altına yazmayı unutma.

Tanımlar değiştikten sonra zamanlayıcıya yansıması için "Zamanlanmış işleri yeniden üret" çalıştırılır: toolbar butonu UiEvalService.ApiGenerateBackgroundWorkers() (§9.13).

Seed: data/App.BackgroundWorkers.RecurringJobs.json (§9.15; seed bayrakları açık — ekrandan yapılan kayıt dosyaya yansır). Prompt ile iş eklerken satırı bu dosyaya yaz; gövde prosedürü ayrıca sql/object/ altında durur.

Otomasyon reçetesi — "belirli zamanda şu iş yapılsın" taleplerinin tamamı bu üçlüyle kurulur, kod yazılmaz:

Talep Kurulum
Zamanlanmış SQL işi (kapanış, özet tablo, temizlik) WorkerType: 2 + BeforeSp + sql/object/ prosedürü
Zamanlanmış e-posta (rapor maili, hatırlatma) WorkerType: 1 + Options (Mail şablonu + TableName) + kuyruğa satır yazan BeforeSp
Olay bazlı bildirimlerin dağıtımı WorkerType: 3 + Scope: 3 (kuyruğu host ve her kiracı için boşaltır) + Notification Type/Rule kayıtları (§9.11)
Oturum temizliği / yedekleme WorkerType: 4 / 5 (+ App:BackupPath)

Zamanlanmış e-posta (MailQueueWorker) — "her sabah geciken siparişler listesi şu kişilere mail atılsın" isteğinin yolu budur. Worker'ın Options alanı bir MailQueueWorkerOptions JSON'udur:

{ "MailType": "OrderDelay", "MailSubject": "Geciken Siparişler",
  "MailTemplate": "<h3>Geciken siparişler</h3>@Model.Tables[0]",
  "TableName": "{0:MT3_GECIKEN_SIPARIS:IN:Order Delay Notification:0:}" }
  • MailTemplate Razor ile render edilir (EmailLayout düzeni içinde).
  • TableName maildeki tabloları tanımlar; IN gövdeye gömülür, PDF/XLS/TXT ek dosya olur.
  • Gönderilecek satırlar BackgroundWorker_MailQueue kuyruk tablosuna yazılır (To, MailParameter, TableName, TableParameter, Attachment, RelatedRecordId); kuyruğa yazma işi genellikle BeforeSp prosedüründe yapılır. Ayrıntılı biçim api/modules/Sozsoft.MailQueue modülündedir.

9.11 Bildirimler (App.Notifications.*)

Dört ekran, tamamı Administration › Bildirimler altındadır: NotificationTemplates (mesaj şablonları), NotificationTypes (olay türü), NotificationRules (kim, hangi türde, hangi kanaldan alacak), Notification (üretilmiş bildirimler / geçmiş ve elle gönderim formu — kod tekildir: App.Notifications.Notification). Dört tablo da kiracı kapsamındadır (Adm_T_NotificationTemplate, Adm_T_NotificationType, Adm_T_NotificationRule, Adm_T_Notification); yeni kiracıya host seed dosyalarındaki tür ve kural kümesi yeni kimliklerle kopyalanır (NotificationTenantDefaultsSeedContributor, README.md §13.5.1).

NotificationRule alanı Anlam
NotificationTypeId Hangi tür olay (YeniSiparis, YeniKullanici…)
RecipientType + RecipientId User / Role / OrganizationUnit / All / Custom + kullanıcı adı, rol adı ya da birim kodu
Channel Mail, Sms, WhatsApp, Telegram, Rocket, Desktop, UiToast, UiActivity — ekran etiketi App.NotificationChannel.{Kanal} (eski App.StaticLookup.* kanal anahtarlarını kullanma)
IsActive Kural açık mı
IsFixed Kullanıcı profilinden kapatılamaz (zorunlu bildirim)
IsCustomized Kullanıcı kendi tercihini yazmış (yalnız sahibini kapatır, alıcıyı kurallar belirler)

Bildirim üretme yolu: bir Notification kaydı oluşur (tür, kural, kanal, alıcı UserId, Subject, Message) ve NotificationWorker (§9.10, Scope: 3) kuyruğu kanala göre gönderir; kayıt anında /notificationhub üzerinden de duyurulur. Kayıt SQL tarafından (InsertAfterCommand, prosedür) ya da Dynamic Service'ten yazılabilir. Bayrak sırası: IsSent olmadan IsRead yazılmaz, IsRead ile ReadTime birlikte yazılır.

Kural yazmadan gönderim istendiğinde ayrı bir ekran/tablo açılmaz: Notification ekranının ekleme formu list-form-dynamic-api/notification-insert ucuna bağlıdır (CreateNotificationInput, yetki App.Notifications.Notification.Create). Form alıcıyı (UserId) ve kanalı (NotificationChannel) çoklu seçer (dxTagBox, §4.3.2.1), şablon (NotificationTemplateId) seçilirse boş bırakılan Subject/Message şablondan dolar; mesajda {AdSoyad}, {KullaniciAdi}, {Eposta}, {Tarih}, {Saat}, {Kiraci} yer tutucuları çözülür. Uç her alıcı × kanal için bir satır yazar, bildirim tipi Manuel'dir.

Seed: data/App.Notifications.{NotificationTemplates,NotificationTypes,NotificationRules}.json (§9.15; seed bayrakları açık, Order ile tür → kural sırası). Üretilmiş bildirimler seed'lenmez.

Onay akışındaki Inform düğümü e-posta gönderir (MailQueue üzerinden); UI bildirimi istiyorsan ayrıca bir bildirim kuralı gerekir.

9.12 Raporlar (App.Reports.*)

Raporlama DevExpress XtraReports tabanlıdır ve iki katmanı vardır:

Katman Ne
Hazır (predefined) raporlar DynamicGrid, DynamicTree, DynamicForm — o anki ekranın sütunlarını, filtrelerini ve verisini alıp yazdırılabilir çıktı üretir
Rapor şablonları App.Reports.ReportTemplates + App.Reports.Categories ekranlarındaki kayıtlar. Raporlar kategorisindeki şablon DynamicReport ile basılan HTML'dir; Id'siyle açılır, MCP save_report_template ile eklenir

Route'lar: görüntüleyici /admin/reports/{report}/view/{id?}/{listFormCode?}, tasarımcı /admin/reports/{report}/design/{id?}/{listFormCode?}.

  • Her List ekranının araç çubuğundaki rapor aksiyonu DynamicGrid (TreeList'te DynamicTree) raporunu o anki filtrelerle açar; bunun için hiçbir tanım yapılmaz.
  • Kendi düzeni istenen kayıt çıktısı (sertifika, taahhütname, teklif, iş emri) Raporlar kategorisinde bir DynamicReport şablonudur: HtmlContent A4'e basılan HTML'dir, ayrı bir .repx çizilmez. Kayıtsız görüntüleme /admin/reports/DynamicReport/view/{ŞablonId} (Standard Taahhütname gibi). Bir ekran satırından açılınca adres /admin/reports/DynamicReport/view/{ŞablonId}/{ListFormCode}?recordId=@{KeyFieldName} olur ve HTML'deki {{AlanAdi}} yer tutucuları o kaydın alanlarıyla doldurulur. Değer, form raporunun bastığı metindir (lookup görünen metni, statik lookup karşılığı, tarih/sayı biçimi) ve HTML olarak kodlanır. Kayıt ekranın okuma yetkisi ve kiracı filtresiyle okunur. @@date rapor tarihidir. Ekranda alanı olmayan bir değer yer tutucu olamaz; gerekiyorsa ekrana gizli alan olarak eklenir.
  • Şablon kiracıda bulunamazsa host şablonu kullanılır: host şablonu ürünle gelen ortak tasarımdır, basılan veri yine kiracının kendi ekranındandır.
  • Şablon ekrana CommandColumnJson satır butonu ile bağlanır; koşullu buton VisibleExpression ile yazılır. Örnek: sertifika yalnızca katılan katılımcıda görünür, ((e)=> e.row.data.AttendanceStatus === 'Attended').
  • MCP ile: list_report_templates → save_report_template (category: "Raporlar", htmlContent, listFormCode). listFormCode verilirse yer tutucular o ekranın alanlarına göre doğrulanır ve cevapta rowButton döner. Buton ekranın wizard cevabına CommandColumns elemanı olarak yazılır (§4.9) ve deploy_wizard ile gider. Ekran henüz migrate edilmediyse listFormCode verilmez; buton adresi viewUrl + /{ListFormCode}?recordId=@Id ile yazılır. Sıra: önce ekran(lar) deploy_wizard, sonra şablon save_report_template, sonra butonlu ekran deploy_wizard (replaceFileName) — ya da şablon önce kaydedilip Id'si butona yazılır. Yeni kategori (save_report_category) yalnızca istenince açılır.
  • Rapor şablonu eksik yetenek değildir: sertifika/taahhütname/belge çıktısı isteyen bir talep bu araçlarla üretilir; analizde "tasarımcıda çizilir" ya da "seed'e yazılmaz" gerekçesiyle Ready to Start engellenmez.
  • Rapor şablonları ve kategorileri data/App.Reports.{ReportTemplates,Categories}.json ile seed'lenir ve seed bayrakları açıktır: ekrandan ya da MCP'den yapılan kayıt dosyaya yansır (§9.14). DynamicReport/design ile kaydedilen sayfa çerçevesi (.repx) sunucudaki PredefinedReports/Reports klasörüne yazılır, seed'e girmez.

9.13 UiEvalService — buton ve script'ten çağrılabilen platform aksiyonları

CommandColumnJson.OnClick ve EditorScript'in custom tarifi eval ile çalıştığı için platformun hazır aksiyonları buradan çağrılır. Uydurma metot yazma; kullanılabilir olanlar:

Çağrı Ne yapar Sunucu yetkisi
UiEvalService.ApiGenerateBackgroundWorkers() Zamanlanmış iş tanımlarını yeniden üretir —
UiEvalService.ApiClearRedisCache() Dağıtık önbelleği temizler —
UiEvalService.ApiKickUser(userId, gridRef) Kullanıcının oturumunu düşürür, grid'i yeniler —
UiEvalService.ApiDeleteDynamicService(id, gridRef) Dynamic Service kaydını onay alarak siler (yüklü assembly de düşer), grid'i yeniler App.DeveloperKit.DynamicServices.Delete
UiEvalService.ApiDbMigrate() Migration + seed akışını başlatır, log panelini açar App.Setup.Migrate

9.14 Platformun hazır gelen diğer konfigürasyon ekranları

Bir istek geldiğinde önce bu listeye bak: karşılığı olan bir ekran varsa yeni artefakt üretilmez, var olan ekrandan kayıt girilir. Aşağıdaki tablo sık kullanılanların özetidir; Saas ve Administration menülerindeki ekranların tamamı, tabloları, kapsamı ve hangi talepte kullanılacağı lowcode-reference.instructions.md §12'dedir.

İstek Ekran / kayıt Route
"Belge numarası otomatik olsun" Sequence (§9.9) /admin/list/App.Definitions.Sequence
"Şu iş her gece çalışsın" Recurring Jobs (§9.10) /admin/list/App.BackgroundWorkers.RecurringJobs
"Şu olayda bildirim gitsin" Notification Types + Rules (§9.11) /admin/list/App.Notifications.NotificationRules
"Fatura/teklif/sertifika çıktısı" Report Templates — Raporlar kategorisi, DynamicReport (§9.12); MCP save_report_template /admin/list/App.Reports.ReportTemplates
"Yeni bir veritabanı bağlantısı" Data Source /admin/list/App.DataSource
"Uygulama ayarı (e-posta, limit, açık/kapalı)" Setting Definitions + Ayarlar ekranı /admin/list/App.SettingDefinitions · /admin/settings
"Metin/çeviri değişsin" Languages + Language Texts /admin/list/App.Languages.LanguageText
"Yetki/rol/kullanıcı düzenlemesi" Roles · Users · Permissions · Organization Units /admin/list/AbpIdentity.Roles · /admin/ous
"Şube kırılımı" Branches /admin/list/App.Branches
"Kimin ne yaptığını görelim" Audit Logs · Sessions · Activity Log /admin/list/App.IdentityManagement.AuditLogs
"Belirli saatlerde/IP'lerden girilsin" Work Hours · IP Restrictions /admin/list/App.Restrictions.WorkHour
"Dosya yükleme/yönetme" File Manager /admin/files
"Global aramada çıksın" Global Search kayıtları (System, Group, Term, Weight, Url) /admin/list/App.Definitions.GlobalSearch
"Duyuru, anket, sosyal akış, etkinlik" Intranet ekranları /admin/list/App.Intranet.*
"Para birimi, ülke/il/ilçe, birim, sektör…" Definitions ekranları /admin/list/App.Definitions.*
"Departman, pozisyon/ünvan, yetkinlik, organizasyon şeması" İK › Definitions · Org Chart (şube bazlı departman/pozisyon) /admin/list/App.Hr.* · /admin/organization
"Menü düzeni" Menu Manager (§9.7) /admin/menuManager
"Genel siteye sayfa" Custom Component (Pub modülü) — Visual Designer'dan yeni component + Pub menü kaydı /admin/developerkit/components/design/:id (README.md §10.4)
"Yapay zekâ asistanı" AI Bot tanımları /admin/list/App.Definitions.AiBot

Seed durumu — hangi kayıt dosyaya düşer? Bir kayıt ekranının ListForm.SeedFilePath'i doluysa veritabanı sıfırlandığında data/ dosyasından döner (§9.15); değilse yalnızca veritabanında yaşar. Üç durum vardır:

Durum Ekranlar (örnek) Prompt ile kayıt eklerken
Seed bayrakları açık — ekrandan yapılan kayıt dosyaya yansır RecurringJobs, NotificationTemplates/Types/Rules, SettingDefinitions, AiBot, CustomEndpoints, DynamicServices, CustomComponents, McpServers, IntranetWidgets, ReportTemplates/Categories, App.Menus.*, Abp.Identity.Permission* Satırı data/{ListFormCode}.json'a yaz (rapor şablonu için save_report_template)
SeedFilePath dolu, bayraklar kapalı — dosya migrate'te uygulanır, ekrandan yapılan değişiklik dosyaya yansımaz Sequence, GlobalSearch, Currency, ContactTitle, Country/City/District, Uom, Hr Department/JobPosition/SkillType, WorkHour, Intranet, Blog, Forum, Orders tanımları Satırı dosyaya yaz; ekrandan eklenen kayıt teslim sayılmaz
SeedFilePath boş — yalnızca veritabanı DataSource, Languages, Branches, Roles/Users/Tenants, AuditLogs, IpRestrictions, Notification (geçmiş), BackgroundWorkers.Jobs sql/execute/{Ad}.sql altında varsa güncelleyen/yoksa ekleyen bir prosedür (§8.4)

Tam liste ListFormSeeder_Saas.cs / ListFormSeeder_Administration.cs / ListFormSeeder_Hr.cs içindeki SeedFilePath ve SeedSync* atamalarıdır. "Ekranı ürettim ama numaratörü/zamanlanmış işi de kurdum" diyorsan, o kayıtların seed karşılığını da yazmadıysan teslim eksiktir.


9.15 Artefakt G — Liste verisi seed dosyası (data/{ListFormCode}.json)

Ekrandan girilen kayıtların (widget tanımı, parametre tablosu, sabit liste) veritabanı sıfırlandığında geri gelmesi için kullanılır. Bağlantı ListForm kaydındaki SeedFilePath alanıdır; boşsa ne geri yükleme ne de senkronizasyon yapılır.

Hedef dosya ya da klasördür. Klasör modunda da her dosya aynı zarfı (ListFormCode, KeyFieldName, GeneratedAt, Order, Rows) taşır; Rows tek satırdır ve dosya adı {anahtar}.json'dur. Ekranın SeedFilePath'i varsayılan data/{ListFormCode}.json iken diskte data/{ListFormCode}/ klasörü varsa migrate yolu klasöre çevirir. SeedFilePath .json ile bitiyorsa bütün satırlar tek dosyadadır (varsayılan). Uzantısız yazılırsa hedef klasördür (data/{ListFormCode}) ve her satır {anahtar}.json olarak kendi dosyasında durur; klasör yoksa ilk kayıtta oluşturulur. Klasör modu satırı büyük ya da sık yazılan ekranlar içindir — şu an tek örnek Custom Components (SeedPathResolver.GetDataFolderPath). Uygulayıcı data/ altını özyinelemeli tarar ve bir klasörün tamamını tek unit of work'te, ekran tanımını bir kez çözerek uygular; dosyadaki ListFormCode boşsa ekran kodu klasör adından gelir. Ekrandaki hangi işlemin dosyaya yansıyacağını SeedSyncInsert, SeedSyncUpdate ve SeedSyncDelete bayrakları belirler — üçü de kapalıyken dosya yalnızca seed sırasında okunur, hiç yazılmaz.

{
  "ListFormCode": "App.DeveloperKit.IntranetWidgets",
  "KeyFieldName": "Id",
  "GeneratedAt": "2026-09-04T00:00:00Z",
  "Rows": [
    { "Id": "542e0ab0-…", "Code": "documents", "Column": 0, "Order": 20, "IsActive": true }
  ]
}
Alan Sözleşme
ListFormCode Verinin ait olduğu ekran; dosya adıyla aynı olmalı. Boşsa dosya adı (klasör modunda klasör adı) kullanılır
KeyFieldName Satırların eşleştiği anahtar alan; dosyadaki değer ekranınkini ezer, boşsa ListForm.KeyFieldName. İş anahtarıyla eşleşen dosyalar (Abp.Identity.Permission* → Name, App.Menus.Menu → Code) Id taşımaz
Order Uygulama sırası; küçük olan önce uygulanır (route → modül → menü, yetki grubu → yetki, ülke → il → ilçe, bildirim tipi → kural). Yoksa 0, eşitlikte yola göre sıralanır. Klasör modunda ilk dosyadan okunur; senkronizasyon yeni satır dosyasına kardeşinin değerini kopyalar
Rows Ekranın select alanlarından oluşan satırlar. Enum'lar sayı olarak yazılır (SQL kolonu ne ise o); dil anahtarları :: öneki olmadan yazılır

Kurallar:

  • Anahtar dosyada durur. Insert varsayılanları anahtarı @NEWID ile üretir; ListFormSeedDataApplier dosyadaki anahtarı geri yazar, böylece aynı dosya birden çok kez uygulandığında kopya kayıt oluşmaz. Elle dosya yazarken anahtarı sabit yaz: Guid anahtarlı satırda iş anahtarından türetilmiş (v5 biçimli) sabit bir GUID; her çalıştırmada yeni GUID üretme.
  • Identity anahtarlı tablolarda satır anahtarsız yazılır. Id alanı dosyada yoksa anahtar veritabanına bırakılır; satırın var olup olmadığı dosyadaki tüm alan değerlerinin (tenant kapsamı dahil) eşleşmesine bakılarak belirlenir — bu satırlar hiçbir zaman güncellenmez.
  • Dosyadaki değer varsayılanı ezer. InsertFieldsDefaultValueJson yalnızca dosyada bulunmayan alanları doldurur (audit kolonları, kapsam, anahtar); ekrandan kayıtta ise varsayılan girilen değeri ezer.
  • @USER:kullanıcıAdı değeri seed sırasında o kullanıcının anahtarına çözülür; kullanıcı anahtarları her kurulumda yeniden üretildiği için dosyaya GUID yazılmaz. Kullanıcı bulunamazsa alan boş kalır.
  • Migrate yalnızca ekler: anahtarı veritabanında bulunmayan satırlar INSERT edilir; var olan kayıt güncellenmez (her migrate'te ekrandaki güncel değerler dosyadaki eskisiyle ezilmesin diye) ve dosyada olmayan satır silinmez. Wizard import'unun kapanışı ise overwriteExisting ile çalışır: var olan satır güncellenir, soft delete izi temizlenir (§7.7). Her hedef kendi unit of work'ündedir; birinin hatası diğerlerini durdurmaz.
  • Toplu yazım: hedef tablonun anahtarları tek sorguda okunur; anahtarlı yeni satırlar ekranın insert komutuyla (InsertCommand dahil) parametreleri @p{sıra}_{ad} olarak yeniden adlandırılıp komut başına en fazla 2000 parametreyle toplu gönderilir. Anahtarsız satırlar tek tek eşleşir.
  • Senkronizasyon tüm listeyi yazar: dosyanın listeyle yeniden yazıldığı durumlarda satırlar sayfa sayfa (10000) okunur; sayfa sınırı satır kaybettirmez.
  • Yazma yönü bayrağa bağlıdır: list-form-data/* ve list-form-dynamic-api/* uçlarında insert → SeedSyncInsert, update → SeedSyncUpdate, delete → SeedSyncDelete; ImportManager toplu yüklemesi SeedSyncInsert bayrağına bakar ve dosyayı listenin tamamıyla yeniden yazar.
  • Audit/tenant kolonları dosyaya yazılmaz; InsertFieldsDefaultValueJson + kapsam üretir.

10. Uçtan uca reçeteler

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

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

10.2 "Ana-detay ekranı"

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

10.3 "Serbest yerleşimli sayfa / dashboard"

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

10.4 "Kanban panosu"

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

10.5 "Rapor sorgusunu API olarak aç"

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

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

Eksik olan bir bileşen olayı/prop'u/küçük yeteneğiyse bu reçete değil §7.3'teki not geçerlidir: var olan bileşene eklenir, yeni bileşen ya da Developer Kit ekranı üretilmez.

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

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

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

configs/seeds/host/
├── sql/object/Hr_T_AdvanceRequest.sql              ← tablo (+ PostgreSQL hedefse postgres/object/…)
├── crud/Hr_T_AdvanceRequest.json                   ← yalnızca Custom Component yolunda gerekir
├── data/App.DeveloperKit.CustomComponents/{Id}.json     ← yalnızca serbest yerleşim isteniyorsa
├── wizard/AdvanceRequestApprovals.json             ← (varsa) menüsüz alt ekran, küçük SeededAt
└── wizard/AdvanceRequests.json                     ← ana ekran + menü + yetki + dil + onay akışı

Karar: Talep formu standart bir kayıt listesi + düzenleme formu + onay akışı ise ComponentKind: 0 (List) yeter ve component seed verisi + crud/ dosyalarına hiç gerek yoktur. Custom Component yoluna yalnızca serbest yerleşim (özet kartlar, çok adımlı sihirbaz, gömülü panolar) isteniyorsa geçilir.

Adım adım:

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

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


11. Teslim öncesi kontrol listesi

  • Menü, route ve ekran sözleşmesi birbirini gösteriyor.
  • Kök yetki + aksiyon yetkileri var; menü RequiredPermissionName ile bağlı.
  • Yetki grubunun EN + TR görünen adı var.
  • Kullanıcıya görünen her metnin EN + TR dil anahtarı var; anahtar tekrar edilmemiş.
  • IsTenant (ve gerekiyorsa IsBranch/IsOrganizationUnit) doğru; sorgular tenant güvenli.
  • Tüm SQL parametreli; string birleştirme yok.
  • DeleteCommand = DefaultDeleteCommand("{Tablo}").
  • Seed dosyası oluştu; InsertedRecords yalnızca gerçekten yaratılan kayıtları içeriyor.
  • Bağımlılıklar (custom component, crud endpoint, sql nesnesi) da seed'li.
  • Hazır ekranlara eklenen kayıtlar (Sequence, zamanlanmış iş, bildirim tipi/kuralı, Custom Endpoint…) ekranın data/ dosyasına yazıldı; SeedFilePath'i olmayan ekranın kaydı için sql/execute/ altında geri getirici prosedür var (§9.14).
  • Export zip'i başka bir ortamda açıldığında ekran ayağa kalkıyor.
  • Geri alma yolu yazılı: Wizard Manager'dan ya da MCP delete_wizard ile sil → dosya, veri aynası ve InsertedRecords içindeki kullanılmayan kayıtlar gider; tablo yalnızca onayda seçilirse (MCP: dropTables: true) scripti ve crud dosyasıyla düşer (§4.5). Dosyayı diskten silmek kayıtları temizlemez.

Prompt ile üretim yaptıysan ek olarak:

  • Dosyalar doğru kapsam klasöründe (host/ ya da tenants/{tenantId}/).
  • Üretim sırası doğru (§0.2) ve bağımlı wizard'ın SeededAt damgası daha büyük.
  • Tablo/kolon adları uydurulmadı; ya var olan nesneden okundu ya da sql/object/ altında üretildi.
  • KeyFieldDbSourceType, tablodaki Id tipiyle uyumlu.
  • Liste ekranında Grid/Card/Pivot/Chart açık, DefaultLayout: "grid"; kapalı olan görünüm yalnızca istek onu açıkça dışarıda bıraktığı için kapalı (§9.8).
  • Açılan her görünümün zorunlu option alanı dolu (§9.8).
  • Alt form olarak üretilen her ekranda (menüsüz ya da menülü) Widgets boş (§4.6.1 istisna).
  • Menüsüz alt formun yetkisi ana ekranın (ya da bağlı olduğu alt formun) yetkisinin altında; Form sekmesinin hedefi AllowAdding + AllowUpdating açık (§4.6).
  • List ekranında en az 3 kartlık Widgets var (istisna değilse); kiracı tablosunda her kart "TenantId" = '@TENANTID' taşıyor, başlıklar dil anahtarı, kolonlar doğrulandı (§4.6.1).
  • Custom component'te route yetkisiz üretildiği için koruma bileşenin içinde (designerPermission / *Permission / checkPermission).
  • Custom component visual modda ise Props.visualDesigner ile Code marker'ı aynı dokümanı taşıyor.
  • Menü ve route elle eklenmedi; wizard dosyasından geliyor.
  • List ekranı deploy ile üretildi (wizard ekranı / MCP deploy_wizard) ya da elle yazılan dosya ListForm + Fields bölümlerini taşıyor (§4.1).
  • Kullanıcıya hangi dosyaların üretildiği ve DB Migrate gerektiği söylendi.
  • Keşif turu işletildi (§0.6): Tur 1 soruları soruldu, yol için gerekçe verildi ve tarif üretimden önce onaylatıldı — kullanıcı "sorma / sen seç" demedikçe.
  • Tarifte varsayılanla doldurulan satırlar (varsayılan) olarak işaretlendi ve üretim sonunda ayrıca hatırlatıldı (§0.6.6).
  • Hangi tabloya yazılacağı, onaycılar ve hesap/iş kuralı formülleri varsayılmadı, soruldu (§0.6.5).
  • Görselden üretildiyse: platformda karşılığı olmayan öğeler ve yapılan varsayımlar (tablo/kolon adları, anahtar tipi, lookup kaynakları) açıkça bildirildi (§0.7.4).
  • Talebin hazır araçlarla karşılanamayan kısmı için yeni bileşen üretilmedi; var olan bileşene eklenen olay/prop/küçük yetenek tarifte yazıldı ve onaylandı (§7.3).
  • Bileşene ekleme yapıldıysa geriye dönük uyumlu: mevcut varsayılan/davranış değişmedi, componentProps.json + (olaysa) DESIGNER_PRIMARY_EVENTS + reference §10.9/§10.10 güncellendi; npm run typecheck ve npm run lint temiz.
  • Çerçeve dışına çıkan hiçbir değişiklik onaysız yapılmadı (§0.8); çerçeve içinde yapılabilen her şey sorudan önce bitirildi.