5521 lines
412 KiB
Markdown
5521 lines
412 KiB
Markdown
# Low-Code Üretim Referansı (Authoring Reference)
|
||
|
||
Bu doküman, bir yapay zekâ ajanının **platformu bir geliştirici gibi kullanarak** ekran ve
|
||
bileşen üretebilmesi için gereken somut şemaları, alan anlamlarını, enum değerlerini ve
|
||
çalışan örnekleri içerir.
|
||
|
||
- Ne yapılacağına karar verme kuralları: `ai.instructions.md`
|
||
- **JSON kolon ve modül sözleşmelerinin tam listesi: `lowcode-reference.instructions.md`**
|
||
- `api/` kod standardı: `dotnet.instructions.md`
|
||
- Modül/liste ekleme prosedürü: `list.instructions.md`
|
||
- Neyin nerede yaşadığı: `../../README.md`
|
||
|
||
Bu dosya **nasıl üretileceğini** anlatır; `lowcode-reference.instructions.md` **neyin
|
||
ayarlanabildiğini** alan alan listeler (grid/pivot/chart JSON'ları, alan seviyesi JSON'lar, SQL
|
||
kancaları, tasarımcı envanteri, mail modülü) ve **§12'de Saas + Administration + İ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/` 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/
|
||
├── 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 (her zaman, §8.3.1)
|
||
├── {sql|postgres}/host/object|execute/… ← yalnızca host veritabanında çalışan script (ör. HangFire'a dayanan view)
|
||
├── crud/{EntityName}.json ← tablonun REST uçları
|
||
├── wizard/{WizardName}.json ← ekran + menü + yetki + dil anahtarları (List yolunda sunucu yazar, §4.1)
|
||
├── docs/modules/{Menü}.md ← modül dokümanı; iskelet + envanteri deploy yazar (§0.13)
|
||
├── 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/Sas.DeveloperKit.CustomComponents/{Id}.json
|
||
```
|
||
|
||
Tenant'a özel seed klasörü yoktur: host ve kendi veritabanı olan her tenant aynı dosyalarla
|
||
kurulur; host veritabanını paylaşan tenant'ta dosyalar yeniden uygulanmaz (`SeedFileScope`).
|
||
|
||
### 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 tablonun scripti `create_table` ile yazılır (biçim tasarımcıyla aynı; tablo DB Migrate'te kurulur); view/procedure/function scripti `save_sql_object` ile iki sağlayıcı için yazılır |
|
||
| 2 | CRUD uçları (gerekiyorsa) | `crud/{Entity}.json` | Custom Component'in data source'ları buna bakar |
|
||
| 3 | Custom Component (gerekiyorsa) | `data/Sas.DeveloperKit.CustomComponents/{Id}.json` | Wizard `Custom` yolunda bileşen adını arar |
|
||
| 4 | Wizard | `wizard/{Ad}.json` + menü/yetki/dil satırları (`data/Sas.Menus.*`, `data/Abp.Identity.Permission*`, `data/Sas.Languages.LanguageText/{CultureName}.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. Dosya ekranı yalnızca `Wizard` bölümüyle taşır; `Data` başlığı (`SelectCommand`/`KeyFieldName`) olmayan List dosyasını 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/Sas.Menus.Routes.json` ya da `data/Sas.DeveloperKit.CustomComponents/{Id}.json` | Yolu üreten dosya yazıldıktan sonra; ziyaretçiye açık hiçbir yol metasız kalmaz (§0.9) |
|
||
| 8 | Modül dokümanı | `docs/modules/{Menü}.md` (iskelet + envanteri deploy açar) + `README.md` §10.5 dizin satırı | Son adımdır; anlatım bölümleri doldurulur, MCP'de `save_module_doc` (§0.13) |
|
||
|
||
**`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/Sas.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/Sas.Languages.LanguageText/{CultureName}.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 `Sas.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 |
|
||
| İşlem tablosunda `BranchId` | Tanım **olmayan** yeni tabloda `TenantId` gibi varsayılan; ekran `IsBranch: true` (§8.1) |
|
||
| `IsActive` varsayılan değeri | `IsActive` taşıyan her tabloda `DEFAULT 1`, formda `true` (§8.1) |
|
||
| 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
|
||
Core : Çerçeve içinde karşılanamayan kısım (yoksa "yok") — yalnızca not, komutsuz yazılmaz (§0.8)
|
||
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` |
|
||
| Yıldızlı puan (1–5) | `dxRating`, kolon `int` | `count`, `allowClear` |
|
||
| 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 (`<userName>` / `<email>` ya da `@Kolon`) 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ı — Core'a yeni özellik yalnızca komutla
|
||
|
||
Platform kendi mekanizmalarıyla büyür. **Core**, `api/` ve `ui/` altındaki kaynak kodun tamamıdır;
|
||
çerçeve ise `configs/seeds/` altında üretilen artefaktlardır. Kullanıcı **"core sisteme ekle"**
|
||
(ya da core değişikliğini adıyla isteyen eşdeğer bir komut: "`GridEditingDto`'ya alan ekle",
|
||
"`Button` bileşenine `onBlur` ekle", "şu servisi kodda değiştir") demedikçe Core'a **yeni bir
|
||
özellik** eklenmez. Küçük olması, geriye dönük uyumlu olması ya da işi tamamlaması izin sayılmaz;
|
||
tek satırlık bir DTO alanı, prop, olay, tarif, `EditorOptions` anahtarı ya da `DefaultValue`
|
||
token'ı da yeni özelliktir.
|
||
|
||
**Önce envanter.** "Core gerekiyor" sonucuna, ListForm'un bütün yetenekleri taranmadan varılmaz:
|
||
`lowcode-reference.instructions.md` §0 katman listesi (24 katman), §1–§9 JSON kolonları, bu
|
||
dosyanın §4 (wizard/alan/lookup/varsayılan/doğrulama/biçim/ek filtre/alt form/widget/iş akışı/
|
||
komut butonları), §5 `EditorScript`, §6 `EditorOptions` (alan seviyesinde sözlük dışı anahtar
|
||
da geçer), §7 Custom Component, §8–§9 SQL nesnesi, Custom Endpoint, Dynamic Service,
|
||
BackgroundWorker, bildirim, rapor. Talebin karşılığı çoğunlukla bunların birleşimidir:
|
||
ekranda yapılamayan yazma `*Command`/Before-After kancasıdır, ekranda olmayan aksiyon
|
||
`CommandColumnJson` butonu + Custom Endpoint'tir, hesap `EditorScript`'tir, ekran dışı iş
|
||
mantığı Dynamic Service'tir.
|
||
|
||
**Ç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 — kod veritabanında yaşar, Core değildir), `crud/`, SQL nesneleri (tablo/view/SP/fonksiyon), Custom Endpoint, Dynamic Service, BackgroundWorker, bildirim türü/kuralı, numaratör, rapor şablonu, dil anahtarı, yetki kaydı, **SEO kaydı** (§0.9), modül dokümanı |
|
||
| Var olan sözleşmeyi kullanma | DTO'da zaten bulunan her `*Json` anahtarı, var olan her `EditorScript` tarifi, `EditorOptions` anahtarı (alan seviyesinde sözlük dışı anahtar dahil — reference §9.8), `DefaultValue` token'ı, tasarımcı düğümü/prop'u/olayı |
|
||
|
||
**Core — yalnızca "core sisteme ekle" komutuyla:**
|
||
|
||
| Alan | Örnek |
|
||
| --- | --- |
|
||
| Var olan bileşeni genişletme | `ui` bileşenine olay/prop/küçük yetenek, `componentProps.json` / `catalog.ts` (`DESIGNER_PRIMARY_EVENTS`, `NAMED_UNION_OPTIONS`, `PROPERTY_OVERRIDES`) girdisi, tasarımcı düğümüne prop/varsayılan (§7.3 notu) |
|
||
| Var olan sözlüğü genişletme | Yeni EditorScript / Script Builder tarifi (`scriptRecipes.ts` ↔ `Domain.Shared/Editors`), yeni `EditorOptions` sözlük girdisi ya da preset, yeni `DefaultValue` token'ı, yeni `CustomValueType`/enum değeri |
|
||
| ListForm sözleşmesi | `ListForm`/`ListFormField` DTO'suna ya da `*Json` kolonuna yeni alan (`GridEditingDto.X` gibi), ListForm editöründe yeni ayar, liste/form runtime'ına yeni davranış (`views/list`, `views/form`) |
|
||
| 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`), MCP aracı/prompt'u, wizard deploy |
|
||
| 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 |
|
||
| Bağımlılık | Yeni NuGet/npm paketi, sürüm yükseltme |
|
||
| Sabit yapılar | `data/Sas.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 |
|
||
|
||
**Core sayılmayan kod işi:** kullanıcının bildirdiği bir hatanın düzeltilmesi (var olan davranışı
|
||
belgelendiği hâline getirmek — yeni ayar, alan ya da seçenek eklemeden), kullanıcının dosya/sınıf
|
||
adıyla istediği kod değişikliği, refactor/temizlik isteği, doküman. Düzeltme sırasında "şu ayar da
|
||
olsa iyi olur" denen her şey yeni özelliktir ve komut bekler.
|
||
|
||
**Komut yoksa:** çerçeve içinde yapılabilen her şeyi bitir, Core'a dokunma; son mesajda tek bir
|
||
**"Core gerektiren kısım"** notu yaz — eksik yetenek, envanterde denenip yetmeyen yollar (bir iki
|
||
cümle), en küçük Core değişikliği ve dokunacağı dosyalar, geri alma. Soru kutusuyla Core seçeneği
|
||
sunulmaz, varsayılan seçenek Core olmaz; karar kullanıcının komutudur. MCP/talep döngüsünde
|
||
(§0.11–§0.12) analizdeki `(onay)` kararı Core komutu değildir: Core ihtiyacı analize ve üretim
|
||
notuna yazılır, üretim çerçeve içinde kalır.
|
||
|
||
**Komut gelince:** yalnızca komutta adı geçen değişiklik yapılır; komut sonraki talebe taşınmaz,
|
||
kapsamı genişletmez. Core'a eklenen her özellik aynı işte `lowcode-reference.instructions.md`
|
||
envanterine (ve ilgiliyse bu dosyaya) yazılır — envanterde olmayan yetenek bir sonraki talepte
|
||
yeniden Core'a gider.
|
||
|
||
---
|
||
|
||
### 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/Sas.Menus.Routes.json` | `/products`, `/blog`, `/demo`, `/checkout` |
|
||
| Custom Component `RoutePath` | `data/Sas.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.
|
||
|
||
```jsonc
|
||
// data/Sas.Menus.Routes.json ya da data/Sas.DeveloperKit.CustomComponents/{Id}.json
|
||
// içindeki satırın üzerine, var olan alanların yanına:
|
||
{
|
||
"Path": "/kurslar", // Custom Component'te: "RoutePath"
|
||
"SeoTitle": "::Sas.PublicSeo.Courses.Title", // dil anahtarı ya da düz metin
|
||
"SeoDescription": "::Sas.PublicSeo.Courses.Description",
|
||
"SeoKeywords": "::Sas.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 (`::Sas.PublicSeo.{Sayfa}.{Alan}`), anahtarlar `data/Sas.Languages.LanguageText/{CultureName}.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`) `Adm.SiteManagement.Seo.*` ayarlarındadır
|
||
(`configs/seeds/data/Sas.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 `Sas.DeveloperKit.McpServers.Invoke`, değiştirenler `.Write`
|
||
ister ve kiracı kullanıcısına hiçbiri açılmaz.
|
||
|
||
**MCP geliştirme araçları yalnızca seed dosyası yazar** (wizard / data / crud JSON'u, `sql`+`postgres`
|
||
object scripti) — Wizard ekranıyla aynı kural. Veritabanına hiçbiri yazmaz; canlıya yansıma her zaman
|
||
kullanıcının çalıştırdığı DB Migrate iledir, model migrate'i beklemez. Kolon adı tahmin edilmez:
|
||
`list_lists` / `describe_list` / `search_schema` geliştiriciye (host + `Sas.Listforms.Wizard`) veri
|
||
sözlüğünde henüz olmayan kaynakları da döner — migrate bekleyen wizard ekranları (`sourceKind: pending`,
|
||
alanlar wizard dosyasının `Groups[].Items[]` öğelerinden) ve yalnızca `create_table` scripti olan tablolar
|
||
(`sourceKind: pendingTable`, kolonlar scriptten). Lookup, widget, view ve `Query` SQL'i bu adlarla yazılır.
|
||
|
||
| 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 (`4.3`) ya da başlık metniyle istenir; aynı numarayı iki başlık taşımamalıdır. Bölüm önce istenen belgede, sonra diğerinde aranır; başlık olmayan bir terim (`dxImageUpload`) terimin geçtiği bölümlerin listesini döndürür |
|
||
| `create_table` | Yeni tablonun `sql/object/{Tablo}.sql` ve PG eşi `postgres/object/{Tablo}.sql` dosyalarını yazar; veritabanında çalıştırmaz, tabloyu DB Migrate kurar (deploy kolonları o zamana kadar scriptten okur) | Ek olarak `Sas.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. `indexes` (`{columns, unique}` → `IX_`/`UQ_{Tablo}_{Kolonlar}`) ve `foreignKeys` (`{column, referencedTable, referencedColumn = Id, onDelete = NoAction|Cascade|SetNull}` → `FK_{Tablo}_{Kolon}`) aynı scripte varlık kontrollü yazılır. Migrate scriptleri dosya adı sırasıyla çalıştırdığı için FK'nin hedefi ya adı bu tablodan önce sıralanan bir seed tablosu ya da veritabanında kurulu bir platform tablosu olmalıdır (`ForeignKeyTableNotInstalledFirst`) |
|
||
| `save_sql_object` | View, procedure ya da function scriptini `sql/object/{Ad}.sql` ve `postgres/object/{Ad}.sql` dosyalarına yazar (`McpSqlObjectWriter`); veritabanında çalıştırmaz | `Sas.SqlQueryManager` ister. İki script de zorunludur ve aynı nesneyi tanımlar (ad scriptten okunur); başlık `CREATE OR ALTER` / `CREATE OR REPLACE`'e çevrilir, aynı adla tekrar yazmak günceller. Tablo scriptinin adını alamaz. Migrate tabloları ve fonksiyonları view/yordamdan önce kurar; view'in okuduğu başka bir view'in adı ondan önce sıralanmalıdır. Birden çok tablodan beslenen ekranda `Query` yerine view (§8.2) |
|
||
| `generate_crud_endpoints` | Tablo/view için SQL Query Manager CRUD diyaloğuyla aynı endpoint kümesini yalnızca `crud/{Entity}.json` dosyasına yazar (`CrudEndpointSeedFileDto.Generate`); kayıtları DB Migrate ekler (`CrudEndpointSeedApplier`) | `Sas.SqlQueryManager.CrudEndpoints` ister. Custom component'in okuyup yazacağı tablo içindir; ListForm ekranı gerekmez. View/procedure/function'da yalnızca `GetList`/`GetById` |
|
||
| `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). Ekranın her ayarı `Wizard` bölümünün bir bloğu ya da alan öğesinin bir alanıdır (§4.1–§4.3): ListForm editörünün sekmeleri `Editing`, `Insert`/`Update`/`Delete`, `FormDefaults`, `FilterRow` … `ChartOptions` bloklarına, alan detayının sekmeleri (`ValidationRules`, `Format`, `Width`, `BandName`, `TotalSummary`/`GroupSummary`, `Join`, `Styling`, `PivotSettings` …) `Groups[].Items[]` öğesine yazılır. Bloklar isteğe bağlıdır — yazılmayan blok deploy'da varsayılanıyla dolar (`ApplyDefaults`); kısayol cevaplar (`AllowAdding` …, `IsRequired`) bloktan üstündür. Eski biçimin `ListForm`/`Fields` bölümleri **reddedilir** (`Sas.ListForm.WizardError.LegacySectionsNotAccepted`). Güncellemede `get_wizard` çıktısı düzenlenip gönderilirse editör ayarları korunur. Kimlik alanları (kod, ad, başlık/açıklama anahtarı, yetki, popup başlığı, durum anahtarı) her deploy'da ekran kodundan yeniden türetilir; tanınmayan anahtar reddedilir. Çı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; `Sas.SqlQueryManager` ister (§4.5) |
|
||
| `start_dev_request` | `Sas.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `Sas.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; `Sas.DevRequests` ister |
|
||
| `save_dev_request_analysis` | `Backlog`/`Analysis`/`Ready to Start` durumundaki talebin analizini `Analysis` kolonuna yazar (var olanı değiştirir); `productionReady: true` (Üretime hazır: Evet) talebi `Ready to Start`'a, `false` (şartlı/Hayır) `Analysis` durumuna alır; diğer durumları reddeder. Modül dokümanına yazmaz; doküman üretimde açılır (§0.13) | Ek olarak `Sas.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 `Adm.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 (Sas.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; satırlar deploy anında bu dosyaya yazılır. `SeedSyncInsert/Update/Delete` (§9.15) demo veriyle
|
||
**açılmaz** — varsayılan `false`'tur; yalnızca istek "ekrandan girilen kayıtlar seed dosyasına
|
||
yansısın" diyorsa wizard cevabına `SeedSync: true` yazılır (wizard ekranında da aynı kural).
|
||
`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 host bağlamında ekrandan girilen satırlar `ListFormSeedDataSynchronizer` ile aynı
|
||
dosyaya yazılır; kiracı bağlamındaki kayıt dosyaya yansımaz.
|
||
|
||
Kurallar:
|
||
- Dosyayı elle yazma; `seedRows` alan adlarını ekranın alanlarına göre doğrular, bilinmeyen alanı reddeder.
|
||
Migrate satırı yalnızca ekranın alanı olan kolonlara yazar; aynı tablonun alt form sekmesindeki
|
||
kolon (ör. `Form` tipi detay sekmesi) ana ekranın satırından yazılmaz. Ret mesajı, kolonun aynı
|
||
tabloyu okuyan hangi wizard ekranında alan olduğunu wizard dosyalarından bulup yazar: kolonu ana
|
||
ekrana alan olarak ekle (`IncludeInEditingForm: false`) ya da satırdan çıkar.
|
||
- 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ü — `Sas.DevRequests` kuyruğu
|
||
|
||
Talepler `/admin/list/Sas.DevRequests` ekranında (`Sas_H_DevRequest`) toplanır; kiracılar da
|
||
talep girer (`Adm.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: `Sas.DevRequests.Update`,
|
||
`Sas.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`
|
||
(analiz "Üretime hazır: Evet" ise MCP doğrudan taşır; şartlı/Hayır analiz `Analysis`'te kalır,
|
||
kullanıcı onaylayıp taşır ya da analiz yeniden yazılır/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: Sas.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. Doküman | `get_module_doc` → `module-doc:todo` bölümleri doldurulur → `save_module_doc` (§0.13); eksik doküman Testing'i engeller | — |
|
||
| 5. Teslim | Sonuç kaydı + `update_row` → `Testing`. DB Migrate beklenmez; talep burada biter | Testing |
|
||
| 6. 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 `Sas.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) bu döngüde analizin **(onay)**
|
||
kararlarıyla çözülmüştür; Core ihtiyacı bunun dışındadır — `(onay)` Core komutu sayılmaz (§0.8).
|
||
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, uygulamanın önbellek anahtarlarını temizler
|
||
(`DistributedCacheCleaner`; Redis'teki diğer uygulamalara dokunmaz) 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. Analiz koşulsuz üretime
|
||
hazırsa (madde 11: **Evet**) talep doğrudan `Ready to Start`'a geçer; şartlı ya da **Hayır** ise
|
||
`Analysis`'te kalır — kullanıcı şartları onaylayıp kendisi taşır ya da analiz değişir (MCP'ye yeniden
|
||
analiz ettirir veya `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: Sas.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, ve madde 11'le aynı `productionReady`. Modül dokümanına yazılmaz; dosya ve dal bölümü üretimde deploy ile açılır, analiz "2. Analiz ve kararlar" alt bölümüne üretimde `save_module_doc` ile taşınır (§0.13) (Timesheets satırını sunucu yazar, §0.11) | Evet → Ready to Start; şartlı/Hayır → 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. Hedef durumu `productionReady` belirler (`true` → `Ready to Start`, `false` → `Analysis`);
|
||
model durumu `update_row` ile değiştirmez. `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; **(onay)** taşıyan
|
||
analiz şartlıdır (madde 11), talep `Analysis`'te kalır ve onu `Ready to Start`'a alan kullanıcı
|
||
kararları 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 |
|
||
|
||
Çalışanın/yöneticinin kendi listesi (persona ekranı) menüye konmaz; satıra "menüsüz — Intranet
|
||
widget'ı" yazılır (§0.12.1).
|
||
|
||
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).
|
||
Seviyenin kodu menü kaydının `Code`'udur, `DisplayName` dil anahtarı değildir: "Human Resources ›
|
||
Definitions" `Hr.Definitions`'tır, `Sas.Platform.Definitions` değil. Deploy yine de yanlış
|
||
yazılmış yolu var olan dala bağlar: kodu bulunamayan seviye, aynı üstün altında `DisplayName`
|
||
anahtarı koda eşit ya da EN/TR adı aynı olan bir dal varsa o dalın koduna çevrilir
|
||
(`ListFormWizardAppService.BindExistingMenuParentsAsync`); ikinci bir ara menü ve ara yetki
|
||
açılmaz. Eşleşme yoksa seviye yenidir ve kurulur.
|
||
**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. Tanım olmayan tabloya (işlem/belge ve alt formları)
|
||
`IsActive` eklenmez; talep aktif/pasif bayrağını açıkça isterse varsayılanı aynıdır:
|
||
`bit NOT NULL DEFAULT 1`, formda `true` (§8.1).
|
||
Hayır ise (işlem/belge tablosu) kolon tablosunda `TenantId`'nin yanında `BranchId`
|
||
(uniqueidentifier, zorunlu) yer alır ve ekran `IsBranch: true` ile kurulur (§8.1 İşlem tablosu).
|
||
**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`, `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`…), yabancı uyruk alanları
|
||
(`PassportNo`, `PassportExpiryDate`, `WorkPermitNo`, `WorkPermitEndDate`), askerlik ve engellilik
|
||
(`MilitaryStatus` — `Done`/`Exempt`/`Postponed`/`NotDone`, `MilitaryPostponeEndDate`, `IsDisabled`,
|
||
`DisabilityRate`), çalışma bilgisi (`RegistrationNo` sicil no, `EmploymentType` — `FullTime`/`PartTime`,
|
||
`ContractType` — `Indefinite`/`Definite`, `ContractEndDate`, `ProbationEndDate`, `SeniorityStartDate`,
|
||
`TerminationDate`, `TerminationReasonId` → `Sas_H_TerminationReason`, `ShiftId` → `Hr_T_Shift`,
|
||
`PdksCardNo`, `DrivingLicense*`). Birden çok satırlı bilgiler kullanıcının alt tablolarındadır:
|
||
eğitim geçmişi `Adm_T_UserEducation` (tek `GraduationSchool` alanı kaldırıldı), yabancı dil
|
||
`Adm_T_UserLanguage`, yetenek `Adm_T_UserSkill`, yeterlilik belgesi `Adm_T_UserCertificate`, eş/çocuk
|
||
`Adm_T_UserFamilyMember`, acil durum kişisi `Adm_T_UserEmergencyContact`, yan hak `Adm_T_UserBenefit`. 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 `Sas.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. Sekmede satır girilen ana ekran için `OpenDetailAfterInsert` kararı
|
||
(kaydedince Detay'a geçiş) burada yazılır — başlık + kalem ekranında varsayılan `true`.
|
||
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**, **Şartlı — …** (**(onay)** etiketli karar ya da
|
||
kullanıcının netleştirmesi gereken bir koşul, adıyla) ya da **Hayır — eksik yetenek: …** (çerçeve
|
||
dışı ihtiyaç, §0.8, adıyla). Talebi daraltan geçici çözüm önerilmez. Bu satır
|
||
`save_dev_request_analysis`'in `productionReady` değeridir: yalnızca **Evet** talebi
|
||
`Ready to Start`'a alır; **Şartlı** talep kullanıcı onaylayana, **Hayır** talep yetenek eklenene
|
||
kadar `Analysis`'te kalır.
|
||
|
||
### 0.12.1 Çalışan (persona) ekranları — Wizard ve MCP için
|
||
|
||
İK menüsü İK departmanının ekranlarını taşır. Çalışanın ya da yöneticinin kendisiyle ilgili listeleri
|
||
("Taleplerim", "Değerlendirmelerim", "Ekibimin …") İK menüsüne konmaz; keşif turunda bu kalıp
|
||
varsayılan olarak önerilir:
|
||
|
||
- **Ekran:** menüsüz wizard (`CreateMenu: false`), kodu `Hr.My{Ekran}` / `Hr.Team{Ekran}`,
|
||
İK ekranıyla aynı tabloya açılır. Satırları `DefaultFilter` + alan varsayılanıyla oturumdaki
|
||
kullanıcıya süzülür (`"EmployeeId" = '@USERID'`); yetki okuma yetkisidir, İK yetkisi verilmez.
|
||
Kalıp: `configs/seeds/docs/modules/Hr.md` › Performans Yönetimi §3.2–§3.3.
|
||
- **Erişim:** Intranet panosunda `Sas_T_IntranetWidget` kaydı (`SourceType: 1`, custom component;
|
||
`PermissionName` = persona ekranının okuma yetkisi) özet gösterir ve listeye bağlanır.
|
||
- **Profil:** `/admin/profile` sekmeleri sabittir — Çalışan Bilgileri, İzinlerim
|
||
(`Hr.MyLeaveRequest`, `Hr.MyLeaveBalance`), Puantajım (`Hr.MyTimesheet`), Performansım
|
||
(`Hr.MyPerformanceReview`, `.MyPerformanceGoal`, `.MyDevelopmentPlan`, `.My360Feedback`).
|
||
Sekmeler çekirdek, **salt okunur** bileşenlerdir (`ui/src/views/admin/profile/components`): persona
|
||
ekranının satırlarını `list-form-select` ile okur, grid/ListForm çizmez; düzenle/sil yoktur. Tek
|
||
aksiyon "İzin Talebi Oluştur" (`/admin/form/Hr.MyLeaveRequest`, `.Create` yetkisiyle). Süreç
|
||
işlemleri (öz değerlendirme, 360 puanlama, açık talebi düzeltme) panodaki widget'ların açtığı persona
|
||
ekranında kalır; çalışanın karar verilmiş kaydı değiştirememesi sunucu tarafında guard
|
||
prosedürüyle korunur (`Hr_S_LeaveRequestGuard`, `Hr_S_PerformanceGuard`). Sekme, ekranlarından
|
||
hiçbirinin yetkisi yoksa görünmez. Yeni bir persona ekranı profile
|
||
kendiliğinden girmez: sekmeye eklemek `ui/src/views/admin/profile` kodu + rota satırıdır, yani
|
||
çerçeve dışıdır (§0.8, onay). Wizard/MCP ekranı ve widget'ı üretir; profil sekmesini üretim notunda
|
||
öneri olarak yazar.
|
||
- **Kullanıcı alanı:** çalışanın kendi kullanıcı alanları profilde `api/account/my-profile` ek
|
||
alanlarından okunur (`PlatformProfileAppService.ProfileExtraProperties`). Kullanıcıya eklenen yeni
|
||
bir alanın profilde görünmesi isteniyorsa liste ve `MyEmployeeInfo.tsx` birlikte değişir; sağlık,
|
||
ücret ve işten çıkış alanları listeye eklenmez.
|
||
|
||
### 0.13 Modül dokümanı — `configs/seeds/docs/modules/{Menü}.md`
|
||
|
||
Üretilen her modül kendi çalışma şeklini bir md dosyasında taşır; "bu ekran nasıl çalışıyor / şu
|
||
puan nereden geliyor" sorusu kod ve seed taranmadan bu dosyadan cevaplanır (MCP'de `get_module_doc`).
|
||
Doküman **wizard'ın parçasıdır**: Wizard ekranından ya da MCP `deploy_wizard` ile üretilen her
|
||
modülde sunucu onu kendisi açar ve güncel tutar; anlatım bölümlerini üreten kişi/AI doldurur.
|
||
Wizard dışı (entity, özel React) modüller bölümünü elle yazar.
|
||
|
||
- **Yer ve ad:** seed klasörü — `configs/seeds/docs/modules/{Menü}.md`, **kök menü başına tek
|
||
dosya**. Ad kök menünün kodudur (`Adm.md`, `Hr.md`,
|
||
`Prc.md`). Ad sabit bir listeden değil menü ve dil seed dosyalarından okunur
|
||
(`ModuleDocCatalog.DocNameOfRoot`); yeni bir kök menü ilk deploy'unda kendi dosyasını açar.
|
||
Sunucu yalnızca seed köküne yazabildiği için depo kökünde değildir. Wizard export zip'i dokümanı
|
||
taşır, import yazar (`docs/modules/*.md`).
|
||
- **Bölüm:** kök menünün altındaki her dal dosyada bir bölümdür; bölüm bir beyan satırıyla başlar,
|
||
başlığı `## `, alt bölümleri `### `'tür:
|
||
```markdown
|
||
<!-- module-doc:section Hr.Recruitment claims=Hr.CandidateSource, Hr.RejectionReason -->
|
||
## İşe Alım (`Hr.Recruitment`)
|
||
### 1. Amaç ve kapsam
|
||
```
|
||
Bölümler menü sırasıyla dizilir; menüde karşılığı olmayan elle yazılmış bölüm (ör.
|
||
`Hr.Personnel`) yerini korur.
|
||
- **Hangi wizard hangi bölüme** (`ModuleDocCatalog`; wizard sözleşmesine alan eklenmez):
|
||
1. Bir bölümün beyanındaki `claims=` ekranı sahipleniyorsa o bölüm (başka dalda duran tanım
|
||
ekranları için).
|
||
2. Menüsüz alt form, onu `SubForms` içinde taşıyan ekranın bölümüne gider.
|
||
3. Ekranın kodu bir bölümün anahtarıysa o bölüm (alt formun 2. kuralı bunu ezer).
|
||
4. Menü ağacında kökün altındaki ilk seviye (`Hr › Hr.Performance › …` →
|
||
`Hr.Performance`); ekran doğrudan kökün altındaysa ekranın kendi kodu. Kök ve dal
|
||
`data/Sas.Menus.Menu.json`'dan bulunur. Ana ekranı henüz deploy edilmemiş menüsüz alt form
|
||
(MCP alt formları ana ekrandan önce deploy eder) kök sayılmaz, kendi dosyasını açmaz: kodunun
|
||
modül önekinin dosyasında (`Prj.ProjectMember` → `Prj.md`) geçici bölüm alır; ana ekran
|
||
gelince onun bölümüne taşınır, doldurulmamış geçici bölüm silinir.
|
||
- **Otomatik kısım:** deploy (`ListFormWizardAppService.Create`), silme, tasarımcı kaydı
|
||
(`WizardSeedSynchronizer`) ve import kapanışı `ModuleDocManager.RefreshAllAsync` çağırır:
|
||
dosya yoksa kök başlığı açılır, wizard'ı olan dalın bölümü yoksa iskeleti menü sırasındaki yerine
|
||
eklenir (alt bölümler `_Henüz yazılmadı._ <!-- module-doc:todo -->`),
|
||
`<!-- module-doc:auto:begin -->` … `<!-- module-doc:auto:end -->` arasındaki **"Ek — Ekran
|
||
envanteri"** bölümlere göre gruplanarak wizard dosyalarından yeniden yazılır (menü, kaynak,
|
||
görünümler, alt formlar, widget'lar, komutlar/prosedürler, varsayılanlar, alan tablosu). Envanter
|
||
elle düzenlenmez. Wizard'ı kalmayan ve hiç yazılmamış bölüm, bölümü kalmayan dosya silinir.
|
||
Wizard'a yeni bir kayıt yolu eklenirse `RefreshAllAsync` çağrısı da eklenir.
|
||
- **Elle/AI ile yazılan kısım:** aşağıdaki alt bölümler; doldurulan alt bölümden `module-doc:todo`
|
||
işareti kaldırılır, boş kalacak alt bölüme "Yok." yazılır. MCP'de `get_module_doc` /
|
||
`save_module_doc` ekran ya da dal koduyla çağrılır: yalnızca o bölüm okunur ve yazılır (beyan
|
||
satırı gönderilmezse mevcut beyan korunur; envanter gönderilmez, sunucu ekler). Dosya adıyla
|
||
çağrılırsa dosyanın tamamıdır. Menüde henüz olmayan modülün kodu (analizdeki `Stk.Stock`)
|
||
kök ve dalı koddan alır: okuma iskelet döner, yazma `Stk.md` dosyasını açar. Claude Code
|
||
depoda dosyayı doğrudan yazar.
|
||
- **Zorunluluk:** MCP döngüsünde talep `update_row` ile `Testing`'e alınırken talebin wizard'larının
|
||
**bölümünde** `module-doc:todo` kalmışsa ya da bölüm yoksa istek reddedilir; aynı dosyadaki
|
||
başka dalların eksikleri talebi engellemez (`IModuleDocAppService.GetIncompleteForDevRequestAsync`).
|
||
- **Dizin:** yeni kök menü `README.md` §10.5 tablosuna bir satır, yeni dal o satırın "Bölümler"
|
||
hücresine bir ad ekler; README'de modül en fazla ilgili bölümde tek cümleyle anılıp dokümana bağlanır.
|
||
- **Alt bölümler** (`ModuleDocRenderer.Sections`, her dal bölümünde `### `):
|
||
1. **Amaç ve kapsam** — kim kullanır, hangi işi çözer.
|
||
2. **Analiz ve kararlar** — keşif turunun cevapları (soru → seçilen seçenek → gerekçe) ya da
|
||
MCP'de onaylanmış analiz, onaylanan tarif ve `(varsayılan)` kalan satırlar.
|
||
3. **Menü ve ekranlar** — menü yolu, `ListFormCode`, persona (kim görür), alt formlar, görünümler, widget'lar.
|
||
4. **Veri modeli** — tablolar, anahtar ilişkiler, lookup kaynakları, durum/enum değerleri.
|
||
5. **İş kuralları ve hesaplar** — formüller, durum geçişleri, prosedürler ve hangi kancadan
|
||
(`*BeforeCommand`/`*AfterCommand`) çağrıldıkları, `EditorScript`'ler.
|
||
6. **Yetki ve görünürlük** — yetki kodları, önerilen rol dağılımı, satır süzmesi.
|
||
7. **Entegrasyonlar** — bildirim tipleri/kuralları, zamanlanmış işler, diğer modüllere yazma/okuma.
|
||
8. **Akış örneği** — uçtan uca bir senaryo (kim, hangi ekranda, ne yapar, sonuç ne olur).
|
||
9. **Sık sorulanlar** — "X neden kilitli?", "Y nasıl hesaplanır?" gibi soru-cevaplar.
|
||
10. **Dosyalar ve geri alma** — üretilen her seed/sql dosyası ve kaldırma yolu.
|
||
|
||
## 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.2 |
|
||
| "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` | `{Modul}.{Liste}` | `Sas.Mrp.Orders` |
|
||
| `MenuCode` | `ListFormCode` ile aynı | `Sas.Mrp.Orders` |
|
||
| Üst menü kodu | `{Modul}` | `Sas.Mrp` |
|
||
| Yetki grubu | `{Modul}` | `Sas.Mrp` |
|
||
| Yetkiler | `{MenuCode}` + `.Create/.Update/.Delete/.Export/.Import` (+ `.Note` yalnızca kayıt kaynağı Table ise) | `Sas.Mrp.Orders.Create` |
|
||
| Dil anahtarı (menü/başlık) | `{MenuCode}` | `Sas.Mrp.Orders` |
|
||
| Dil anahtarı (alan) | `Sas.Listform.ListformField.{Alan}` | `Sas.Listform.ListformField.OrderNo` |
|
||
| Route (List) | `/admin/list/{ListFormCode}` — statik `:listFormCode` route'u karşılar, DB'ye kayıt yazılmaz | `/admin/list/Sas.Mrp.Orders` |
|
||
| Route (Custom) | Bileşenin `RoutePath` değeri; route doğrudan bundan üretilir | `/admin/order-board` |
|
||
| Wizard seed dosyası | `{WizardName}.json`; yeni wizard'ın adı `{ModuleId}_{Ad}` — modül öneki sunucuda eklenir (§4.4 Adım 2) | `Mrp_Orders.json` |
|
||
| Custom component seed | `data/Sas.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 | `Adm.Definitions.Sequence.json` |
|
||
|
||
Seed klasörü tektir: `configs/seeds/…`. Çö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). `DbQuery`'nin `SqlQuery` metnindeki yer tutucular (`'@USERID'`,
|
||
`'@TENANTID'`, `@USERNAME` …) liste sorgularındaki gibi `DefaultValueHelper` ile çözülür.
|
||
|
||
**`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` (1) `numeric` (2) `range` (3) `stringLength` (4) `custom` (5) `compare` (6) `pattern` (7)
|
||
`email` (8) `async` (9) (§4.3.6). `compare` `ComparisonTarget`, `custom`/`async` `ValidationCallback` ister.
|
||
|
||
**`WorkerType`** (`WorkerTypeEnum`) — `1` MailQueueWorker · `2` SqlWorker ·
|
||
`3` NotificationWorker · `4` SessionCleanupWorker · `5` BackupWorker · `6` ProbationReminderWorker (§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 `Sas.NotificationChannel.{Kanal}` dil anahtarlarıdır |
|
||
| Bildirim alıcı tipi (`NotificationRecipientTypes`) | `User` · `Role` · `OrganizationUnit` · `All` · `Custom` |
|
||
| Pivot `Area` | `row` · `column` · `data` · `filter` |
|
||
| Pivot `GroupInterval` | `""` (yok) · `day` · `dayOfWeek` · `month` · `quarter` · `year` |
|
||
| Stil `RowType` | `data` · `detail` · `detailAdaptive` · `filter` · `group` · `groupFooter` · `header` · `totalFooter` |
|
||
| `CascadeFilterOperator` | `=` · `<>` · `<` · `<=` · `>` · `>=` · `contains` · `notcontains` · `startswith` · `endswith` · `isblank` · `isnotblank` · `between` |
|
||
| `JoinType` | `INNER JOIN` · `LEFT JOIN` · `RIGHT JOIN` |
|
||
| `FixedPosition` · `SortDirection` | `left` · `right` · `asc` · `desc` |
|
||
|
||
**`DbSourceType` / `KeyFieldDbSourceType`** (`System.Data.DbType`):
|
||
|
||
| # | Ad | SQL karşılığı |
|
||
| --- | --- | --- |
|
||
| 0 | AnsiString | `varchar` (entity varsayılanı; wizard `16` yazar) |
|
||
| 1 | Binary | `binary`, `varbinary`, `image` |
|
||
| 2 | Byte | `tinyint` |
|
||
| 3 | Boolean | `bit` |
|
||
| 4 | Currency | `money` |
|
||
| 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` |
|
||
| 13 | Object | `sql_variant` |
|
||
| 14 · 15 | SByte · Single | — · `real` |
|
||
| 16 | String | `nvarchar`, `varchar`, `nchar`, `char`, `text` |
|
||
| 17 | Time | `time` |
|
||
| 18 · 19 · 20 | UInt16 · UInt32 · UInt64 | — |
|
||
| 21 | VarNumeric | — |
|
||
| 22 · 23 | AnsiStringFixedLength · StringFixedLength | `char` · `nchar` |
|
||
| 25 | Xml | `xml` |
|
||
| 26 | DateTime2 | `datetime2` |
|
||
| 27 | DateTimeOffset | `datetimeoffset` |
|
||
|
||
İstemci tipi bu sayıdan türetilir (reference §9.7 `SourceDbType`): Byte, SByte, Binary, Time, Xml ve
|
||
VarNumeric boş tip verir; `tinyint` kolonunda sayı filtresi/biçimi için `10` Int16 yaz.
|
||
|
||
**Editör tipleri (`EditorType2`)** — 22 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`, `dxRating` (+ `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 21 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/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 ve aynı `ListFormWizardDto` nesnesini başlıklara toplanmış hâlde taşır. List yolunda
|
||
dosyayı deploy'a ürettir; `Data` başlığı (`SelectCommand`/`KeyFieldName`) 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` | `Wizard` bölümü (wizard cevapları = ekranın bütün sözleşmesi, §4.1), `InsertedRecords`, `Pending`, `DevRequest` |
|
||
| `data/Sas.Menus.Module.json`, `data/Sas.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/Sas.Languages.LanguageText/{CultureName}.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; klasördeki diğer dil dosyalarına çevirileri eklenir (`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).
|
||
Her wizard dosyası kendi unit of work'ünde kurulur: hata veren dosya geri alınır (önceki ekran yerinde
|
||
kalır, `Pending` silinmez), sonraki dosyalar etkilenmez. İş akışı kurulumdan önce
|
||
`ListFormWizardDto.NormalizeWorkflow`'dan geçer (adım kimliği `{ListFormCode}-N…`, boş onaycı/kolon
|
||
`''`); deploy girdisiyle gelen `WorkflowDto` deploy'da da aynı metottan geçer.
|
||
**Yetkiler hiçbir role otomatik atanmaz** — admin dahil; ekranı hangi rolün göreceği rol ekranından
|
||
seçilir. Tek istisna yeni kiracının yöneticisidir: ilk seed'de, o anda tanımlı olan kiracı tarafı
|
||
yetkilerin hepsini alır (`PlatformIdentityDataSeeder`).
|
||
|
||
**Deploy hattı** (`ListFormWizardAppService.Create`): önce `ListFormWizardDto.ApplyDefaults(isDeletedField,
|
||
isCreatedField, isModifiedField)` cevapta boş bırakılan her bloğu wizard varsayılanıyla doldurur
|
||
(`WizardDefaults` + `WizardConsts`; üç bayrak hedef tablonun `IsDeleted`/`CreatorId`/`LastModifierId`
|
||
kolonlarını taşıyıp taşımadığıdır — katalogdan ya da seed scriptinden okunur, dosyaya yazılmaz; ekran
|
||
kodundan türeyen yetki adları, popup başlığı ve durum anahtarı her deploy'da yeniden yazılır), sonra
|
||
`BuildListForm()` / `BuildFields()` ekranı üretir (salt eşleme, parametresiz), ardından
|
||
`LocalizeStaticLookupsAsync` ve `EditorScript.Rebuild` çalışır, üretilen ekran `RefreshFrom(listForm, fields)`
|
||
ile cevaplara geri yazılır ve dosyaya **yalnızca `Wizard` bölümü** kaydedilir (`WizardSeedFileDto.ToSeedJson`).
|
||
Migrate'te `WizardDataSeeder` aynı `BuildListForm()/BuildFields()` ile dosyadan ekranı kurar; ListForm
|
||
detayından yapılan değişikliği `WizardSeedSynchronizer` `RefreshFrom` ile dosyaya yazar. Üç yolun çıktısı bu
|
||
yüzden birbirinden ayrışamaz.
|
||
|
||
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
|
||
|
||
```jsonc
|
||
{
|
||
"Wizard": { // ekranın BÜTÜN sözleşmesi — 4.2, 4.3
|
||
"ComponentKind": 0, "WizardName": "Prj_Tags", "WizardCode": "Prj.Tags",
|
||
"SeededAt": "2026-09-01T09:03:00", "CustomComponentName": null,
|
||
"PermissionGroupName": "Prj", "ModuleId": "Prj",
|
||
"Menu": { "MenuParentPath": [ { "Code": "…", "Icon": "…", "En": "…", "Tr": "…" } ], // yalnızca derin yolda
|
||
"MenuParentCode": "…", "MenuParentIcon": "…", // "CreateMenu": false ise eklenir
|
||
"MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…" },
|
||
"Language": { "MenuParentEn": "…", /* MenuParent/PermissionGroup/Menu/Title/Desc · EN + TR */
|
||
"Texts": [ { "Key": "…", "En": "…", "Tr": "…" } ] }, // Texts yalnızca doluysa
|
||
"Data": { "DataSourceCode": "Default", /* "DataSourceConnectionString" yalnızca doluysa */
|
||
"SelectCommandType": 1, "SelectCommand": "Prj_T_Tag", "TableName": null,
|
||
"KeyFieldName": "Id", "KeyFieldDbSourceType": 9,
|
||
"IsTenant": true, "IsBranch": false, "IsOrganizationUnit": false,
|
||
"DefaultFilter": "\"IsDeleted\" = 'false'",
|
||
"SeedFilePath": null, "SeedSyncInsert": false, "SeedSyncUpdate": false, "SeedSyncDelete": false,
|
||
"SelectFieldsDefaultValue": [] },
|
||
"Details": { "ListFormType": "List", "PageSize": 10, "Width": 0, "Height": 0, "FullHeight": false,
|
||
"ShowNote": false, "IsSubForm": false, "SubFormsListFormType": "Form", "SortMode": "single",
|
||
"UserId": null, "RoleId": null, "CustomJsSources": [], "CustomStyleSources": [] },
|
||
"Layout": { "DefaultLayout": "grid", "Grid": true, "Card": true, "Pivot": true, "Chart": true,
|
||
"Tree": false, "Gantt": false, "Scheduler": false, "Todo": false },
|
||
"Editing": { /* GridEditingDto — mod, popup, izin bayrakları … */ },
|
||
"Insert": { "ServiceAddress": "list-form-data/insert", "Command": null, "BeforeCommand": null,
|
||
"AfterCommand": null, "FieldsDefaultValue": [ /* FieldsDefaultValueDto */ ] },
|
||
"Update": { /* aynı yapı */ }, "Delete": { /* aynı yapı */ },
|
||
"FormDefaults": [ /* FieldsDefaultValueDto — öğenin sabit DefaultValue'suna sığmayanlar */ ],
|
||
"Permission": { "C": "…", "R": "…", "U": "…", "D": "…", "E": "…", "I": "…", "N": "…" },
|
||
"FilterRow": {}, "HeaderFilter": {}, "FilterPanel": {}, "SearchPanel": {}, "GroupPanel": {},
|
||
"Selection": {}, "ColumnOption": {}, "Row": {}, "Pager": {}, "StateStoring": {}, "PivotOption": {},
|
||
"TreeOptionDto": {}, "GanttOptionDto": {}, "SchedulerOptionDto": {}, "TodoOptionDto": {},
|
||
"ExtraFilters": [], "CommandColumns": [], "SubForms": [], "Widgets": [],
|
||
"WorkflowDto": {}, "ChartOptions": { /* WizardChartDto — grafik sekmeleri */ },
|
||
"Groups": [ { "Caption": "", "ColCount": 1, "ColSpan": 1,
|
||
"Items": [ { "FieldName": "…", "CaptionName": "…", "En": "…", "Tr": "…",
|
||
/* alan detayının bütün alanları — 4.3 */ } ] } ]
|
||
},
|
||
"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
|
||
}
|
||
```
|
||
|
||
Dosyada **yalnızca `Wizard` bölümü** vardır ve ekranın bütün sözleşmesi oradadır: ListForm detayının
|
||
her sekmesi bir blok (`Editing`, `Insert`/`Update`/`Delete`, `FilterRow` … `ChartOptions`), alan
|
||
detayının her sekmesi `Groups[].Items[]` öğesinin bir alanıdır (§4.2, §4.3). Anahtar sırası sabittir:
|
||
`ComponentKind`, `WizardName`, `WizardCode`, `SeededAt`, `CustomComponentName`, `PermissionGroupName`,
|
||
`ModuleId`, ardından `Menu`, `Language`, `Data`, `Details`, `Layout` başlıkları, ardından bloklar
|
||
`Editing`, `Insert`, `Update`, `Delete`, `FormDefaults`, `Permission`, `FilterRow`, `HeaderFilter`,
|
||
`FilterPanel`, `SearchPanel`, `GroupPanel`, `Selection`, `ColumnOption`, `Row`, `Pager`, `StateStoring`,
|
||
`PivotOption`, `TreeOptionDto`, `GanttOptionDto`, `SchedulerOptionDto`, `TodoOptionDto`, `ExtraFilters`,
|
||
`CommandColumns`, `SubForms`, `Widgets`, `WorkflowDto`, `ChartOptions`, `Groups`. Kök seviyede
|
||
`ListForm`/`Fields` bölümü ve `IsDeletedField`/`IsCreatedField` **yoktur**; eski biçimdeki bir dosya
|
||
okunursa (`FromSeedJson`) iki bölüm `RefreshFrom` ile `Wizard`'a aktarılır ve bir sonraki yazımda
|
||
düşer. Dosyaya yazılmayan kısayollar: `AllowAdding/AllowUpdating/AllowDeleting/AllowDetail/
|
||
OpenDetailAfterInsert/ConfirmDelete` (`Editing`'den), `SeedData`/`SeedSync` (`Data.SeedFilePath`/
|
||
`SeedSync*`'tan), öğedeki `IsRequired` (`ValidationRules`'tan) — okurken türetilir
|
||
(`SyncDerivedAnswers`), deploy girdisinde yazılırsa bloktan üstündür.
|
||
|
||
**Custom yolunda** (`ComponentKind: 1`) ekrana ait anahtarların hiçbiri (`Data`, `Details`, `Layout`,
|
||
bloklar, `Groups`) dosyaya yazılmaz; `Wizard` yalnızca kimlik, `Menu` ve `Language` taşır.
|
||
Bu yolun dosyası elle yazılabilir — seeder yalnızca menü, yetki ve dil kaydı üretir.
|
||
|
||
**`Wizard` bölümü ekranın tek gerçeğidir.** Seeder ekranı `Wizard.BuildListForm()` /
|
||
`Wizard.BuildFields()` ile kurar (§4.0 deploy hattı). Ekran **ListForm detayından** (liste ayarları,
|
||
alan ayarları, JSON satır editörleri) düzenlendiğinde sunucu aynı dosyanın `Wizard` bölümünü
|
||
`RefreshFrom` ile geri yazar (`WizardSeedSynchronizer`); böylece migrate ya da import ekranı dosyadan
|
||
kurarken tasarımcıdaki iş kaybolmaz. Bunun şartı eşliktir: `GridOptionsEditDto`/`ColumnFormatEditDto`'ya
|
||
eklenen her alanın `ListFormWizardDto`/`WizardColumnItemInputDto`'da karşılığı + `RefreshFrom` +
|
||
`BuildListForm/BuildFields` eşlemesi olmalıdır, yoksa tasarımcı değişikliği migrate'te kaybolur.
|
||
Elle bu dosyayı yazarken blokları uydurma — wizard'ı deploy et, dosyayı sunucu üretsin; yazmadığın
|
||
blok deploy'da varsayılanıyla dolar.
|
||
|
||
`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 ekranın **bütün** sözleşmesidir: menü, yetki, dil metinleri, bileşen yolu, veri
|
||
kaynağı, ekran ayarları, ListForm detayının her sekmesi (blok olarak) ve alan tanımları (öğe
|
||
olarak). Ekrana ait hiçbir bilgi dosyada başka bir bölümde değildir.
|
||
|
||
Dosyada wizard alanları beş başlık altında toplanır — `Menu` (menü kaydı), `Language` (dil
|
||
metinleri), `Data` (veri kaynağı, kayıt kaynağı, anahtar, kırılım bayrakları, okuma koşulu, veri
|
||
aynası), `Details` (Detay sekmesinin genel ayarları), `Layout` (görünüm bayrakları); bloklar ve
|
||
`Groups` 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.
|
||
|
||
Tablolardaki köşeli işaret alanın dosyadaki yerini söyler: `[Data]`/`[Details]`/`[Layout]` o başlığın
|
||
altında, `[Blok]` kök seviyede kendi adıyla, `[Item]` `Groups[].Items[]` öğesinde. `[Kısayol]` işaretli
|
||
alanlar **yalnızca istek girdisidir**: dosyaya yazılmazlar, sahibi olan bloktan türetilirler
|
||
(`SyncDerivedAnswers`) ve deploy girdisinde yazıldıklarında bloğun aynı adlı alanını ezerler.
|
||
|
||
**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ğı. Bütün modüllerde tekildir; yeni wizard'da `{ModuleId}_{Ad}` biçimindedir (`ListFormWizardDto.ResolveWizardName`). |
|
||
| `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). Ekranın her ayarı `Wizard` bölümünde olduğu için güncelleme dosyadaki değerlerle açılır (wizard ekranı dosyayı yükler, MCP'de `get_wizard` çıktısı düzenlenip gönderilir) ve editör ayarları korunur; yazılmayan blok varsayılana döner (§0.10). 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: "Hr"`, `MenuParentPath` yok; `İnsan Kaynakları › Tanımlamalar › Yeterlilik Belgeleri` (üç seviye) → `MenuParentPath: [{ Code: "Hr", … }]`, `MenuParentCode: "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 kaydı 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ı (`'Sas.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` yalnızca `SelectCommandType = Table` iken), Custom yolunda yalnızca `.Create .Update .Delete`.
|
||
**Not yalnızca tablo kaynaklı listede vardır:** View, Query, StoredProcedure ve TableValuedFunction kaynaklı listede
|
||
`PermissionJson.N` boş, `ShowNote` kapalıdır ve `.Note` yetki satırı yazılmaz — view/sorgu satırının notun bağlanacağı kalıcı
|
||
kaydı yoktur (`ListFormWizardDto.HasNote`; C# seeder'da `DefaultPermissionJson(name, hasNote: false)`). Hiçbiri bir role otomatik
|
||
atanmaz (admin dahil); ekranı görecek roller yetkiyi rol ekranından alır.
|
||
|
||
**Veri**
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `DataSourceCode` | `[Data]` Bağlantı kodu; varsayılan `"Default"`. |
|
||
| `DataSourceConnectionString` | `[Data]` Yalnızca **yeni** bir bağlantı tanımlanıyorsa doldurulur; boşsa dosyaya hiç yazılmaz. **Asla gerçek kimlik bilgisi yazma.** |
|
||
| `SelectCommandType` | `[Data]` Bkz. §3. |
|
||
| `SelectCommand` | `[Data]` Tablo/view adı, fonksiyon çağrısı ya da sorgu metni. |
|
||
| `TableName` | `[Data]` Sorgu kaynağında türetilmiş tablonun takma adı; boşsa deploy `WizardConsts.QuerySourceAlias` yazar. |
|
||
| `KeyFieldName` / `KeyFieldDbSourceType` | `[Data]` 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`. |
|
||
| `DefaultFilter` | `[Data]` Okuma koşulu; boşsa soft delete tablosunda deploy `"IsDeleted" = 'false'` yazar. |
|
||
| `SeedFilePath` / `SeedSyncInsert` / `SeedSyncUpdate` / `SeedSyncDelete` | `[Data]` Veri aynası (§9.15); kısayolları `SeedData`/`SeedSync` aşağıda. |
|
||
| `SelectFieldsDefaultValue` | `[Data]` Veritabanı › Select sekmesindeki varsayılanlar (`SelectFieldsDefaultValueJson`); çalışma anında okunmaz (§4.3.5.3). |
|
||
|
||
**Davranış**
|
||
|
||
| Alan | Anlam |
|
||
| --- | --- |
|
||
| `IsTenant` / `IsBranch` / `IsOrganizationUnit` | `[Data]` Otomatik kırılım filtresi. Tabloda `TenantId` varsa `IsTenant: true` **olmalı**. |
|
||
| `AllowAdding` / `AllowUpdating` / `AllowDeleting` / `ConfirmDelete` | `[Kısayol]` Toolbar ve satır aksiyonları; `Editing` bloğunun aynı adlı alanlarına yazılır. |
|
||
| `SeedData` | `[Kısayol]` `true` ise `Data.SeedFilePath = data/{ListFormCode}.json` üretilir; migrate dosyadaki satırları yükler. Wizard ekranında sorulmaz; demo veri taşıyan MCP üretimi için (§0.10.1). Varsayılan `false`; okurken `SeedFilePath` doluysa `true`. |
|
||
| `SeedSync` | `[Kısayol]` `SeedData` açıkken `Data.SeedSyncInsert/Update/Delete`'i açar: ekrandan eklenen/değiştirilen/silinen kayıt dosyaya yansır. **Varsayılan `false`**; demo veri bunu açmaz, yalnızca istekte açıkça istenirse `true` yazılır. Tek tek ayar `Data` başlığında yapılır. |
|
||
| `AllowDetail` | `[Kısayol]` 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. **`SubForms` doluysa cevaptan bağımsız `true` üretilir** (`ListFormWizardDto.BuildListForm`): wizard alt formları `Form` tipinde kurar ve sekmeler yalnızca Detay görünümünde açılır. Elle yazılan wizard dosyasında da alt formu olan ekranın `EditingOptionDto.AllowDetail` değeri `true` yazılır. **Alt formu olmayan listede `false` kalır** — MCP/AI üretiminde Detay düğmesi yalnızca `SubForms` taşıyan listeye açılır; kullanıcı açıkça istemedikçe tek tablolu liste ya da alt form ekranının kendisi (`PerformancePeriodSkill` gibi) Detay almaz. |
|
||
| `OpenDetailAfterInsert` | `[Kısayol]` Popup'tan **yeni** kayıt kaydedilince listede kalınmaz, aynı sekmede kaydın Detay formuna (`/admin/form/{ListFormCode}/{anahtar}`) geçilir (`EditingOptionDto.OpenDetailAfterInsert`; Grid, Tree, Card, Scheduler). Varsayılan `false`. **Analizde karar verilir:** alt form sekmesinde satır girilen başlık + kalem ekranında (talep + talep satırları, sipariş + sipariş kalemleri, masraf + masraf kalemleri) `true` yazılır — kalemler yalnız Detay'da girildiği için kullanıcı başlığı kaydedip doğrudan kalem girişine geçer; kullanıcı listede kalmak istediğini söylerse `false`. Alt formu olmayan listede ve alt form ekranının kendisinde `false`. `AllowDetail` ile birlikte anlamlıdır; `KeyFieldName` ve insert'in anahtarı döndürmesi şarttır (varsayılan insert döndürür). |
|
||
| `DefaultLayout` | `[Layout]` `"grid"`, `"card"`, `"pivot"`, `"chart"`, `"tree"`, `"gantt"`, `"scheduler"`, `"todo"`. |
|
||
| `Grid` `Card` `Pivot` `Chart` `Tree` `Gantt` `Scheduler` `Todo` | `[Layout]` 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. |
|
||
|
||
**Detay** — `[Details]`: `ListFormType` (`List`/`Form`, boşsa List), `PageSize` (10), `Width`/`Height`
|
||
(0), `FullHeight`, `ShowNote` (boşsa tablo kaynaklı ve alt formu ya da iş akışı olan ekranda açık),
|
||
`IsSubForm`, `SubFormsListFormType`, `SortMode` (`single`/`multiple`/`none`), `UserId`/`RoleId`
|
||
(ekran varyantı), `CustomJsSources`/`CustomStyleSources`.
|
||
|
||
**ListForm detayının sekmeleri** — `[Blok]`; her blok ekranın ilgili `*Json` kolonunun tipli
|
||
karşılığıdır, `null` bırakılan blok deploy'da wizard varsayılanıyla dolar (`ApplyDefaults`):
|
||
|
||
| Blok | Sekme / kolon |
|
||
| --- | --- |
|
||
| `Editing` | Düzenleme (`EditingOptionJson`, `GridEditingDto`): mod, popup, `RefreshMode`, `NewRowPosition`, `AllowDuplicate`, `AllowAllDeleting`, `AddPageUrl`/`AddDialogName`…; izin bayrakları kısayollardan gelir, `Popup.Title` ekran kodundan yeniden yazılır |
|
||
| `Insert` / `Update` / `Delete` | Veritabanı sekmesinin üç bloğu: `{ ServiceAddress, Command, BeforeCommand, AfterCommand, FieldsDefaultValue[] }` (eski `InsertCommand`, `InsertFieldsDefaultValueDto` …; §4.3.5.3) |
|
||
| `FormDefaults` | `FormFieldsDefaultValueJson` içinde öğenin sabit `DefaultValue`'suna sığmayanlar: `CustomKey`, `DbQuery`, `Sequence` (§4.3.5). Deploy önce öğelerin sabitlerini grup sırasıyla, sonra bu listeyi yazar; aynı alan için açık liste kazanır |
|
||
| `Permission` | `PermissionCrudDto`; her deploy'da ekran kodundan yeniden türetilir (yetki satırlarıyla aynı kalmalı) |
|
||
| `FilterRow`, `HeaderFilter`, `FilterPanel`, `SearchPanel`, `GroupPanel`, `Selection`, `ColumnOption`, `Row`, `Pager`, `StateStoring`, `PivotOption` | Liste sekmeleri (Filtreleme, Arama, Gruplama, Seçim, Sayfalama, Satır, Durum, Pivot); `StateStoring.StorageKey` ekran kodundan yeniden yazılır |
|
||
| `ExtraFilters` | Ek filtreler (`ExtraFilterEditDto[]`) |
|
||
| `CommandColumns` | Komutlar sekmesi (§4.9) |
|
||
| `SubForms` · `Widgets` · `WorkflowDto` | Alt formlar (§4.6), widget'lar (§4.6.1), iş akışı (§4.7) |
|
||
| `ChartOptions` | Grafik sekmelerinin tamamı (`WizardChartDto`): `Common`, `AdaptiveLayout`, `Animation`, `Annotations`, `ArgumentAxis`, `CommonAnnotationSettings`, `CommonAxisSettings`, `CommonPaneSettings`, `CommonSeriesSettings`, `Crosshair`, `Export`, `Legend`, `Margin`, `Panes`, `ScrollBar`, `Series`, `Size`, `Title`, `Tooltip`, `ValueAxis`, `ZoomAndPan` |
|
||
|
||
**Görünüm option blokları** — `[Blok]`; 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ı düzenleme formunun yerleşimini belirler (form sırası = öğenin gruptaki
|
||
yeri); grid sütun sırası öğenin `ListOrderNo`'sudur — yazılmamışsa grup sırası, yazılmışsa olduğu
|
||
gibi korunur ve `ListOrderNo` taşımayan öğeler en büyük değerin ardına eklenir (`ResolveListOrders`).
|
||
|
||
```jsonc
|
||
"Groups": [
|
||
{
|
||
"Caption": "Genel",
|
||
"ColCount": 2,
|
||
"ColSpan": null, // grubun formda kapladığı kolon; null ise ColCount
|
||
"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 her öğe **alan detayının tamamını** taşır: alan editörünün her sekmesi öğede bir
|
||
alan ya da bloktur (`WizardColumnItemInputDto`); öğe kırpılmaz. Dosyada ilk dört anahtar
|
||
`FieldName`, `CaptionName`, `En`, `Tr`'dir (C#'ta `EnglishCaption`/`TurkishCaption`), geri kalanı
|
||
sözleşme sırasıyla gelir. Aynı alanın dil/rol/kullanıcı varyantları (`CultureName`/`RoleId`/`UserId`)
|
||
ayrı öğelerdir. `null` bırakılan blok (`Filter`, `Header` …) deploy'da varsayılanıyla dolar.
|
||
|
||
Her `Items` öğesi (`[Item]`):
|
||
|
||
| 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` | Bkz. §3. |
|
||
| `EditorOptions` | DevExtreme editör JSON'u — §6. |
|
||
| `EditorScript` | Alan davranış script'i — §5. |
|
||
| `DbSourceType` | Bkz. §3. |
|
||
| `IsRequired` | `[Kısayol]` Zorunluluk: deploy `ValidationRules`'a `required` kuralı ekler/çıkarır; dosyaya yazılmaz, okurken kural listesinden türetilir. |
|
||
| `IncludeInEditingForm` | `false` ise yalnızca grid sütunu olur, forma girmez (`EditGroupOrderNo`). |
|
||
| `ColSpan` | Formda kaç kolon kaplar (grubun `ColCount` değeri içinde). |
|
||
| `LookupDataSourceType` | `1/2/3` — bkz. §3. Lookup yalnızca `LookupQuery` doluysa üretilir; wizard ekranı tipi `1` ile açar. |
|
||
| `LookupQuery` | `Query` tipinde SQL; `StaticData` tipinde JSON dizisi; `WebService` tipinde URL. |
|
||
| `ValueExpr` / `DisplayExpr` | Lookup'ın değer ve etiket kolonları (açıkça `null` verilirse `null` yazılır). |
|
||
| `DefaultValue` | Yeni kayıt **sabit** varsayılanı; `FormFieldsDefaultValueJson`'a `CustomValueType: 1` olarak yazılır (§4.3.5.1). Sabit olmayan türler `FormDefaults` bloğundadır. |
|
||
| `CascadeParentFields` · `CascadeRelationField` · `CascadeFilterOperator` · `CascadeEmptyFields` | Bağımlı liste (§4.3.4). Wizard ekranında `DefaultValue` ve `Cascade*` için giriş yoktur; MCP/seed girdisiyle gelir, düzenlemede korunur. |
|
||
| `CultureName` · `RoleId` · `UserId` | Alan varyantı: dil (boşsa platform varsayılan dili), rol adı, kullanıcı adı. |
|
||
| `PlaceHolder` · `BandName` · `ReadOnly` | Editör yer tutucusu, sütun bandı, salt okunur. |
|
||
| `Visible` · `IsActive` · `AllowSearch` | Listede görünür (`null` ise anahtar alan gizli, diğerleri görünür) · sorguya dahil · arama panelinde aranır. |
|
||
| `Alignment` · `Format` · `Width` | Hizalama, görüntü biçimi (adlı DevExtreme biçimi), sütun genişliği (`0`/`null` otomatik). |
|
||
| `ListOrderNo` | Grid sütun sırası (yukarıda); formdaki sıradan bağımsızdır. |
|
||
| `SortIndex` · `SortDirection` | Açılış sıralaması. |
|
||
| `AllowEditing` · `AllowAdding` | Satır düzenlemede değiştirilebilir · yeni kayıtta girilebilir (`null` ise evet). |
|
||
| `ColumnCssClass` · `ColumnCssValue` | CSS sekmesi. |
|
||
| `Filter` · `Header` · `Grouping` · `Customization` | Filtreleme (`ColumnFilterDto`), başlık filtresi (`ColumnHeaderDto`), gruplama (`ColumnGroupingDto`), sabitleme/sıralama (`ColumnCustomizationDto`). |
|
||
| `TotalSummary` · `GroupSummary` | Özetler (`ColumnTotalSummaryDto`). |
|
||
| `Join` | Join sekmesi (`JoinTableDto`). |
|
||
| `ValidationRules` | Doğrulama kuralları (`ValidationRuleDto[]`, §4.3.6). |
|
||
| `Styling` | Koşullu stil (`ColumnStylingDto[]`). |
|
||
| `Permission` | Alan yetkisi (`ListFormFieldPermissionDto`); ekran kodundan türetilir. |
|
||
| `PivotSettings` | Pivot sekmesi (`ListFormFieldPivotSettingsDto`). |
|
||
|
||
### 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` | `Sas.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` (lookup'lı alanda varsayılan açık — liste popup'ı ve form sayfası), `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` |
|
||
| `dxRating` | Yıldızlı puan (platform). Tıklanan yıldızın sırası (1..`count`) tam sayı olarak kaydedilir; seçili yıldıza yeniden tıklamak değeri boşaltır (`allowClear: false` kapatır). Liste/kart hücresinde yıldız olarak çizilir; yıldız şablonu olmayan görünümde (Scheduler) sayı kutusudur | Int32 (11) | `count` (varsayılan 5), `allowClear` |
|
||
| `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ü. `accept` belge türlerini de içerirse (`application/pdf,.doc,.docx` — CV, imzalı form) görsel olmayan `data:` değeri listede ve formda belge ikonuyla çizilir, tıklanınca blob olarak yeni sekmede açılır (`imageHoverPreview.thumbnailSource`). `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:
|
||
|
||
```jsonc
|
||
{
|
||
"FieldName": "StatusId", "EditorType": "dxSelectBox", "DbSourceType": 11,
|
||
"LookupDataSourceType": 1,
|
||
"LookupQuery": "[{\"key\":1,\"name\":\"Taslak\"},{\"key\":2,\"name\":\"Onayda\"},{\"key\":3,\"name\":\"Onaylandı\"}]",
|
||
"ValueExpr": "Key", "DisplayExpr": "Name"
|
||
}
|
||
```
|
||
|
||
İ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.
|
||
Lookup öğesi renk taşıyorsa (etiket tanımındaki `dxColorBox` kolonu gibi) sorgu onu `AS [Color]`
|
||
takma adıyla döndürür (`StaticData`'da `color`); `dxTagBox` etiketleri formda ve grid hücresinde bu
|
||
renkle çizilir (`views/shared/lookupColor.tsx`), renk yoksa varsayılan rozet rengi kalır. Dinamik
|
||
raporlar (`DynamicGrid`/`DynamicTree`/`DynamicForm`) `Query` lookup'ında takma adı görünce rapor
|
||
SQL'ine `{Alan}__LookupColor` kolonu ekler ve değeri renkli rozet olarak basar
|
||
(`DynamicReportColorHelper`); sorgu rapor kurulurken çalıştırılmadığı için takma ad yazılmazsa
|
||
rapor düz metin kalır. `dxColorBox` editörlü kolon grid'de ve raporlarda değerin yanında renk
|
||
örneğiyle gösterilir.
|
||
|
||
`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`): `CascadeParentFields` ve `CascadeEmptyFields` doluysa yazılır;
|
||
`CascadeRelationField` ile `CascadeFilterOperator` yalnızca `CascadeParentFields` doluysa yazılır ve
|
||
operatör boşsa da `"="` olarak yazılır. `LookupJson` yalnızca `LookupQuery` doluysa üretilir — lookup'ı
|
||
olmayan bir ebeveyn alana wizard girdisiyle `CascadeEmptyFields` yazılamaz; böyle bir alanın `LookupJson`'ı
|
||
deploy sonrası ListForm editöründen (alan detayı › Lookup) verilir. 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` (`LookupDto`); wizard'ın ürettiği `DefaultLookupJson` küçük harfli `key` / `name` yazar. Query cevabındaki `Key`/`Name`/`Group` kolonları istemcide `key`/`name`/`group`'a eşlenir |
|
||
| `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. Ebeveyn çocuğu, değeri boşken kilitler (`!value`: bool ebeveynde `false`, sayı ebeveynde `0` da boştur) ve değiştiğinde temizler; ebeveyn yalnızca gerçekten süzülen liste için yazılır, hesap için kullanılmaz. 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 |
|
||
|
||
Cascade liste popup'ında (Grid, Tree), satır içi düzenlemede ve form sayfasında (`FormDevExpress`)
|
||
aynı kuralla çalışır; kural tek yerdedir (`list/shared/cascade.ts`, `getCascadeClearFields`): bir alan
|
||
değişince ebeveyni o alan olan çocuklar **ve** alanın `CascadeEmptyFields` listesi boşaltılır. Kilit
|
||
ve temizleme platform editörlerinde (TagBox, GridBox, ImageUpload) de uygulanır. Form sayfasında çocuk
|
||
yalnızca ebeveynin değeri **gerçekten değiştiğinde** temizlenir ve listesi yeni ebeveyn değeriyle
|
||
yeniden kurulur; formun aynı değeri tekrar bildirmesi (veri forma geri verilince) yok sayılır.
|
||
|
||
**Liste popup'ı ↔ form sayfası editör eşliği.** İkisi aynı ListForm tanımından beslendiği için
|
||
editör yetenekleri ortak koddan gelir; bir tarafa yeni yetenek ortak yardımcıya eklenerek verilir:
|
||
|
||
| Yetenek | Ortak kaynak |
|
||
| --- | --- |
|
||
| `EditorOptions` varsayılanları (tarih maskesi/biçimi, HtmlEditor, telefon) | `withEditorTypeDefaults` (`list/shared/editingForm.ts`) |
|
||
| Lookup varsayılanları (`valueExpr`, `displayExpr`, `searchEnabled`) | `getLookupEditorDefaults` |
|
||
| Buton `onClick` (fonksiyon metni, `@SCAN_BARCODE`) ve bağlamı | `resolveEditorButtons` + `withButtonContext` |
|
||
| Cascade temizleme/kilit | `list/shared/cascade.ts` |
|
||
| Grid/Tree `onEditorPreparing` gövdesi | `prepareEditingEditor` (`list/shared/editingFormScripts.ts`) |
|
||
| TagBox, GridBox, ImageUpload, ImageViewer, Rating | Tek uygulama `form/editors/*`; liste hücresi `list/editors/cellEditors.tsx` adaptörüyle bağlanır |
|
||
|
||
DataGrid/TreeList platform editörlerini `editCellTemplate` ile çizer ve `onEditorPreparing` onlar için
|
||
çalışmaz; salt-okunur/devre dışı kararı, cascade ve `EditorScript` bu editörlere
|
||
`createCustomEditorCellAdapter` ile verilir.
|
||
Adres zincirinin hazır örneği Kiracılar ekranıdır: Ülke › İl › İlçe › Mahalle; mahalle lookup'ı
|
||
`PostalCode`'u `group` olarak döndürür ve `copy("Group", "PostalCode")` tarifi posta kodunu doldurur.
|
||
|
||
**`LookupQuery` biçimleri:**
|
||
|
||
| Tip | Biçim | Not |
|
||
| --- | --- | --- |
|
||
| `1` StaticData | `[{"key":"Wares","name":"Sas.StaticLookup.Wares"},{"key":"Service","name":"Sas.StaticLookup.Service"}]` | `name` **her zaman dil anahtarıdır** (§4.3.4.1), düz metin yazılmaz. 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 TenantId = '@TENANTID' AND 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 çalışır, `@USERID` gibi token'lar çözülür. **Lookup ekranın kiracı süzgecinden geçmez:** kaynağın en dış tablosu kiracı tablosuysa (`IMultiTenant` entity ya da `TenantId` kolonlu seed tablosu — `_T_` önekliler, `AbpUsers`/`AbpRoles`/`AbpOrganizationUnits`) sorgu `TenantId = '@TENANTID'` taşır (host'ta `IS NULL` çözülür). Host tanımları (`Sas_H_*`) ve anahtarla bağlanan alt sorgu/join filtre istemez. C# seeder'da `LookupQueryValues.DefaultLookupQueryJson` filtreyi tablonun kapsamından (`TableNameResolver.IsTenantTable`) kendisi ekler; elle yazılan sorgu `TenantFilter` kalıbını kullanı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`. Eksik ya da boş seçici `a.id` / `a.name` / `a.group`'a düşer; seçici ifade (`a.id`) ya da ok fonksiyonu (`a => a.id`) olabilir. Store anahtarı `valueExpr`'in küçük harflisidir; boş ebeveyn gövdeye `null` metni olarak yazılır (tırnak içindeyse `"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`):
|
||
|
||
```jsonc
|
||
// 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):
|
||
|
||
```jsonc
|
||
{ "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 TenantId = '@TENANTID' AND (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.4.1 Sabit seçenekler dil anahtarıyla — zorunlu
|
||
|
||
RadioGroup, SelectBox, TagBox, Autocomplete gibi `StaticData` lookup'ının seçenek metni (`name`) ekran
|
||
diline göre çevrilir; bu yüzden düz metin değil `Sas.StaticLookup.*` dil anahtarıdır ve anahtarın EN + TR
|
||
satırları `data/Sas.Languages.LanguageText/{CultureName}.json`'a yazılır (örnek: `ListFormSeeder_Adm`
|
||
iş tipleri, `Sas.StaticLookup.MailQueueWorker`).
|
||
|
||
Wizard ve MCP deploy'u bunu kendisi yapar (`ListFormWizardAppService.LocalizeStaticLookupsAsync`):
|
||
`name` var olan bir dil anahtarı değilse
|
||
|
||
1. aynı TR metnini taşıyan bir `Sas.StaticLookup.*` anahtarı varsa o kullanılır (yeni satır açılmaz);
|
||
2. yoksa anahtar `Sas.StaticLookup.{Key}` olur — `key` kelime/sayı değilse (`0 Rh+`, `Ziyaretçi`)
|
||
`Sas.StaticLookup.{Alan}{Sıra}`, sayısal `key`'de `Sas.StaticLookup.{Alan}{Key}`; ad başka bir metinle
|
||
alınmışsa `Sas.StaticLookup.{Alan}.{Key}`;
|
||
3. TR satırı verilen metin, EN satırı anahtarın kelimeleridir (`BySerialNumber` → `By Serial Number`;
|
||
kelime olmayan anahtarda TR metni). Yeni anahtar wizard'ın `InsertedRecords.LanguageKeys`'ine girer.
|
||
|
||
MCP'de EN metni bilinen seçenekte en doğrusu anahtarı ve EN + TR metnini wizard'ın `Language.Texts`
|
||
alanında vermektir; o anahtara deploy dokunmaz.
|
||
|
||
### 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 etkin liste vardır ve hepsi aynı öğe şeklini taşır (aynı
|
||
`FieldName` iki kez verilirse sonraki geçerlidir):
|
||
|
||
| 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 |
|
||
|
||
Beşinci kolon `SelectFieldsDefaultValueJson` ListForm editöründe (Veritabanı › Select) düzenlenebilir ama
|
||
`DefaultValueManager` onu **okumaz** — Select işlemi de `FormFieldsDefaultValueJson`'ı kullanır. Okuma
|
||
sorgusuna kullanıcı/kiracı koşulu gerekiyorsa `DefaultFilter`'a token'la yaz (`"CreatorId" = '@USERID'`).
|
||
|
||
```jsonc
|
||
[
|
||
{ "FieldName": "Id", "FieldDbType": 9, "Value": "@NEWID", "CustomValueType": 2 },
|
||
{ "FieldName": "CreationTime", "FieldDbType": 6, "Value": "@NOW", "CustomValueType": 2 },
|
||
{ "FieldName": "CreatorId", "FieldDbType": 9, "Value": "@USERID", "CustomValueType": 2 },
|
||
{ "FieldName": "IsDeleted", "FieldDbType": 3, "Value": "false", "CustomValueType": 1 },
|
||
{ "FieldName": "OrderNo", "FieldDbType": 16, "Value": "SiparisNo","CustomValueType": 5 },
|
||
{ "FieldName": "ExchangeRate", "FieldDbType": 7, "Value": "", "CustomValueType": 3,
|
||
"SqlQuery": "SELECT TOP 1 Rate FROM Fin_T_ExchangeRate ORDER BY Date DESC" }
|
||
]
|
||
```
|
||
|
||
**`CustomValueType` (`FieldCustomValueTypeEnum`)**
|
||
|
||
| # | Tip | `Value` ne demek |
|
||
| --- | --- | --- |
|
||
| 1 | `Value` | Sabit değer (`false`, `3`, `Taslak`). SQL değil **parametredir**: `GETDATE()` gibi ifade tarih kolonunda parse edilemez ve alan **NULL** yazılır — şimdiki zaman için `CustomKey` `@NOW` |
|
||
| 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); sorgudaki `@USERID`, `@TENANTID` gibi token'lar `DefaultValueHelper` ile önceden çözülür |
|
||
| 4 | `QueryParams` | Değer, adresteki query string parametresinden okunur; `Value` parametre adıdır, değer `FieldDbType`'a göre biçimlenir |
|
||
| 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`, `@AUTONUMBER` hariç — o yalnız istemcide,
|
||
`Value` tipiyle çalışır) — `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 token'lar dışında `@{AlanAdı}` yazılırsa Insert/Update'te gönderilen kaydın o alanının değeri
|
||
kopyalanır (alan seviyesinde `C`/`U` yetkisi gerekir); başka her değer `null` üretir ve alan yazılmaz.
|
||
Bu yüzden `@AUTONUMBER`, `CustomKey` değil **`CustomValueType: 1` (Value)** ile yazılır:
|
||
|
||
```jsonc
|
||
{ "FieldName": "SiraNo", "FieldDbType": 11, "Value": "@AUTONUMBER", "CustomValueType": 1 }
|
||
```
|
||
|
||
`FormFieldsDefaultValueJson`'a böyle yazılan alan istemciye `column.defaultValue` olarak iner ve
|
||
yeni satır **tarayıcıda** artan bir numara alır — geçici, oturum içi bir numaradır; kalıcı belge
|
||
numarası için `Sequence` (`CustomValueType: 5`, §9.9) kullan.
|
||
|
||
> **`DefaultValue` / `DefaultValueType` bir `ListFormField` kolonu değildir.** İstemcinin
|
||
> gördüğü bu iki alan, `ListFormSelectAppService`'in her metadata isteğinde
|
||
> `FormFieldsDefaultValueJson`'ı çözerek ürettiği hesaplanmış değerlerdir. Yani "alanın
|
||
> varsayılanı" her zaman ekran seviyesindeki dört listenin birine yazılır.
|
||
|
||
> Boş/`null` üretilen değer yazılmaz. `FieldDbType` yanlış verilirse değer
|
||
> `QueryHelper.GetFormattedValue` tarafından sessizce yanlış biçimlenir — kolon tipiyle aynı
|
||
> `DbType` numarasını yaz (§3).
|
||
|
||
#### 4.3.5.1 Wizard / MCP ekranında yeni kayıt varsayılanı — zorunlu
|
||
|
||
Wizard ve MCP ile üretilen **her** liste ekranında yeni kayıt şu değerlerle açılır:
|
||
|
||
| Kolon | Form varsayılanı | Tablodaki `DEFAULT` |
|
||
| --- | --- | --- |
|
||
| `IsActive` | `true` | `bit NOT NULL DEFAULT 1` |
|
||
| Ölçü birimi (`UomId`, `*UomId`; lookup `Adm_T_Uom` — kiracı tablosudur (`Uom : IMultiTenant`), lookup `TenantId = '@TENANTID'` taşır) | `Adet` | `DEFAULT 'Adet'` |
|
||
| Para birimi (`CurrencyId`, `*Currency`; lookup `Sas_H_Currency`) | `TRY` (lookup'ın anahtarı adsa onun karşılığı) | `DEFAULT 'TRY'` |
|
||
| Talepte sabit varsayılanı olan her kolon (ör. Süre = 60) | o değer | `DEFAULT 60` |
|
||
|
||
**Değer koda yazılmaz, tablodan okunur.** Deploy (`ListFormWizardAppService`) her alan için kolonun
|
||
sabit `DEFAULT`'unu veritabanı katalogundan, tablo henüz kurulmadıysa seed scriptinden
|
||
(`PostgreSqlTableScript.ReadSeedColumns` → `SqlSeedColumn.ConstantValue`) okur ve
|
||
`FormFieldsDefaultValueJson`'a `CustomValueType: 1` (Value) olarak yazar; `bit` kolonda `1/0` →
|
||
`true/false`. Fonksiyon varsayılanı (`NEWID()`, `GETUTCDATE()`) form değeri sayılmaz. Bu yüzden
|
||
yukarıdaki kolonlar tabloda bu `DEFAULT`'larla kurulur — MCP'de `create_table` kolonunda
|
||
`defaultValue: "1"` / `"'Adet'"` / `"'TRY'"` / `"60"`.
|
||
|
||
Alana özel değer (tablo `DEFAULT`'undan farklı) wizard öğesinin **`DefaultValue`** cevabıyla verilir
|
||
(`Groups[].Items[].DefaultValue`); dosyada da öğede durur. Alan kaydındaki
|
||
`DefaultValue`/`DefaultValueType` select servisinin hesapladığı değerdir, saklanmaz. `CustomKey`,
|
||
`DbQuery`, `Sequence` gibi sabit olmayan türler `Wizard.FormDefaults` listesine yazılır (§4.2); deploy
|
||
önce öğelerin sabitlerini grup sırasıyla, sonra `FormDefaults`'u birleştirir, aynı alan için açık
|
||
liste kazanır (`BuildFormDefaults`). Deploy girdisinde sözleşmede karşılığı olmayan bir anahtar adıyla
|
||
reddedilir — sessizce yok sayılmaz.
|
||
|
||
#### 4.3.5.2 Wizard / MCP ekranında otomatik numara — `Adm.Definitions.Sequence`
|
||
|
||
Bir alanın otomatik numaralanmasına talep karar verdiğinde ("numara otomatik gelsin") ya da analiz
|
||
karar verdiğinde (proje/sipariş/talep/belge numarası: `ProjectNo`, `OrderNo`, `RequestNo`,
|
||
`DocumentNo` …), numara **`Adm.Definitions.Sequence`** numaratöründen alınır; numara koda, sorguya ya
|
||
da forma yazılmaz. Numara/kod alanı otomatik değilse (kullanıcı girer) kural uygulanmaz:
|
||
|
||
1. Numaratör `data/Adm.Definitions.Sequence.json` dosyasına satır olarak eklenir (`Name`, `Prefix`,
|
||
`PaddingSize`, `StartNumber`/`NextNumber`, `ResetPeriod`, `FormatTemplate` — §9.9; `Id` iş anahtarından
|
||
türetilmiş sabit GUID, `LastPeriodKey` yazılmaz). Aynı numarayı üreten uygun bir numaratör zaten
|
||
varsa (`query_list Adm.Definitions.Sequence`) o kullanılır, ikincisi açılmaz.
|
||
2. Alanın varsayılanı `Wizard.FormDefaults` listesine `CustomValueType: 5` ile yazılır;
|
||
`Value` numaratörün `Name`'idir:
|
||
|
||
```jsonc
|
||
{ "FieldName": "ProjectNo", "FieldDbType": 16, "Value": "Prj.Project", "CustomValueType": 5, "SqlQuery": "" }
|
||
```
|
||
|
||
Formda numara önizlenir, kalıcı numara kayıt anında alınır (§9.9).
|
||
3. `@AUTONUMBER`, `DbQuery` ile `MAX(...) + 1`, sabit metin ya da `InsertAfterCommand` ile numara
|
||
üretme yazılmaz. Tek istisna talebin açıkça boşluksuz numara istemesidir (§9.9 son paragraf);
|
||
o zaman karar analizde **(onay)** ile yazılır.
|
||
Belgeyi formdan değil bir yordam açıyorsa (teklif talebinin alternatif kopyası, tekliften
|
||
dönüşen sipariş) numara yine aynı numaratörden `Adm_P_SequenceNext` ile alınır (§9.9).
|
||
4. Analiz numaratörü madde 8'de (alan → numaratör adı, `Prefix`, `FormatTemplate`, `ResetPeriod`)
|
||
gösterir; talepte biçim yoksa varsayılan `{Prefix}{Number}{Suffix}`, `PaddingSize: 5`, `ResetPeriod: 0`
|
||
ve modül kısaltmasından `Prefix` kararı madde 9'a yazılır.
|
||
|
||
Kullanıcının kendi girdiği kodlar (Depo Kodu `WH`, birim kodu) numaratör değildir; bu kural
|
||
yalnızca otomatik üretilecek numara içindir. `Sequence` kiracıya aittir (`IMultiTenant`):
|
||
host veritabanını paylaşan kiracıya seed dosyası uygulanmadığı için numaratör satırı o kiracıda
|
||
ekrandan açılır.
|
||
|
||
#### 4.3.5.3 Özel komutun parametreleri — `*FieldsDefaultValueJson` komutla birlikte yazılır (zorunlu)
|
||
|
||
MCP, prompt ya da wizard ile üretilen veya düzenlenen her ekranda bir `InsertCommand` / `UpdateCommand` /
|
||
`DeleteCommand` ya da `*BeforeCommand` / `*AfterCommand` kancası yazıldığında, komut metnindeki **her
|
||
`@Parametre` aynı işte bir kaynağa bağlanır**. Komut yalnızca kendisine verilen parametrelerle çalışır
|
||
(`QueryManager` komutu olduğu gibi çalıştırır, eksik parametreyi tamamlamaz); kaynağı olmayan parametre
|
||
çalışma anında `Must declare the scalar variable "@X"` hatası verir ve ekran manuel düzeltme ister. Wizard'ın
|
||
kendiliğinden yazdığı varsayılanlar (§4.4.1 tablosu) yalnızca tablonun audit kolonlarına göredir:
|
||
view / sorgu / prosedür kaynaklı ekranda ve özel komutta eksik kalır — bu boşluğu üreten doldurur.
|
||
|
||
| Komuttaki parametre | Kaynağı |
|
||
| --- | --- |
|
||
| `@TenantId` | `IsTenant: true` ise sunucu ekler; listeye yazılmaz |
|
||
| `@Alan` (kaydın bir alanı) | Gönderilen kayıttan gelir: alan ekranda tanımlı olmalı ve Insert'te `C`, Update'te `U` yetkisi taşımalı. Kaydı ızgara/form değil buton gönderiyorsa (`dynamicFetch('list-form-data/…')`) alan çağrının `data`'sına yazılır |
|
||
| `@Id` — Update | Özel `UpdateCommand`'da `UpdateFieldsDefaultValueJson` → `Id = @ID` (tek değer: `= @Id`, `EXEC … @Id`). `UpdateCommand` boşsa (üretilen `UPDATE`) anahtar dizi olarak eklenir; bu ekranın Update kancalarında `IN @Id` yazılır |
|
||
| `@Id` — Delete | `DeleteFieldsDefaultValueJson` → `Id = @ID` (seçili anahtarların tamamı; komutta `IN @Id`) |
|
||
| `@Id` — Insert (yeni anahtar) | `InsertFieldsDefaultValueJson` → `Id = @NEWID` |
|
||
| `@LastModifierId` · `@LastModificationTime` | `UpdateFieldsDefaultValueJson` → `@USERID` · `@NOW` |
|
||
| `@DeleterId` | `DeleteFieldsDefaultValueJson` → `@USERID` |
|
||
| `@CreatorId` · `@CreationTime` | `InsertFieldsDefaultValueJson` → `@USERID` · `@NOW` |
|
||
| Sabit ya da sorgudan gelen değer | `CustomValueType: 1` (Value) / `3` (DbQuery), §4.3.5 |
|
||
|
||
`SelectCommand` parametre almaz ve `SelectFieldsDefaultValueJson` çalışma anında okunmaz (§4.3.5): okuma
|
||
koşulu `DefaultFilter`'a token'la yazılır (`"CreatorId" = '@USERID'`).
|
||
|
||
Örnekler (Satınalma › Planlama):
|
||
|
||
```jsonc
|
||
// Prc.MaterialRequestPlanLine — InsertCommand: EXEC Prc_P_MaterialRequestPlanSuggest @BaseId, @TenantId, @CreatorId
|
||
// @BaseId gönderilen kayıttan, @TenantId sunucudan, @CreatorId listeden
|
||
"InsertFieldsDefaultValueDto": [
|
||
{ "FieldName": "CreationTime", "FieldDbType": 6, "Value": "@NOW", "CustomValueType": 2 },
|
||
{ "FieldName": "CreatorId", "FieldDbType": 9, "Value": "@USERID", "CustomValueType": 2 }
|
||
],
|
||
// Prc.MaterialRequestPlan — UpdateCommand: EXEC Prc_P_MaterialRequestPlanConfirm @Id, @TenantId, @LastModifierId
|
||
"UpdateFieldsDefaultValueDto": [
|
||
{ "FieldName": "LastModificationTime", "FieldDbType": 6, "Value": "@NOW", "CustomValueType": 2 },
|
||
{ "FieldName": "LastModifierId", "FieldDbType": 9, "Value": "@USERID", "CustomValueType": 2 },
|
||
{ "FieldName": "Id", "FieldDbType": 9, "Value": "@ID", "CustomValueType": 2 }
|
||
],
|
||
// DefaultDeleteCommand (… "DeleterId"=@DeleterId … WHERE "Id" IN @Id) — view kaynaklı ekranda da
|
||
"DeleteFieldsDefaultValueDto": [
|
||
{ "FieldName": "DeleterId", "FieldDbType": 9, "Value": "@USERID", "CustomValueType": 2 },
|
||
{ "FieldName": "Id", "FieldDbType": 9, "Value": "@ID", "CustomValueType": 2 }
|
||
]
|
||
```
|
||
|
||
**Kontrol (teslimden önce):** her komut metninden `@\w+` adları çıkarılır ve her biri tablodaki bir kaynağa
|
||
eşlenir; `@ID`, `@USERID`, `@NOW` gibi token'lar komutta değil listenin `Value`'sunda durur. Eşlenmeyen
|
||
parametre varsa ekran teslim edilmez. Wizard dosyasında listeler `Insert.FieldsDefaultValue` /
|
||
`Update.FieldsDefaultValue` / `Delete.FieldsDefaultValue` adıyla, komutla (`Command`/`BeforeCommand`/`AfterCommand`)
|
||
aynı blokta durur; MCP `deploy_wizard`'da da komutla aynı çağrının aynı bloğunda verilir — komutu yazıp
|
||
listeyi sonraya bırakmak yoktur.
|
||
|
||
### 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.
|
||
|
||
Grid/Tree'de kendi şablonuyla çizilen editörlere (lookup'lı `dxTagBox`/`dxGridBox`, `dxImageUpload`,
|
||
`dxRating`) DevExtreme kolon kuralı uygulamaz (E1050); bu alanlara kolon kuralı verilmez, `required`
|
||
kuralı kayıttan önce istemcide denetlenir (`getTemplateEditorRequiredErrors`) ve boşsa kayıt
|
||
iptal edilip popup açık kalır. Bu editörlerde yalnızca `required` çalışır; diğer kural tipleri
|
||
gerekiyorsa sunucu tarafı kontrol yazılır.
|
||
|
||
Kurallar liste popup'ı ve form sayfası için aynı yardımcıdan (`list/shared/validationRules.ts`) üretilir.
|
||
`Message` dil anahtarıdır ve çevrilir; boş bırakılırsa DevExtreme'in yerelleştirilmiş varsayılanı çıkar.
|
||
Form sayfasında zorunluluk `isRequired` ile işaretlenir, `Message`'ı olan `required` kuralı ayrıca item'a
|
||
geçer. Doğrulama istemcidedir; iş kuralı zorunluysa sunucu tarafında da uygulanır.
|
||
|
||
| `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` | `ComparisonTarget` (aynı kayıttaki alan adı), `ComparisonType` (`==`, `===`, `!=`, `!==`, `<`, `<=`, `>`, `>=`; boşsa `==`) — "Bitiş ≥ Başlangıç": `{Type:"compare", ComparisonTarget:"StartDate", ComparisonType:">="}` |
|
||
| `custom` | `ValidationCallback`: `eval` edilen JS fonksiyonu, `true` geçerlidir — `"(e) => e.value > 0"`; `e.data` kaydın tamamıdır. `Reevaluate` |
|
||
| `async` | `ValidationCallback` Promise dönebilir (`"(e) => fetch(...).then(r => r.ok)"`); `Reevaluate`. Derlenemeyen ya da hata veren fonksiyon kuralı geçmiş sayar |
|
||
|
||
```jsonc
|
||
"ValidationRuleJson": "[{\"Type\":\"required\",\"Message\":\"Sas.Validation.Required\"},
|
||
{\"Type\":\"range\",\"Min\":0,\"Max\":1000000,\"Message\":\"Sas.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` · `detailAdaptive` · `groupFooter` · `totalFooter`. Koşul yalnızca `data` satırında değerlendirilir; diğer satır tiplerinde stil koşulsuz uygulanır |
|
||
| `UseRow` | `true` ise stil hücreye değil **tüm satıra** uygulanır |
|
||
| `Condition` | `=`, `<>`, `<`, `<=`, `>`, `>=`, `contains`, `notcontains`, `startswith`, `endswith`, `isblank`, `isnotblank`, `anyof`, `noneof`. Karşılaştırma gevşektir (`==`). `between`'de `ConditionValue` `[min, max]` dizisidir (`[10, 20]`, uçlar dahil). Yönetim formu yalnızca bu operatörleri sunar |
|
||
| `ConditionValue` | Boş bırakılırsa koşul aranmaz, stil her zaman uygulanır; `isblank`/`isnotblank` değer istemez. `anyof`/`noneof`'ta JSON dizi metni: `["A","B"]` |
|
||
| `CssStyles` | `anahtar:değer` çiftleri, aralarında `|`: `background-color:#fee | font-weight:bold` |
|
||
| `CssClassName` | Hazır bir CSS sınıfı kullanılacaksa |
|
||
|
||
Stil Grid, Tree ve Pivot'ta uygulanır, Card'da uygulanmaz. 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. Grid, Tree ve Card'da çizilir (Gantt, Scheduler, Todo, Pivot, Chart'ta yok);
|
||
`Caption` **çevrilmez**, düz metin basılır.
|
||
|
||
```jsonc
|
||
[
|
||
{ "FieldName": "Status", "Caption": "Durum", "Operator": "=", "ControlType": "Select",
|
||
"DefaultValue": "1",
|
||
"Items": [ { "Key": "1", "Value": "Taslak" }, { "Key": "2", "Value": "Onayda" } ] },
|
||
{ "FieldName": "OrderNo", "Caption": "Sipariş No", "Operator": "contains", "ControlType": "Text" }
|
||
]
|
||
```
|
||
|
||
- `ControlType`: `Select` · diğer her değer metin kutusudur (Enter ya da odak kaybında uygulanır).
|
||
- `Select` seçenekleri ya `Items` dizisinden gelir ya da `SqlQuery` alanına yazılan sorgudan;
|
||
sorgu **`Key` ve `Value` adlı iki kolon** döndürmelidir ve ekranın veri kaynağında/tenant
|
||
bağlamında çalışır.
|
||
- Seçilen değerler `[FieldName, Operator, Value]` üçlüsüne çevrilir, URL'deki `filter` parametresiyle
|
||
`and` ile birleştirilir. Böylece filtreli ekran adresi paylaşılabilir.
|
||
- `DefaultValue` dolu ise seçim temizlenemez ve liste ilk açılışta o değerle gelir.
|
||
- Ek filtre ile yönetilen alan yeni kayıtta **atlanır**: ne `DefaultValue` ne URL değeri uygulanır,
|
||
alan boş açılır. Yeni kayıtta dolu gelmesi gerekiyorsa `FormFieldsDefaultValueJson` ya da
|
||
`?filter=` adresi kullanılır (reference §5.8).
|
||
### 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 `FromDeployJson` olduğu için seed dosyası biçimi
|
||
(`{"Wizard": {...}}`, `Menu`/`Language`/`Data`/`Details`/`Layout` başlıkları, `WizardCode`/`ModuleId`
|
||
adları) da kabul edilir; `ListForm`/`Fields` bölümü reddedilir. Yazılmayan blok deploy'da varsayılanıyla
|
||
dolar (`ApplyDefaults`). Deploy bunu okuyup §4.1 biçimindeki dosyayı yazar; tablonun
|
||
`IsDeleted`/`CreatorId`/`LastModifierId` kolonlarını taşıyıp taşımadığı katalogdan ya da seed
|
||
scriptinden, `InsertedRecords` gerçekten yaratılan kayıtlardan **sunucu tarafından** okunur —
|
||
cevaba yazılmaz.
|
||
|
||
```jsonc
|
||
{
|
||
"Wizard": {
|
||
"ComponentKind": 0,
|
||
"WizardName": "Orders",
|
||
"ListFormCode": "Sas.Mrp.Orders",
|
||
"MenuCode": "Sas.Mrp.Orders",
|
||
"MenuOrder": 1,
|
||
"CreateMenu": true,
|
||
"MenuUrl": "/admin/list/Sas.Mrp.Orders",
|
||
"IsTenant": true, "IsBranch": false, "IsOrganizationUnit": false,
|
||
"AllowAdding": true, "AllowUpdating": true, "AllowDeleting": true,
|
||
"AllowDetail": false, "OpenDetailAfterInsert": 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": "Sas.Mrp",
|
||
"PermissionGroupEn": "MRP", "PermissionGroupTr": "MRP",
|
||
"MenuParentCode": "Sas.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": "Sas.Listform.ListformField.Id",
|
||
"Tr": "Kimlik", "En": "Id",
|
||
"EditorType": "dxTextBox", "DbSourceType": 9,
|
||
"IsRequired": false, "IncludeInEditingForm": false, "ColSpan": 1
|
||
},
|
||
{
|
||
"FieldName": "OrderNo", "CaptionName": "Sas.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": "Sas.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 TenantId = '@TENANTID' AND IsDeleted = 0 ORDER BY Name",
|
||
"ValueExpr": "Key", "DisplayExpr": "Name"
|
||
},
|
||
{
|
||
"FieldName": "Quantity", "CaptionName": "Sas.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": "Sas.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": "Sas.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 'Sas.Widget.Total' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'blue' AS \"Color\", 'Sas.Mrp.Orders' AS \"SubTitle\", 'FaShoppingCart' AS \"Icon\" FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID' UNION ALL SELECT 'Sas.Widget.Today', CAST(COUNT(*) AS VARCHAR(32)), 'green', 'Sas.Mrp.Orders', 'FaCalendarDay' FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID' AND CAST(\"CreationTime\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE) UNION ALL SELECT 'Sas.Widget.TotalRevenue', CAST(COALESCE(SUM(\"Total\"), 0) AS VARCHAR(32)), 'purple', 'Sas.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,
|
||
"ApprovalIsManualStart": false, "Criteria": [] }
|
||
}
|
||
}
|
||
```
|
||
|
||
Deploy sonrası dosyada `Wizard` bölümü `Menu`/`Language`/`Data`/`Details`/`Layout` başlıklarına
|
||
toplanır, boş bırakılan bloklar varsayılanlarıyla dolu yazılır ve her öğe alan detayının tamamını
|
||
taşır (§4.1); kısayollar (`AllowAdding` …, `IsRequired`) dosyada yer almaz. `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.
|
||
> **Wizard/MCP deploy'u script'i kurallardan yeniden üretir** (`EditorScript.Rebuild`): `// @builder`
|
||
> başlıklı script'in gövdesi atılıp kurallardan yazılır; `EditorScript` alanına yalnızca kural JSON'u
|
||
> (`{"v":1,"rules":[…]}`) da verilebilir. MCP script'i elle yazmaz, kuralları verir — elle yazılan
|
||
> gövde (yanlış yardımcı kullanımı) çalışmayan script üretiyordu. Başlıksız (tamamen elle yazılmış)
|
||
> script olduğu gibi kalır.
|
||
|
||
### 4.4.1 Deploy'un ve seeder'ın senin yerine ürettikleri — cevaba yazma
|
||
|
||
Aşağıdakileri deploy wizard cevabından **kendisi** üretir (`ListFormWizardDto.ApplyDefaults` →
|
||
`BuildListForm` / `BuildFields`; menü, yetki ve dil satırları `ListFormWizardAppService` →
|
||
`WizardSeedRowStore`). Üretilen ekran `RefreshFrom` ile `Wizard` bölümüne geri yazılır;
|
||
`WizardDataSeeder` migrate'te ekranı dosyadaki bloklardan `BuildListForm/BuildFields` +
|
||
`GridOptionsEditDto.ApplyTo` ile kurar, cevaptan 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` yalnızca Table kaynağında) |
|
||
| Yetkilerin dil anahtarları | Ortak sabitler: `Sas.Platform.Create/Update/Delete/Export/Import/Note` |
|
||
| Üst menü zinciri | `MenuParentPath` + `MenuParentCode` kökten yürünür; kodu bulunamayan seviye önce aynı üstün altındaki dallarla adıyla eşleştirilir (dalın `DisplayName` anahtarı = verilen kod, ya da EN/TR adı aynı) ve eşleşen dalın koduna bağlanır; eşleşme yoksa 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 |
|
||
| `Delete.Command` | Tabloda `IsDeleted` varsa `WizardConsts.DefaultDeleteCommand(SelectCommand)` (soft delete `UPDATE`); yoksa `null` |
|
||
| `Data.DefaultFilter` | Tabloda `IsDeleted` varsa `"IsDeleted" = 'false'` |
|
||
| `Insert.FieldsDefaultValue` | Tabloda `CreatorId` varsa `CreationTime=@NOW`, `CreatorId=@USERID`, `IsDeleted=false`, `Id=@NEWID`; değilse yalnızca `Id=@NEWID` |
|
||
| `Delete.FieldsDefaultValue` | Tabloda `IsDeleted` varsa `DeleterId=@USERID`, `Id=@ID`; değilse yalnızca `Id=@ID` |
|
||
| `Update.FieldsDefaultValue` | Tabloda `LastModifierId` varsa `LastModificationTime=@NOW`, `LastModifierId=@USERID`; yoksa boş. `*AfterCommand`/`*BeforeCommand` içindeki `@LastModifierId` bu satırdan gelir. Özel komut eklenen ekranda (view kaynağı dahil) bu üç liste komutun parametrelerine göre tamamlanır — §4.3.5.3 |
|
||
| `ValidationRuleJson` | `IsRequired: true` olan alanlara required kuralı |
|
||
| `LookupJson` | `LookupQuery` doluysa `LookupDataSourceType` + `DisplayExpr` + `ValueExpr` ile |
|
||
| `ShowNote` | Kayıt kaynağı Table ise ve alt form veya iş akışı varsa açılır; Table dışı kaynakta hep kapalı |
|
||
| `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.
|
||
- Tabloda `IsDeleted` kolonu yoksa ekranda **kalıcı silme** olmaz (`Delete.Command` null kalır);
|
||
soft delete kolonları olmayan tabloda silme istiyorsan `Delete.Command`'ı cevapta ya da ListForm
|
||
editöründen yazmalısın.
|
||
- Dil anahtarı zaten varsa metni **ezilmez**. Yanlış metin görüyorsan anahtar başka bir yerde
|
||
tanımlanmıştır. Anahtar yalnızca veritabanında varsa (seed dosyasında satırı yoksa) deploy onu
|
||
veritabanındaki metniyle `Sas.Languages.LanguageText/{CultureName}.json`'a taşır; sahipliğini almaz.
|
||
|
||
### 4.4.2 Ekran açıklaması — başlıktaki ⓘ (`DescTr` / `DescEn`)
|
||
|
||
Liste ekranının başlığının yanındaki bilgi simgesi `ListForm.Description` dil anahtarını
|
||
(`{ListFormCode}.Desc`) bir diyalogda gösterir (`views/list/List.tsx` → `RichText`). Bu metin
|
||
ekranın **kullanım kılavuzudur**; tek satırlık özet yetmez. Wizard ve MCP ile üretilen her liste
|
||
ekranında (alt formlar dahil) `DescTr` ve `DescEn` şunları anlatır:
|
||
|
||
- İlk satır: ekranın adı ve ne işe yaradığı (tek cümle).
|
||
- `Nerede:` menü yolu ya da hangi ekranın hangi sekmesi olduğu; kim kullanır.
|
||
- `Alanlar:` ana alanlar madde olarak (`• Alan → açıklama`), lookup kaynağı ve değer listeleri.
|
||
- `Otomatik:` numaratör, hesaplar, durum geçişleri, bildirimler, başka ekrana yazan komutlar.
|
||
- `İlişkili:` / `Kullanıldığı yerler:` bağlı ekranlar; `Widget'lar:`; gerekiyorsa `Dikkat:`.
|
||
|
||
Biçim `RichText` sözleşmesidir (HTML yazılmaz): her satır bir blok, `• ` ile başlayan satırlar
|
||
madde listesi, `Etiket: …` satırı kalın başlık, maddede `Ad → açıklama` kalıbı. Wizard ekranında
|
||
alan çok satırlıdır. MCP `deploy_wizard` her iki dilde **en az 3 satır ve 150 karakter** olmayan
|
||
açıklamayı reddeder (`McpErrorCodes.WizardDescriptionTooShort`). Metin değiştirildiğinde dil
|
||
satırı `data/Sas.Languages.LanguageText/{CultureName}.json`'a yazılır; kurulu ekranda yeni metin wizard
|
||
yeniden deploy edilip (bekleyen kurulum) DB Migrate çalışınca gelir.
|
||
|
||
### 4.4.3 Çok dilli metin — her anahtar bütün dil dosyalarına
|
||
|
||
Wizard, MCP, Custom Component ya da elle üretilen her ekranın dil anahtarı `data/Sas.Languages.LanguageText/`
|
||
klasöründeki **bütün** dil dosyalarında bulunur (dosya listesi = `Sas.Languages.Language.json`'daki her `CultureName`).
|
||
|
||
| Yol | EN + TR satırı | Diğer dillerin satırı |
|
||
| --- | --- | --- |
|
||
| Wizard ekranı / `deploy_wizard` | Deploy yazar (`Language.*En/Tr`, `Language.Texts`, sabit lookup anahtarları) | Üreten, deploy'dan sonra her anahtar için kalan dillerin satırlarını ilgili dil dosyasına yazar (MCP: `Sas.Languages.LanguageText` ekranına `insert_row`, dil başına bir satır; Claude Code: dosyaya doğrudan) |
|
||
| MCP `save_component`, `save_report_category/template` ve satır araçları | Anahtar metni EN + TR yazılır | Aynı şekilde diğer diller |
|
||
| Custom Component / Visual Designer | Bileşenin `::Anahtar` metinleri (SEO dahil) EN + TR | Aynı şekilde diğer diller |
|
||
| Menü / yetki grubu / widget başlığı / `Sas.StaticLookup.*` | Deploy ya da seeder yazar | Aynı şekilde diğer diller |
|
||
|
||
Kurallar: (1) satır `ResourceName` + `Key` + `CultureName` üçlüsüyle tektir; `Id` = `Sas_H_LanguageText:{ResourceName}:{Key}:{CultureName}`
|
||
v5 GUID. (2) Çeviri yer tutucuyu (`{0}`), HTML etiketini, kodu ve marka adını aynen korur. (3) Dil listesi koda gömülmez;
|
||
`Sas.Languages.Language.json`'dan okunur. Yeni dil eklenirse klasöre `{CultureName}.json` açılır ve bütün anahtarlar çevrilir.
|
||
(4) Anahtarı silen iş bütün dillerin satırını siler. (5) Klasör dil başına tek dosyadır: dosya adı dilin
|
||
`CultureName`'i (`tr.json`), `Rows` o dilin bütün satırları; satır başına dosya açılmaz (gruplu klasör, §9.15). (6) Eksik dil bırakılmaz; son mesajda yazılan anahtar ve dil sayısı tek satırla bildirilir.
|
||
|
||
### 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 `Wizard` bölümüne
|
||
(`RefreshFrom`) 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 değişiklikler için. Dosyalar yeniden yazılır, ekran DB Migrate'te
|
||
cevaptan yeniden kurulur (§4.0). Ekranın her ayarı cevabın bir bloğu olduğu için editör ayarları
|
||
korunur: wizard ekranı dosyayı yükler, MCP'de `get_wizard` çıktısı düzenlenip gönderilir; cevapta
|
||
hiç yazılmayan blok varsayılana döner (§0.10).
|
||
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 (`Sas.Menus.{Menu,Module}.json`,
|
||
`Abp.Identity.Permission{s,Groups}.json`, `Sas.Languages.LanguageText/{CultureName}.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 `Sas.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
|
||
(`Hr_HealthCardHistory` → `Hr_HealthCardResult` → … → `Hr_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 `Sas.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 (`Stk_T_Transfer`'ı paylaşan `TransferOther`/`TransferNote` gibi) tabloyu bir kez listeler. Bağlantılar veri
|
||
kaynağı kaydı silinmeden önce çözülür. Tablo seçmek yine `Sas.SqlQueryManager` ister.
|
||
- **Talebin Custom Endpoint'leri de gider:** tablo talebe aitse (`create_table`/tasarımcı scripti var,
|
||
talep dışında ekran kullanmıyor) onu sorgulayan Custom Endpoint'ler tabloyu tutmaz; veritabanından
|
||
ve `data/Sas.DeveloperKit.CustomEndpoints/` klasöründeki satır dosyasından (migrate edilmemiş satır dahil) silinir —
|
||
tablo seçilmese de (`WizardDeleteTableDto.CustomEndpoints`, `WizardTableCleaner.DeleteCustomEndpointsAsync`).
|
||
Diyalog bunları tablonun altında listeler. Tek wizard silmede endpoint yine tabloyu "kullanımda" tutar.
|
||
- 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:
|
||
|
||
```jsonc
|
||
"SubForms": [
|
||
{
|
||
"TabType": "List", // List|Card|Tree|Gantt|Scheduler|Todo|Form|Chart|Pivot
|
||
"TabTitle": "Sas.Mrp.OrderItems", // dil anahtarı
|
||
"Code": "Sas.Mrp.OrderItems", // hedef ListFormCode
|
||
"IsRefresh": false, // true: alt sekmede yazınca ana kayıt yeniden çekilir; MCP yeni bağlantıda false yazar
|
||
"Relation": [
|
||
{ "ParentFieldName": "Id", "ChildFieldName": "OrderId", "DbType": 11 }
|
||
]
|
||
}
|
||
]
|
||
```
|
||
|
||
`Relation` bir dizidir ama istemci yalnızca değeri dolu **ilk** eşlemeyi kullanır — bileşik anahtar
|
||
desteklenmez. `Form` sekmesi kaydı ilişki alanıyla (`ChildFieldName = ana alan değeri`) yükler, yeni kayıtta
|
||
ilişki alanını doldurup kilitler; `Chart` sekmesi ana ekranın grafiğini ilişkisiz çizer (reference §5.2). Sekmelerin liste sayfasında,
|
||
seçili satıra bağlı çizilmesi için ana ekranda `SubFormsListFormType: "List"` (reference §5.6).
|
||
|
||
**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 (`Hr.HealthCard › Hr.HealthCardGeneral › 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. Sekme okuma yetkisi olmayan kullanıcıda da
|
||
"kayıt bulunamadı" gösterir; yeni alt formun yetkisi role verilmeden sekme boş görünür.
|
||
- **Aynı satırı sekmelere bölmek (`Id → Id`):** Kalabalık bir kartın bölümleri (Sağlık Kartları,
|
||
Kontaklar'ın Satış / Satınalma / Muhasebe… sekmeleri) ana ekranla **aynı tabloya** bağlı,
|
||
menüsüz `Form` sekmeleridir; her biri yalnızca kendi alanlarını taşır, ilişki `Id → Id`'dir. Satır
|
||
ana ekrandan eklendiği için bu sekmelerde yalnızca `AllowUpdating` açıktır — `AllowAdding` ve
|
||
`AllowDeleting` kapalı yazılır (eklemek tabloya ikinci bir ana kayıt açardı). Taşınan alanlar ana
|
||
ekranın `Groups` öğelerinden çıkar; yeni kayıt önce ana formda oluşur, sekmeler kayıttan sonra açılır.
|
||
Tek formda sekme şeridi isteniyorsa (alt form yerine) grupların `ItemType`'ı `tabbed` yazılır
|
||
(`lowcode-reference` §3.2).
|
||
- **`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:
|
||
|
||
```jsonc
|
||
"Widgets": [
|
||
{
|
||
"SqlQuery": "SELECT 'Sas.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": "", // değeri JS olarak eval edilen kolonun adı ('window.open(''/admin/list/X'')' AS Tikla)
|
||
"ValueClassName": "text-3xl",
|
||
"ColSpan": 1,
|
||
"ColGap": 3,
|
||
"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 `Sas.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
|
||
`Sas.Widget.*` (`Total`, `Active`, `Passive`, `Published`, `Draft`, `Today`, `Upcoming`,
|
||
`Pending`, `Sent`, `TotalViews`, `TotalRevenue`…), lookup'ın `Sas.StaticLookup.*` anahtarları,
|
||
alanın `Sas.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. **İş akışlı her tablo** tablo
|
||
tasarımcısının Workflow şablonundaki (`WORKFLOW_COLUMNS`, `SqlTableDesignerDialog`) dört kolonu taşır ve
|
||
bu dördü dışında akış kolonu açılmaz:
|
||
|
||
| Kolon | SQL Server | PostgreSQL | `WorkflowDto` alanı |
|
||
| --- | --- | --- | --- |
|
||
| `ApprovalUserName` | `nvarchar(256) NULL` | `varchar(256) NULL` | `ApprovalUserFieldName` — sıradaki onaycı(lar)ın kullanıcı adı, `\|` ayrılmış |
|
||
| `ApprovalStatus` | `nvarchar(50) NULL` | `varchar(50) NULL` | `ApprovalStatusFieldName` — bulunulan düğümün başlığı; boş = akış başlamadı (taslak) |
|
||
| `ApprovalDate` | `datetime NULL` | `timestamp NULL` | `ApprovalDateFieldName` |
|
||
| `ApprovalDescription` | `nvarchar(200) NULL` | `varchar(200) NULL` | `ApprovalDescriptionFieldName` — karar açıklaması; reddin nedeni de buraya yazılır |
|
||
|
||
- Ayrı bir durum kolonu (`Status` vb.) açılmaz; kaydın durumu `ApprovalStatus`'tür ve yalnız akış yazar.
|
||
Widget/filtre sorguları düğüm başlığıyla süzer (`"ApprovalStatus" = N'Onaylandı'`).
|
||
- Onaylayan Guid kolonu (`ApproverId`) açılmaz; motor kullanıcı adı yazar.
|
||
- Red nedeni kolonu açılmaz: onay düğümünün `RejectReason` tanımı (aşağıda) nedeni listeden sorar ve
|
||
`ApprovalDescription`'a yazar.
|
||
- Dört alan popup düzenleme formuna konmaz (`EditGroupOrderNo`/`EditOrderNo` boş, ayrı "Onay" grubu
|
||
açılmaz); listede kolon olarak görünür (`Visible: true`).
|
||
|
||
| 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: onaycı kolonu **boş** olan (akış başlatılabilsin diye) ya da `\|` ile bölünmüş değerlerinde kullanıcının UserName/Email/Name'i geçen satırlar |
|
||
| `ApprovalIsResetWorkflow` | Toolbar'daki **Taslak** (iş akışı sıfırlama) düğmesini açar (yalnızca akışı başlamış satırda etkin); otomatik sıfırlama yoktur, bayrak kapalıyken `…/reset` reddedilir. `ApprovalIsManualStart` açıksa sıfırlama akışı yeniden çalıştırmaz; kayıt taslağa döner ve akış başlat düğmesiyle başlar |
|
||
| `ApprovalIsManualStart` | Yeni kayıt akışı kendiliğinden başlatmasın; kayıt taslak kalır, akış toolbar'daki başlat düğmesiyle başlar. Onaya gönderilmiş başlığa satır eklemeyi engelleyen alt formu olan ekranlarda (talep + satırlar) açılır; kapalıyken Grid/Tree yeni kaydın ardından akışı başlatır |
|
||
|
||
`Criteria` düğüm grafiğidir. `Kind` değerleri: **`Start`**, **`Compare`**, **`Approval`**,
|
||
**`Inform`**, **`End`**.
|
||
|
||
```jsonc
|
||
"WorkflowDto": {
|
||
"ApprovalUserFieldName": "ApprovalUserName",
|
||
"ApprovalDateFieldName": "ApprovalDate",
|
||
"ApprovalStatusFieldName": "ApprovalStatus",
|
||
"ApprovalDescriptionFieldName": "ApprovalDescription",
|
||
"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": "<userName|email>", "NextOnApprove": "N5", "NextOnReject": "N6",
|
||
"PositionX": 300, "PositionY": 260 },
|
||
|
||
{ "Id": "N4", "Kind": "Approval", "Title": "Yönetici onayı",
|
||
"Approver": "<userName|email>", "NextOnApprove": "N5", "NextOnReject": "N6",
|
||
"PositionX": 40, "PositionY": 260 },
|
||
|
||
{ "Id": "N5", "Kind": "Inform", "Title": "Talep sahibine bilgi",
|
||
"Approver": "<userName|email>", "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 (`N` ile başlayan her id, büyük/küçük harf duyarsız; diğer id'ler olduğu gibi kalır). Bu yüzden
|
||
dosyada `N1`, `N2` yazman yeterlidir ve **tekil olmaları şarttır**. Runtime düğümleri
|
||
`ListFormWorkflow` tablosundan okur; `WorkflowDto.Criteria` select cevabında tablodan doldurulur.
|
||
- **Onaycı = kullanıcı adı / e-posta.** `Approver` `|` ile ayrılmış **kullanıcı adı ya da e-posta**
|
||
listesidir; **rol ve Id desteklenmez** (onay yetkisi `CurrentUser.UserName/Email/Name` ile eşleşir).
|
||
Rol bazlı onay gerekiyorsa `@Kolon` onaycısı + rolün kullanıcılarını kolona yazan prosedür kullanılır.
|
||
Sabit `Approver` boş bırakılırsa ya da `ApprovalUserFieldName` tanımsızsa ekranın **Update yetkisi
|
||
olan herkes** onaylayabilir — onay düğümünü onaycısız bırakma.
|
||
- **Yetki ve görünürlük:** start/decision/reset/`reject-reasons` uçlarının hepsi ekranın **Update**
|
||
yetkisini ister. Toolbar akış düğmeleri (Başlat dahil) yalnızca `ApprovalStatusFieldName` dolu, en az bir
|
||
`Approval` düğümü var ve **`UpdateServiceAddress` boş değilse** görünür. Çoklu seçimde start/reset/
|
||
decision her anahtar için ayrı çalışır.
|
||
- **Yazma yolu:** her düğüm geçişi ekranın Update yoluyla yazar (`QueryManager`): `UpdateBefore/After`
|
||
kancaları ve `UpdateFieldsDefaultValueJson` **her düğümde** çalışır, özel `UpdateCommand` varsa akış
|
||
parametreleriyle o çalışır. Karar veren kullanıcının dört akış alanında (ve `RejectReason.TargetField`
|
||
alanında) alan seviyesinde `U` yetkisi yoksa yazım **sessizce düşer**.
|
||
- **Karar anı:** onaycı kolonuna karar verenin `UserName`'i, tarihe `Clock.Now`, açıklamaya
|
||
`"{neden} - {not}"` yazılır. Durum boşken karar verilirse akış önce kendiliğinden başlatılır. Her işlem
|
||
`Note` kaydı açar (`Type = "workflow"`, `EntityName = ListFormCode`, içerik HTML tablo); düğüm
|
||
başlıkları dil anahtarı olabilir (sondaki rakam son ek olarak korunur).
|
||
- **`End` düğümü:** onaycı kolonu boşsa admin e-postası, tarih boşsa `Clock.Now`, açıklama boşsa
|
||
"Sistem tarafından otomatik olarak onaylandı." yazar. `NextOn*` boş kalan dal akışı sessizce bitirir
|
||
(durumda son düğümün başlığı kalır); döngü hata verir.
|
||
- `Compare` düğümünde çok dallı karar için `CompareOutcomes` kullanılır; iki dallı basit karar
|
||
için `NextOnTrue` / `NextOnFalse` yeterlidir. Outcome'lar sırayla denenir, bir outcome'daki koşullar
|
||
**AND** ile bağlanır, koşulsuz outcome "else" dalıdır; hiçbiri tutmazsa `NextOnTrue`/`NextOnFalse`
|
||
kullanılır (kayıtta bu ikisi `outcomes[0]`/`[1]`'in hedefiyle ezilir). Koşulun kolonu satırda yoksa
|
||
düğümün `CompareColumn`'u, o da yoksa satırdaki tek sayısal kolon kullanılır; değer `null` ise hata.
|
||
Karşılaştırma **sayısaldır** (`EvaluateCondition`: `>`, `>=`, `<`, `<=`, `=`, `!=`; sunucu `==` ve `<>`'yi de kabul eder, bilinmeyen operatör `false`); bit/metin kolon
|
||
okunamaz ve hata verir. Kayda göre adım atlamak için prosedür sayısal bir bayrak kolonu yazar ve
|
||
kolon ekranda gizli alan olur (örnek: İzin Yönetimi'nde üst pozisyonu olmayan çalışanın müdür
|
||
adımı, `RequiresManagerApproval`, `Hr.md` › İzin Yönetimi §5.6).
|
||
- `Approval` düğümü `NextOnApprove` / `NextOnReject`, `Start` ve `Inform` düğümleri
|
||
`NextOnStart` alanını kullanır. `End` düğümünün çıkışı yoktur.
|
||
- **Red nedeni listesi:** `Approval` düğümü opsiyonel `RejectReason { TableName, ValueField, TextField,
|
||
TargetField, Required }` taşır (entity'de `RejectReasonJson`; `Required` varsayılanı `true`, `TableName`/
|
||
`ValueField`/`TextField` üçü dolu değilse tanım `null` saklanır; liste metin kolonuna göre sıralanır). Boşsa red yalnızca açıklama ister; doluysa
|
||
karar diyaloğu tablonun Id/metin kolonlarından seçim listeler (`reject-reasons` ucu; tablo ekranın veri
|
||
kaynağındadır, adlar katalogdan çözülür, `TenantId`/`IsDeleted`/`IsActive` kolonu varsa süzülür).
|
||
`Required` açıkken neden seçilmeden ret verilemez. Seçilen nedenin metni açıklamanın başına
|
||
(`ApprovalDescriptionFieldName`) ve iş akışı notuna, Id'si `TargetField` doluysa kaydın o kolonuna yazılır;
|
||
metin istemciden alınmaz, sunucu tablodan doğrular. Sıfırlama `TargetField` kolonlarını da temizler.
|
||
- **Kayda göre değişen onaycı:** `Approver` değeri `@Kolon` ise (ör. `@ManagerUserNames`) onaycı kaydın o
|
||
kolonundan okunur (`ListFormWorkflowAppService.ResolveNodeApprover`); kolon `|` ile ayrılmış kullanıcı
|
||
adı/e-posta taşır ve ekranın alanı olmalıdır (satır ListForm sorgusundan okunur; gizli alan yeter). Kolon
|
||
boşsa akış hata verir — boş onaycı herkese onay yetkisi verirdi. Kolonu kayıt sonrası prosedür doldurur
|
||
(örnek: İzin Yönetimi'nde departman müdürü, `Hr.md` › İzin Yönetimi). Tasarımcıda onaycı listesi kullanıcılarla
|
||
birlikte `Alan: {Kolon}` seçeneklerini sunar.
|
||
- **`Inform` düğümü e-posta gönderir** — kuyruk değil, gönderici (`erpEmailSender`, Amazon SES) ile
|
||
**senkron**; gönderim başarısızsa adım hata verip düşer. Alıcı `Approver`'daki değerlerdir: `@` içeren
|
||
doğrudan e-posta, diğerleri kullanıcı adından çözülür; önceki adımların notları eklenir. 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` **işlevsel olarak** tekil olmalıdır: durum kolonu düğümün başlığını saklar, mevcut düğüm Id ya da
|
||
başlıkla (büyük/küçük harf duyarsız) bulunur, toolbar onay düğmesi de başlıkla eşleşir. Kayıt çakışan
|
||
başlığa numara ekler (`NormalizeUniqueTitleAsync`, wizard'da `EnsureUniqueWorkflowCriteriaTitles`).
|
||
- Kayıt varsayılanları: `Kind` boşsa `Compare`, `CompareColumn` boşsa `Price`, `CompareOperator` boşsa `>`,
|
||
`Title` boşsa `Kind`; konum ≤ 0 ise 32/150.
|
||
- Akış varsa wizard ListForm'a `SelectionMode = single` yazar (runtime çoklu anahtarı da destekler) ve not
|
||
paneli otomatik açılır. Wizard `ApprovalUserFieldName` ile `ApprovalStatusFieldName`'i zorunlu tutar.
|
||
- 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` | Dil varyantı (`CultureName`). `UserId`/`RoleId` ekran düzeyinde **okunmaz** — kullanıcı/rol farkı alan (`ListFormField`) ve kişiselleştirme düzeyindedir (reference §5.7) |
|
||
| `ListFormType` | `List` · `Form` · `Chart` — ekranın türü |
|
||
| `IsSubForm` / `SubFormsListFormType` | `IsSubForm` bilgi amaçlıdır; `SubFormsListFormType` bu ekranın alt form sekmelerinin yeri: `List` liste altında seçili satıra bağlı, `Form` yalnız detay sayfasında (reference §5.6) |
|
||
|
||
**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` | Runtime **okumaz** (§4.3.5); okuma koşulu `DefaultFilter`'a yazılır |
|
||
| `DefaultFilter` | Her sorgunun sonuna eklenen WHERE (soft delete filtresi buradadır) |
|
||
| `IsTenant` · `IsBranch` · `IsOrganizationUnit` | Otomatik kırılım filtreleri (reference §4.2) |
|
||
|
||
**Yazma (CRUD)**
|
||
|
||
| Kolon | Ne yapar |
|
||
| --- | --- |
|
||
| `InsertCommand` / `UpdateCommand` / `DeleteCommand` | Özel SQL komutları; boşsa platform üretir. Üretilen silme her zaman anahtar alanla sınırlanır, `TenantId` gibi diğer parametreler yalnızca ek koşuldur (`QueryManager`) |
|
||
| `*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`, `CommonJson`, `AdaptiveLayoutJson`, `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` | `KeyExpr` + `ParentIdExpr` + `TitleExpr` + `StartExpr` + `EndExpr` + `ProgressExpr` |
|
||
| **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; seçilen görünüm tarayıcının localStorage'ında
|
||
saklanır (sunucuda değil). `Grid` her zaman açıktı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` | Özel toolbar ve satır butonları (§4.9); hazır butonlar reference §5.8 |
|
||
| `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` | Boyut |
|
||
| `ExportJson` | Dışa aktarma: `Enabled` + `PermissionJson.E`; Grid `pdf`/`xlsx`, Pivot `xlsx` (reference §5.8) |
|
||
|
||
**`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 `Wizard.CommandColumns` olarak durur ve düzenlemede
|
||
korunur. Wizard ekranında **Düzenleme** adımının Komutlar sekmesinden, ekranın detayındaki
|
||
**Komutlar** sekmesinden ya da `deploy_wizard` ile yazılır.
|
||
|
||
```jsonc
|
||
[
|
||
{
|
||
"ButtonPosition": 1, // 0 = satır aksiyon sütunu · 1 = toolbar
|
||
"AuthName": "Hr.AdvanceRequests.Update", // yetki yoksa buton hiç çizilmez
|
||
"Text": "Hr.AdvanceRequests.SendToApproval", // dil anahtarı (başına :: eklenir)
|
||
"Hint": "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/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). **Form sayfasında** toolbar butonları ve Düzenle/Sil/Detay dışındaki satır
|
||
butonları form araç çubuğuna taşınır (satır bağlamı enjekte edilir). Gantt, Scheduler, Pivot ve
|
||
Chart'ta özel buton çizilmez. Buton anahtarı `Hint`'tir — aynı `Hint`'li iki buton yazma.
|
||
- Satır bazlı aksiyon istiyorsan `ButtonPosition: 0` kullan; buton komut sütununda görünür.
|
||
- `VisibleExpression` bir ok fonksiyonudur ve `eval` edilir:
|
||
`"(e) => e.row.data.Status === 'Taslak'"`. **Satır butonunda** (`ButtonPosition: 0`) `false` butonu
|
||
gizler (`IsVisible` de yalnız satır butonunda okunur). **Toolbar butonunda** buton gizlenmez, **pasif**
|
||
olur: ifade seçili her satır için ayrı çalışır (`e.row.data` = o satır), biri `false` dönerse ya da
|
||
seçim yoksa buton tıklanamaz; seçim değişince yeniden hesaplanır (Grid, Tree, Card ve form sayfası —
|
||
`isToolbarItemDisabled` / `updateSelectionToolbarItems`, `useToolbar.tsx`). İfadesi olmayan toolbar
|
||
butonu her zaman tıklanabilir (görünürlüğü `AuthName` belirler). Durum adımı butonları
|
||
(Onayla, İptal, Taslağa al …) izin verilen durumları buraya yazar; sunucudaki durum kontrolü yine
|
||
kalır. Örnek: Satınalma › Teklif Talepleri ve Siparişler (`Prc.md`).
|
||
- 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ı (`@LISTFORMCODE` yok). Sütun
|
||
listesinde olmayan bir alan adı satır butonunda değiştirilmez. Her token yalnızca **ilk geçtiği yerde**
|
||
değiştirilir, değer URL-encode edilmez ve önek çakışır: `@Id`, `@IdentityNo` içinde de değişir —
|
||
adı başka bir alan adının öneki olan alanı URL'de kullanma.
|
||
`VisibleExpression` kart görünümünde `{row, card}`, form sayfasında `{row:{data,key,isEditing}}` alır.
|
||
- **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) — liste toolbar'ında `@` parametresi **çalışma anı hatası** verir;
|
||
form sayfasında çalışır. **`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`, `DiaryEntry`, `PurchaseDocGrid`, `MaterialRequestPlan`. Kayıt yeri
|
||
`views/shared/DialogContext/DialogShowComponent.tsx`'tir; her diyalog `open` + `onDialogClose` prop'larını alır.
|
||
Son ikisi satır ya da toolbar butonunun `OnClick`'inden `dialog.setConfig({ component, props, onClose })` ile
|
||
açılır ve ekranları platformun Grid'iyle `BaseId` filtresine süzerek gösterir; ekran kodları prop'tur, bileşene yazılmaz:
|
||
- `PurchaseDocGrid` — tek ekran: `docId`, `listFormCode`, `title`, `info` (dil anahtarları `::` öneksiz).
|
||
- `MaterialRequestPlan` — sekmeli karar ızgaraları + taslak ızgarası + Onayla: `itemId`, `planListFormCode` (özet satırı
|
||
`Id` ile buradan okunur, Onayla bu ekranın `UpdateCommand`'ını `list-form-data/update` ile çalıştırır, buton
|
||
`{planListFormCode}.Update` ister), `tabs` (ekran kodları; başlık `{kod}.Title`), `activeTab`, `draftListFormCode`,
|
||
`info`. Bir ızgaradaki kayıt (`refreshData`) diğer ızgaraları ve özeti yeniler; kaydı yapan ızgara yeniden kurulmaz,
|
||
süren batch kaydı kesilmesin diye. Örnek: Satınalma › Talepler › Planlama (`Prc.md`).
|
||
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** (`Sas.DeveloperKit.DynamicServices`) —
|
||
liste, KPI şeridi ve Swagger butonu ListForm'da; kod yazma `DynamicServiceEditor` diyalogunda (§9.3).
|
||
|
||
> **Adres değişiminde veritabanı uyarısı:** `data/Sas.Menus.Menu.json` ve `Sas.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`, `Data` (`DataSourceCode` (+`DataSourceConnectionString`), `SelectCommandType`, `SelectCommand`, `KeyFieldName`, `KeyFieldDbSourceType`, `IsTenant/IsBranch/IsOrganizationUnit`; `SeedData` adımda sorulmaz, düzenlemede korunur), `AllowAdding/Updating/Deleting`, `ConfirmDelete`, `AllowDetail`, `OpenDetailAfterInsert`, `Layout` (`DefaultLayout` + 8 görünüm bayrağı), `Language.TitleEn/Tr`, `Language.DescEn/Tr` |
|
||
| 4 | Alanlar | yalnız List | `Groups[]` (aşağıda); karttaki ek ayarlar `Visible`/`IsActive`/`ReadOnly`/`AllowSearch`/`AllowEditing`/`AllowAdding`, `Width`, `Alignment`, `Format`, `SortDirection`/`SortIndex`, `DefaultValue`, `PlaceHolder`, `BandName`; alan kartından açılan detay sekmeleri öğenin bloklarını (`Filter`, `Header`, `Grouping`, `TotalSummary`/`GroupSummary`, `Join`, `ValidationRules`, `Styling`, `ColumnCss*`, `Permission`, `PivotSettings` …), sütun sırası paneli `ListOrderNo`'yu doldurur |
|
||
| 5 | Liste Ayarları | yalnız List | ListForm editörünün sekmeleri taslak modunda: Detay (`Details`: tip, sayfa boyu, boyutlar, not, alt form, kaynak dosyaları, rol/kullanıcı varyantı), Sütunlar (`ColumnOption`), Sıralama (`Details.SortMode`), Filtreleme (`FilterRow`, `HeaderFilter`, `FilterPanel`), Satır (`Row`), Arama (`SearchPanel`), Gruplama (`GroupPanel`), Seçim (`Selection`), Sayfalama (`Pager`), Durum (`StateStoring`), Pivot (`PivotOption`; yalnız `Pivot` açıksa) |
|
||
| 6 | Düzenleme ve Veri | yalnız List | Düzenleme (`Editing`; izin bayrakları Ayarlar adımıyla iki yönlü eşlenir), Veritabanı Select/Insert/Update/Delete (`Data.SelectFieldsDefaultValue`, `Data.SeedFilePath`/`SeedSync*`, `Insert`/`Update`/`Delete`), Butonlar (`CommandColumns`), Ek Filtreler (`ExtraFilters`) |
|
||
| 7 | Alt Formlar | yalnız List | `SubForms[]` |
|
||
| 8 | Widget'lar | yalnız List | `Widgets[]` |
|
||
| 9 | İş Akışı | yalnız List | `WorkflowDto` |
|
||
| 10–13 | Todo / Tree / Gantt / Scheduler | List + ilgili bayrak açıksa | `TodoOptionDto` / `TreeOptionDto` / `GanttOptionDto` / `SchedulerOptionDto` |
|
||
| 14 | Grafik | List + `Chart` açıksa | `ChartOptions` (grafik sekmeleri: Genel, Seriler, Eksen, Paneller, Animasyon, Açıklamalar, Yakınlaştırma, Gösterge, Crosshair, Dışa Aktarma) |
|
||
| 15 | Deploy | her zaman | — (özet + "Ekran ayarları" bölümü + üretim; dosyaya alan yazmaz) |
|
||
|
||
Liste Ayarları / Düzenleme ve Veri / Grafik adımları (`WizardStepScreenTabs.tsx`) aynı ListForm editörü
|
||
sekmelerini **taslak modunda** gösterir (`ListFormDraftContext`): sekmede Kaydet düğmesi yoktur,
|
||
değişiklik veritabanına değil wizard cevabına toplanır ve deploy'da dosyaya yazılır; böylece
|
||
editörde yapılabilen her ayar wizard'da da yapılır ve dosyada kendi bloğunda durur (§4.2). Adıma her
|
||
girişte o adımın ilk sekmesi açılır (Detay, Düzenleme, Genel). Ekran ↔ cevap eşlemesi `wizardScreen.ts`'tedir
|
||
(`screenFromWizard`/`wizardFromScreen`); kimlik, veri kaynağı, anahtar, görünüm ve izin bayrakları
|
||
wizard adımının cevabından gelir (`withWizardAnswers`), sekmede değişen izin/görünüm bayrağı da Ayarlar
|
||
adımına geri yazılır. Boş bloklar `GET list-form-wizard/screen-defaults` ile sunucunun varsayılanlarından açılır.
|
||
Custom yolunda 3–14 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. Wizard adı seed dosyasının adıdır ve bütün
|
||
modüllerde tekil olmak zorundadır; aynı ekran adı birden çok modülde geçebilir (İK ve Satınalma
|
||
altında `RejectionReason`). Bu yüzden yeni wizard'ın adı modül önekini taşır: `{ModuleId}_{Ad}`
|
||
(`Hr_RejectionReason`, `Prc_RejectReason`, `Stk_Warehouse`). Önek `MenuParentModuleId`'den
|
||
gelir ve deploy'da eklenir (`ListFormWizardDto.ResolveWizardName`); ad zaten önekle başlıyorsa
|
||
tekrar eklenmez. `configs/seeds/wizard/` altındaki bütün dosyalar bu düzendedir; öneksiz ad yalnızca
|
||
eski bir export arşivinden gelebilir ve o wizard düzenlenirken adı korunur. Ekran, menü ve
|
||
yetki kodları (`ListFormCode`, `MenuCode`) önekten etkilenmez. Üretim planında ve `replaceFileName`'de
|
||
dosya adı önekli yazılır (`deploy_wizard: Prc_RejectReason.json`). `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:
|
||
`Sas.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ı `Sas.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. Alt çubuktaki
|
||
**Ek alan ayarları** anahtarı (varsayılan açık) her kartta alan editörünün Detay/Seçenekler sekmelerinin
|
||
en çok kullanılan ayarlarını gösterir: Görünür, Aktif, Salt okunur, Arama, Düzenleme, Ekleme kutuları;
|
||
Genişlik, Hizalama, Biçim, Sıralama yönü/indeksi, Varsayılan değer, Yer tutucu, Bant. Boş bırakılan
|
||
değer öğeye `null` yazılır ve deploy sunucu varsayılanını uygular. ⚙ düğmesi alan
|
||
detayı diyaloğunu (`WizardFieldDetailDialog.tsx`) açar: ListForm alan editörünün 15 sekmesi taslak
|
||
modunda, her değişiklik anında öğeye yazılır (`fieldFromItem`/`itemFromField`); Doğrulama sekmesinde
|
||
`required` kuralı ile kartın Zorunlu kutusu aynı değerdir. **Sütun sırası** paneli ↑/↓ ile `ListOrderNo`'yu
|
||
verir; yazılmayan öğe en sona düşer (`ResolveListOrders`).
|
||
|
||
**Adım 9 — İş Akışı.** Zorunlu ikili: `ApprovalUserFieldName` + `ApprovalStatusFieldName`;
|
||
opsiyonel `ApprovalDateFieldName`, `ApprovalDescriptionFieldName`, `ApprovalIsFilterUserName`
|
||
(liste yalnız sıradaki onaycıya düşsün), `ApprovalIsResetWorkflow` (toolbar'a Taslak
|
||
düğmesi; otomatik sıfırlama yok), `ApprovalIsManualStart` (yeni kayıt taslak kalsın, akış başlat düğmesiyle başlasın). Onay düğümleri (`Criteria`) görsel akış tasarımcısında kurulur ve `ListFormWorkflow`
|
||
kayıtlarına gider (§4.7).
|
||
|
||
**Adım 15 — 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/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** |
|
||
| Sihirbaz sekmesindeyken **Kaydet** | Kurala dokunulduysa ya da kod kurallarla aynıysa kurallardan üretilen metin yazılır; elle yazılmış (ayrışmış) kod kurala dokunulmadan kaydedilirse olduğu gibi kalır |
|
||
| Kod sekmesinde bayat başlıklı script kaydı | Yalnızca ilk satır (`// @builder …`) silinir; `// @runOnOpen` kalır |
|
||
|
||
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}'))`; `text` `true`/`false` ise mantıksal sabit yazılır: `set('T', false)` — onay kutusuna metin `'false'` dolu sayılırdı. Runtime da mevcut değeri mantıksal olan alana gelen `'true'/'false'/'1'/'0'`'ı mantıksala çevirir |
|
||
| 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ışmanın ikinci tetiği:** script yalnızca `// @runOnOpen` ile değil, metninde
|
||
> `readOnly(`, `isReady`, `setEditorReadOnly` ya da `runtimeSetEditorReadOnly` geçtiğinde de form
|
||
> açılışında çalışır (`shouldRunEditorScriptOnContentReady`, `list/shared/editingForm.ts`). Tetikleyicisi
|
||
> `change` olan bir `readOnly` kuralı guard üretmez ama script yine açılışta koşar; aynı script'teki
|
||
> guard'sız `ask`/`notify`/`api` satırları da **her açılışta** çalışır. `readOnly` kuralını uyarı/servis
|
||
> kurallarıyla aynı alana koyuyorsan tetikleyicisini `both` yap ki diğer satırlar `if (!isReady)` ile
|
||
> sarmalansın.
|
||
|
||
Açılışta çalışma güvenliği runtime tarafında korunur: değişmeyen değer yazılmaz
|
||
(`Object.is` kontrolü), yazmalar ertelenir (Grid/Tree `onContentReady`'de `setTimeout 0`, runtime
|
||
flush'ı `queueMicrotask`) 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()` | **Alan adı** alır, değeri değil: `str('ProductType')`. `str(get('X'))` ya da `str(value)` değeri alan adı sanıp boş döner — koşul hiç tutmaz. Değişen alanın değeri için `str(field)` ya da doğrudan `value` kullanılır. `bool` için `true`/`'true'`/`1`/`'1'` doğrudur; `num` sayıya çevrilemeyen değerde `0` döner |
|
||
| `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. Seçili kayıt, olay formdan gelse de (popup form, form sayfası) değişen alanın editöründen okunur; lookup sorgusu taşınacak kolonları döndürmelidir (ör. `SELECT Id AS [Key], …, DepartmentId, JobPositionId`). Seçili kayıt yoksa sessizce hiçbir şey yapmaz |
|
||
| `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). Grid popup'ında açılış çalışması (`isReady`) form her yeniden çizildiğinde (her `cellValue` yazımı) tekrarlanır; veri grid'in düzenlenen satırından güncel okunur (`getEditingRow`), `str('Alan')` yeterlidir |
|
||
| `round(x, 2)` · `sum('A','B')` | Matematik |
|
||
| `days('Bas','Bit')` · `hours('Bas','Bit')` | Gün farkı (bitiş dahil; negatif sonuç `0`'a kırpılır) · saat farkı (negatifse +24, `0,1`'e yuvarlanır). Tarihlerden biri boşsa ikisi de `null` döner |
|
||
| `tpl('{Alan} - {value}')` | Şablon: `{Alan}` (nokta yolu destekler), `{value}`, `{selected.Yol}` — çözüm sırası `{value}` → `{selected.X}` → `{Alan}` |
|
||
| `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; hedef verilmezse `_blank` |
|
||
| `await api(url, 'data.name', options?)` | `/api/` ile başlayan uygulama adresi platform istemcisinden gider (oturum + kiracı başlığı; `options.method`/`options.body` taşınır). Dış adres ham `fetch`'tir, başlık eklemez. `/api/` adresinde metin `options.body` önce `JSON.parse` edilir. |
|
||
| `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.
|
||
|
||
**Zincir ve kalıcı readOnly (Grid/Tree popup).** Script'in `set` ile yazdığı alanın kendi script'i de
|
||
çalışır — kullanıcı o alanı değiştirmiş gibi (`runChainedScripts`; derinlik sınırı yoktur, zincir yazılan kadar ilerler; `ProductType` →
|
||
`TrackInventory = false` → `TrackingMethod` salt okunur). Grid'e `cellValue` ile yazılan değer editörün
|
||
değişiklik olayını tetiklemediği için zincir olmadan bağımlı kural ancak kullanıcı alanı elle değiştirince
|
||
çalışırdı. Yazma popup formunu yeniden çizer (`editForm.repaint`) ve açılış script'leri yeniden çalışır;
|
||
formun `formData`'sı açılıştaki nesne olduğundan veri grid'in düzenlenen satırından okunur
|
||
(`getEditingRow`, `list/shared/editingForm.ts` — yeni satırın anahtarı nesnedir, `row.key ===` eşleşmez).
|
||
Eski veriyle çalışan script kendi `readOnly` kararını geri alırdı. Popup formu
|
||
`getController('editing')._editForm`'dadır (`getActiveEditingForm`). Form sayfası `updateData` ile alan
|
||
olayını zaten tetikler.
|
||
|
||
Zincirde bir alanın aynı değerle ikinci kez tetiklenmesi döngüdür (A → B → A) ve durdurulur; değeri her turda
|
||
değişen döngüyü 1000 adımlık emniyet sınırı yakalar. İkisi de kullanıcıya hata olarak gösterilir.
|
||
|
||
**Hatalı script kullanıcıya gösterilir** — derleme ya da çalışma hatası konsola düşmekle kalmaz, ekranda
|
||
`Sas.EditorScript.Error` bildirimi çıkar (alan adı + hata; `reportEditorScriptError`,
|
||
`utils/editorScriptRuntime.ts`). Form her yeniden çizildiğinde aynı hata 5 saniyede bir kez gösterilir.
|
||
|
||
**Derlenemeyen script hiç çalışmaz** — bir satırdaki söz dizimi hatası (eksik parantez) diğer kuralları da
|
||
durdurur. Script Builder kaydetmeden önce metni derler ve hatada kaydetmez (`Sas.ScriptBuilder.SyntaxError`).
|
||
|
||
**Script Builder ile MCP aynı kodu üretir.** Kod `// @builder` başlığındaki kurallardan üretilir; MCP/wizard
|
||
deploy'u gövdeyi atıp kuralları `EditorScript.Rebuild` ile yeniden üretir (C# çıktısı `scriptRecipes.ts` ile
|
||
birebir). Onay kutusu/anahtar alanına değer yazan `setValue` kuralında dialog serbest metin yerine Evet/Hayır
|
||
sectirir (`getParamChoices`); deploy aynı alana gelen `true`/`1`/`yes`/`evet`/`on` değerini `true`'ya,
|
||
`false`/`0`/`no`/`hayir`/`hayır`/`off` değerini `false`'a çevirir (`WithBooleanValue`).
|
||
Kod editöründe kurallarla eşleşmeyecek şekilde elle değiştirilen script'te dialog uyarır
|
||
(`Sas.ScriptBuilder.StaleHeader`) ve kaydederken bayat başlığı siler; başlık kalsaydı deploy gövdeyi başlıktan
|
||
yeniden üretip elle yapılan düzeltmeyi geri alırdı.
|
||
`readOnly` tarifinde `invert: "true"` koşulu tersler: "koşul sağlanmıyorsa salt okunur". "X seçilince kilitle"
|
||
için `invert` boş bırakılır.
|
||
|
||
### 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ı kullanıcıya `Sas.EditorScript.Error` bildirimiyle gösterilir (aynı hata 5 sn
|
||
içinde tekrar gösterilmez); derleme hatası script'in tamamını durdurur, çalışma hatası form akışını kesmez.
|
||
|
||
### 5.8 Kod modunda çalışmak
|
||
|
||
Sihirbazın karşılamadığı bir şey için önce `formula`, sonra `custom` tarifini dene — bunlar
|
||
sihirbazı **korur**. Tamamen elle yazılan script sihirbazı kapatır. Yardım panelindeki hazır
|
||
kalıplar (koşullu yazma, açılış/değişim ayrımı, toplu `set`, seçili kayıttan doldurma, onay,
|
||
`try/catch`'li API çağrısı, alan kilitleme) doğrudan editöre eklenir.
|
||
|
||
```js
|
||
// Açılışta ve değişimde farklı davranış
|
||
if (isReady) {
|
||
set('Durum', str('Durum') || 'Taslak')
|
||
} else if (!ask('Fiyatı değiştirmek istediğinize emin misiniz?')) {
|
||
return
|
||
}
|
||
```
|
||
|
||
### 5.9 Seeder tarafı (C#) — tercih edilen yol
|
||
|
||
`api/src/Sozsoft.Platform.Domain.Shared/Editors/` — `scriptRecipes.ts` dosyasının portudur.
|
||
|
||
| Tarif | C# |
|
||
| --- | --- |
|
||
| multiply / subtract | `EditorScript.Multiply(left, right, target, digits)` · `Subtract(...)` |
|
||
| percent | `EditorScript.Percent(amount, rate, target, digits, EditorScriptPercentMode.Add\|Subtract\|Only)` |
|
||
| sum | `EditorScript.Sum(target, "A", "B")` · `Sum(target, IEnumerable<string> fields, digits)` |
|
||
| 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)` |
|
||
|
||
Toplu üretim: `EditorScript.Build(params rules)`; tek kural `EditorScriptRule.ToScript()` ya da örtük
|
||
`string` dönüşümü; var olan script'in gövdesini başlıktaki kurallardan yeniden üretmek
|
||
`EditorScript.Rebuild(script, isBooleanField)` (wizard/MCP deploy bunu çağırır). `percent` modunda
|
||
`Only` = `null` (yalnızca tutar), `EditorScriptTrigger.Default` = grup varsayılanı.
|
||
|
||
Akıcı ekler: `.When(...)` (hepsi sağlanmalı), `.WhenAny(...)` (biri yeterli),
|
||
`.OnChange()` / `.OnOpen()` / `.OnOpenAndChange()` / `.WithTrigger(EditorScriptTrigger.X)`,
|
||
`.WithId("multiply_1")` (verilmezse tarif adı + sıra numarasından üretilir).
|
||
|
||
Koşullar: `EditorScriptCondition.Is / IsNot / Contains / IsEmpty / IsNotEmpty / GreaterThan /
|
||
LessThan / IsTrue / IsFalse / Always`.
|
||
|
||
```csharp
|
||
EditorScript = EditorScript.Build(
|
||
EditorScript.Multiply("Quantity", "UnitPrice", "Total", digits: 2),
|
||
EditorScript.Percent("Total", "VatRate", "VatAmount", 2, EditorScriptPercentMode.Add)
|
||
.When(EditorScriptCondition.IsNotEmpty("VatRate")),
|
||
EditorScript.ReadOnly("Total"),
|
||
EditorScript.Today("OrderDate").OnOpen(),
|
||
EditorScript.Copy("Customer.TaxNumber", "TaxNumber"),
|
||
EditorScript.Ask("Fiyatı değiştirmek istediğinize emin misiniz?")
|
||
.When(EditorScriptCondition.GreaterThan("UnitPrice", "1000")));
|
||
```
|
||
|
||
Tek kurallı script için `EditorScriptRule` doğrudan `string`'e dönüşür:
|
||
|
||
```csharp
|
||
EditorScript = EditorScript.Days("StartDate", "EndDate", "DayCount");
|
||
```
|
||
|
||
> **Eşleme kuralı:** `scriptRecipes.ts` ile bu sınıflar aynı çıktıyı üretmek zorundadır. Birinde
|
||
> bir tarif değişirse aynı görevde diğeri de değişir; aksi hâlde seeder ile basılan script
|
||
> dialogda "elle düzenlenmiş" sayılır ve kural editörü kapanır.
|
||
> İki tarafta aynı kurallar: `custom` yalnızca sondaki tek `;`'ü siler, `today` ofseti tam güne kırpılır,
|
||
> ek `always` koşulları başlıkta korunur.
|
||
|
||
### 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 | `"shortDate"` |
|
||
| `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` · yalnız `dxTextArea`: `autoResizeEnabled`, `minHeight`, `maxHeight` | `dxTextBox`, `dxTextArea`, `dxAutocomplete` |
|
||
| Sayı | `min`, `max`, `step`, `showSpinButtons`, `useLargeSpinButtons`, `format.type`, `format.precision`, `format.currency`, `invalidValueMessage`, `useMaskBehavior` | `dxNumberBox` (`useMaskBehavior` `dxDateBox`'ta da görünür) |
|
||
| Tarih | `type`, `pickerType`, `displayFormat`, `dateSerializationFormat`, `interval`, `min`, `max`, `openOnFieldClick`, `applyValueMode`, `invalidDateMessage`, `calendarOptions.firstDayOfWeek`, `calendarOptions.zoomLevel` | `dxDateBox`, `dxCalendar`, `dxDateRangeBox` |
|
||
| Açılır liste | `searchEnabled` (\* yalnız TagBox DTO'sunda), `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 | `width`\*, `height`\*, `uploadUrl`\*, `fileFieldName`, `accept`\*, `capture`\*, `multiple`\*, `maxFileSize`\* | `dxImageUpload`, `dxImageViewer` |
|
||
| Seçim | `text` (`dxCheckBox`), `switchedOnText`/`switchedOffText` (`dxSwitch`), `layout` (`dxRadioGroup`), `editAlphaChannel`/`keyStep` (`dxColorBox`), `count`/`allowClear` (`dxRating`) | 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; telefon token'ları `phoneGlobal`/`phoneTR`/`phoneIntl`/`phoneCountry` — §6.4.1), `encodeHtml`, `buttons` | hepsi — bu üçü **grid sütununu** da etkiler |
|
||
| Sözlük dışı | `phoneCountryField` (`phoneCountry` ile ülke alanının adı) — sözlükte yok, preset ya da "Sözlük dışı ayarlar" bölümünden yazılır | `dxTextBox` |
|
||
|
||
`\*` 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. `\*` işaretli
|
||
`showClearButton` GridBox ve TagBox DTO'sundadır, Görsel'in `width`/`height`'ı `ImageUploadOptionsDto`'dadı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%`). `displayFormat` DevExtreme'in adlı biçimidir — `shortDate` · `shortDateShortTime` · `shortTime` — aktif dilin düzeniyle çizilir; `dd/MM/yyyy` gibi sabit desen yazılmaz |
|
||
| Telefon maskesi | `mask`, `maskRules.X`, `maskInvalidMessage` (dil anahtarı, ör. `Sas.PresetPhoneMask.InvalidMessage`; liste/form aktif dile çevirir), `placeholder`, `useMaskedValue:false` — token değil sabit maskedir; `phoneGlobal` token'ının preset'i yoktur, `format` alanına elle yazılır |
|
||
| Telefon (Türkiye) · (uluslararası) · (ülkeye göre) | `{"format":"phoneTR"}` · `{"format":"phoneIntl"}` · `{"format":"phoneCountry","phoneCountryField":"Country"}` — üçü de `showClearButton:true` ekler; ayrıntı §6.4.1 |
|
||
| Otomatik büyüyen metin | `autoResizeEnabled`, `minHeight:80`, `maxHeight:320` |
|
||
| Aranabilir liste | `searchEnabled`, `searchMode:contains`, `searchTimeout`, `showClearButton` |
|
||
| TagBox anında seçim | `showSelectionControls`, `applyValueMode:instantly`, `searchEnabled`, `showClearButton` |
|
||
| GridBox tek/çok seçim | `columns`, `selectionMode`, `filterRowVisible`, `width`×`height` (tek 320×520 + `showClearButton`, çok 360×560) |
|
||
| Ç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[]` — `{name:"scanBarcode", location:"after", icon:"photo"}`; `onClick` = `"@SCAN_BARCODE"` token'ı. Yalnız `dxTextBox`. Token'ı `resolveEditorButtons` (`editingForm.ts`) kamera diyaloğuna bağlar, okunan kod alana yazılır; liste popup'ı ve form sayfası aynı. HTTPS şart. Fonksiyon metni yazılan `onClick` olayla birlikte `formData`, `fieldName`, `mode` alır (popup'ta ek olarak `rowKey`, `rowIndex`). |
|
||
| 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.4.1 Telefon biçimleri (`format` token'ı)
|
||
|
||
Telefon alanı (`dxTextBox`) biçimini `editorOptions.format` token'ından alır; maske, örnek ve liste/kart
|
||
görüntüsü `ui/src/utils/phoneFormat.ts`'te üretilir (form editörü `withEditorTypeDefaults`, kolon
|
||
`useListFormColumns` üzerinden — Grid, Tree, Card, Scheduler ve Form sayfası aynı yoldan geçer). Değer
|
||
her biçimde **yalnızca rakam** saklanır (`useMaskedValue: false`); yapılandırmada açıkça yazılan seçenek
|
||
varsayılanı ezer.
|
||
|
||
| Token | Editör | Saklanan | Listede |
|
||
| --- | --- | --- | --- |
|
||
| `phoneGlobal` | `(000) 000-0000` | 10 hane | `(555) 123-4567` |
|
||
| `phoneTR` | `(000) 000 00 00` | 10 hane | `(532) 123 45 67` |
|
||
| `phoneIntl` | `+` + en çok 15 hane | ülke kodu + numara | ülke kodu `Sas_H_Country.PhoneCode`'dan (en uzun önek) bulunur: `+49 301 234 567` |
|
||
| `phoneCountry` | formdaki `phoneCountryField` (varsayılan `Country`) alanının ülkesine göre: kod sabit önek, `PhoneNumberFormat` kalıbı, `PhoneNumberMinLength` kadar hane zorunlu, `PhoneNumberMaxLength`'e kadar isteğe bağlı; ülke seçilmemişse **Türkiye** (`+90 (000) 000-0000`) | ulusal numara (kodsuz) | satırın ülkesiyle `+49 301 234 567`; ülkesiz satır Türkiye biçiminde |
|
||
|
||
- **Ülke ve telefon alanı olan her ekran `phoneCountry` kullanır** (Kiracılar, Şubeler, Satış Siparişleri,
|
||
Kontaklar, Adaylar). Ülke alanı olmayan ekran `phoneGlobal`/`phoneTR`/`phoneIntl`'den birini seçer.
|
||
- Formda telefon alanları adres zincirinden (Ülke › Şehir › İlçe › varsa Mahalle/Posta Kodu) **sonra** gelir
|
||
(`EditGroupOrderNo` + `EditOrderNo`): kullanıcı önce ülkeyi seçer, maske ona göre açılır.
|
||
- Ülke alanı olan her ekranda yeni kayıt Türkiye ile açılır: `FormFieldsDefaultValueJson` →
|
||
`{ FieldName: "Country", FieldDbType: String, Value: "Türkiye", CustomValueType: Value }`.
|
||
- Ülke alanı değişince maske yenilenir: Card/Scheduler/Form sayfasında formun `fieldDataChanged` olayıyla,
|
||
Grid/Tree'de `onEditorPreparing`'deki (`prepareEditingEditor`) `attachFieldValueBroadcast` olayıyla (DataGrid formu `fieldDataChanged`
|
||
yayınlamaz; `editorOptions.onValueChanged` da kullanılamaz — editör fabrikası onu kendi değer yazan
|
||
işleyicisinin üstüne birleştirir). Satır içi düzenlemede (form yok) maske satırın ülkesiyle açılır, değişikliği izlemez.
|
||
- `phoneCountry` ekranında sabit uzunluk `pattern`'ı (`^[0-9]{10}$`) yazılmaz; uzunluğu maske denetler,
|
||
gerekirse `^[0-9]{4,15}$`.
|
||
- Ülke listesi `GET /api/app/public/country`'den bir kez yüklenir ve önbellekte kalır.
|
||
- Dinamik raporlar (Grid/Tree/Form) aynı biçimi sunucuda `DynamicReportPhoneFormatter` ile basar; `phoneCountry`
|
||
kolonu satırın ülke kolonunu biçim ifadesine beşinci operand olarak verir. Kural `phoneFormat.ts` ile birebir aynıdır.
|
||
- Listede `phoneGlobal`/`phoneTR` değeri 10 haneden kısaysa **ham** gösterilir, uzunsa son 10 hane alınır.
|
||
Bilinmeyen `format` değeri `phoneGlobal` gibi davranır. `phoneIntl` örneği `+90 532 123 45 67`'dir.
|
||
|
||
### 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()` | `{"format":"#0.##'%'","useMaskBehavior":true,"showSpinButtons":true}` |
|
||
| Tarih / tarih-saat / saat | `EditorOptions.Date()` · `DateTime()` · `Time(15)` | UI preset'leriyle (`date` · `dateTime` · `timePicker`) birebir aynı: `type` + `displayFormat` (`shortDate` · `shortDateShortTime` · `shortTime`, DevExtreme adlı biçim — sabit desen yok) + `dateSerializationFormat`; saatte ayrıca `pickerType:list`, `interval`, `width:100%` |
|
||
| Telefon | `EditorOptions.Phone()` | `format:"phoneGlobal"` + `mask` + `maskInvalidMessage` + `useMaskedValue:false` + `maskRules` + `placeholder` |
|
||
| Telefon (ülkeye göre) | `EditorOptions.PhoneByCountry("Country")` | `{"format":"phoneCountry","phoneCountryField":"Country","showClearButton":true}` |
|
||
| Kaydırıcı | `EditorOptions.Slider()` | `{"tooltip":{"enabled":true}}` |
|
||
| Görsel yükleme | `EditorOptions.ImageUpload(multiple: true, 80, 80)` | `width`/`height`/`multiple` (UI preset'indeki `accept:"image/*"` yok — gerekiyorsa `.Text("accept","image/*")`) |
|
||
| 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; varsayılan `minHeight` 200). `elementAttr`'ı tamamen yazar — aynı JSON'daki `elementAttr.class` kaybolur |
|
||
|
||
Akıcı ekler: `.Placeholder()`, `.MaxLength()`, `.Height(int\|string)`, `.Width(int\|string)`,
|
||
`.Format()`, `.FixedPointFormat(p)`, `.DisplayFormat()`, `.DateSerializationFormat()`,
|
||
`.Mask(mask, invalidMessage, useMaskedValue)`, `.ShowSpinButtons()`, `.UseMaskBehavior()`,
|
||
`.Disabled()`, `.ReadOnly()`, `.ShowClearButton()` ve tip bazlı genel yazıcılar:
|
||
`.Text(key, value)`, `.Number(key, value)`, `.Flag(key, value)`, `.Raw(key, json)`.
|
||
|
||
```csharp
|
||
EditorOptions = EditorOptions.Number(4).ShowClearButton(),
|
||
EditorOptions = EditorOptions.Multiline(60).Placeholder("Açıklama"),
|
||
EditorOptions = EditorOptions.New().Flag("acceptCustomValue", true),
|
||
EditorOptions = EditorOptions.New().Raw("columns", "[\"key\",\"name\"]").Text("selectionMode", "single"),
|
||
```
|
||
|
||
Kurallar:
|
||
|
||
- Aynı anahtar iki kez verilirse sonuncusu geçerlidir; anahtar sırası korunur.
|
||
- `.Raw` değeri **geçerli JSON** olmalıdır; doğrulanmaz.
|
||
- Hiç seçenek yoksa `Build()` boş metin döner — alan `null` kalır, `{}` yazılmaz.
|
||
- C# karşılığı olmayan UI preset'leri (para, sağa hizalı, `phoneTR`/`phoneIntl`, otomatik büyüyen metin,
|
||
aranabilir liste, TagBox anında seçim, GridBox tek/çok, kamerayla foto, barkod butonu) genel
|
||
yazıcılarla kurulur: `.Text("format","phoneTR")`, `.Raw("buttons", "[…]")`.
|
||
|
||
### 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/data/Sas.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.
|
||
|
||
```jsonc
|
||
{
|
||
"ListFormCode": "Sas.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('{Modül}.…')` kullan ve dil anahtarını wizard
|
||
dosyasında ya da `data/Sas.Languages.LanguageText/{CultureName}.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('{Modül}.…')` 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`)
|
||
|
||
```ts
|
||
DesignerDocument = {
|
||
version: 1
|
||
sourceMode: 'visual' | 'code' // 'code' tek yönlüdür, kanvasa dönülmez
|
||
nodes: DesignerNode[]
|
||
canvas: { width: 'responsive' | 'desktop' | 'tablet' | 'mobile' }
|
||
lifecycle: { onMount: string } // sayfa açılışında çalışan script (§7.8)
|
||
dataSources: DesignerDataSource[]
|
||
permissionCode?: string // Wizard'ın bu bileşen için ürettiği okuma yetkisi
|
||
}
|
||
|
||
DesignerNode = {
|
||
id: string // 'cmp_…' — opak
|
||
type: string // 'Form', 'Grid', 'Input', 'ListView', 'FlexRow' …
|
||
kind: 'html' | 'ui' | 'layout' | 'platform' | 'custom'
|
||
slot?: string
|
||
ref?: string // 'btnSave' — script'ler birbirine bununla erişir
|
||
props: Record<string, unknown>
|
||
events: Record<string, string> // olay adı → script (§7.8)
|
||
bindings: Record<string, DesignerBinding>
|
||
children: DesignerNode[]
|
||
}
|
||
|
||
DesignerBinding = {
|
||
sourceId: string // 'source_…' (data source) ya da 'cmp_…' (kapsayıcı Form kaydı)
|
||
path: string // okunacak kolon
|
||
labelPath?: string
|
||
valuePath?: string
|
||
columns?: string[] // ekranda görünmeyen ek kolonlar; refs.<ad>.getColumn('x') ile okunur
|
||
}
|
||
|
||
DesignerDataSource = {
|
||
id: string
|
||
name: string
|
||
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||
url: string
|
||
responsePath: string // yanıt içindeki liste yolu; boşsa yanıtın kendisi
|
||
filters?: DesignerDataSourceFilter[]
|
||
}
|
||
|
||
DesignerDataSourceFilter = {
|
||
id: string
|
||
field: string
|
||
operator: 'eq'|'ne'|'contains'|'startswith'|'endswith'|'gt'|'gte'|'lt'|'lte'|'in'|'isnull'|'notnull'
|
||
source: 'static' | 'query' | 'route' | 'record'
|
||
value: string // static → değer · query/route → URL parametre adı · record → <formRef>.<kolon>
|
||
previewValue?: string // yalnızca tasarımcı önizlemesi; üretilen koda girmez
|
||
required?: boolean // true ise değer yoksa istek hiç atılmaz
|
||
}
|
||
```
|
||
|
||
Filtre → query string sözleşmesi: `eq` çıplak parametredir (`?RoleId=5`), diğerleri adını son ek
|
||
olarak taşır (`?Name.contains=abc`). Bu, CrudEndpoint `GetList`'in okuduğu sözleşmedir.
|
||
|
||
> `required: true` varsayılandır ve bilinçlidir: değeri henüz gelmemiş bir filtre isteği
|
||
> **tutmalıdır**; filtresiz koleksiyonu yüklemek "filtre çalışmıyor" gibi okunur.
|
||
|
||
### 7.3 Toolbox
|
||
|
||
| Aile (`toolboxGroup`) | Bileşenler |
|
||
| --- | --- |
|
||
| `layout` | `PageContainer` (maxWidth, padding, gap, className), `FlexRow` (columns, firstColumnWidth, gap, wrap, align, className), `Table` |
|
||
| `data` | `Form` (dört CRUD endpoint'ini sahiplenen kapsayıcı) + veri bağlanabilen bileşenler: `Select`, `AutoComplete`, `Dropdown`, `Menu`, `Pagination`, `Radio.Group`, `Tabs`, `Grid` |
|
||
| `platform` | `ListView`, `DataGridView`, `TreeView`, `GanttView`, `TodoBoard`, `CardView`, `SchedulerView`, `PivotView`, `ChartView` — her biri `listFormCode` ile bir ListForm ekranını gömer. 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 Core'dur, komutla eklenir; yeni bileşen yazılmaz.** İstenen komponent
|
||
> Developer Kit araçlarıyla (toolbox + konfigürasyon) büyük ölçüde kuruluyor ama bir bileşende
|
||
> olay, prop ya da küçük bir yetenek eksikse: komponent hazır araçlarla kurulabildiği kadar
|
||
> **üretilir**, eksik parça "yapılamaz" denip sessizce atlanmaz, son mesajda "Core gerektiren
|
||
> kısım" notuyla bildirilir (§0.8). Kullanıcı **"core sisteme ekle"** derse çözüm yine yeni bir
|
||
> bileşen / aile / Developer Kit ekranı değildir; **var olan bileşene küçük ekleme** yapılır:
|
||
> eksik bir **olay** (`onBlur`, `onKeyDown`, `onDoubleClick`…), eksik bir **prop** (`maxLength`,
|
||
> `prefix`, `readOnly`, `variant` değeri, `size`…) ya da var olan davranışı bozmadan eklenen
|
||
> **küçük bir yetenek** (ikon slotu, ek görünüm seçeneği, `ref` API'sine yeni metot). Önce ilk
|
||
> satıra bak: olay/prop bileşende zaten varsa iş yalnızca panelde göstermektir — o da `ui/`
|
||
> değişikliğidir ve komut ister. Komutla çalışan 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) komut o ekleme için
|
||
> açıkça verilmiş olmalıdır (§0.8).
|
||
>
|
||
> İhtiyaç görsel davranış değil de veri/aksiyonsa Custom Endpoint (§9.2), iş mantığıysa
|
||
> Dynamic Service (§9.3, §10.6) her zaman açıktır.
|
||
>
|
||
> Keşif turu tarifinde (§0.6.6) hangi bileşene ne ekleneceği açıkça yazılır ve tarifle
|
||
> birlikte onaylatılır; bu **çerçeve içi** bir genişletmedir (§0.8).
|
||
|
||
### 7.3.1 Çalışan minimal doküman — bir Form + gömülü liste
|
||
|
||
Aşağıdaki `Props` değeri (JSON string olarak yazılır) tek başına çalışır: üstte bir CRUD formu,
|
||
altında var olan bir ListForm ekranı.
|
||
|
||
```jsonc
|
||
{
|
||
"visualDesigner": {
|
||
"version": 1,
|
||
"sourceMode": "visual",
|
||
"canvas": { "width": "responsive" },
|
||
"lifecycle": { "onMount": "" },
|
||
"permissionCode": "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": "::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": "Hr.AdvanceRequests",
|
||
"height": "520px",
|
||
"className": "mt-4",
|
||
"designerPermission": "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 `::{Modül}.…` biçiminde yaz (`"title": "::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: `Sas.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:
|
||
|
||
```jsonc
|
||
{
|
||
"Wizard": {
|
||
"ComponentKind": 1,
|
||
"WizardName": "OrderBoard",
|
||
"ListFormCode": "Sas.Mrp.OrderBoard",
|
||
"MenuCode": "Sas.Mrp.OrderBoard",
|
||
"CustomComponentName": "OrderBoard",
|
||
"MenuUrl": "/admin/order-board",
|
||
"CreateMenu": true,
|
||
"MenuParentCode": "Sas.Mrp",
|
||
"PermissionGroupName": "Sas.Mrp",
|
||
"PermissionGroupEn": "MRP", "PermissionGroupTr": "MRP",
|
||
"MenuEn": "Order Board", "MenuTr": "Sipariş Panosu",
|
||
"MenuIcon": "FcKanban", "MenuOrder": 2
|
||
},
|
||
"InsertedRecords": {
|
||
"LanguageKeys": ["Sas.Mrp.OrderBoard"],
|
||
"PermissionGroupNames": [],
|
||
"PermissionNames": ["Sas.Mrp.OrderBoard", "Sas.Mrp.OrderBoard.Create",
|
||
"Sas.Mrp.OrderBoard.Update", "Sas.Mrp.OrderBoard.Delete"],
|
||
"MenuCodes": ["Sas.Mrp.OrderBoard"], "DataSourceCodes": []
|
||
}
|
||
}
|
||
```
|
||
|
||
Yukarıdaki düz cevap deploy girdisidir (§4.4); dosyada `Menu`/`Language` başlıklarına toplanır,
|
||
ekrana ait anahtarlar (`Data`, `Details`, `Layout`, bloklar, `Groups`) hiç yazılmaz. Custom yolunda
|
||
ListForm kaydı **ü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/Sas.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ü (`Sas.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/Sas.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; seed kökündeki script klasörleri (`host/`
|
||
alt klasörleri dahil) 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` bölümü ya da `Data` başlığı, 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` bölümü ya da List yolunda
|
||
`Data` başlığı (`SelectCommand`/`KeyFieldName`) 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: `Sas.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('{Modül}.…')` 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 `Sas.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/sql/object/{TabloAdı}.sql` altına; PostgreSQL eşi
|
||
aynı script'ten `PostgreSqlTableScript` ile üretilip `postgres/object/{TabloAdı}.sql` altına yazılır. 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, `isNullable: false`, `defaultValue: "1"`) |
|
||
| Liste | Görünür kolon, `dxCheckBox` |
|
||
| Form | `dxCheckBox`, yeni kayıt `true` ile açılır: `FormFieldsDefaultValueJson`'da `{ "FieldName": "IsActive", "FieldDbType": 3, "Value": "true", "CustomValueType": 1 }` (istemciye `column.defaultValue` olarak iner); deploy bu satırı tablonun `DEFAULT 1`'inden kendisi yazar (§4.3.5.1). `InsertFieldsDefaultValueJson`'a yazılmaz — orası her insert'te değeri ezer, kullanıcı pasif kayıt açamaz |
|
||
| Widget | Toplam · Aktif · Pasif kartları (§4.6.1 madde 2) |
|
||
| Demo veri | Satırlar `IsActive: true` ile yazılır |
|
||
| Dil | `Sas.Listform.ListformField.IsActive` (var olan anahtar) |
|
||
|
||
**Tanım olmayan tabloda `IsActive` yoktur.** İşlem/belge tablosu ve onun alt formları (satır,
|
||
malzeme, süre, duruş, not listeleri) aktif/pasif bayrağı taşımaz; kayıt silinir ya da durumla
|
||
kapanır. Yalnızca talep ya da görsel aktif/pasif bayrağını açıkça istiyorsa eklenir ve o zaman Tablo
|
||
ve Form satırları yukarıdaki gibi yazılır; `NULL` kabul eden ya da varsayılansız `IsActive` kolonu
|
||
üretilmez.
|
||
|
||
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).
|
||
|
||
**İşlem tablosu → `BranchId` otomatik.** Tanım olmayan her yeni tablo (sipariş, talep, proje, hareket,
|
||
günlük gibi işlem/belge kayıtları) `TenantId` nasıl sorulmadan geliyorsa şubeyi de öyle taşır. Karar
|
||
yukarıdaki tanım kararının tersidir; ayrı sorulmaz.
|
||
|
||
| Katman | `BranchId` |
|
||
| --- | --- |
|
||
| Tablo | `[BranchId] uniqueidentifier NOT NULL` (PG `"BranchId" uuid NOT NULL`), `TenantId`'den hemen sonra |
|
||
| Ekran | `IsBranch: true` — liste yalnızca kullanıcıya atanmış şubelerin (`Sas_T_BranchUsers`) satırlarını getirir (`lowcode-reference.instructions.md` — Örtük filtreler) |
|
||
| Alan | Şube (`Sas.Listform.ListformField.BranchId`, var olan anahtar), `dxSelectBox`, zorunlu; lookup **standarttır ve her ekranda aynıdır** — kullanıcıya atanmış aktif şubeler: `SELECT b."Id" AS "Key", b."Name" AS "Name" FROM "Sas_T_Branch" b INNER JOIN "Sas_T_BranchUsers" bu ON bu."BranchId" = b."Id" AND bu."UserId" = '@USERID' WHERE b."IsDeleted" = 'false' AND b."IsActive" = 'true' AND b."TenantId" = '@TENANTID' ORDER BY b."Name"` (C# seeder'da `LookupQueryValues.UserBranchValues`). Şube seçilince depo/konum da gelecekse aynı sorguya `bu."DefaultWarehouseId", bu."DefaultLocationId"` kolonları eklenir, sorgu başka türlü değiştirilmez |
|
||
| Form varsayılanı | `FormFieldsDefaultValueJson`'da `CustomValueType: 3` (`DbQuery`): **lookup'ın ilk seçeneği** — aynı join, `SELECT b."Id" … ORDER BY b."Name"` (ilk satır; C# seeder'da `LookupQueryValues.UserDefaultBranchValue`). Departman şubesi, sabit şube ya da başka bir sorgu yazılmaz; depo/konum varsayılanı da aynı join'den `bu."DefaultWarehouseId"` / `bu."DefaultLocationId"` ile okunur. Form ekleme kapalı ekranda (`AllowAdding: false`, salt okunur şube) varsayılan yazılmaz (örnek: `Prc.PurchaseOrder`, `Prj.Project`) |
|
||
| View / rapor | Tablodan okuyan view `BranchId`'yi taşır (PG `CREATE OR REPLACE VIEW` kolon ekleyemediği için listenin **sonuna**); ekran `IsBranch: true`, Şube kolonu salt okunur lookup (`Sas_T_Branch`) |
|
||
| Ana kayda bağlı tablo (satır, alt form, kendi menüsü olan iş paketi/günce gibi) | Aynı kolonu taşır; form **cascade** ile kurulur: Şube alanı ana kayıttan önce gelir (standart şube lookup'ı + ilk şube `DbQuery` varsayılanı, `CascadeEmptyFields` = ana kayıt alanı), ana kayıt lookup'ı şubeye göre süzülür (`CascadeParentFields: BranchId`, sorguda `(p.BranchId = @param0 OR @param0 IS NULL)`); ana ekranın `SubFormsDto` ilişkisine `BranchId → BranchId` satırı eklenir (sekmeden açılan yeni kayıt şubeyi oradan alır). Ekran yine `IsBranch: true`; ana kaydın şubesini `DefaultFilter` alt sorgusuyla süzme (örnek: `Prj.WorkPackage`, `Prj.Diary`) |
|
||
| Widget | Widget SQL'i `IsBranch` filtresinden geçmez; şube kırılımı gerekiyorsa sorguya yazılır |
|
||
|
||
Kullanıcı "şube yok" derse kolon ve `IsBranch` çıkarılır. Var olan işlem tablosuna kolon eklemek
|
||
şema değişikliğidir; istekle yapılır (yama betiği yazılmaz, kolon `CREATE` içine konur).
|
||
|
||
**Şube alanı sözleşmesi `IsBranch`'e bağlı değildir.** `BranchId` kolonu taşıyan her ekran — tanım
|
||
ekranları (depo, konum, departman, pozisyon, ilan) ve `IsBranch` olmayan listeler dahil — Alan ve Form
|
||
varsayılanı satırlarındaki aynı lookup'ı ve aynı varsayılanı kullanır; ekrana özel şube sorgusu
|
||
yazılmaz. Kullanıcı hiçbir şubeye atanmamışsa seçenek ve varsayılan boş kalır; çözüm ekranda değil
|
||
Kullanıcılar › Yetkiler › Şube Yönetimi'ndedir.
|
||
|
||
### 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/{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ı | **Her nesne iki dosyadır**: `sql/…/X.sql` (SQL Server) ve `postgres/…/X.sql` (PostgreSQL), aynı göreli yol ve ad. Biri yazılıp/değiştirilip diğeri bırakılmaz (§8.3.1). |
|
||
| İdempotency | SQL Server: `CREATE OR ALTER VIEW/PROCEDURE/FUNCTION`, tablolar için `IF NOT EXISTS` koruması. PostgreSQL: `CREATE OR REPLACE VIEW/FUNCTION/PROCEDURE`, `CREATE TABLE IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, constraint için `pg_constraint` kontrollü `DO $$` bloğu. |
|
||
| Şema | SQL Server'da `dbo`; PostgreSQL'de `public` (EF tabloları oradadır, `dbo` şeması yoktur). PG'de tanımlayıcılar çift tırnaklıdır (`public."Hr_T_AdvanceRequest"`, `"TenantId"`) çünkü adlar PascalCase'tir. |
|
||
| Toplu ayraç | SQL Server'da batch'ler `GO` ile ayrılır. PostgreSQL dosyasında `GO` gerekmez; dosya tek komut metni olarak da çalışır. |
|
||
| Yorum | Dosyanın ilk satırında ne ürettiğini yaz (`-- Create View`). |
|
||
|
||
Tablo yazarken §8.1'deki varsayılan kolonlar dahil edilir. Örnek iskelet (SQL Server):
|
||
|
||
```sql
|
||
-- Create Table
|
||
IF OBJECT_ID(N'[dbo].[Hr_T_AdvanceRequest]', N'U') IS NULL
|
||
BEGIN
|
||
CREATE TABLE [dbo].[Hr_T_AdvanceRequest] (
|
||
[Id] UNIQUEIDENTIFIER NOT NULL CONSTRAINT [PK_Hr_T_AdvanceRequest] PRIMARY KEY,
|
||
[TenantId] UNIQUEIDENTIFIER NULL,
|
||
-- iş kolonları buraya
|
||
[CreationTime] DATETIME2 NOT NULL,
|
||
[CreatorId] UNIQUEIDENTIFIER NULL,
|
||
[LastModificationTime] DATETIME2 NULL,
|
||
[LastModifierId] UNIQUEIDENTIFIER NULL,
|
||
[IsDeleted] BIT NOT NULL CONSTRAINT [DF_Hr_T_AdvanceRequest_IsDeleted] DEFAULT (0),
|
||
[DeletionTime] DATETIME2 NULL,
|
||
[DeleterId] UNIQUEIDENTIFIER NULL
|
||
);
|
||
END
|
||
```
|
||
|
||
> `Id` tipini ne seçtiysen wizard dosyasındaki `KeyFieldDbSourceType` ile aynı olmalıdır
|
||
> (`UNIQUEIDENTIFIER` → `9`, `INT IDENTITY` → `11`). Uyumsuzluk sessiz veri hatası üretir.
|
||
|
||
#### 8.3.1 PostgreSQL eşi
|
||
|
||
Uygulama iki sağlayıcıda da çalışır; `sql/` altına yazılan ya da değiştirilen her dosyanın
|
||
`postgres/` eşi **aynı işte** yazılır (CLAUDE.md standing default 16). Eş, aynı nesnenin aynı
|
||
davranışla PG diyalektidir: aynı satırları yazar, aynı doğrulamayı yapar, aynı (Türkçe) mesajı verir.
|
||
|
||
| Konu | Kural |
|
||
| --- | --- |
|
||
| Rutin dili | Fonksiyon ve prosedürler `LANGUAGE plpgsql`. Fonksiyonlar (`_F_`) view'lardan önce, tablolardan sonra kurulur; aynı türde sıra alfabetiktir. `plpgsql` gövdesi kurulumda doğrulanmaz, `LANGUAGE sql` doğrulanır — gövdesindeki nesne o anda yoksa düşer. |
|
||
| Rutin türü | Skaler/tablo fonksiyonu → `FUNCTION … RETURNS <tip>` / `RETURNS TABLE(…)`. Sonuç kümesi döndürmeyen prosedür → `PROCEDURE` (çağrı `CALL`). Sonunda satır döndüren T-SQL prosedürü → `FUNCTION … RETURNS TABLE` (PG prosedürü sonuç kümesi döndüremez). `execute/` dosyası her zaman `PROCEDURE`'dür (`FinalSeeder` yalnızca `prokind = 'p'` çağırır). Zamanlanmış işin `BeforeSp`'si (`SpRepository`) PG'de katalogdan türe bakar: prosedür `CALL`, fonksiyon `SELECT * FROM` ile, ad tırnaklı `public."Ad"` olarak çağrılır. |
|
||
| Parametre | Ad ve **sıra** T-SQL ile aynı (çağıranlar sırayla geçer); `@Ad` → `p_Ad`, yerel değişken `v_Ad` — kolon adlarıyla belirsizlik olmasın diye. |
|
||
| Transaction | `BEGIN TRAN/COMMIT`, `SET NOCOUNT/XACT_ABORT` yazılmaz; çağıranın transaction'ı atomikliği sağlar. |
|
||
| Hata | `THROW 51100, N'Mesaj', 1` → `RAISE EXCEPTION 'Mesaj' USING ERRCODE = 'P0001', DETAIL = '51100'` (mesajdaki `%` → `%%`). |
|
||
| Tipler | `uniqueidentifier`→`uuid`, `nvarchar(n)`→`varchar(n)`, `nvarchar(MAX)`→`text`, `bit`→`boolean`, `datetime2`→`timestamp`, `decimal`→`numeric`, `int`→`integer`, `tinyint`→`smallint`, `float`→`double precision`. |
|
||
| Varsayılan | `NEWID()`→`gen_random_uuid()`, `GETDATE()`→`LOCALTIMESTAMP`, `GETUTCDATE()`→`(NOW() AT TIME ZONE 'UTC')`, bit `0/1`→`FALSE/TRUE`. |
|
||
| Boolean | `bit` kolon karşılaştırması `= 0/1` değil `= FALSE/TRUE` (check ve index filtresi dahil). |
|
||
| İfadeler | `ISNULL`→`COALESCE`, `TOP n`→`LIMIT n`, `+` (metin)→`\|\|`, `OUTER/CROSS APPLY`→`LEFT/CROSS JOIN LATERAL`, `DATEADD/DATEDIFF`→interval/tarih farkı, `CHARINDEX`→`strpos`, `STRING_SPLIT`→`string_to_table`, `@@ROWCOUNT`→`GET DIAGNOSTICS`, tablo değişkeni/`#temp`→`CREATE TEMP TABLE … ON COMMIT DROP`, `MERGE`→`INSERT … ON CONFLICT`. |
|
||
| Ad uzunluğu | PG tanımlayıcısı en fazla 63 karakterdir; uzun constraint/index adı sessizce kısalır ve varlık kontrolü bir dahaki migrate'te tutmaz. Adı 63 karakterin altında tut. |
|
||
| Büyük/küçük harf | SQL Server karşılaştırması harf duyarsızdır, PG duyarlıdır; kullanıcı girdisi/ad karşılaştırmasında `lower()`/`ILIKE` kullan. |
|
||
|
||
Doğrulama: PG eşi bir PostgreSQL veritabanında **iki kez** çalıştırılır (idempotent) ve her rutin
|
||
gerçekçi veriyle en az bir kez çağrılır — `plpgsql` tip/belirsiz kolon hatalarını yalnızca çalışırken verir.
|
||
|
||
### 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
|
||
`sql/`, PostgreSQL ise `postgres/`. Aynı anda ikisi birden çalışmaz ama iki dosya da her
|
||
zaman yazılır (§8.3.1); yazılmayan eş, o sağlayıcıdaki kurulumu ilk migrate'te kırar.
|
||
2. Sıra: `object/` → `execute/`; her klasörün içinde önce tablolar (`{Modül}_T_`, `_H_`), sonra fonksiyonlar
|
||
(`_F_`), en son view/yordam/tetikleyici; aynı türde **dosya adına göre alfabetik** (`SqlDataSeeder.GetObjectKindRank`).
|
||
Modül önekleri bağımlılık sırasında değildir (`Prc_V_*` view'ı `Stk_T_*` tablosunu okur), tür sırası bunu karşılar.
|
||
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.
|
||
- 2. 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) ve `postgres/object/` altına diyalekt eşini yaz (§8.3.1).
|
||
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` (soft delete / audit kolonlarını deploy tablodan okur, cevaba yazılmaz). 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.
|
||
|
||
### 8.6 Ölü artefakt taraması (her işin sonunda)
|
||
|
||
Yeni bir nesne eskisinin işini devraldığında eskisi **aynı işte** silinir; "sonra temizlenir" yoktur.
|
||
Bir artefakt şu kaynaklardan biri onu kullanıyorsa yaşar, hiçbiri kullanmıyorsa silinir:
|
||
|
||
| Artefakt | Kullanılıyor sayılır, eğer… |
|
||
| --- | --- |
|
||
| SQL nesnesi (`{sql,postgres}/{object,execute,host}/X.sql`: tablo, view, yordam, fonksiyon) | adı bir wizard'ın `Wizard` bölümünde (`Data.SelectCommand`, `Insert`/`Update`/`Delete` komutları, öğe `LookupQuery`'si, `Widgets`, `CommandColumns`), `data/` ya da `crud/` dosyasında, `api/`/`ui/` kodunda geçiyor; `TableNameResolver` ile çözülüyor (`nameof(TableNameEnum.X)` + önek); ya da **kullanılan** başka bir SQL nesnesinin gövdesinden çağrılıyor (zincir izlenir). `execute/` scriptleri migrate'te her zaman çalıştığı için köktür |
|
||
| Wizard (`wizard/*.json`) | kendi menüsü var (`CreateMenu` varsayılan) ya da başka bir wizard'ın `SubForms`/komut/URL'si, kod veya veri onu gösteriyor |
|
||
| `data/{Kod}.json` / `data/{Kod}/` | `{Kod}` bir wizard'ın ya da `ListFormSeeder_*.cs`'in kurduğu ekran (`listFormName`), ya da platform satır dosyası (menü, yetki, dil, ABP yetki ekranları) |
|
||
| Dil anahtarı | bir ekran öğesi (alan başlığı, buton, form grubu, widget, menü, yetki) ya da kod/veri onu gösteriyor |
|
||
|
||
Sayılmayanlar: yorum satırları (`--`, `//`), wizard'ın `Language` bölümündeki açıklama metni,
|
||
`InsertedRecords` sahiplik listesi, modül dokümanı ve geliştirme talebi kayıtları (`data/Sas.DevRequests/`, açıklama/analiz metni) — bunlarda geçen ad nesneyi yaşatmaz.
|
||
|
||
**Dil anahtarı dikkat:** kodun çalışma anında birleştirdiği anahtarlar metin aramasında referanssız görünür
|
||
(`Sas.Listform.ListformField.${field}` — profil sekmeleri, `Sas.Mcp.Error.*` hata kodları, enum adından
|
||
türeyen `Sas.ListForm.Wizard*`, ABP'nin `Abp.Identity.*` mesajları). Bu ailelerden anahtar silinmeden
|
||
önce birleştiren kod okunur; toplu silme yapılmaz.
|
||
|
||
**Silme yolu:**
|
||
- SQL nesnesi: `sql/` ve `postgres/` dosyası birlikte silinir. Dosyayı silmek kurulu veritabanından
|
||
nesneyi düşürmez; kurulu ortam için seed dışında tek seferlik `DROP VIEW/PROCEDURE/FUNCTION` betiği
|
||
verilir. Tablo düşürülmez (şema kuralı: hiçbir tablo `DROP` edilmez) — yalnız scripti kalkar, veri kararını kullanıcı verir.
|
||
- Wizard: Wizard Manager ya da MCP `delete_wizard` (dosyayı elle silmek kayıtları temizlemez, §4.5);
|
||
elle silinen alt formun kayıtları üst wizard'ın `Pending.Replaced`'ına yazılır.
|
||
- Kurulu ekranın sahip olduğu dil anahtarı: wizard'ın `Language.Texts` ve `InsertedRecords.LanguageKeys`
|
||
listesinden çıkarılır, `Pending.Replaced.LanguageKeys`'e yazılır (migrate veritabanı satırını siler),
|
||
EN + TR satırı ve diğer dillerin satırları `data/Sas.Languages.LanguageText/{CultureName}.json`'dan silinir.
|
||
|
||
Tarama sonunda iki sağlayıcının nesne listesi aynı olmalı (her `sql/` dosyasının `postgres/` eşi var)
|
||
ve kurulu veritabanında seed dosyası olmayan yordam/view/fonksiyon kalmamalıdı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/crud/{EntityName}.json` · Okuyan: `CrudEndpointSeedApplier`
|
||
(migrate'te `CrudDataSeeder`, import kapanışında `CompleteImport` üzerinden — kural tek yerdedir)
|
||
|
||
Çalışma zamanı sınırı (`CrudEndpointAppService` + `DynamicEntityManager`): uçlar **giriş ister**
|
||
(anonim/public sayfadan çağrılamaz). Tabloda
|
||
`TenantId` varsa okuma, güncelleme ve silme isteği yapanın kiracısıyla sınırlıdır (host için
|
||
`TenantId IS NULL`), ekleme `TenantId`'yi sunucudan yazar; istemcinin gönderdiği `Id`/`TenantId` yok sayılır.
|
||
`PasswordHash` ve `SecurityStamp` yanıtta dönmez ve yazılamaz.
|
||
|
||
```jsonc
|
||
{
|
||
"EntityName": "Mrp_T_Order",
|
||
"GeneratedAt": "2026-08-20T06:13:28Z",
|
||
"Endpoints": [
|
||
{ "Method": "GET", "Path": "/api/app/crudendpoint/Mrp_T_Order", "OperationType": "GetList", "IsActive": true },
|
||
{ "Method": "GET", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "GetById", "IsActive": true },
|
||
{ "Method": "POST", "Path": "/api/app/crudendpoint/Mrp_T_Order", "OperationType": "Create", "IsActive": true },
|
||
{ "Method": "PUT", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "Update", "IsActive": true },
|
||
{ "Method": "DELETE", "Path": "/api/app/crudendpoint/Mrp_T_Order/{id}", "OperationType": "Delete", "IsActive": true }
|
||
]
|
||
}
|
||
```
|
||
|
||
Üretilen C# kodu dosyada tutulmaz; entity adı ve operasyondan yeniden türetilir.
|
||
|
||
**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: `Sas.SqlQueryManager.CrudEndpoints`. İzin yoksa butonlar görünmez ve
|
||
`crud-endpoint-generate` uçlarının tamamı reddedilir.
|
||
- Dispatcher üzerinden çağrı kapıları: `Sas.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/Sas.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ı:** `Sas.DeveloperKit.CustomEndpoints.Get/.Post/.Put/.Remove` — kullanıcıda bu
|
||
yetki yoksa istek endpoint'e hiç ulaşmaz.
|
||
2. **Eşleme:** istek yolu URL-decode edilir, `/api/app/custom-endpoint` öneki atılır, başına ve
|
||
sonuna `/` garanti edilir; kayıt `path.StartsWith(Url) && Method == verb` ile bulunur.
|
||
Bu bir **önek eşlemesidir** — `Url: "/orders"` hem `/orders/` hem `/orders/123/` isteklerini
|
||
yakalar; ilk eşleşen kayıt kazanır. Çakışmayan, ayırt edici yollar seç.
|
||
3. **Endpoint yetkisi:** `PermissionsJson` kurallarından **en az biri** tutmalıdır:
|
||
|
||
| `ResourceType` | `ResourceId` | Kim geçer |
|
||
| --- | --- | --- |
|
||
| `Global` | — | Verb kapısını geçen herkes |
|
||
| `Role` | rol **adı** | O roldeki kullanıcılar |
|
||
| `User` | kullanıcı **adı** (`UserName`, id değil) | O kullanıcı |
|
||
|
||
> **Boş `PermissionsJson` = kimse çağıramaz (401).** Endpoint'i açmak bilinçli bir karardır;
|
||
> en azından bir `Role` kuralı yaz, herkese açıksa `Global` yaz.
|
||
|
||
4. **Parametre çözümü** (sırayla; hepsi tek `param` sözlüğünde toplanır ve SQL'e **adıyla**
|
||
bağlanır):
|
||
|
||
| `Type` | Değer nereden | Kurallar |
|
||
| --- | --- | --- |
|
||
| `Static` | `DefaultValue` | Token'lar çözülür (aşağıda) |
|
||
| `Query` | `?name=…` | Yoksa: `IsRequired` → hata; değilse `DefaultValue` (token'lı) |
|
||
| `Path` | URL segmenti | `Path` alanı bir **desen**dir: `/orders/:id/` — `:name` hangi segmentteyse gerçek yoldan aynı sıradaki segment okunur |
|
||
| `Body` | JSON gövde ya da form (yalnız POST/PUT) | Yalnızca **kök seviye** özellikler okunur, ad eşleşmesi büyük/küçük harf duyarsızdır. `Path` alanı Body'de **kullanılmaz** — iç içe yol çözülmez, gövdeyi düz tut |
|
||
|
||
`Static`/varsayılan değerlerde çözülen token'lar (`DefaultValueHelper`): `@USERID`,
|
||
`@USERNAME`, `@ROLES`, `@DATE`, `@NOW`, `@DAY`, `@MONTH`, `@YEAR`, `@TENANTID`
|
||
(host bağlamında `= '@TENANTID'` deseni `IS NULL`'a çevrilir).
|
||
|
||
5. **Çalıştırma:** sorgu seçilen bağlantıda `QueryAsync(Sql, conn, param)` ile koşar; sonuç
|
||
satır dizisi olarak JSON döner. Hata → 500 + `{ Message, CorrelationId }` (iç hata mesajı
|
||
kullanıcıya sızdırılmaz). Sorgu, dönüşsüz bir `UPDATE`/`EXEC` de olabilir — `Method`'u
|
||
anlamına uygun seç.
|
||
|
||
```jsonc
|
||
// ParametersJson
|
||
[
|
||
{ "Type": "Query", "Name": "customerId", "DefaultValue": "", "Path": "", "IsRequired": true },
|
||
{ "Type": "Path", "Name": "id", "DefaultValue": "", "Path": "/orders/:id/", "IsRequired": true },
|
||
{ "Type": "Body", "Name": "note", "DefaultValue": "", "Path": "", "IsRequired": false },
|
||
{ "Type": "Static","Name": "userId", "DefaultValue": "@USERID", "Path": "", "IsRequired": true }
|
||
]
|
||
|
||
// PermissionsJson
|
||
[ { "ResourceType": "Role", "ResourceId": "SalesManager" },
|
||
{ "ResourceType": "User", "ResourceId": "ali.akman" } ]
|
||
```
|
||
|
||
Endpoint **tanımlarını** düzenleme yetkileri ListForm ekranının
|
||
`Sas.DeveloperKit.CustomEndpoints{,.Create,.Update,.Delete}` yetkileridir — çağırma
|
||
yetkilerinden (`.Get/.Post/.Put/.Remove` + `PermissionsJson`) ayrıdır.
|
||
|
||
**Seed:** `configs/seeds/data/Sas.DeveloperKit.CustomEndpoints/{Id}.json` (§9.15) — ekran klasör
|
||
modundadır, her endpoint kendi satır dosyasındadır. Ekranın
|
||
`SeedSyncInsert/Update/Delete` bayrakları **açıktır**: ekrandan eklenen/değiştirilen/silinen kayıt
|
||
dosyasına yansır, migrate'te eksikse geri eklenir. Prompt ile üretimde satırı `{Id}.json` dosyası olarak 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/Sas.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:
|
||
|
||
```csharp
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using Volo.Abp.Application.Services;
|
||
using Volo.Abp.Domain.Repositories;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
|
||
namespace DynamicServices
|
||
{
|
||
[Authorize]
|
||
public class DynamicCustomerAppService : ApplicationService
|
||
{
|
||
public virtual async Task<string> GetHelloWorldAsync()
|
||
=> await Task.FromResult("Hello World from Dynamic AppService!");
|
||
}
|
||
}
|
||
```
|
||
|
||
- Sınıf **`AppService` ya da `ApplicationService` ile bitmeli** ve `ApplicationService`
|
||
tabanından türemelidir; aksi hâlde derlense bile controller olarak keşfedilmez.
|
||
- Bağımlılıklar **constructor injection** ile gelir (`DynamicControllerActivator` çözer):
|
||
`IRepository<TEntity, TKey>`, `ICurrentTenant`, `ICurrentUser`, platform servisleri —
|
||
derleme referansları yüklü tüm `Volo.Abp`, `Sozsoft.Platform` ve `Microsoft.AspNetCore`
|
||
assembly'leridir, yani platform entity ve servislerinin tamamı görünürdür.
|
||
- **Derleme referansları dardır:** temel runtime (`System.Private.CoreLib`, `System.Runtime`,
|
||
`System.Linq`, `System.Collections`, `netstandard`) + yüklü assembly'lerden adı `Volo.Abp`,
|
||
`Sozsoft.Platform` ya da `Microsoft.AspNetCore` içerenler (`DynamicServiceCompiler.GetDefaultReferences`).
|
||
`Microsoft.Extensions.*` (Logging, Localization, Identity.Core), `System.Data`, `System.Linq.Expressions`
|
||
ve `Sozsoft.Notifications.*` görünmez; imzasında bunlardan bir tip geçen üye `CS0012` ile derlenmez.
|
||
Bu yüzden `UserFriendlyException`/`BusinessException` (`LogLevel`), `L`/`Logger`, ifade ağacı alan
|
||
repository metotları (`AnyAsync(predicate)`, `IRepository<T>.GetAsync` overload'ları),
|
||
`UnitOfWorkManager.Begin(...)` (`IsolationLevel`), `UserManager` ve `INotificationManager`
|
||
kullanılamaz. Yerine: kullanıcı için `IIdentityUserAppService` (yetkisini kendisi arar), entity
|
||
okuma/yazma için `IIdentityUserRepository` / `IBasicRepository`, tablo ve dil metni için
|
||
`IDynamicDataManager` ile parametreli SQL; iş kuralı reddi istisna yerine sonuç DTO'sunda döner.
|
||
EF ile yazılan satırı aynı istekte SQL'den okuyan sorgu `WITH (READPAST)` kullanır — commit
|
||
edilmemiş satırın kilidini beklemez.
|
||
- **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:
|
||
`Sas.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:** listenin seed bayrakları açıktır (`data/Sas.DeveloperKit.DynamicServices.json`, dosya ilk
|
||
kayıtta oluşur), ama bu dosya **kodu taşıyamaz**: `ListFormSeedDataApplier` yalnızca ekranın alan
|
||
olarak tanımlı kolonlarını yazar; `Code` listede alan değildir (diyalogda düzenlenir), bu yüzden
|
||
dosyaya elle yazılan bir servis satırı migrate'te `Code` NULL hatasıyla reddedilir. Dynamic Service
|
||
seed dosyasıyla teslim edilen bir artefakt olarak üretilmez; ekrandan yazılıp yayınlanır.
|
||
Açılışta yalnızca `IsActive && CompilationStatus == Success` olanlar derlenir.
|
||
|
||
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/Sas.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ü: `Sas.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` | `Sas.SqlQueryManager` | SQL nesneleri, CRUD endpoint'ler | 9.5 |
|
||
| **Custom Endpoints** | `/admin/list/Sas.DeveloperKit.CustomEndpoints` | `Sas.DeveloperKit.CustomEndpoints` | Elle yazılmış SQL'den REST endpoint | 9.2 |
|
||
| **Dynamic Services** | `/admin/list/Sas.DeveloperKit.DynamicServices` | `Sas.DeveloperKit.DynamicServices` | Runtime derlenen C# AppService | 9.3 |
|
||
| **Components** | `/admin/list/Sas.DeveloperKit.CustomComponents` | `Sas.DeveloperKit.CustomComponents` | Custom Component + Visual Designer | 7 |
|
||
| **ListForm** | `/admin/list/Sas.Listforms.Listform` | `Sas.Listforms.Listform` | Var olan ekranların ham tanımı | 9.6 |
|
||
| **Wizard Manager** | `/admin/listform/wizardManager` | `Sas.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/Sas.DeveloperKit.McpServers` | `Sas.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 | 9 |
|
||
| **DB Migrate** | `DbMigrateButton` | Migration + seed tetikleme (`Sas.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/{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. Satırlar sürüklenerek sıralanır (`form-fields/fieldOrder.ts`, `SortableFieldRow.tsx`): **Liste sırası** modu `ListOrderNo`'yu bütün alanlar üzerinde, **Form sırası** modu `EditOrderNo`'yu yalnızca aynı `EditGroupOrderNo` grubundaki forma giren alanlar arasında 1'den yeniden dağıtır; gruplar arası taşıma Seçenekler alt sekmesindendir. Sıra kültür/rol/kullanıcı kaydı başına ayrıdır, yalnızca değişen alanlar kaydedilir; `Update` yetkisi ister | 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` |
|
||
|
||
`*FieldsDefaultValueJson` satır diyaloğunda `FieldName`, `SelectCommand`'in kolon listesinden seçilir ve
|
||
seçilen kolonun tipi `FieldDbType`'a yazılır (`sqlDataTypeToDbType`); liste kısıtlamaz — `Query` tipli
|
||
kaynakta kolonlar katalogdan okunamadığı ve tabloda olmayan bir parametre adı da gerekebildiği için
|
||
serbest değer de yazılabilir (`JsonRowOpDialogDatabase.tsx`).
|
||
|
||
**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.
|
||
|
||
**Butonlar**, **Form** ve **Alt Formlar** sekmelerinde satırlar tutamaçtan sürüklenerek sıralanır
|
||
(`ListFormJsonRowAppService.MoveAsync`, UI `SortableJsonRows`). Buton ve alt form dizideki sırayla
|
||
çizilir; form grubu `Order` ile sıralandığı ve alanlar gruba `EditGroupOrderNo` ile bağlı olduğu için
|
||
taşımada grup numaraları yeni sıraya göre yeniden verilir, alanlar gruplarıyla birlikte taşınır.
|
||
|
||
> **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ü: `Sas.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/Sas.Menus.Routes` | `Sas.Menus.Routes` | Fiziksel React sayfalarının route kayıtları (`Key`, `Path`, `ComponentType`, `ComponentPath`, `RouteType`, `Authority[]`) |
|
||
| **Modules** | `/admin/list/Sas.Menus.Module` | `Sas.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/Sas.Menus.Menu` | `Sas.Menus.Menu` | Menü kayıtlarının ham listesi |
|
||
| **Menu Manager** | `/admin/menuManager` | `Sas.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/Sas.Menus.Menu.json` (anahtar `Code`) platformun kendi ekranları ve `Pub` modülü
|
||
(public site, §0.9) içindir. `Sas.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
|
||
`Sas.Menus.Menu` ekranının seed dosyasına (`data/Sas.Menus.Menu.json`) ve menü adı
|
||
`Sas.Languages.LanguageText/{CultureName}.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
|
||
`Sas.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/Sas.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` (`Adm.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). Wizard/MCP üretiminde otomatik numara her zaman bu yoldan üretilir (§4.3.5.2).
|
||
|
||
Ekran: `/admin/list/Adm.Definitions.Sequence` · Yetki: `Adm.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/Adm.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.
|
||
|
||
**SQL tarafında açılan belge.** Kaydı formun değil bir yordamın açtığı durumda (ör. `Prc_P_PurchaseRfqAward`
|
||
kazanan teklif için sipariş açar) numara `EXEC Adm_P_SequenceNext N'{Name}', @TenantId, @No OUTPUT`
|
||
(PG: `CALL public."Adm_P_SequenceNext"('{Name}', v_tenant, v_no)`) ile aynı numaratörden alınır; satırı
|
||
kilitleyerek `NextNumber`'ı ilerletir ve `FormatTemplate`/`ResetPeriod`'u `Sequence.GetNextSequence` ile
|
||
birebir aynı uygular. `MAX + 1` ya da elle birleştirilmiş numara yazılmaz.
|
||
|
||
### 9.10 Zamanlanmış işler — `BackgroundWorker` (`Sas.BackgroundWorkers.RecurringJobs`)
|
||
|
||
"Her gece şu prosedür çalışsın", "her 5 dakikada bildirim kuyruğu boşalsın" istekleri.
|
||
Ekranlar: `Sas.BackgroundWorkers.RecurringJobs` (tanımlar) ve `Sas.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 · `6` ProbationReminderWorker |
|
||
| `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 `Sas.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/Sas.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`) |
|
||
| Deneme süresi bitiş hatırlatması | `WorkerType: 6` + `Scope: 3`; bildirim tipi `DenemeSuresiBitiyor`, alıcı/kanal tipin kurallarından |
|
||
|
||
**Zamanlanmış e-posta (`MailQueueWorker`)** — "her sabah geciken siparişler listesi şu kişilere
|
||
mail atılsın" isteğinin yolu budur. Worker'ın `Options` alanı bir `MailQueueWorkerOptions`
|
||
JSON'udur:
|
||
|
||
```jsonc
|
||
{ "MailType": "OrderDelay", "MailSubject": "Geciken Siparişler",
|
||
"MailTemplate": "<h3>Geciken siparişler</h3>@Model.Tables[0]",
|
||
"TableName": "{0:MT3_GECIKEN_SIPARIS:IN:Order Delay Notification:0:}" }
|
||
```
|
||
|
||
- `MailTemplate` Razor ile render edilir (`EmailLayout` düzeni içinde).
|
||
- `TableName` maildeki tabloları tanımlar; `IN` gövdeye gömülür, `PDF`/`XLS`/`TXT` ek dosya olur.
|
||
- Gönderilecek satırlar `BackgroundWorker_MailQueue` kuyruk tablosuna yazılır
|
||
(`To`, `MailParameter`, `TableName`, `TableParameter`, `Attachment`, `RelatedRecordId`);
|
||
kuyruğa yazma işi genellikle `BeforeSp` prosedüründe yapılır. Ayrıntılı biçim
|
||
`api/modules/Sozsoft.MailQueue` modülündedir.
|
||
|
||
### 9.11 Bildirimler (`Adm.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: `Adm.Notifications.Notification`). Dört tablo da kiracı kapsamındadır
|
||
(`Adm_T_NotificationTemplate`, `Adm_T_NotificationType`, `Adm_T_NotificationRule`,
|
||
`Adm_T_Notification`); kendi veritabanı olan kiracı tür ve kuralları seed dosyasından alır; host veritabanını paylaşan
|
||
kiracıya aynı dosyalardaki küme 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 `Sas.NotificationChannel.{Kanal}` (eski `Sas.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 `Adm.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/Adm.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** (gönderici ile senkron, kuyruk değil — §4.7); UI bildirimi
|
||
istiyorsan ayrıca bir bildirim kuralı gerekir.
|
||
|
||
### 9.12 Raporlar (`Adm.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ı** | `Adm.Reports.ReportTemplates` + `Adm.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?}`.
|
||
|
||
- List ekranının araç çubuğundaki **Rapor › Liste** aksiyonu `DynamicGrid` (Tree'de `DynamicTree`)
|
||
raporunu **o anki filtre, sıralama ve kolon genişlikleriyle** açar; `FilterRowJson.AllowUserUiFilterSave`
|
||
(varsayılan `true`) açıkken Grid, Tree ve Card'da görünür, başka tanım gerekmez. Form sayfasındaki
|
||
**Rapor › Form** `DynamicForm` üretir (alt formda yok).
|
||
- 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/Adm.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()` | Uygulamanın dağıtık önbellek anahtarlarını ve dil sözlüğünün süreç içi kopyasını temizler (`DistributedCacheCleaner`) | `Sas.Languages.LanguageText` |
|
||
| `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 | `Sas.DeveloperKit.DynamicServices.Delete` |
|
||
| `UiEvalService.ApiDbMigrate()` | Önce onay sorar (`Sas.DbMigrate.ConfirmMessage`), sonra migration + seed akışını başlatır, log panelini açar. Kendi onay diyaloğu olan `DbMigrateButton` (SQL Query Manager, Wizard Manager) ikinci parametreyle (`confirmed: true`) bu adımı atlar | `Sas.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/Adm.Definitions.Sequence` |
|
||
| "Şu iş her gece çalışsın" | Recurring Jobs (§9.10) | `/admin/list/Sas.BackgroundWorkers.RecurringJobs` |
|
||
| "Şu olayda bildirim gitsin" | Notification Types + Rules (§9.11) | `/admin/list/Adm.Notifications.NotificationRules` |
|
||
| "Fatura/teklif/sertifika çıktısı" | Report Templates — `Raporlar` kategorisi, `DynamicReport` (§9.12); MCP `save_report_template` | `/admin/list/Adm.Reports.ReportTemplates` |
|
||
| "Yeni bir veritabanı bağlantısı" | Data Source | `/admin/list/Sas.DataSource` |
|
||
| "Uygulama ayarı (e-posta, limit, açık/kapalı)" | Setting Definitions + Ayarlar ekranı | `/admin/list/Sas.SettingDefinitions` · `/admin/settings` |
|
||
| "Metin/çeviri değişsin" | Languages + Language Texts | `/admin/list/Sas.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/Sas.Branches` |
|
||
| "Kimin ne yaptığını görelim" | Audit Logs · Sessions · Activity Log | `/admin/list/Adm.IdentityManagement.AuditLogs` |
|
||
| "Belirli saatlerde/IP'lerden girilsin" | Work Hours · IP Restrictions | `/admin/list/Adm.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/Sas.Definitions.GlobalSearch` |
|
||
| "Duyuru, anket, sosyal akış, etkinlik" | İK › Intranet ekranları | `/admin/list/Hr.Intranet.*` |
|
||
| "Çalışan kendi izin/puantaj/performans/kişisel bilgisini görsün" | Profil sekmeleri (Çalışan Bilgileri · İzinlerim · Puantajım · Performansım) + Intranet panosu widget'ı; persona ekranı menüsüz `Hr.My*` wizard'ıdır (§0.12.1) | `/admin/profile/*` · `/admin/intranet` |
|
||
| "Para birimi, ülke/il/ilçe, birim, sektör…" | Definitions ekranları | `/admin/list/Sas.Definitions.*` · `Adm.Definitions.*` |
|
||
| "Departman, pozisyon/ünvan, yetkinlik, organizasyon şeması" | İK › Definitions · Org Chart (şube bazlı departman/pozisyon) | `/admin/list/Hr.*` · `/admin/organization` |
|
||
| "Eğitim, araç/şoför/araç masrafı, vardiya, olay günlüğü, disiplin dosyası" | İK › Trainings · Vehicles · Shift · Event Log · Disciplinary Case | `/admin/list/Hr.{Training,Vehicle,Driver,VehicleExpense,VehicleMovement,Shift,EventLog,DisciplinaryCase}` |
|
||
| "İşe alım: kadro talebi, ilan, aday, başvuru, mülakat, teklif" | İK › Recruitment | `/admin/list/Hr.{StaffRequest,JobPosting,Candidate,JobApplication,Interview,JobOffer}` |
|
||
| "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/Sas.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, `Sas.Menus.*`, `Abp.Identity.Permission*` | Satırı ekranın `SeedFilePath` tanımına yaz: `.json` ile bitiyorsa o dosya, uzantısızsa klasördeki `{anahtar}.json` (§9.15) (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_Sas.cs` / `ListFormSeeder_Adm.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, klasör ya da gruplu klasördür.** Klasör modlarında da her dosya aynı zarfı (`ListFormCode`,
|
||
`KeyFieldName`, `GeneratedAt`, `Order`, `Rows`) taşır. Biçimi yalnızca `SeedFilePath` belirler; diskte ne
|
||
bulunduğu kararı değiştirmez:
|
||
|
||
| `SeedFilePath` | Biçim | Kullanım |
|
||
| --- | --- | --- |
|
||
| `data/{ListFormCode}.json` | Tek dosya, bütün satırlar `Rows`'ta (varsayılan) | Küçük listeler |
|
||
| `data/{ListFormCode}` (uzantısız) | Klasör; her satır `{anahtar}.json`, `Rows` tek satır (`SeedPathResolver.GetDataFolderPath`) | Satırı büyük ya da sık yazılan ekran (Custom Component) |
|
||
| `data/{ListFormCode}/{Alan}.json` | Gruplu klasör; aynı `{Alan}` değerini taşıyan satırlar `{değer}.json`'da (`SeedPathResolver.GetDataGroupedFolderPath`) | Satır başına dosyanın on binlerce dosya, tek dosyanın her kayıtta onlarca MB yazım üreteceği ekran (dil metinleri: `{CultureName}` → `tr.json`) |
|
||
|
||
Klasör yoksa ilk kayıtta oluşturulur. Gruplu klasörde insert/update satırı yalnızca kendi grup dosyasına
|
||
yazar (grup alanı değiştiyse eski dosyadan çıkarır), delete yalnızca anahtarı taşıyan dosyaya dokunur,
|
||
satırı kalmayan grup dosyası silinir, yeni grup değeri kendi dosyasını açar; grup değeri boş satır yazılmaz. Hedefi okuyan ve yazan her yol
|
||
`ListFormSeedTarget`'ı kullanır. 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. Ekranın tanımladığı hedefin dışında kalan
|
||
dosya (biçim değiştirilmiş ekranın eski dosyası) uygulanmaz, uyarıyla atlanır. Ekrandaki hangi işlemin dosyaya
|
||
yansıyacağını `SeedSyncInsert`, `SeedSyncUpdate` ve `SeedSyncDelete` bayrakları belirler — üçü de
|
||
kapalıyken dosya yalnızca seed sırasında okunur, hiç yazılmaz.
|
||
|
||
```jsonc
|
||
{
|
||
"ListFormCode": "Sas.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`, `Sas.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/Sas.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: Core'dur, yalnızca "core sisteme ekle" komutuyla var olan bileşene eklenir (§0.8).
|
||
|
||
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/
|
||
├── sql/object/Hr_T_AdvanceRequest.sql ← tablo (+ postgres/object/… PG eşi, §8.3.1)
|
||
├── crud/Hr_T_AdvanceRequest.json ← yalnızca Custom Component yolunda gerekir
|
||
├── data/Sas.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ı + §4.7'deki dört iş akışı kolonu
|
||
(`ApprovalUserName`, `ApprovalStatus`, `ApprovalDate`, `ApprovalDescription`; ayrı `Status` yok).
|
||
Talep sahibi için de bir kolon (`EmployeeId`) bulunmalıdır.
|
||
2. **Wizard dosyası** — §4.4'teki iskelet:
|
||
- `IsTenant: true` (tabloda `TenantId` var); soft delete / audit davranışı tablodaki
|
||
`IsDeleted`/`CreatorId`/`LastModifierId` kolonlarından deploy'da okunur
|
||
- `MenuParentCode` = modül kökü (`Hr`); yoksa wizard yaratır, `Order = max + 1`
|
||
- `PermissionGroupName` = `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** — düzenleme formunda yalnız "Talep" grubu (tutar, tarih, açıklama) bulunur. İş akışı
|
||
alanları (onay durumu, onaylayan, onay tarihi, onay açıklaması) forma konmaz, listede görünür;
|
||
akış doldurur (§4.7).
|
||
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 toolbar'daki Taslak düğmesi akışı başa alır.
|
||
6. **Yetki** — wizard `Hr.AdvanceRequests` kökünü ve `.Create/.Update/.Delete/.Export/
|
||
.Import/.Note` alt yetkilerini üretir (`.Note` tablo kaynaklı olduğu için). 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/Sas.Languages.LanguageText/{CultureName}.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). Kalem taşıyan türevlerde ana ekran `AllowDetail: true` +
|
||
`OpenDetailAfterInsert: true` ile gelir: başlık kaydedilince kalem girişi için Detay açılır.
|
||
|
||
---
|
||
|
||
## 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ş.
|
||
- [ ] Her yeni anahtar `Sas.Languages.LanguageText/` klasöründeki bütün dil dosyalarında çevrilmiş satırla var (§4.4.3).
|
||
- [ ] `IsTenant` (ve gerekiyorsa `IsBranch`/`IsOrganizationUnit`) doğru; sorgular tenant güvenli.
|
||
- [ ] Tüm SQL parametreli; string birleştirme yok.
|
||
- [ ] `DeleteCommand = DefaultDeleteCommand("{Tablo}")`.
|
||
- [ ] Her `*Command` / `*BeforeCommand` / `*AfterCommand` parametresinin kaynağı var: gönderilen alan,
|
||
`IsTenant` ile gelen `@TenantId` ya da `Insert/Update/DeleteFieldsDefaultValueJson` satırı (§4.3.5.3).
|
||
- [ ] 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.
|
||
- [ ] Ölü artefakt yok: yerini yenisine bırakan SQL nesnesi/wizard/veri dosyası ve kullanılmayan dil
|
||
anahtarı silindi (§8.6).
|
||
- [ ] 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.
|
||
- [ ] Modül dokümanı (`configs/seeds/docs/modules/{Menü}.md`) yazıldı ya da güncellendi; analiz/kararlar,
|
||
ekranlar, hesaplar ve yetkiler üretilen artefaktların gerçek adlarıyla yazılı, README §10.5
|
||
dizininde satırı var (§0.13).
|
||
|
||
Prompt ile üretim yaptıysan ek olarak:
|
||
|
||
- [ ] Dosyalar `configs/seeds/` altındaki doğru klasörde (§0.1).
|
||
- [ ] Ü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).
|
||
- [ ] `AllowDetail: true` yalnızca `SubForms` taşıyan listede; alt formu olmayan liste ve alt form ekranının kendisi `false`.
|
||
- [ ] Başlık + kalem ekranında (kalemler alt form sekmesinde giriliyor) `OpenDetailAfterInsert: true`; kararı analiz yazdı (§4.2 Davranış).
|
||
- [ ] 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 yalnızca `Wizard` bölümünü taşıyor ve `Data` başlığı dolu (§4.1); `ListForm`/`Fields`
|
||
bölümü yok.
|
||
- [ ] 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).
|
||
- [ ] "Core sisteme ekle" komutu olmadan `api/` ya da `ui/` altına yeni özellik yazılmadı (§0.8);
|
||
çerçeve içinde karşılanamayan kısım son mesajda "Core gerektiren kısım" notu olarak bildirildi.
|
||
- [ ] Komutla Core'a ekleme yapıldıysa yalnızca komuttaki değişiklik yapıldı, geriye dönük uyumlu
|
||
(mevcut varsayılan/davranış değişmedi), envanter (`lowcode-reference` ilgili bölüm, bileşense
|
||
§10.9/§10.10) güncellendi; `npm run typecheck` / `npm run lint` ya da `dotnet build` temiz.
|