WizardFileManager düzenlemesi

This commit is contained in:
Sedat ÖZTÜRK 2026-10-20 11:52:23 +03:00
parent 702dffe50e
commit 679f8fcb89
293 changed files with 331335 additions and 382459 deletions

View file

@ -965,7 +965,7 @@ up so `RollbackImport` can undo the whole batch), closed by `CompleteImport`.
`AnalyzeImport` also **validates** before anything is written (`WizardImportValidator`). With at `AnalyzeImport` also **validates** before anything is written (`WizardImportValidator`). With at
least one `Error` finding the session is marked `blocked` on the server and `ApplyImport` refuses, least one `Error` finding the session is marked `blocked` on the server and `ApplyImport` refuses,
so the check cannot be bypassed by calling the endpoint directly. Errors: malformed JSON; a wizard so the check cannot be bypassed by calling the endpoint directly. Errors: malformed JSON; a wizard
file with no `Wizard`/`ListForm` section; a screen's or a `crud/{Entity}.json`'s table missing in file with no `Wizard` section or, on the List path, no `Data` heading (`SelectCommand`/`KeyFieldName`); a screen's or a `crud/{Entity}.json`'s table missing in
the target **and** absent from the archive's scripts; a `data/{code}.json` whose ListForm exists the target **and** absent from the archive's scripts; a `data/{code}.json` whose ListForm exists
neither in the archive nor in the target; an unresolvable custom-component `Dependencies` / neither in the archive nor in the target; an unresolvable custom-component `Dependencies` /
`DataSources[].SeedFile`; an empty component name or route on the `Custom` path; a sub-form `DataSources[].SeedFile`; an empty component name or route on the `Custom` path; a sub-form
@ -1011,7 +1011,8 @@ later steps would fail on it; (b) columns missing from existing tables are added
through `ICrudEndpointSeedApplier`; (d) the `data/*.json` rows, except the platform files that hold through `ICrudEndpointSeedApplier`; (d) the `data/*.json` rows, except the platform files that hold
the wizards' menu/permission/language rows (`WizardSeedRowStore.ListFormCodes`); (e) `wizard/*.json` the wizards' menu/permission/language rows (`WizardSeedRowStore.ListFormCodes`); (e) `wizard/*.json`
— the screens are **not** installed: like a deploy, the file is marked `Pending` and the next DB — the screens are **not** installed: like a deploy, the file is marked `Pending` and the next DB
Migrate installs it (`WizardDataSeeder`) from the file's `ListForm`/`Fields` contract as-is, even Migrate installs it (`WizardDataSeeder`) from the file's `Wizard` section as-is
(`Wizard.BuildListForm()/BuildFields()`; the file has no separate `ListForm`/`Fields` sections), even
when the target already has the screen. when the target already has the screen.
A wizard deploy (wizard screen, MCP `deploy_wizard`, edit) never writes to the database: it only A wizard deploy (wizard screen, MCP `deploy_wizard`, edit) never writes to the database: it only

View file

@ -19,10 +19,10 @@ sorusunda buraya bak.
**Ortak kurallar** **Ortak kurallar**
- Bütün `*Json` kolonları veritabanında **tek satır JSON string** olarak durur. Wizard seed - Bütün `*Json` kolonları veritabanında **tek satır JSON string** olarak durur. Wizard seed
dosyasının `ListForm`/`Fields` bölümleri ekranın DTO sözleşmesini taşıdığı için orada bu dosyasının `Wizard` bölümü ekranın DTO sözleşmesini taşıdığı için orada bu kolonlar kaçışlanmış
kolonlar kaçışlanmış metin değil, tipli karşılıkları (`SchedulerOptionJson` → metin değil, tipli bloklar (`SchedulerOptionJson` → `SchedulerOptionDto`, `EditingOptionJson` →
`SchedulerOptionDto`) olarak yer alır; kaçışlama yalnızca `EditorOptions`, `EditorScript`, `Editing`, alan `ValidationRuleJson` → öğenin `ValidationRules`'ı) olarak yer alır; kaçışlama
`LookupQuery` gibi gerçekten metin olan alanlar içindir. yalnızca `EditorOptions`, `EditorScript`, `LookupQuery` gibi gerçekten metin olan alanlar içindir.
- Varsayılan değeri olan bir alanı tekrar yazmak zorunda değilsin; yalnızca farklı olanı yaz. - Varsayılan değeri olan bir alanı tekrar yazmak zorunda değilsin; yalnızca farklı olanı yaz.
- `Accepted Values` yazan yerlerde değer birebir eşleşmelidir (büyük/küçük harf dahil). - `Accepted Values` yazan yerlerde değer birebir eşleşmelidir (büyük/küçük harf dahil).
- **Bilinmeyen anahtar sessizce düşer — tarayıcıya hiç ulaşmaz.** `GridOptionsDto` (ekran) ve - **Bilinmeyen anahtar sessizce düşer — tarayıcıya hiç ulaşmaz.** `GridOptionsDto` (ekran) ve
@ -107,7 +107,7 @@ ve çoğu isteğin cevabı aslında oradadır:
| `TableName` | `SelectCommand` | Üretilen `SELECT`'te kolonların ve join'lerin önüne yazılan **alias**. Boşsa `SelectCommand` kullanılır. `4` Query'de **zorunludur**: sorgu `FROM (…) AS "{TableName}"` biçiminde türetilmiş tablo olarak sarılır ve boş bırakılırsa sorgu metni alias olur (SQL hatası). Wizard/MCP `deploy_wizard` Query ekranına `"Q"` (`WizardConsts.QuerySourceAlias`) yazar; fonksiyon çağrısında da kısa bir ad yaz | | `TableName` | `SelectCommand` | Üretilen `SELECT`'te kolonların ve join'lerin önüne yazılan **alias**. Boşsa `SelectCommand` kullanılır. `4` Query'de **zorunludur**: sorgu `FROM (…) AS "{TableName}"` biçiminde türetilmiş tablo olarak sarılır ve boş bırakılırsa sorgu metni alias olur (SQL hatası). Wizard/MCP `deploy_wizard` Query ekranına `"Q"` (`WizardConsts.QuerySourceAlias`) yazar; fonksiyon çağrısında da kısa bir ad yaz |
| `KeyFieldName` · `KeyFieldDbSourceType` | — · `16` String | Anahtar kolon ve `System.Data.DbType` karşılığı. `4` Query'de ikisi de **zorunludur** (kolonlar katalogdan okunamaz); wizard/`deploy_wizard` anahtarsız Query ekranını reddeder, tip boşsa (`0`) seçilen alanlardaki karşılığından tamamlar. Ana tablo (en dıştaki `FROM`) `IsDeleted` taşıyorsa `DefaultFilter` yazılır ve sorgu `IsDeleted`'i seçmek zorundadır | | `KeyFieldName` · `KeyFieldDbSourceType` | — · `16` String | Anahtar kolon ve `System.Data.DbType` karşılığı. `4` Query'de ikisi de **zorunludur** (kolonlar katalogdan okunamaz); wizard/`deploy_wizard` anahtarsız Query ekranını reddeder, tip boşsa (`0`) seçilen alanlardaki karşılığından tamamlar. Ana tablo (en dıştaki `FROM`) `IsDeleted` taşıyorsa `DefaultFilter` yazılır ve sorgu `IsDeleted`'i seçmek zorundadır |
| `SelectFieldsDefaultValueJson` | — | ListForm editöründe (Veritabanı › Select) düzenlenir ama runtime **okumaz** (`DefaultValueManager` Select'te `FormFieldsDefaultValueJson`'ı kullanır). Okuma koşulu `DefaultFilter`'a token'la yazılır (`lowcode` §4.3.5) | | `SelectFieldsDefaultValueJson` | — | ListForm editöründe (Veritabanı › Select) düzenlenir ama runtime **okumaz** (`DefaultValueManager` Select'te `FormFieldsDefaultValueJson`'ı kullanır). Okuma koşulu `DefaultFilter`'a token'la yazılır (`lowcode` §4.3.5) |
| `DefaultFilter` | — | Her sorgunun sonuna `AND` ile eklenen sabit `WHERE` koşulu. `DefaultValueHelper` üzerinden geçer, yani `@USERID`/`@TENANTID`/`@NOW` gibi token'lar çözülür. Soft delete'li tabloda (`IsDeletedField: true`) wizard buraya `"IsDeleted" = 'false'` yazar | | `DefaultFilter` | — | Her sorgunun sonuna `AND` ile eklenen sabit `WHERE` koşulu. `DefaultValueHelper` üzerinden geçer, yani `@USERID`/`@TENANTID`/`@NOW` gibi token'lar çözülür. Soft delete'li tabloda (tablo `IsDeleted` taşıyorsa; deploy katalogdan okur) wizard buraya `"IsDeleted" = 'false'` yazar (`Wizard.Data.DefaultFilter`) |
| `SortMode` | — | **Metin**: `"single"` \| `"multiple"` \| `"none"` (`PlatformConsts.GridOptions.SortMode*`; wizard `single` yazar) | | `SortMode` | — | **Metin**: `"single"` \| `"multiple"` \| `"none"` (`PlatformConsts.GridOptions.SortMode*`; wizard `single` yazar) |
| `PageSize` | `50` (DB) | Sayfa başına kayıt; `0` = tümü. C# varsayılanı `0`, veritabanı varsayılanı `50`; boşsa grid `20` kullanır | | `PageSize` | `50` (DB) | Sayfa başına kayıt; `0` = tümü. C# varsayılanı `0`, veritabanı varsayılanı `50`; boşsa grid `20` kullanır |
| `Name` · `Title` · `Description` | — | Ekran adı, başlığı, açıklaması — **dil anahtarı** yazılır | | `Name` · `Title` · `Description` | — | Ekran adı, başlığı, açıklaması — **dil anahtarı** yazılır |

View file

@ -71,7 +71,7 @@ kurulur; host veritabanını paylaşan tenant'ta dosyalar yeniden uygulanmaz (`S
| 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 | | 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 | | 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 | | 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.json`) | Deploy (MCP `deploy_wizard` ya da wizard ekranı, §0.10) **yalnızca bu dosyaları yazar**; ekran, menü ve yetkiler DB Migrate ile kurulur (§4.0). Dosyaları sunucu yazar. Elle yazılan List dosyası `ListForm` + `Fields` taşımak zorundadır, yoksa seeder atlar (§4.1) | | 4 | Wizard | `wizard/{Ad}.json` + menü/yetki/dil satırları (`data/Sas.Menus.*`, `data/Abp.Identity.Permission*`, `data/Sas.Languages.LanguageText.json`) | Deploy (MCP `deploy_wizard` ya da wizard ekranı, §0.10) **yalnızca bu dosyaları yazar**; ekran, menü ve yetkiler DB Migrate ile kurulur (§4.0). Dosyaları sunucu yazar. 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 | | 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) | | 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) | | 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) |
@ -539,7 +539,7 @@ object scripti) — Wizard ekranıyla aynı kural. Veritabanına hiçbiri yazmaz
kullanıcının çalıştırdığı DB Migrate iledir, model migrate'i beklemez. Kolon adı tahmin edilmez: 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 `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`, sözlüğünde henüz olmayan kaynakları da döner — migrate bekleyen wizard ekranları (`sourceKind: pending`,
alanlar wizard dosyasının `Fields` bölümünden) ve yalnızca `create_table` scripti olan tablolar 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. (`sourceKind: pendingTable`, kolonlar scriptten). Lookup, widget, view ve `Query` SQL'i bu adlarla yazılır.
| Araç | Ne yapar | Not | | Araç | Ne yapar | Not |
@ -549,7 +549,7 @@ alanlar wizard dosyasının `Fields` bölümünden) ve yalnızca `create_table`
| `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) | | `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` | | `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` | | `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). Wizard cevabının üretmediği ekran ayarları (`FormFieldsDefaultValueDto` gibi varsayılanlar §4.3.5, `ValidationRuleDto`, `Format`, `Width`, `BandName`, özet/gruplama, `ExtraFilter`, komutlar, `PageSize` …) aynı JSON'un `ListForm` ve `Fields` bölümlerine `get_wizard` çıktısındaki adlarla yazılır: bölümler kısmi olabilir, `Fields` öğeleri `FieldName` ile eşleşir, `*Json` adları `*Dto` karşılığına çevrilir (`WizardScreenOverlay`). Güncellemede `get_wizard` çıktısı bütünüyle düzenlenip gönderilirse editör ayarları korunur; aynı ayarı Wizard bölümündeki cevap da söylüyorsa cevap kazanır. Kimlik alanları (kod, ad, başlık/açıklama anahtarı, yetki) wizard'dan gelir; tanınmayan ya da hesaplanan 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 | | `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) | | `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) | | `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 | | `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 |
@ -1151,7 +1151,8 @@ Okuyan: `WizardDataSeeder` (migrate) · Yazan: `ListFormWizardAppService` (deplo
**İki biçim vardır, karıştırma:** *wizard cevabı* (düz `ListFormWizardDto`, §4.4) deploy'un **İ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 girdisidir — wizard ekranı ve MCP `deploy_wizard` bunu alır; *seed dosyası* (§4.1) deploy'un
çıktısıdır. List yolunda dosyayı deploy'a ürettir; `ListForm` bölümü olmayan List dosyası çı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**. seeder tarafından uyarıyla **atlanır**.
### 4.0 Deploy dosya yazar, migrate kurar ### 4.0 Deploy dosya yazar, migrate kurar
@ -1161,7 +1162,7 @@ hiçbir şey silmez**. Tek çıktısı seed dosyalarıdır:
| Dosya | İçerik | | Dosya | İçerik |
| --- | --- | | --- | --- |
| `wizard/{WizardName}.json` | Ekranın `ListForm`/`Fields` sözleşmesi, wizard cevapları, `InsertedRecords`, `Pending`, `DevRequest` | | `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/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/Abp.Identity.PermissionGroups.json`, `data/Abp.Identity.Permissions.json` | İzin grubu, okuma yetkisi ve alt yetkileri |
| `data/Sas.Languages.LanguageText.json` | Menü, başlık, açıklama, grup, alan başlıkları ve `Language.Texts` anahtarlarının (widget başlıkları) EN + TR metinleri (`Id` iş anahtarından v5 GUID, `SeedRowIds.LanguageText`) | | `data/Sas.Languages.LanguageText.json` | Menü, başlık, açıklama, grup, alan başlıkları ve `Language.Texts` anahtarlarının (widget başlıkları) EN + TR metinleri (`Id` iş anahtarından v5 GUID, `SeedRowIds.LanguageText`) |
@ -1177,11 +1178,23 @@ Kurulum **DB Migrate**'tedir (Wizard Manager'daki buton, `POST /api/setup/migrat
Her wizard dosyası kendi unit of work'ünde kurulur: hata veren dosya geri alınır (önceki ekran yerinde 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 kalır, `Pending` silinmez), sonraki dosyalar etkilenmez. İş akışı kurulumdan önce
`ListFormWizardDto.NormalizeWorkflow`'dan geçer (adım kimliği `{ListFormCode}-N…`, boş onaycı/kolon `ListFormWizardDto.NormalizeWorkflow`'dan geçer (adım kimliği `{ListFormCode}-N…`, boş onaycı/kolon
`''`); MCP'nin `ListForm` katmanıyla gelen `WorkflowDto` deploy'da da aynı metottan geçer. `''`); 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 **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ı 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`). 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. 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 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. deploy onu dosyaya yazar, migrate `Order` sırasıyla (route → modül → menü, grup → yetki) ekler.
@ -1210,22 +1223,42 @@ yoksa bölüm dosyaya yazılmaz. Migrate, senkronizasyon ve import bölümü old
```jsonc ```jsonc
{ {
"Wizard": { // yalnızca ekrandan üretilemeyen bilgiler — 4.2 "Wizard": { // ekranın BÜTÜN sözleşmesi — 4.2, 4.3
"ComponentKind": 0, "WizardName": "Tags", "WizardCode": "Prj.Tags", "ComponentKind": 0, "WizardName": "Prj_Tags", "WizardCode": "Prj.Tags",
"SeededAt": "2026-09-01T09:03:00", "CustomComponentName": null, "SeededAt": "2026-09-01T09:03:00", "CustomComponentName": null,
"PermissionGroupName": "Prj", "ModuleId": "Prj", // boş bağlantı metni yazılmaz "PermissionGroupName": "Prj", "ModuleId": "Prj",
"Menu": { "MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…", "Menu": { "MenuParentPath": [ { "Code": "…", "Icon": "…", "En": "…", "Tr": "…" } ], // yalnızca derin yolda
"MenuParentPath": [ { "Code": "…", "Icon": "…", "En": "…", "Tr": "…" } ], // yalnızca derin yolda "MenuParentCode": "…", "MenuParentIcon": "…", // "CreateMenu": false ise eklenir
"MenuParentCode": "…", "MenuParentIcon": "…" }, // "CreateMenu": false ise eklenir "MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…" },
"Language": { "MenuEn": "…", /* Menu/Title/Desc/MenuParent/PermissionGroup · EN + TR */ "Language": { "MenuParentEn": "…", /* MenuParent/PermissionGroup/Menu/Title/Desc · EN + TR */
"Texts": [ { "Key": "…", "En": "…", "Tr": "…" } ] }, // Texts yalnızca doluysa "Texts": [ { "Key": "…", "En": "…", "Tr": "…" } ] }, // Texts yalnızca doluysa
"Groups": [ { "Items": [ { "FieldName": "…", "CaptionName": "…", "Data": { "DataSourceCode": "Default", /* "DataSourceConnectionString" yalnızca doluysa */
"En": "…", "Tr": "…" } ] } ] // grup Caption/ColCount yok "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 */ } ] } ]
}, },
"ListForm": { /* GridOptionsEditDto — ekranın kendisi, ListForm detayıyla aynı sözleşme */ },
"Fields": [ /* ColumnFormatEditDto — alanlar, alan detayıyla aynı sözleşme */ ],
"IsDeletedField": true, // tabloda IsDeleted var mı (soft delete)
"IsCreatedField": true, // tabloda CreatorId var mı (audit)
"InsertedRecords": { // bu çalıştırmanın GERÇEKTEN yarattığı kayıtlar "InsertedRecords": { // bu çalıştırmanın GERÇEKTEN yarattığı kayıtlar
"LanguageKeys": [], "PermissionGroupNames": [], "PermissionNames": [], "LanguageKeys": [], "PermissionGroupNames": [], "PermissionNames": [],
"MenuCodes": [], "DataSourceCodes": [], "ModuleIds": [] "MenuCodes": [], "DataSourceCodes": [], "ModuleIds": []
@ -1234,26 +1267,35 @@ yoksa bölüm dosyaya yazılmaz. Migrate, senkronizasyon ve import bölümü old
} }
``` ```
`Wizard` bölümü yalnızca ekrandan üretilemeyen bilgileri taşır (menü, yetki, dil metinleri, Dosyada **yalnızca `Wizard` bölümü** vardır ve ekranın bütün sözleşmesi oradadır: ListForm detayının
bileşen yolu, alan başlıklarının dil karşılıkları); `ListForm`/`Fields` içinde karşılığı olan her sekmesi bir blok (`Editing`, `Insert`/`Update`/`Delete`, `FilterRow` … `ChartOptions`), alan
hiçbir alan orada tekrar edilmez (§4.2). Anahtar sırası sabittir: `ComponentKind`, `WizardName`, detayının her sekmesi `Groups[].Items[]` öğesinin bir alanıdır (§4.2, §4.3). Anahtar sırası sabittir:
`WizardCode`, `SeededAt`, `CustomComponentName`, `PermissionGroupName`, `ModuleId`, `ComponentKind`, `WizardName`, `WizardCode`, `SeededAt`, `CustomComponentName`, `PermissionGroupName`,
`DataSourceConnectionString`, `Menu`, `Language`, `Groups`. `ListForm`/`Fields` içindeki `ModuleId`, ardından `Menu`, `Language`, `Data`, `Details`, `Layout` başlıkları, ardından bloklar
`Id`/`CreationTime`/`EditType` gibi çalışma zamanı alanları sıfırlanmış yazılır. `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`) `ListForm` `null`, `Fields` `[]` yazılır ve kırpma **Custom yolunda** (`ComponentKind: 1`) ekrana ait anahtarların hiçbiri (`Data`, `Details`, `Layout`,
yapılmaz: `Wizard` bloğu List'e ait anahtarları (`Grid`/`Card`/…, `SelectCommand`, option bloklar, `Groups`) dosyaya yazılmaz; `Wizard` yalnızca kimlik, `Menu` ve `Language` taşır.
blokları) varsayılan değerleriyle taşımaya devam eder; bunlar kullanılmaz, temizlemeye çalışma.
Bu yolun dosyası elle yazılabilir — seeder yalnızca menü, yetki ve dil kaydı üretir. Bu yolun dosyası elle yazılabilir — seeder yalnızca menü, yetki ve dil kaydı üretir.
**`ListForm` + `Fields` ekranın tek gerçeğidir.** Seeder ekranı bu iki bölümden kurar; `Wizard` **`Wizard` bölümü ekranın tek gerçeğidir.** Seeder ekranı `Wizard.BuildListForm()` /
bölümünden yalnızca menü, yetki, dil anahtarları ve veri kaynağı üretilir. Ekran **ListForm `Wizard.BuildFields()` ile kurar (§4.0 deploy hattı). Ekran **ListForm detayından** (liste ayarları,
detayından** (liste ayarları, alan ayarları, JSON satır editörleri) düzenlendiğinde sunucu aynı alan ayarları, JSON satır editörleri) düzenlendiğinde sunucu aynı dosyanın `Wizard` bölümünü
dosyanın `ListForm`/`Fields` bölümlerini ve `Wizard` bölümündeki karşılıklarını geri yazar `RefreshFrom` ile geri yazar (`WizardSeedSynchronizer`); böylece migrate ya da import ekranı dosyadan
(`WizardSeedSynchronizer`); böylece migrate ya da import ekranı dosyadan kurarken tasarımcıdaki iş kurarken tasarımcıdaki iş kaybolmaz. Bunun şartı eşliktir: `GridOptionsEditDto`/`ColumnFormatEditDto`'ya
kaybolmaz (`EditFileName` ile yeniden deploy ise ekranı cevaptan üretir, §4.5). eklenen her alanın `ListFormWizardDto`/`WizardColumnItemInputDto`'da karşılığı + `RefreshFrom` +
Elle bu dosyayı yazarken `ListForm`/`Fields` bölümlerini uydurma — wizard'ı deploy et, dosyayı `BuildListForm/BuildFields` eşlemesi olmalıdır, yoksa tasarımcı değişikliği migrate'te kaybolur.
sunucu üretsin. 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 `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 kayıtlar silinmeye **aday** olur, önceden var olan kayıtlara (var olan izin grubu, var olan üst
@ -1262,23 +1304,22 @@ Elle dosya yazarken buraya var olan bir kaydı koyma — kullanılmıyorsa silme
### 4.2 `Wizard` bloğu — alan alan ### 4.2 `Wizard` bloğu — alan alan
`Wizard` bloğu **yalnızca ekranın sözleşmesinden üretilemeyen** bilgileri taşır: menü, yetki, `Wizard` bloğu ekranın **bütün** sözleşmesidir: menü, yetki, dil metinleri, bileşen yolu, veri
dil metinleri, bileşen yolu ve alan başlıklarının iki dilli karşılıkları. Ekrana ait olan her kaynağı, ekran ayarları, ListForm detayının her sekmesi (blok olarak) ve alan tanımları (öğe
şey (veri kaynağı, select komutu, anahtar alan, kırılım bayrakları, düzen/görünüm bayrakları, olarak). Ekrana ait hiçbir bilgi dosyada başka bir bölümde değildir.
düzenleme izinleri, görünüm option blokları, alt formlar, widget'lar, iş akışı, alan tanımları)
`ListForm`/`Fields` bölümlerindedir ve dosyaya **ikinci kez yazılmaz**.
Dosyada wizard alanları üç başlık altında toplanır — `Menu` (menü kaydı), `Language` (dil Dosyada wizard alanları beş başlık altında toplanır — `Menu` (menü kaydı), `Language` (dil
metinleri), `Groups` (alan başlıklarının dil karşılıkları); geri kalanı kök seviyededir. metinleri), `Data` (veri kaynağı, kayıt kaynağı, anahtar, kırılım bayrakları, okuma koşulu, veri
Gruplama ile `ListFormCode → WizardCode`, `MenuParentModuleId → ModuleId` ve `LanguageTexts → Language.Texts` adlandırmaları aynası), `Details` (Detay sekmesinin genel ayarları), `Layout` (görünüm bayrakları); bloklar ve
yalnızca **dosya biçimidir**; C# `Groups` kök seviyededir. Gruplama ile `ListFormCode → WizardCode`, `MenuParentModuleId → ModuleId`
sözleşmesi (`ListFormWizardDto`) düzdür ve dosya okunurken düzleştirilir ve `LanguageTexts → Language.Texts` adlandırmaları yalnızca **dosya biçimidir**; C# sözleşmesi
(`WizardSeedFileDto.FromSeedJson` ↔ `ToSeedJson`). Aşağıdaki tablolar C# alan adlarını kullanır. (`ListFormWizardDto`) düzdür ve dosya okunurken düzleştirilir (`WizardSeedFileDto.FromSeedJson` ↔
`ToSeedJson`). Aşağıdaki tablolar C# alan adlarını kullanır.
Aşağıdaki tablolarda `[ListForm]` işaretli alanlar **istek girdisidir**: sihirbaz bunları POST Tablolardaki köşeli işaret alanın dosyadaki yerini söyler: `[Data]`/`[Details]`/`[Layout]` o başlığın
eder, sunucu ekranı bunlardan üretir, ama seed dosyasında `ListForm` bölümündeki karşılıkları altında, `[Blok]` kök seviyede kendi adıyla, `[Item]` `Groups[].Items[]` öğesinde. `[Kısayol]` işaretli
saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldurulur alanlar **yalnızca istek girdisidir**: dosyaya yazılmazlar, sahibi olan bloktan türetilirler
(`WizardSeedFileDto.FromSeedJson`), dolayısıyla wizard'ı okuyan kod tam nesneyi görür. (`SyncDerivedAnswers`) ve deploy girdisinde yazıldıklarında bloğun aynı adlı alanını ezerler.
**Kimlik ve yol** **Kimlik ve yol**
@ -1291,7 +1332,7 @@ saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldur
| `MenuCode` | Menü kaydının kodu; yetki adlarının kökü de budur. | | `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. | | `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ı. | | `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). Wizard ekranından yapılan güncellemede ListForm editöründe yapılmış, wizard'da karşılığı olmayan ayarlar kaybolur; MCP'de aynı çağrıya `ListForm`/`Fields` bölümleri (`get_wizard` çıktısı) verilirse korunur (§0.10). Seed dosyasına yazılmaz. | | `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ü** **Menü**
@ -1311,7 +1352,7 @@ saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldur
> Dil metinlerinin tamamı dosyada `Language` başlığı altındadır; başlık zaten bölümü işaretlediği > 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). > için anahtarlar ortak `LanguageText` öneki olmadan yazılır (C# sözleşmesinde önek durur).
> `ListForm` yalnızca dil **anahtarını** taşır (`Title = "{ListFormCode}.Title"`), metinlerin tek > ListForm kaydı yalnızca dil **anahtarını** taşır (`Title = "{ListFormCode}.Title"`), metinlerin tek
> kaynağı burasıdır. > kaynağı burasıdır.
| Alan | Anlam | | Alan | Anlam |
@ -1335,26 +1376,50 @@ atanmaz (admin dahil); ekranı görecek roller yetkiyi rol ekranından alır.
| Alan | Anlam | | Alan | Anlam |
| --- | --- | | --- | --- |
| `DataSourceCode` | `[ListForm]` Bağlantı kodu; varsayılan `"Default"`. | | `DataSourceCode` | `[Data]` Bağlantı kodu; varsayılan `"Default"`. |
| `DataSourceConnectionString` | Yalnızca **yeni** bir bağlantı tanımlanıyorsa doldurulur; boşsa dosyaya hiç yazılmaz. **Asla gerçek kimlik bilgisi yazma.** | | `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` | `[ListForm]` Bkz. §3. | | `SelectCommandType` | `[Data]` Bkz. §3. |
| `SelectCommand` | `[ListForm]` Tablo/view adı, fonksiyon çağrısı ya da sorgu metni. | | `SelectCommand` | `[Data]` Tablo/view adı, fonksiyon çağrısı ya da sorgu metni. |
| `KeyFieldName` / `KeyFieldDbSourceType` | `[ListForm]` Birincil anahtar ve tipi. Wizard ekranının başlangıç değeri `11` (Int32), C# DTO'nun varsayılanı yok — her zaman açıkça yaz; `Guid` anahtarda `9`. | | `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ış** **Davranış**
| Alan | Anlam | | Alan | Anlam |
| --- | --- | | --- | --- |
| `IsTenant` / `IsBranch` / `IsOrganizationUnit` | `[ListForm]` Otomatik kırılım filtresi. Tabloda `TenantId` varsa `IsTenant: true` **olmalı**. | | `IsTenant` / `IsBranch` / `IsOrganizationUnit` | `[Data]` Otomatik kırılım filtresi. Tabloda `TenantId` varsa `IsTenant: true` **olmalı**. |
| `AllowAdding` / `AllowUpdating` / `AllowDeleting` / `ConfirmDelete` | `[ListForm]` Toolbar ve satır aksiyonları (`EditingOptionDto`). | | `AllowAdding` / `AllowUpdating` / `AllowDeleting` / `ConfirmDelete` | `[Kısayol]` Toolbar ve satır aksiyonları; `Editing` bloğunun aynı adlı alanlarına yazılır. |
| `SeedData` | `[ListForm]` `true` ise `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`. | | `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` | `[ListForm]` `SeedData` açıkken `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. Okurken üç bayraktan biri açıksa `true`; tek tek ayar `ListForm` bölümünde yapılır. | | `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` | `[ListForm]` Satırda **Detay** düğmesi çıkar; `/admin/form/{ListFormCode}/{anahtar}` adresini yeni sekmede açar — aynı ekranın tek kayıtlık form görünümüdür. `KeyFieldName` boşsa çalışmaz. **`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. | | `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` | `[ListForm]` 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). | | `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` | `[ListForm]` `"grid"`, `"card"`, `"pivot"`, `"chart"`, `"tree"`, `"gantt"`, `"scheduler"`, `"todo"`. | | `DefaultLayout` | `[Layout]` `"grid"`, `"card"`, `"pivot"`, `"chart"`, `"tree"`, `"gantt"`, `"scheduler"`, `"todo"`. |
| `Grid` `Card` `Pivot` `Chart` `Tree` `Gantt` `Scheduler` `Todo` | `[ListForm]` Hangi görünümlerin açık olduğu (`LayoutDto`). **Liste ekranında `Grid`/`Card`/`Pivot`/`Chart` varsayılan `true`**, diğer dördü `false` (§9.8). Açtığın her görünümün option bloğunu da doldur. | | `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. |
**Görünüm option blokları** — `[ListForm]`; yalnızca ilgili bayrak `true` ise anlamlıdır: **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 | | Blok | Zorunlu alanlar |
| --- | --- | | --- | --- |
@ -1367,14 +1432,16 @@ atanmaz (admin dahil); ekranı görecek roller yetkiyi rol ekranından alır.
`Groups`, hem grid sütunlarını hem düzenleme formunun düzenini tanımlar. Buradaki her `Items` `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 öğesi bire bir bir **`ListFormField` kaydına** dönüşür (§4.8); ekranda görünen sütunların tek
kaynağı odur. Grup yapısı yalnızca düzenleme formunun yerleşimini belirler — grid sütun sırası kaynağı odur. Grup yapısı düzenleme formunun yerleşimini belirler (form sırası = öğenin gruptaki
`Items` sırasından gelir. 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 ```jsonc
"Groups": [ "Groups": [
{ {
"Caption": "Genel", "Caption": "Genel",
"ColCount": 2, "ColCount": 2,
"ColSpan": null, // grubun formda kapladığı kolon; null ise ColCount
"Items": [ /* WizardColumnItemInputDto */ ] "Items": [ /* WizardColumnItemInputDto */ ]
} }
] ]
@ -1384,30 +1451,46 @@ Grup başlığı yalnızca düzenleme formunda **birden fazla grup** varsa yazı
başlık popup başlığını tekrarlar ve boş bırakılır. MCP `deploy_wizard` bunu kendisi uygular: 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`). düzenleme formuna giren tek grup varsa `Caption` boşaltılır (`McpDevelopAppService.DeployWizardAsync`).
Seed dosyasında `Groups` **kırpılmış** hâlde durur: grup başlığı/kolon sayısı ve alanların Seed dosyasında her öğe **alan detayının tamamını** taşır: alan editörünün her sekmesi öğede bir
editör bilgileri `ListForm.EditingFormDto` ve `Fields` içinde olduğu için tekrarlanmaz; dosyada alan ya da bloktur (`WizardColumnItemInputDto`); öğe kırpılmaz. Dosyada ilk dört anahtar
her öğe yalnızca `FieldName`, `CaptionName` ve `En`/`Tr` taşır — çünkü `FieldName`, `CaptionName`, `En`, `Tr`'dir (C#'ta `EnglishCaption`/`TurkishCaption`), geri kalanı
alan başlıklarının dil metinlerinin başka kaynağı yoktur. Okurken tümü geri doldurulur. 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: Her `Items` öğesi (`[Item]`):
| Alan | Anlam | | Alan | Anlam |
| --- | --- | | --- | --- |
| `FieldName` | Veri kolonunun adı (SQL'deki adıyla birebir). | | `FieldName` | Veri kolonunun adı (SQL'deki adıyla birebir). |
| `CaptionName` | Dil anahtarı ya da düz başlık. | | `CaptionName` | Dil anahtarı ya da düz başlık. |
| `En` / `Tr` | Alan başlığının dil metinleri; ikisi de doldurulur. | | `En` / `Tr` | Alan başlığının dil metinleri; ikisi de doldurulur. |
| `EditorType` | `[Fields]` Bkz. §3. | | `EditorType` | Bkz. §3. |
| `EditorOptions` | `[Fields]` DevExtreme editör JSON'u — §6. | | `EditorOptions` | DevExtreme editör JSON'u — §6. |
| `EditorScript` | `[Fields]` Alan davranış script'i — §5. | | `EditorScript` | Alan davranış script'i — §5. |
| `DbSourceType` | `[Fields]` Bkz. §3. | | `DbSourceType` | Bkz. §3. |
| `IsRequired` | `[Fields]` Zorunluluk (`ValidationRuleDto`). | | `IsRequired` | `[Kısayol]` Zorunluluk: deploy `ValidationRules`'a `required` kuralı ekler/çıkarır; dosyaya yazılmaz, okurken kural listesinden türetilir. |
| `IncludeInEditingForm` | `[Fields]` `false` ise yalnızca grid sütunu olur, forma girmez (`EditGroupOrderNo`). | | `IncludeInEditingForm` | `false` ise yalnızca grid sütunu olur, forma girmez (`EditGroupOrderNo`). |
| `ColSpan` | `[Fields]` Formda kaç kolon kaplar (grubun `ColCount` değeri içinde). | | `ColSpan` | Formda kaç kolon kaplar (grubun `ColCount` değeri içinde). |
| `LookupDataSourceType` | `[Fields]` `1/2/3` — bkz. §3. Lookup yalnızca `LookupQuery` doluysa üretilir; wizard ekranı tipi `1` ile açar. | | `LookupDataSourceType` | `1/2/3` — bkz. §3. Lookup yalnızca `LookupQuery` doluysa üretilir; wizard ekranı tipi `1` ile açar. |
| `LookupQuery` | `[Fields]` `Query` tipinde SQL; `StaticData` tipinde JSON dizisi; `WebService` tipinde URL. | | `LookupQuery` | `Query` tipinde SQL; `StaticData` tipinde JSON dizisi; `WebService` tipinde URL. |
| `ValueExpr` / `DisplayExpr` | `[Fields]` Lookup'ın değer ve etiket kolonları (açıkça `null` verilirse `null` yazılır). | | `ValueExpr` / `DisplayExpr` | Lookup'ın değer ve etiket kolonları (açıkça `null` verilirse `null` yazılır). |
| `DefaultValue` | `[Fields]` Yeni kayıt varsayılanı; `FormFieldsDefaultValueJson`'a `CustomValueType: 1` olarak yazılır (§4.3.5.1). | | `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` | `[Fields]` Bağımlı liste (§4.3.4). Wizard ekranında `DefaultValue` ve `Cascade*` için giriş yoktur; MCP/seed girdisiyle gelir, düzenlemede korunur. | | `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 ### 4.3.1 Sütunlar nasıl otomatik üretilir
@ -1567,7 +1650,7 @@ yazılır (`WizardConsts.DefaultLookupJson`): `CascadeParentFields` ve `CascadeE
`CascadeRelationField` ile `CascadeFilterOperator` yalnızca `CascadeParentFields` doluysa yazılır ve `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'ı 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`'ı olmayan bir ebeveyn alana wizard girdisiyle `CascadeEmptyFields` yazılamaz; böyle bir alanın `LookupJson`'ı
`ListForm`/`Fields` katmanında elle verilir (§4.4). Wizard ekranında bu alanların 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; 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: cascade eksik yetenek değildir ve analizde `Ready to Start`'ı engellemez. Tam sözleşme:
@ -1774,11 +1857,11 @@ yukarıdaki kolonlar tabloda bu `DEFAULT`'larla kurulur — MCP'de `create_table
`defaultValue: "1"` / `"'Adet'"` / `"'TRY'"` / `"60"`. `defaultValue: "1"` / `"'Adet'"` / `"'TRY'"` / `"60"`.
Alana özel değer (tablo `DEFAULT`'undan farklı) wizard öğesinin **`DefaultValue`** cevabıyla verilir Alana özel değer (tablo `DEFAULT`'undan farklı) wizard öğesinin **`DefaultValue`** cevabıyla verilir
(`Groups[].Items[].DefaultValue`); dosyada `ListForm.FormFieldsDefaultValueDto`'da durur ve okurken (`Groups[].Items[].DefaultValue`); dosyada da öğede durur. Alan kaydındaki
öğeye geri döner. Alan kaydındaki `DefaultValue`/`DefaultValueType` select servisinin hesapladığı `DefaultValue`/`DefaultValueType` select servisinin hesapladığı değerdir, saklanmaz. `CustomKey`,
değerdir, saklanmaz: MCP'de `Fields[].DefaultValue` yazılırsa deploy onu öğenin `DefaultValue`'suna `DbQuery`, `Sequence` gibi sabit olmayan türler `Wizard.FormDefaults` listesine yazılır (§4.2); deploy
taşır. `CustomKey`, `DbQuery`, `Sequence` gibi diğer türler `ListForm.FormFieldsDefaultValueDto` önce öğelerin sabitlerini grup sırasıyla, sonra `FormDefaults`'u birleştirir, aynı alan için açık
katmanıyla yazılır (§0.10). Deploy girdisinde sözleşmede karşılığı olmayan bir anahtar adıyla liste kazanır (`BuildFormDefaults`). Deploy girdisinde sözleşmede karşılığı olmayan bir anahtar adıyla
reddedilir — sessizce yok sayılmaz. reddedilir — sessizce yok sayılmaz.
#### 4.3.5.2 Wizard / MCP ekranında otomatik numara — `Adm.Definitions.Sequence` #### 4.3.5.2 Wizard / MCP ekranında otomatik numara — `Adm.Definitions.Sequence`
@ -1792,7 +1875,7 @@ da forma yazılmaz. Numara/kod alanı otomatik değilse (kullanıcı girer) kura
`PaddingSize`, `StartNumber`/`NextNumber`, `ResetPeriod`, `FormatTemplate` — §9.9; `Id` iş anahtarından `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 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. varsa (`query_list Adm.Definitions.Sequence`) o kullanılır, ikincisi açılmaz.
2. Alanın varsayılanı `ListForm.FormFieldsDefaultValueDto`'ya `CustomValueType: 5` ile yazılır; 2. Alanın varsayılanı `Wizard.FormDefaults` listesine `CustomValueType: 5` ile yazılır;
`Value` numaratörün `Name`'idir: `Value` numaratörün `Name`'idir:
```jsonc ```jsonc
@ -1863,9 +1946,10 @@ koşulu `DefaultFilter`'a token'la yazılır (`"CreatorId" = '@USERID'`).
**Kontrol (teslimden önce):** her komut metninden `@\w+` adları çıkarılır ve her biri tablodaki bir kaynağa **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 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 `ListForm.InsertFieldsDefaultValueDto` / parametre varsa ekran teslim edilmez. Wizard dosyasında listeler `Insert.FieldsDefaultValue` /
`UpdateFieldsDefaultValueDto` / `DeleteFieldsDefaultValueDto` adıyla durur; MCP `deploy_wizard`'da komutla aynı `Update.FieldsDefaultValue` / `Delete.FieldsDefaultValue` adıyla, komutla (`Command`/`BeforeCommand`/`AfterCommand`)
çağrının `ListForm` bölümünde verilir — komutu yazıp listeyi sonraya bırakmak yoktur. 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` ### 4.3.6 Doğrulama — `ValidationRuleJson`
@ -1947,10 +2031,12 @@ varsayılan değeri olur. Grid, Tree ve Card'da çizilir (Gantt, Scheduler, Todo
Aşağıdaki **düz** biçim wizard ekranının POST ettiği ve MCP `deploy_wizard`'ın aldığı cevaptır 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 (`ListFormWizardDto`; `Groups[].Items[]` = `WizardColumnItemInputDto`, `En`/`Tr` = C#'taki
`EnglishCaption`/`TurkishCaption`). Okuyucu `FromSeedJson` olduğu için `Menu`/`Language` blokları `EnglishCaption`/`TurkishCaption`). Okuyucu `FromDeployJson` olduğu için seed dosyası biçimi
ya da `WizardCode`/`ModuleId` adları da kabul edilir. Deploy bunu okuyup ekranı kurar ve §4.1 (`{"Wizard": {...}}`, `Menu`/`Language`/`Data`/`Details`/`Layout` başlıkları, `WizardCode`/`ModuleId`
biçimindeki dosyayı yazar; `IsDeletedField`/`IsCreatedField` tablodaki `IsDeleted`/`CreatorId` adları) da kabul edilir; `ListForm`/`Fields` bölümü reddedilir. Yazılmayan blok deploy'da varsayılanıyla
kolonlarından, `InsertedRecords` gerçekten yaratılan kayıtlardan **sunucu tarafından** doldurulur — 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. cevaba yazılmaz.
```jsonc ```jsonc
@ -2056,9 +2142,10 @@ cevaba yazılmaz.
} }
``` ```
Deploy sonrası dosyada `Wizard` bölümü `Menu`/`Language`/`Groups` başlıklarına toplanır ve Deploy sonrası dosyada `Wizard` bölümü `Menu`/`Language`/`Data`/`Details`/`Layout` başlıklarına
yalnızca `FieldName`/`CaptionName`/`En`/`Tr` kalır; ekranın kendisi `ListForm` + `Fields` toplanır, boş bırakılan bloklar varsayılanlarıyla dolu yazılır ve her öğe alan detayının tamamını
bölümlerine düşer (§4.1). `SeededAt` ilk deploy anında sunucu tarafından yazılır. 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 > `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`) > tutarlı** olmalıdır: açılışta çalışan bir kural varsa (hesap tarifleri varsayılan `both`)
@ -2072,10 +2159,11 @@ bölümlerine düşer (§4.1). `SeededAt` ilk deploy anında sunucu tarafından
### 4.4.1 Deploy'un ve seeder'ın senin yerine ürettikleri — cevaba yazma ### 4.4.1 Deploy'un ve seeder'ın senin yerine ürettikleri — cevaba yazma
Aşağıdakileri deploy wizard cevabından **kendisi** üretir (`ListFormWizardDto.BuildListForm` / Aşağıdakileri deploy wizard cevabından **kendisi** üretir (`ListFormWizardDto.ApplyDefaults` →
`BuildFields`; menü, yetki ve dil satırları `ListFormWizardAppService` → `WizardSeedRowStore`). `BuildListForm` / `BuildFields`; menü, yetki ve dil satırları `ListFormWizardAppService` →
Ekrana ait olanlar dosyanın `ListForm`/`Fields` bölümlerine düşer; `WizardDataSeeder` migrate'te `WizardSeedRowStore`). Üretilen ekran `RefreshFrom` ile `Wizard` bölümüne geri yazılır;
ekranı oradan olduğu gibi kurar (`GridOptionsEditDto.ApplyTo`), yeniden türetmez. Menü, yetki ve `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` 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: ile ekler (§4.0). Sıra değerleri veritabanı ile dosyadaki satırların birleşiminden hesaplanır:
@ -2092,11 +2180,11 @@ ile ekler (§4.0). Sıra değerleri veritabanı ile dosyadaki satırların birle
| `EditingFormJson` | `Groups` içinden; anahtar alan ve `IncludeInEditingForm: false` olanlar hariç | | `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) | | 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 | | `FilterRowJson`, `HeaderFilterJson`, `SearchPanelJson`, `GroupPanelJson`, `ColumnOptionJson`, `PagerOptionJson`, `ExportJson` | Varsayılanlar |
| `DeleteCommand` | `IsDeletedField: true` ise `WizardConsts.DefaultDeleteCommand(SelectCommand)` (soft delete `UPDATE`); `false` ise `null` | | `Delete.Command` | Tabloda `IsDeleted` varsa `WizardConsts.DefaultDeleteCommand(SelectCommand)` (soft delete `UPDATE`); yoksa `null` |
| `DefaultFilter` | `IsDeletedField: true` ise `"IsDeleted" = 'false'` | | `Data.DefaultFilter` | Tabloda `IsDeleted` varsa `"IsDeleted" = 'false'` |
| `InsertFieldsDefaultValueJson` | `IsCreatedField: true` ise `CreationTime=@NOW`, `CreatorId=@USERID`, `IsDeleted=false`, `Id=@NEWID`; değilse yalnızca `Id=@NEWID` | | `Insert.FieldsDefaultValue` | Tabloda `CreatorId` varsa `CreationTime=@NOW`, `CreatorId=@USERID`, `IsDeleted=false`, `Id=@NEWID`; değilse yalnızca `Id=@NEWID` |
| `DeleteFieldsDefaultValueJson` | `IsDeletedField: true` ise `DeleterId=@USERID`, `Id=@ID`; değilse yalnızca `Id=@ID` | | `Delete.FieldsDefaultValue` | Tabloda `IsDeleted` varsa `DeleterId=@USERID`, `Id=@ID`; değilse yalnızca `Id=@ID` |
| `UpdateFieldsDefaultValueJson` | 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 | | `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ı | | `ValidationRuleJson` | `IsRequired: true` olan alanlara required kuralı |
| `LookupJson` | `LookupQuery` doluysa `LookupDataSourceType` + `DisplayExpr` + `ValueExpr` ile | | `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ı | | `ShowNote` | Kayıt kaynağı Table ise ve alt form veya iş akışı varsa açılır; Table dışı kaynakta hep kapalı |
@ -2112,8 +2200,9 @@ ile ekler (§4.0). Sıra değerleri veritabanı ile dosyadaki satırların birle
anahtar kolonun `ListFormField` kaydı hiç oluşmaz ve satır anahtarı okunamadığı için düzenleme, 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 = 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. false`) ve forma girmez; tip bilgisi de oradan okunur.
- `IsDeletedField: false` verirsen ekranda **kalıcı silme** olmaz (`DeleteCommand` null kalır); - Tabloda `IsDeleted` kolonu yoksa ekranda **kalıcı silme** olmaz (`Delete.Command` null kalır);
soft delete kolonları olmayan tabloda silme istiyorsan ListForm editöründen komut yazmalısın. 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 - 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 tanımlanmıştır. Anahtar yalnızca veritabanında varsa (seed dosyasında satırı yoksa) deploy onu
veritabanındaki metniyle `Sas.Languages.LanguageText.json`'a taşır; sahipliğini almaz. veritabanındaki metniyle `Sas.Languages.LanguageText.json`'a taşır; sahipliğini almaz.
@ -2152,13 +2241,13 @@ Sonuç: **kurulu bir ekranı seed dosyasını elle düzenleyerek değiştiremezs
deploy'dur). Değiştirmenin yolları: deploy'dur). Değiştirmenin yolları:
1. **ListForm editörü** (`/admin/listform/edit/{kod}`) — alan ekleme, JSON kolonları, komutlar, 1. **ListForm editörü** (`/admin/listform/edit/{kod}`) — alan ekleme, JSON kolonları, komutlar,
görünümler. Her kayıt `WizardSeedSynchronizer` ile aynı dosyanın `ListForm`/`Fields`/`Wizard` görünümler. Her kayıt `WizardSeedSynchronizer` ile aynı dosyanın `Wizard` bölümüne
bölümlerine geri yazılır; **ince ayar için tercih edilen yol** budur. (`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ü, 2. **`EditFileName` ile yeniden deploy** (wizard ekranı ya da MCP `replaceFileName`) — menü,
yetki grubu, veri kaynağı gibi wizard cevabına ait değişiklikler için. Dosyalar yeniden yazılır, yetki grubu, veri kaynağı gibi değişiklikler için. Dosyalar yeniden yazılır, ekran DB Migrate'te
ekran DB Migrate'te cevaptan yeniden kurulur (§4.0); editörde yapılmış, wizard'da karşılığı cevaptan yeniden kurulur (§4.0). Ekranın her ayarı cevabın bir bloğu olduğu için editör ayarları
olmayan ayarlar kaybolur — MCP'de `get_wizard` çıktısının `ListForm`/`Fields` bölümleri de korunur: wizard ekranı dosyayı yükler, MCP'de `get_wizard` çıktısı düzenlenip gönderilir; cevapta
gönderilirse korunur (§0.10). hiç yazılmayan blok varsayılana döner (§0.10).
3. İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır. 3. İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır.
> `List` yolunda seeder ekranı uygularken `ListForm`, `ListFormField` ve `ListFormWorkflow` > `List` yolunda seeder ekranı uygularken `ListForm`, `ListFormField` ve `ListFormWorkflow`
@ -2317,7 +2406,7 @@ seçili satıra bağlı çizilmesi için ana ekranda `SubFormsListFormType: "Lis
menüsüz `Form` sekmeleridir; her biri yalnızca kendi alanlarını taşır, ilişki `Id → Id`'dir. Satır 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 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 `AllowDeleting` kapalı yazılır (eklemek tabloya ikinci bir ana kayıt açardı). Taşınan alanlar ana
ekranın `Fields`'ından çıkar; yeni kayıt önce ana formda oluşur, sekmeler kayıttan sonra açılır. 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 Tek formda sekme şeridi isteniyorsa (alt form yerine) grupların `ItemType`'ı `tabbed` yazılır
(`lowcode-reference` §3.2). (`lowcode-reference` §3.2).
- **`TabType: "List"` — ana kayda bağlı çok kayıt:** `AllowAdding`/`AllowUpdating`/`AllowDeleting` - **`TabType: "List"` — ana kayda bağlı çok kayıt:** `AllowAdding`/`AllowUpdating`/`AllowDeleting`
@ -2664,9 +2753,9 @@ Grid'in araç çubuğuna ve satır aksiyon sütununa **özel butonlar** eklenir.
`ListForm.CommandColumnJson` bir `CommandColumnDto[]` taşır. Wizard cevabında (§4.4) ve MCP `ListForm.CommandColumnJson` bir `CommandColumnDto[]` taşır. Wizard cevabında (§4.4) ve MCP
`deploy_wizard` girdisinde aynı dizi **`CommandColumns`** alanıdır; deploy onu `deploy_wizard` girdisinde aynı dizi **`CommandColumns`** alanıdır; deploy onu
`CommandColumnJson`'a yazar, seed dosyasında `ListForm` bölümünde durur ve düzenlemede korunur. `CommandColumnJson`'a yazar, seed dosyasında `Wizard.CommandColumns` olarak durur ve düzenlemede
Wizard ekranında bu alanın adımı yoktur; buton ekranın detayındaki **Komutlar** sekmesinden ya da korunur. Wizard ekranında **Düzenleme** adımının Komutlar sekmesinden, ekranın detayındaki
`deploy_wizard` ile yazılır. **Komutlar** sekmesinden ya da `deploy_wizard` ile yazılır.
```jsonc ```jsonc
[ [
@ -2793,15 +2882,26 @@ bölümünü doldurur.** Keşif turu (§0.6) soruları da bu adımlarla bire bir
| 0 | Komponent Tipi | her zaman | `ComponentKind` (`0` List · `1` Custom) | | 0 | Komponent Tipi | her zaman | `ComponentKind` (`0` List · `1` Custom) |
| 1 | Custom Component | yalnız Custom yolu | `CustomComponentName`, `MenuUrl` (bileşenin `RoutePath`'i) | | 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` | | 2 | Menü | her zaman | `WizardName`, `CreateMenu`, `MenuParentCode` (ekranda eksik seviyeler menü diyaloğuyla kurulur; MCP `MenuParentPath` yazar), `MenuCode`, `Language.MenuEn/Tr`, `PermissionGroupName` (+ görünen adlar), `MenuIcon`, `MenuOrder` |
| 3 | Ayarlar | yalnız List yolu | `ListFormCode`, `DataSourceCode` (+`DataSourceConnectionString`), `SelectCommandType`, `SelectCommand`, `KeyFieldName`, `KeyFieldDbSourceType`, `IsTenant/IsBranch/IsOrganizationUnit`, `AllowAdding/Updating/Deleting`, `ConfirmDelete`, `AllowDetail`, `OpenDetailAfterInsert`, `DefaultLayout` + 8 görünüm bayrağı (`SeedData` adımda sorulmaz, düzenlemede korunur), `Language.TitleEn/Tr`, `Language.DescEn/Tr` | | 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) | | 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 | Alt Formlar | yalnız List | `SubForms[]` | | 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 | Widget'lar | yalnız List | `Widgets[]` | | 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 | İş Akışı | yalnız List | `WorkflowDto` | | 7 | Alt Formlar | yalnız List | `SubForms[]` |
| 8–11 | Todo / Tree / Gantt / Scheduler | List + ilgili bayrak açıksa | `TodoOptionDto` / `TreeOptionDto` / `GanttOptionDto` / `SchedulerOptionDto` | | 8 | Widget'lar | yalnız List | `Widgets[]` |
| 12 | Deploy | her zaman | — (özet + üretim; dosyaya alan yazmaz) | | 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) |
Custom yolunda 3–11 adımları **hiç görünmez**; menü + yetki + dil + bileşen bağlama üretilir. 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 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ıma döner.
@ -2839,15 +2939,23 @@ taşır. Sihirbazın kendi otomatikleri — dosya yazarken aynısını uygula:
| `ValueExpr`/`DisplayExpr` | `Key` / `Name` | | `ValueExpr`/`DisplayExpr` | `Key` / `Name` |
Alan kartındaki **Seçenekler** düğmesi Editor Options Builder'ı (§6), **Script** düğmesi Script 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. 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 7 — İş Akışı.** Zorunlu ikili: `ApprovalUserFieldName` + `ApprovalStatusFieldName`; **Adım 9 — İş Akışı.** Zorunlu ikili: `ApprovalUserFieldName` + `ApprovalStatusFieldName`;
opsiyonel `ApprovalDateFieldName`, `ApprovalDescriptionFieldName`, `ApprovalIsFilterUserName` opsiyonel `ApprovalDateFieldName`, `ApprovalDescriptionFieldName`, `ApprovalIsFilterUserName`
(liste yalnız sıradaki onaycıya düşsün), `ApprovalIsResetWorkflow` (toolbar'a Taslak (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` 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). kayıtlarına gider (§4.7).
**Adım 12 — Deploy.** Sol tarafta tüm adımların özeti, sağda üretim log'u. Log, deploy'un seed **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ı, 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): ardından `ListFormSeedDataApplier` menü/yetki/dil satırlarını kurar (§4.0):
@ -3745,7 +3853,6 @@ Bileşen tek başına bir ekran değildir; menü ve yetki Wizard'ın `Custom` yo
"MenuEn": "Order Board", "MenuTr": "Sipariş Panosu", "MenuEn": "Order Board", "MenuTr": "Sipariş Panosu",
"MenuIcon": "FcKanban", "MenuOrder": 2 "MenuIcon": "FcKanban", "MenuOrder": 2
}, },
"IsDeletedField": false, "IsCreatedField": false,
"InsertedRecords": { "InsertedRecords": {
"LanguageKeys": ["Sas.Mrp.OrderBoard"], "LanguageKeys": ["Sas.Mrp.OrderBoard"],
"PermissionGroupNames": [], "PermissionGroupNames": [],
@ -3757,8 +3864,8 @@ Bileşen tek başına bir ekran değildir; menü ve yetki Wizard'ın `Custom` yo
``` ```
Yukarıdaki düz cevap deploy girdisidir (§4.4); dosyada `Menu`/`Language` başlıklarına toplanır, Yukarıdaki düz cevap deploy girdisidir (§4.4); dosyada `Menu`/`Language` başlıklarına toplanır,
`ListForm: null`, `Fields: []` yazılır. Custom yolunda `ListForm` **üretilmez**; `Title`/`Desc` ekrana ait anahtarlar (`Data`, `Details`, `Layout`, bloklar, `Groups`) hiç yazılmaz. Custom yolunda
dil anahtarları da üretilmez. ListForm kaydı **üretilmez**; `Title`/`Desc` dil anahtarları da üretilmez.
`Export`/`Import`/`Note` yetkileri bu yolda yoktur. `Export`/`Import`/`Note` yetkileri bu yolda yoktur.
> **Güvenlik — route yetkisiz üretilir.** Custom component route'ları `RoutePath` değerinden > **Güvenlik — route yetkisiz üretilir.** Custom component route'ları `RoutePath` değerinden
@ -3826,7 +3933,7 @@ ileri geçiş koşulludur: çözülmemiş çakışma varken Kontroller'e, engell
Yapılacaklar'a geçilmez. Yapılacaklar'a geçilmez.
Engelleyici bulgular kaynağına göre ikiye ayrılır (`WizardImportIssueDto.RequiresReexport`): Engelleyici bulgular kaynağına göre ikiye ayrılır (`WizardImportIssueDto.RequiresReexport`):
arşivin kendi eksikliği/bozukluğu — bozuk JSON, eksik `Wizard`/`ListForm` bölümü, karşılığı olmayan 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 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 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; düğmesi olmadan gösterir. `WizardImportValidator.Add` üzerinden geçen her `Error` bu sınıftadır;
@ -3860,8 +3967,8 @@ dosyalardan süzerek taşır; import onları hedef dosyayla anahtar üzerinden b
**Analiz doğrulama da yapar** (`WizardImportValidator`) — dosyalar yazılmadan önce. En az bir **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 `Error` varken import başlatılamaz; sunucu oturuma `blocked` işaret dosyası bırakır, dolayısıyla
`ApplyImport` doğrudan çağrılsa bile reddeder. Error: bozuk JSON; `Wizard`/`ListForm` bölümü eksik `ApplyImport` doğrudan çağrılsa bile reddeder. Error: bozuk JSON; `Wizard` bölümü ya da List yolunda
wizard dosyası; ekranın ya da bir `crud/{Entity}.json`'un tablosu hedefte yok **ve** zip onu `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 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 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ı, bileşen adı ya da rota boş; alt form referansı karşılıksız. Warning: nesne varlığı sorgulanamadı,
@ -4268,7 +4375,7 @@ içindir.
ile tabloya bağlanır. Custom Component kullanacaksa `crud/{Entity}.json` de yaz (§9). ile tabloya bağlanır. Custom Component kullanacaksa `crud/{Entity}.json` de yaz (§9).
4. Wizard dosyasında `SelectCommand` = tablo adı, `KeyFieldName` = `Id`, 4. Wizard dosyasında `SelectCommand` = tablo adı, `KeyFieldName` = `Id`,
`KeyFieldDbSourceType` = tablodaki `Id` tipinin sayısı, `IsTenant` = tabloda `TenantId` varsa `KeyFieldDbSourceType` = tablodaki `Id` tipinin sayısı, `IsTenant` = tabloda `TenantId` varsa
`true`, `IsDeletedField`/`IsCreatedField` = ilgili kolonlar varsa `true`. Anahtar alanı `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). `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. 5. DB Migrate çalıştır; log'da `Executing: CREATE TABLE …` satırını gör.
@ -4279,7 +4386,7 @@ Bir artefakt şu kaynaklardan biri onu kullanıyorsa yaşar, hiçbiri kullanmıy
| Artefakt | Kullanılıyor sayılır, eğer… | | 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 `ListForm`/`Fields` bölümünde (kaynak, komut, lookup, widget, `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 | | 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 | | 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ı) | | `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 | | Dil anahtarı | bir ekran öğesi (alan başlığı, buton, form grubu, widget, menü, yetki) ya da kod/veri onu gösteriyor |
@ -5289,7 +5396,8 @@ isteniyorsa geçilir.
(`ApprovalUserName`, `ApprovalStatus`, `ApprovalDate`, `ApprovalDescription`; ayrı `Status` yok). (`ApprovalUserName`, `ApprovalStatus`, `ApprovalDate`, `ApprovalDescription`; ayrı `Status` yok).
Talep sahibi için de bir kolon (`EmployeeId`) bulunmalıdır. Talep sahibi için de bir kolon (`EmployeeId`) bulunmalıdır.
2. **Wizard dosyası** — §4.4'teki iskelet: 2. **Wizard dosyası** — §4.4'teki iskelet:
- `IsTenant: true` (tabloda `TenantId` var), `IsDeletedField: true`, `IsCreatedField: true` - `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` - `MenuParentCode` = modül kökü (`Hr`); yoksa wizard yaratır, `Order = max + 1`
- `PermissionGroupName` = `Hr`, EN + TR görünen adlarıyla - `PermissionGroupName` = `Hr`, EN + TR görünen adlarıyla
- Görünümler: varsayılan dörtlü (`Grid` + `Card` + `Pivot` + `Chart`, `DefaultLayout: "grid"`); - Görünümler: varsayılan dörtlü (`Grid` + `Card` + `Pivot` + `Chart`, `DefaultLayout: "grid"`);
@ -5370,7 +5478,8 @@ Prompt ile üretim yaptıysan ek olarak:
taşıyor. taşıyor.
- [ ] Menü ve route elle eklenmedi; wizard dosyasından geliyor. - [ ] Menü ve route elle eklenmedi; wizard dosyasından geliyor.
- [ ] List ekranı deploy ile üretildi (wizard ekranı / MCP `deploy_wizard`) ya da elle yazılan - [ ] List ekranı deploy ile üretildi (wizard ekranı / MCP `deploy_wizard`) ya da elle yazılan
dosya `ListForm` + `Fields` bölümlerini taşıyor (§4.1). 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. - [ ] 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 - [ ] 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. üretimden **önce** onaylatıldı — kullanıcı "sorma / sen seç" demedikçe.

View file

@ -331,28 +331,37 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
bloklar birebir aynıdır. İstemci iç içe blokları tek seviye birleştirdiği için seed edilen bloklar birebir aynıdır. İstemci iç içe blokları tek seviye birleştirdiği için seed edilen
`toolbar` varsayılanın yerine geçer; biri değişip diğeri değişmezse seed'li ekranlar eski araç `toolbar` varsayılanın yerine geçer; biri değişip diğeri değişmezse seed'li ekranlar eski araç
çubuğunu gösterir. çubuğunu gösterir.
- **Wizard seed dosyasında tekrar yok** → `Wizard` bölümü yalnızca ekrandan üretilemeyen bilgileri - **Wizard seed dosyası tek `Wizard` bölümüdür** → dosyada `ListForm`/`Fields` bölümü yoktur; ekranın
taşır. `ListForm`/`Fields` içinde karşılığı olan her alan (veri kaynağı, select, anahtar alan, bütün sözleşmesi `Wizard` bölümündedir. ListForm detayının her sekmesi orada bir bloktur (`Editing`,
kırılım/düzen/düzenleme bayrakları, görünüm option blokları, alt form, widget, iş akışı, alan `Insert`/`Update`/`Delete`, `FormDefaults`, `Permission`, `FilterRow` … `PivotOption`, görünüm option
tanımları) dosyaya yazılmadan çıkarılır (`WizardSeedFileDto.ToSeedJson`) ve okurken geri blokları, `ExtraFilters`, `CommandColumns`, `SubForms`, `Widgets`, `WorkflowDto`, `ChartOptions`), alan
doldurulur (`FromSeedJson`). Wizard'a yeni bir alan eklerken karşılığı ListForm'da varsa listeye ekle (`CommandColumns` → detayının her sekmesi `Groups[].Items[]` öğesinin bir alanıdır (`WizardColumnItemInputDto`: `Filter`,
`CommandColumnJson` bu yolla taşınır; wizard ekranında adımı yoktur, UI modeli düzenlemede korur). `Header`, `Grouping`, `TotalSummary`/`GroupSummary`, `Join`, `ValidationRules`, `Styling`, `Permission`,
Dosyada kalan wizard alanları üç başlık altında toplanır — `Menu`, `Language`, `Groups` — ve `PivotSettings`, `ListOrderNo` …). Wizard alanları `Menu`, `Language`, `Data`, `Details`, `Layout`
`ListFormCode` → `WizardCode`, `MenuParentModuleId` → `ModuleId`, `LanguageTexts` → `Language.Texts` başlıklarına toplanır ve `ListFormCode` → `WizardCode`, `MenuParentModuleId` → `ModuleId`,
adıyla yazılır; varsayılanı olan alanlar (`CreateMenu: true`, boş `DataSourceConnectionString`, boş `LanguageTexts` → `Language.Texts` adıyla yazılır; varsayılanı olan alanlar (`CreateMenu: true`, boş
`MenuParentPath` / `LanguageTexts`) hiç yazılmaz. Bu yalnızca dosya biçimidir; C# `DataSourceConnectionString`, boş `MenuParentPath` / `LanguageTexts`) hiç yazılmaz. Kısayol cevaplar
sözleşmesi düzdür, dönüşüm `WizardSeedFileDto.ToSeedJson` / `FromSeedJson` içindedir. **Wizard (`AllowAdding/AllowUpdating/AllowDeleting/AllowDetail/OpenDetailAfterInsert/ConfirmDelete`,
seed dosyası her zaman bu iki metotla okunup yazılır**; düz `JsonSerializer` çağrısı bloklu `SeedData`/`SeedSync`, öğedeki `IsRequired`) **dosyaya yazılmaz**: `Editing`, `Data.SeedFilePath`/`SeedSync*`
bölümleri sessizce düşürür. ve `ValidationRules`'tan türetilir (`SyncDerivedAnswers`); deploy girdisinde yazılırsa bloğu ezer. Bu yalnızca
- **Wizard ile üretilen ekran** → seed dosyası (`configs/seeds/wizard/{WizardName}.json`) dosya biçimidir; C# sözleşmesi düzdür, dönüşüm `WizardSeedFileDto.ToSeedJson` / `FromSeedJson` içindedir.
wizard cevaplarının yanında ekranın `GridOptionsEditDto` (`ListForm`) ve alanların **Wizard seed dosyası her zaman bu iki metotla okunup yazılır**; düz `JsonSerializer` çağrısı başlıkları
`ColumnFormatEditDto` (`Fields`) sözleşmesini taşır. ListForm detayından yapılan her kayıt ve türetilen kısayolları sessizce bozar. `FromSeedJson` eski biçimin `ListForm`/`Fields` bölümlerini
(`ListFormsAppService`, `ListFormFieldsAppService`, `ListFormJsonRowAppService`) bu bölümleri okuyup `RefreshFrom` ile `Wizard`'a aktarır; bir sonraki yazımda düşerler.
ve `Wizard` bölümündeki karşılıklarını geri yazar (`WizardSeedSynchronizer`). Bu üçlüden birine - **Wizard ile üretilen ekran** → ListForm detayından yapılan her kayıt (`ListFormsAppService`,
yeni bir kayıt yolu eklenirse senkronizasyon çağrısı da eklenir; yoksa dosya bayatlar. `ListFormFieldsAppService`, `ListFormJsonRowAppService`) seed dosyasının `Wizard` bölümünü
- **Wizard girdisinden ekran üretimi** → tek kaynak `ListFormWizardDto.BuildListForm/BuildFields`, `ListFormWizardDto.RefreshFrom(listForm, fields)` ile geri yazar (`WizardSeedSynchronizer`). Bu üçlüden
entity'ye yazma `GridOptionsEditDto.ApplyTo` / `ColumnFormatEditDto.ApplyTo`. Deploy, seeder ve birine yeni bir kayıt yolu eklenirse senkronizasyon çağrısı da eklenir; yoksa dosya bayatlar.
senkronizasyon aynı metotları çağırır; ikinci bir üretim kopyası yazma. **Eşlik kuralı:** `GridOptionsEditDto`/`ColumnFormatEditDto`'ya eklenen her alanın
`ListFormWizardDto`/`WizardColumnItemInputDto`'da karşılığı + `RefreshFrom` + `BuildListForm/BuildFields`
eşlemesi olmak zorundadır; yoksa tasarımcıda yapılan değişiklik migrate'te kaybolur.
- **Wizard girdisinden ekran üretimi** → tek hat `ListFormWizardDto.ApplyDefaults(isDeleted, isCreated,
isModified)` (boş blokları `WizardDefaults` + `WizardConsts` ile doldurur; yetki adları, popup başlığı,
durum anahtarı ekran kodundan yeniden türetilir) → `BuildListForm()` / `BuildFields()` (salt eşleme,
parametresiz) → `RefreshFrom` (üretilen ekran cevaplara geri yazılır, dosya öyle kaydedilir); entity'ye
yazma `GridOptionsEditDto.ApplyTo` / `ColumnFormatEditDto.ApplyTo`. Deploy, seeder (`WizardDataSeeder`
dosyadan `Wizard.BuildListForm()/BuildFields()`) ve senkronizasyon aynı metotları çağırır; ikinci bir
üretim kopyası yazma.
- **Katman sınırı (DDD)** → `DbMigrator` Application'a referans vermez. Seeder ile AppService'in - **Katman sınırı (DDD)** → `DbMigrator` Application'a referans vermez. Seeder ile AppService'in
paylaştığı dönüşüm `Application.Contracts`'ta DTO metodudur; entity'ye yazma Domain.Shared'deki paylaştığı dönüşüm `Application.Contracts`'ta DTO metodudur; entity'ye yazma Domain.Shared'deki
`IListFormArtifact` / `IListFormFieldArtifact` sözleşmeleri üzerinden yapılır. `IListFormArtifact` / `IListFormFieldArtifact` sözleşmeleri üzerinden yapılır.
@ -471,9 +480,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
düzeltilebilen girdi (bilinmeyen seçenek, boş/tekrar eden index) sessizce normalize edilir. düzeltilebilen girdi (bilinmeyen seçenek, boş/tekrar eden index) sessizce normalize edilir.
Kolon adları tahmin edilmez: şema araçları geliştiriciye migrate bekleyen wizard ekranlarını ve Kolon adları tahmin edilmez: şema araçları geliştiriciye migrate bekleyen wizard ekranlarını ve
seed-only tabloları da döner (`McpSchemaAppService.GetSeedOnlySourcesAsync`). `deploy_wizard` seed-only tabloları da döner (`McpSchemaAppService.GetSeedOnlySourcesAsync`). `deploy_wizard`
girdisindeki `ListForm`/`Fields` bölümleri ekrana katman olarak uygulanır (`WizardSeedFileDto.FromDeployJson` girdisi dosya biçimi (`{"Wizard": {...}}`, başlıklı) ya da düz cevaptır (`WizardSeedFileDto.FromDeployJson`);
+ `WizardScreenOverlay`); `ListFormWizardDto`'ya editörde karşılığı olan alan eklenirse katman onu bloklar isteğe bağlıdır (yazılmayan blok `ApplyDefaults` ile dolar), kısayol cevap bloktan üstündür,
kendiliğinden taşır. eski biçimin `ListForm`/`Fields` bölümleri reddedilir (`Sas.ListForm.WizardError.LegacySectionsNotAccepted`);
`get_wizard` yeni biçimi döner.
- **MCP yetki vermez** → yeni bir MCP aracı kullanıcının uygulamadaki yetkisini aşamaz: liste verisi - **MCP yetki vermez** → yeni bir MCP aracı kullanıcının uygulamadaki yetkisini aşamaz: liste verisi
ekranın sorgusundan (`IListFormSelectAppService`/`IListFormDataAppService`) geçer; liste ekranın sorgusundan (`IListFormSelectAppService`/`IListFormDataAppService`) geçer; liste
yetkisini aşan bir yetenek (ham SQL gibi) `McpToolNames.RequiredPermissions`'a uygulamadaki yetkisini aşan bir yetenek (ham SQL gibi) `McpToolNames.RequiredPermissions`'a uygulamadaki
@ -569,8 +579,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
Kapanış (`CompleteImport`) bağımlılık sırasıyla çalışır: `sql/object` → `sql/execute` → `crud` Kapanış (`CompleteImport`) bağımlılık sırasıyla çalışır: `sql/object` → `sql/execute` → `crud`
→ `data` → `wizard`. Tablo eksikse önce scripti çalışır; sıra değiştirilmez. Ekran import'ta → `data` → `wizard`. Tablo eksikse önce scripti çalışır; sıra değiştirilmez. Ekran import'ta
**kurulmaz**: wizard dosyasına `Pending` yazılır ve ekran deploy'daki gibi DB Migrate'te kurulur; **kurulmaz**: wizard dosyasına `Pending` yazılır ve ekran deploy'daki gibi DB Migrate'te kurulur;
dosyadaki `ListForm`/`Fields` sözleşmesi olduğu gibi kullanılır, wizard cevaplarından yeniden dosyanın `Wizard` bölümündeki bloklar olduğu gibi uygulanır (`BuildListForm/BuildFields`), varsayılanlar
türetilmez — türetmek tasarımcı değişikliklerini silerdi. Wizard'ın menü/yetki/dil satırlarını yeniden üretilmez — tasarımcı değişiklikleri bloklarda durduğu için korunur. Wizard'ın menü/yetki/dil satırlarını
taşıyan platform dosyaları `data` adımında uygulanmaz (`WizardSeedRowStore.ListFormCodes`). taşıyan platform dosyaları `data` adımında uygulanmaz (`WizardSeedRowStore.ListFormCodes`).
Engelleyici bulgu iki sınıftır: **arşiv kaynaklı** (`RequiresReexport` — bozuk/eksik dosya, Engelleyici bulgu iki sınıftır: **arşiv kaynaklı** (`RequiresReexport` — bozuk/eksik dosya,
karşılığı olmayan referans) hedef ortamda giderilemez, export tekrarlanır; **ortam kaynaklı** karşılığı olmayan referans) hedef ortamda giderilemez, export tekrarlanır; **ortam kaynaklı**

View file

@ -545,7 +545,7 @@ listesi). Formlar içinde iş akışına bağlı özel butonlar gösterilebilir.
5. **Permission** kayıtları (`.Default`, `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`; `.Note` yalnızca kayıt kaynağı Table ise) 5. **Permission** kayıtları (`.Default`, `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`; `.Note` yalnızca kayıt kaynağı Table ise)
6. **Dil anahtarları** (menü, başlık, açıklama; EN + TR) 6. **Dil anahtarları** (menü, başlık, açıklama; EN + TR)
7. Gerekiyorsa **ayar/entegrasyon** bağımlılıkları 7. Gerekiyorsa **ayar/entegrasyon** bağımlılıkları
8. **Seed dosyası** — `configs/seeds/wizard/{Ad}.json` (wizard cevapları + üretilen `ListForm`/`Fields` sözleşmesi) 8. **Seed dosyası** — `configs/seeds/wizard/{Ad}.json` (tek `Wizard` bölümü: wizard cevapları = ekranın bütün sözleşmesi)
9. **Doğrulama ve geri alma** notları 9. **Doğrulama ve geri alma** notları
**Adım bileşenleri.** Adımlar numaralı dosya adları yerine işlevleriyle adlandırılır ve **Adım bileşenleri.** Adımlar numaralı dosya adları yerine işlevleriyle adlandırılır ve
@ -556,7 +556,8 @@ adım sayısından bağımsızdır.
| --- | --- | --- | | --- | --- | --- |
| Menü ve kimlik | `WizardStepMenu.tsx` | Wizard adı, ListForm kodu, menü ağacı/üst menü, ikon, sıra, izin grubu, dil metinleri | | Menü ve kimlik | `WizardStepMenu.tsx` | Wizard adı, ListForm kodu, menü ağacı/üst menü, ikon, sıra, izin grubu, dil metinleri |
| Veri ayarları | `WizardStepDataSettings.tsx` | Veri kaynağı, select komutu tipi, anahtar alan, CRUD endpoint diyaloğu | | Veri ayarları | `WizardStepDataSettings.tsx` | Veri kaynağı, select komutu tipi, anahtar alan, CRUD endpoint diyaloğu |
| Alanlar | `WizardStepFields.tsx` | Sütun grupları, editör tipleri, lookup, doğrulama | | Alanlar | `WizardStepFields.tsx` · `WizardFieldDetailDialog.tsx` | Sütun grupları, editör tipleri, lookup, doğrulama; kartta ek ayarlar (görünürlük/aktif/salt okunur/arama/düzenleme/ekleme, genişlik, hizalama, biçim, sıralama, varsayılan, yer tutucu, bant); ⚙ ile alan detayının bütün sekmeleri, sütun sırası paneli |
| Liste Ayarları · Düzenleme ve Veri · Grafik | `WizardStepScreenTabs.tsx` | ListForm editörünün sekmeleri taslak modunda (`edit/listFormDraftContext.ts`); eşleme `wizardScreen.ts` |
| Alt formlar | `WizardStepSubForms.tsx` | Ana–detay ilişki eşlemesi | | Alt formlar | `WizardStepSubForms.tsx` | Ana–detay ilişki eşlemesi |
| Widget'lar | `WizardStepWidgets.tsx` | KPI kartları | | Widget'lar | `WizardStepWidgets.tsx` | KPI kartları |
| İş akışı | `WizardStepWorkflow.tsx` | Onay/koşul grafiği | | İş akışı | `WizardStepWorkflow.tsx` | Onay/koşul grafiği |
@ -564,12 +565,24 @@ adım sayısından bağımsızdır.
| Bileşen | `WizardStepComponent.tsx` · `WizardStepCustomComponent.tsx` | Custom yolunda bağlanacak bileşen | | Bileşen | `WizardStepComponent.tsx` · `WizardStepCustomComponent.tsx` | Custom yolunda bağlanacak bileşen |
| Yayınlama | `WizardStepDeploy.tsx` | Özet, doğrulama ve seed dosyalarının üretimi; ardından Wizard Manager'a dönülür | | Yayınlama | `WizardStepDeploy.tsx` | Özet, doğrulama ve seed dosyalarının üretimi; ardından Wizard Manager'a dönülür |
Alanlar adımından sonra üç ek adım — **Liste Ayarları** (Detay, Sütunlar, Sıralama, Filtreleme, Satır,
Arama, Gruplama, Seçim, Sayfalama, Durum, Pivot), **Düzenleme ve Veri** (Düzenleme, Veritabanı
Select/Insert/Update/Delete, Butonlar, Ek Filtreler) ve **Grafik** (Chart görünümü açıkken, İş Akışı
ve düzen adımlarından sonra) — ListForm editörünün sekmelerini **taslak modunda** gösterir
(sekmede Kaydet yoktur; adıma girişte ilk sekme açılır); Alanlar adımındaki alan
kartı da alan detayının sekmelerini (Filtreleme, Başlık, Gruplama, Özetler, Join, Doğrulama, Stil,
CSS, Yetki, Pivot…) ve bir sütun sırası paneli açar. Değişiklikler veritabanına değil wizard
cevabına toplanır ve deploy'da dosyaya yazılır.
Menü ağacı yardımcıları (`menuTree.ts`) adım bileşenlerinden ayrı bir modüldedir; böylece Wizard Menü ağacı yardımcıları (`menuTree.ts`) adım bileşenlerinden ayrı bir modüldedir; böylece Wizard
ve `SqlTableDesignerDialog` bu fonksiyonları kullanırken adım bileşenlerini pakete çekmez. ve `SqlTableDesignerDialog` bu fonksiyonları kullanırken adım bileşenlerini pakete çekmez.
**Seed dosyası ekranın kendisini taşır.** Dosya `Wizard` bölümünün yanında ekranın **Seed dosyası ekranın kendisini taşır — tek `Wizard` bölümüyle.** ListForm detayının her sekmesi
`GridOptionsEditDto` sözleşmesini (`ListForm`) ve alanların `ColumnFormatEditDto` sözleşmesini `Wizard` bölümünde bir bloktur (`Editing`, `Insert`/`Update`/`Delete`, `FormDefaults`, `FilterRow` …
(`Fields`) tutar; `WizardDataSeeder` ekranı bu iki bölümden kurar. **Deploy veritabanına yazmaz:** `ChartOptions`), alan detayının her sekmesi `Groups[].Items[]` öğesinin bir alanıdır; ayrı
`ListForm`/`Fields` bölümü yoktur. Deploy `ListFormWizardDto.ApplyDefaults` ile boş blokları
doldurur, `BuildListForm()/BuildFields()` ile ekranı üretir ve `RefreshFrom` ile cevaplara geri yazar;
`WizardDataSeeder` ekranı aynı iki metotla dosyadan kurar. **Deploy veritabanına yazmaz:**
wizard dosyasının yanında menü, modül, izin grubu, yetki ve dil metinleri platform seed wizard dosyasının yanında menü, modül, izin grubu, yetki ve dil metinleri platform seed
dosyalarına (`data/Sas.Menus.{Menu,Module}.json`, `data/Abp.Identity.Permission{Groups,s}.json`, dosyalarına (`data/Sas.Menus.{Menu,Module}.json`, `data/Abp.Identity.Permission{Groups,s}.json`,
`data/Sas.Languages.LanguageText.json`) satır olarak yazılır (`WizardSeedRowStore`) ve hepsi DB `data/Sas.Languages.LanguageText.json`) satır olarak yazılır (`WizardSeedRowStore`) ve hepsi DB
@ -577,10 +590,11 @@ Migrate ile kurulur. Deploy/import dosyaya `Pending` yazar; Wizard Manager dosya
bekliyor" rozetiyle gösterir, migrate bitince liste ve menü yenilenir. Yetkiler hiçbir role bekliyor" rozetiyle gösterir, migrate bitince liste ve menü yenilenir. Yetkiler hiçbir role
otomatik atanmaz. Böylece Wizard ile ListForm otomatik atanmaz. Böylece Wizard ile ListForm
detayı aynı veri yapısını konuşur ve tasarımcıdaki değişiklikler wizard'a geri yazılabilir. detayı aynı veri yapısını konuşur ve tasarımcıdaki değişiklikler wizard'a geri yazılabilir.
`Wizard` bölümü yalnızca ekrandan üretilemeyen bilgileri (menü, yetki, dil metinleri, bileşen Wizard alanları dosyada `Menu`, `Language`, `Data`, `Details`, `Layout` başlıkları altında toplanır,
yolu, alan başlıklarının dil karşılıkları) taşır; `ListForm`/`Fields` içinde karşılığı olan alanlar bloklar ve `Groups` kök seviyededir (`WizardSeedFileDto.ToSeedJson` / `FromSeedJson`); kısayol cevaplar
dosyaya yazılmaz, okuma sırasında geri doldurulur. Kalanlar dosyada `Menu`, `Language` ve `Groups` (`AllowAdding` …, `SeedData`/`SeedSync`, öğedeki `IsRequired`) dosyaya yazılmaz, sahibi olan bloktan
başlıkları altında toplanır (`WizardSeedFileDto.ToSeedJson` / `FromSeedJson`). Dosya adı `{Ad}.json`'dır (zaman damgası yok); türetilir. Eski biçimdeki `ListForm`/`Fields` bölümleri okunurken `Wizard`'a aktarılır ve bir sonraki
yazımda düşer. Dosya adı `{Ad}.json`'dır (zaman damgası yok);
uygulama sırası `Wizard.SeededAt` damgasından gelir. MCP `deploy_wizard` dosyaya etkin talebi uygulama sırası `Wizard.SeededAt` damgasından gelir. MCP `deploy_wizard` dosyaya etkin talebi
`DevRequest` (`Id`, `Title`) olarak yazar; elle yapılan kayıt bu bağı korur, talepsiz wizard'da bölüm yoktur. `DevRequest` (`Id`, `Title`) olarak yazar; elle yapılan kayıt bu bağı korur, talepsiz wizard'da bölüm yoktur.
@ -730,9 +744,9 @@ Adımların altında yatan üç sunucu işlemi şunlardır:
5. **Ekran tanımları** (`wizard/*.json`) — ekran import'ta **kurulmaz**: dosyaya `Pending` yazılır, 5. **Ekran tanımları** (`wizard/*.json`) — ekran import'ta **kurulmaz**: dosyaya `Pending` yazılır,
Wizard Manager onu "Migrate bekliyor" gösterir ve ekran, deploy'daki gibi, DB Migrate'te Wizard Manager onu "Migrate bekliyor" gösterir ve ekran, deploy'daki gibi, DB Migrate'te
`WizardDataSeeder` tarafından kurulur (hedefte kurulu olsa da dosyadaki hâliyle). Dosya ekranın `WizardDataSeeder` tarafından kurulur (hedefte kurulu olsa da dosyadaki hâliyle). Ekranın bütün
üretilmiş `ListForm`/`Fields` sözleşmesini taşıdığı için bunlar wizard cevaplarından **yeniden ayarları dosyanın `Wizard` bölümündeki bloklardadır; seeder onları olduğu gibi uygular
türetilmez**. Wizard'ın menü/yetki/dil satırlarını taşıyan platform dosyaları 4. adımda (`BuildListForm/BuildFields`), **yeniden türetmez**. Wizard'ın menü/yetki/dil satırlarını taşıyan platform dosyaları 4. adımda
uygulanmaz; onları da migrate ekler. uygulanmaz; onları da migrate ekler.
**Analiz doğrulama da yapar** (`WizardImportValidator`). Zip'in hedef ortamda yarım bir kurulum **Analiz doğrulama da yapar** (`WizardImportValidator`). Zip'in hedef ortamda yarım bir kurulum
@ -743,7 +757,7 @@ dosyası bırakır, dolayısıyla `ApplyImport` ucu doğrudan çağrılsa bile r
| Kural | Ağırlık | | Kural | Ağırlık |
| --- | --- | | --- | --- |
| Bozuk JSON (`wizard`, `crud`, `data`) | Error | | Bozuk JSON (`wizard`, `crud`, `data`) | Error |
| Wizard bölümü ya da adı olmayan wizard dosyası; `ListForm` bölümü eksik List wizard'ı | Error | | Wizard bölümü ya da adı olmayan wizard dosyası; `Data` başlığı (`SelectCommand`/`KeyFieldName`) eksik List wizard'ı | Error |
| Ekranın tablosu (`SelectCommand`, `Table`/`View`) hedefte **yok** ve zip onu oluşturan `.sql` dosyasını **taşımıyor** | Error | | Ekranın tablosu (`SelectCommand`, `Table`/`View`) hedefte **yok** ve zip onu oluşturan `.sql` dosyasını **taşımıyor** | Error |
| `crud/{Entity}.json`'un tablosu hedefte yok ve zip'te oluşturan script yok | Error | | `crud/{Entity}.json`'un tablosu hedefte yok ve zip'te oluşturan script yok | Error |
| `data/{code}.json`'un ekranı ne zip'te ne hedefte var | Error | | `data/{code}.json`'un ekranı ne zip'te ne hedefte var | Error |
@ -1587,7 +1601,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| Araç | İş | | Araç | İş |
| --- | --- | | --- | --- |
| `server_info` | Bağlantının bağlamı (`scope`: `host` / `tenant`; kiracı adı `server.tenantName`), açık araçlar, veri sözlüğünün tazelik bilgisi. Sözlüğün `dictionaryKey` alanı (host'ta `Sozsoft`) sözlüğün yazıldığı anahtardır, kiracı bağlamını göstermez. Her zaman açıktır. | | `server_info` | Bağlantının bağlamı (`scope`: `host` / `tenant`; kiracı adı `server.tenantName`), açık araçlar, veri sözlüğünün tazelik bilgisi. Sözlüğün `dictionaryKey` alanı (host'ta `Sozsoft`) sözlüğün yazıldığı anahtardır, kiracı bağlamını göstermez. Her zaman açıktır. |
| `list_lists` | Listeler: kod, başlık, açıklama, arkasındaki tablo. Host'ta `Sas.Listforms.Wizard` yetkisi olan geliştiriciye veri sözlüğünde henüz olmayan kaynaklar da döner: migrate bekleyen wizard ekranları (`sourceKind: pending`, alanlar wizard dosyasının `Fields` bölümünden) ve yalnızca seed scripti olan tablolar (`pendingTable`, kolonlar scriptten). `describe_list` ve `search_schema` aynı kümeyi kullanır; model lookup/widget/view SQL'ini bu adlarla yazar. | | `list_lists` | Listeler: kod, başlık, açıklama, arkasındaki tablo. Host'ta `Sas.Listforms.Wizard` yetkisi olan geliştiriciye 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 seed scripti olan tablolar (`pendingTable`, kolonlar scriptten). `describe_list` ve `search_schema` aynı kümeyi kullanır; model lookup/widget/view SQL'ini bu adlarla yazar. |
| `describe_list` | Bir listenin sütunları: alan adı, görünen başlık, SQL tipi, lookup ilişkisi, `isSensitive`. | | `describe_list` | Bir listenin sütunları: alan adı, görünen başlık, SQL tipi, lookup ilişkisi, `isSensitive`. |
| `search_schema` | Liste ve sütun adları/başlıkları üzerinde aksan ve büyük/küçük harf duyarsız arama. | | `search_schema` | Liste ve sütun adları/başlıkları üzerinde aksan ve büyük/küçük harf duyarsız arama. |
| `query_list` | Listenin verisini ekranın kendi sorgusuyla (`GetSelectAsync`) okur: filtre, sıralama, gruplama, özet (DevExtreme biçimi). Sıralama/gruplama/özet JSON'u, arayüzün `Utils.ts`'te yaptığı gibi select ucunun metin biçimine (`Alan desc|Alan2 asc`) `McpLoadOptions` ile çevrilir; select ucu JSON'u tanımaz. Normal kullanıcının veri okuma yolu. | | `query_list` | Listenin verisini ekranın kendi sorgusuyla (`GetSelectAsync`) okur: filtre, sıralama, gruplama, özet (DevExtreme biçimi). Sıralama/gruplama/özet JSON'u, arayüzün `Utils.ts`'te yaptığı gibi select ucunun metin biçimine (`Alan desc|Alan2 asc`) `McpLoadOptions` ile çevrilir; select ucu JSON'u tanımaz. Normal kullanıcının veri okuma yolu. |
@ -1600,7 +1614,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| `save_sql_object` | View/procedure/function scriptini `sql/object/{Ad}.sql` ve `postgres/object/{Ad}.sql` dosyalarına yazar (`McpSqlObjectWriter`); veritabanında çalıştırılmaz, nesneyi DB Migrate kurar. Gövde diyalektler arasında güvenle çevrilemediği için iki script de istemciden gelir ve aynı nesneyi tanımlamalıdır (`SqlObjectInvalid`); ad scriptten okunur, başlık `CREATE OR ALTER` / `CREATE OR REPLACE`'e çevrilir. Tablo scriptinin üstüne yazmaz. `.Write` + `Sas.SqlQueryManager` ister. | | `save_sql_object` | View/procedure/function scriptini `sql/object/{Ad}.sql` ve `postgres/object/{Ad}.sql` dosyalarına yazar (`McpSqlObjectWriter`); veritabanında çalıştırılmaz, nesneyi DB Migrate kurar. Gövde diyalektler arasında güvenle çevrilemediği için iki script de istemciden gelir ve aynı nesneyi tanımlamalıdır (`SqlObjectInvalid`); ad scriptten okunur, başlık `CREATE OR ALTER` / `CREATE OR REPLACE`'e çevrilir. Tablo scriptinin üstüne yazmaz. `.Write` + `Sas.SqlQueryManager` ister. |
| `generate_crud_endpoints` | SQL Query Manager CRUD diyaloğuyla aynı endpoint kümesini (`CrudEndpointSeedFileDto.Generate` — diyaloğun servisi de bunu kullanır) yalnızca `crud/{Entity}.json` dosyasına yazar; kayıtları DB Migrate ekler (`CrudEndpointSeedApplier`). `.Write` + `Sas.SqlQueryManager.CrudEndpoints` ister. | | `generate_crud_endpoints` | SQL Query Manager CRUD diyaloğuyla aynı endpoint kümesini (`CrudEndpointSeedFileDto.Generate` — diyaloğun servisi de bunu kullanır) yalnızca `crud/{Entity}.json` dosyasına yazar; kayıtları DB Migrate ekler (`CrudEndpointSeedApplier`). `.Write` + `Sas.SqlQueryManager.CrudEndpoints` ister. |
| `list_wizards` / `get_wizard` | Wizard File Manager dosyaları; `get_wizard` seed dosyasını diskteki biçimiyle döndürür. | | `list_wizards` / `get_wizard` | Wizard File Manager dosyaları; `get_wizard` seed dosyasını diskteki biçimiyle döndürür. |
| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …, "ListForm": …, "Fields": […]}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromDeployJson`'dan geçer: Wizard bölümünde yazılan cevap korunur, yazılmayan ekran alanı `ListForm`/`Fields`'tan tamamlanır, iki bölüm de ekrana katman olarak uygulanır (`WizardScreenOverlay`, üç yönlü: bölümdeki değer wizard'ın kendi ürettiğiyle aynıysa cevap kazanır, farklıysa editör ayarıdır ve korunur; kimlik alanları wizard'dan gelir, tanınmayan/hesaplanan anahtar reddedilir). Böylece MCP, ListForm editörünün yapabildiği her ayarı wizard dosyasına yazar ve `replaceFileName` ile güncelleme editör ayarlarını silmez. Yeni kaydın form varsayılanı kolonun tablodaki sabit `DEFAULT`'udur (katalog ya da seed scripti, `SqlSeedColumn.ConstantValue`; IsActive → true, UomId → Adet, para birimi → TRY tablonun `DEFAULT`'larından gelir); alana özel değer öğenin `DefaultValue`'su ile verilir (`lowcode.instructions.md` §4.3.5.1). Girdide sözleşmede olmayan anahtar adıyla reddedilir, sessizce yok sayılmaz. `StaticData` lookup seçeneklerinin düz metni `Sas.StaticLookup.*` dil anahtarına çevrilir ve EN + TR satırı yazılır (`LocalizeStaticLookupsAsync`, `lowcode.instructions.md` §4.3.4.1). Dosyaya kullanıcının etkin talebi `DevRequest` olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur (`SeedSync*` bayrakları demo veriyle açılmaz; yalnızca istekte açıkça istenirse wizard'ın `SeedSync: true` cevabıyla) — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). Yeni wizard'ın adına (= dosya adı) modül öneki eklenir: `{ModuleId}_{Ad}` (`ListFormWizardDto.ResolveWizardName`; aynı ekran adı başka modülde de geçebilir), çıkıştaki `fileName` gerçek adı verir. `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. | | `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": {...}}`, başlıklar toplanmış) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromDeployJson`'dan geçer. Ekranın her ayarı `Wizard` bölümünün bir bloğu ya da alan öğesinin bir alanıdır; yazılmayan blok deploy'da varsayılanıyla dolar (`ListFormWizardDto.ApplyDefaults`), kısayol cevaplar (`AllowAdding` …, `IsRequired`) bloktan üstündür. Eski biçimin `ListForm`/`Fields` bölümleri reddedilir (`Sas.ListForm.WizardError.LegacySectionsNotAccepted`); kimlik alanları (yetki adları, popup başlığı, durum anahtarı) ekran kodundan yeniden türetilir, tanınmayan anahtar reddedilir. Böylece MCP, ListForm editörünün yapabildiği her ayarı wizard dosyasına yazar ve `replaceFileName` ile güncelleme (`get_wizard` çıktısı düzenlenip gönderilir) editör ayarlarını silmez. Yeni kaydın form varsayılanı kolonun tablodaki sabit `DEFAULT`'udur (katalog ya da seed scripti, `SqlSeedColumn.ConstantValue`; IsActive → true, UomId → Adet, para birimi → TRY tablonun `DEFAULT`'larından gelir); alana özel değer öğenin `DefaultValue`'su ile verilir (`lowcode.instructions.md` §4.3.5.1). Girdide sözleşmede olmayan anahtar adıyla reddedilir, sessizce yok sayılmaz. `StaticData` lookup seçeneklerinin düz metni `Sas.StaticLookup.*` dil anahtarına çevrilir ve EN + TR satırı yazılır (`LocalizeStaticLookupsAsync`, `lowcode.instructions.md` §4.3.4.1). Dosyaya kullanıcının etkin talebi `DevRequest` olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur (`SeedSync*` bayrakları demo veriyle açılmaz; yalnızca istekte açıkça istenirse wizard'ın `SeedSync: true` cevabıyla) — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). Yeni wizard'ın adına (= dosya adı) modül öneki eklenir: `{ModuleId}_{Ad}` (`ListFormWizardDto.ResolveWizardName`; aynı ekran adı başka modülde de geçebilir), çıkıştaki `fileName` gerçek adı verir. `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. |
| `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). | | `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). |
| `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `Sas.DevRequests` döngüsünün adımı değildir — döngü `Testing`'de biter, kullanıcı elle test edip `Staging`'e alır (`lowcode.instructions.md` §0.11). | | `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `Sas.DevRequests` döngüsünün adımı değildir — döngü `Testing`'de biter, kullanıcı elle test edip `Staging`'e alır (`lowcode.instructions.md` §0.11). |
| `start_dev_request` | Geliştirme talebini Id ya da tam başlıkla üstlenir: yalnızca `Ready to Start` durumundakini `In Progress` durumuna alır, diğerlerini reddeder; talebin metnini döndürür. Açıklamaya yapıştırılmış base64 görseller (`data:image/png|jpeg|gif|webp`) metinden ayıklanır (`McpDevRequestImages`), yerlerinde `[image-N]` kalır ve her biri ayrı MCP resim içeriği (`ImageContentBlock`) olarak döner — model görseli resim olarak görür, görsel hiçbir yere yüklenmez. Sonuç istemcinin 1 MB sınırına sığdırılır: büyük görseller küçültülüp JPEG'e çevrilir (`IMcpImageShrinker`), sığmayan gönderilmez ve metinde bildirilir. `.Write` + `Sas.DevRequests.Update` ister. | | `start_dev_request` | Geliştirme talebini Id ya da tam başlıkla üstlenir: yalnızca `Ready to Start` durumundakini `In Progress` durumuna alır, diğerlerini reddeder; talebin metnini döndürür. Açıklamaya yapıştırılmış base64 görseller (`data:image/png|jpeg|gif|webp`) metinden ayıklanır (`McpDevRequestImages`), yerlerinde `[image-N]` kalır ve her biri ayrı MCP resim içeriği (`ImageContentBlock`) olarak döner — model görseli resim olarak görür, görsel hiçbir yere yüklenmez. Sonuç istemcinin 1 MB sınırına sığdırılır: büyük görseller küçültülüp JPEG'e çevrilir (`IMcpImageShrinker`), sığmayan gönderilmez ve metinde bildirilir. `.Write` + `Sas.DevRequests.Update` ister. |
@ -2230,8 +2244,8 @@ configs/seeds/
> Wizard dosyası `EditFileName` ile güncellenirse deploy veritabanına dokunmaz: dosyalar cevaptan > Wizard dosyası `EditFileName` ile güncellenirse deploy veritabanına dokunmaz: dosyalar cevaptan
> yeniden yazılır (ad değiştiyse eski dosya silinir), önceki sürümün kayıtları `Pending.Replaced`'a > yeniden yazılır (ad değiştiyse eski dosya silinir), önceki sürümün kayıtları `Pending.Replaced`'a
> alınır ve DB Migrate onları kaldırıp ekranı yeniden kurar (`lowcode.instructions.md` §4.0). Wizard'ın ürettiği ekran **ListForm detayından** düzenlendiğinde > alınır ve DB Migrate onları kaldırıp ekranı yeniden kurar (`lowcode.instructions.md` §4.0). Wizard'ın ürettiği ekran **ListForm detayından** düzenlendiğinde
> de aynı dosya güncellenir (`WizardSeedSynchronizer`): `ListForm`/`Fields` bölümleri ve > de aynı dosya güncellenir (`WizardSeedSynchronizer`): `Wizard` bölümü veritabanındaki güncel
> `Wizard` bölümündeki karşılıkları veritabanındaki güncel haliyle değiştirilir. > ekrandan `RefreshFrom` ile yeniden yazılır.
--- ---

View file

@ -467,18 +467,20 @@ public class McpDevelopAppService(
if (!string.Equals(seed.Wizard?.SelectCommand?.Trim(), table, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(seed.Wizard?.SelectCommand?.Trim(), table, StringComparison.OrdinalIgnoreCase))
continue; continue;
var found = columns var fieldNames = (seed.Wizard?.Groups ?? [])
.Where(column => (seed.Fields ?? []).Any(field => string.Equals(field.FieldName, column, StringComparison.OrdinalIgnoreCase))) .SelectMany(group => group.Items ?? [])
.ToList(); .Select(item => item.FieldName)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var found = columns.Where(fieldNames.Contains).ToList();
if (found.Count > 0) if (found.Count > 0)
owners[seed.ListForm?.ListFormCode ?? file.ListFormCode ?? file.WizardName] = found; owners[seed.Wizard?.ResolveListFormCode() ?? file.ListFormCode ?? file.WizardName] = found;
} }
return owners; return owners;
} }
/// <summary> /// <summary>
/// Demo satirlarini ekranin veri seed hedefine (<c>ListForm.SeedFilePath</c>; tanima gore tek /// Demo satirlarini ekranin veri seed hedefine (<c>Data.SeedFilePath</c>; tanima gore tek
/// dosya ya da satir klasoru) yazar; veritabanina dokunmaz. Migrate ekrani kurduktan sonra /// dosya ya da satir klasoru) yazar; veritabanina dokunmaz. Migrate ekrani kurduktan sonra
/// <c>ListFormSeedDataApplier</c> satirlari ekler ve kiraci/audit alanlarini doldurur. Guid anahtar /// <c>ListFormSeedDataApplier</c> satirlari ekler ve kiraci/audit alanlarini doldurur. Guid anahtar
/// burada uretilir: dosya her uygulandiginda ayni kayda karsilik gelir. Hedefte ayni icerikli satir /// burada uretilir: dosya her uygulandiginda ayni kayda karsilik gelir. Hedefte ayni icerikli satir
@ -486,12 +488,12 @@ public class McpDevelopAppService(
/// </summary> /// </summary>
private async Task<McpProblem?> WriteSeedRowsAsync(string fileName, List<Dictionary<string, JsonElement>> rows) private async Task<McpProblem?> WriteSeedRowsAsync(string fileName, List<Dictionary<string, JsonElement>> rows)
{ {
var listForm = (await wizardAppService.GetFile(fileName)).ListForm; var wizard = (await wizardAppService.GetFile(fileName)).Wizard;
var target = ListFormSeedTarget.Create(SeedPathResolver.GetRootPath(configuration), listForm?.SeedFilePath); var target = ListFormSeedTarget.Create(SeedPathResolver.GetRootPath(configuration), wizard?.SeedFilePath);
if (listForm == null || target == null) if (wizard == null || target == null)
return Problem(McpErrorCodes.SeedRowsNotAllowed); return Problem(McpErrorCodes.SeedRowsNotAllowed);
var keyFieldName = listForm.KeyFieldName; var keyFieldName = wizard.KeyFieldName;
var existing = (await target.ReadRowsAsync()).Select(row => RowContent(row, keyFieldName)).ToHashSet(StringComparer.Ordinal); var existing = (await target.ReadRowsAsync()).Select(row => RowContent(row, keyFieldName)).ToHashSet(StringComparer.Ordinal);
var added = new List<(object Key, Dictionary<string, object> Row)>(); var added = new List<(object Key, Dictionary<string, object> Row)>();
@ -501,14 +503,14 @@ public class McpDevelopAppService(
if (!existing.Add(RowContent(values, keyFieldName))) if (!existing.Add(RowContent(values, keyFieldName)))
continue; continue;
if (listForm.KeyFieldDbSourceType == DbType.Guid && !values.Keys.Contains(keyFieldName, StringComparer.OrdinalIgnoreCase)) if (wizard.KeyFieldDbSourceType == DbType.Guid && !values.Keys.Contains(keyFieldName, StringComparer.OrdinalIgnoreCase))
values[keyFieldName] = GuidGenerator.Create(); values[keyFieldName] = GuidGenerator.Create();
added.Add((values.GetValueOrDefault(keyFieldName) ?? string.Empty, values)); added.Add((values.GetValueOrDefault(keyFieldName) ?? string.Empty, values));
} }
// Satir klasorunde her satir anahtariyla adlanan dosyadadir; anahtarsiz satir yazilamaz. // Satir klasorunde her satir anahtariyla adlanan dosyadadir; anahtarsiz satir yazilamaz.
return added.Count > 0 && (await target.UpsertRowsAsync(listForm.ListFormCode, keyFieldName, added)).Count > 0 return added.Count > 0 && (await target.UpsertRowsAsync(wizard.ResolveListFormCode(), keyFieldName, added)).Count > 0
? Problem(McpErrorCodes.SeedRowsInvalid) ? Problem(McpErrorCodes.SeedRowsInvalid)
: null; : null;
} }
@ -1100,10 +1102,9 @@ public class McpDevelopAppService(
} }
/// <summary> /// <summary>
/// Wizard'i seed dosyasi biciminden (<c>{"Wizard": {...}, "ListForm": {...}, "Fields": [...]}</c>) /// Wizard'i seed dosyasi biciminden (<c>{"Wizard": {...}}</c>, basliklar toplanmis) ya da duz
/// ya da duz cevaplardan okur; Menu/Language bloklari ve WizardCode/ModuleId adlari dosyadakiyle /// cevaplardan okur; Menu/Language/Data/Details/Layout bloklari ve WizardCode/ModuleId adlari
/// ayni anlama gelir. ListForm/Fields bolumleri ekrana katman olarak uygulanir /// dosyadakiyle ayni anlama gelir (<see cref="WizardSeedFileDto.FromDeployJson"/>).
/// (<see cref="WizardSeedFileDto.FromDeployJson"/>).
/// </summary> /// </summary>
private McpOutcome<ListFormWizardDto> ParseWizard(string? json) private McpOutcome<ListFormWizardDto> ParseWizard(string? json)
{ {

View file

@ -192,7 +192,7 @@ public class McpSchemaAppService(
/// <summary> /// <summary>
/// Seed klasorunde olup sozlukte olmayan kaynaklar: wizard dosyalarinin ekranlari (alanlari /// Seed klasorunde olup sozlukte olmayan kaynaklar: wizard dosyalarinin ekranlari (alanlari
/// <c>Fields</c> bolumunden) ve yalnizca <c>sql/object</c> scripti olan tablolar (kolonlari scriptten). /// <c>Groups</c> alanlarindan) ve yalnizca <c>sql/object</c> scripti olan tablolar (kolonlari scriptten).
/// <see cref="McpListSummaryDto.SourceKind"/> <c>pending</c> / <c>pendingTable</c> olarak isaretlenir: /// <see cref="McpListSummaryDto.SourceKind"/> <c>pending</c> / <c>pendingTable</c> olarak isaretlenir:
/// kolon adlari dogrudur ama query_list migrate'ten once calismaz. /// kolon adlari dogrudur ama query_list migrate'ten once calismaz.
/// </summary> /// </summary>
@ -208,9 +208,12 @@ public class McpSchemaAppService(
continue; continue;
var seed = await wizardAppService.GetFile(file.FileName); var seed = await wizardAppService.GetFile(file.FileName);
if (seed.ListForm is not { } listForm) if (seed.Wizard is not { } wizard || string.IsNullOrWhiteSpace(wizard.SelectCommand))
continue; continue;
// Alanlar dosyadaki cevaplardan ekranin sozlesmesine cevrilir; sozluk ekranla ayni degerleri gorur.
var listForm = wizard.BuildListForm();
var table = listForm.SelectCommandType == SelectCommandTypeEnum.Query ? null : listForm.SelectCommand?.Split('.')[^1].Trim('[', ']', '"'); var table = listForm.SelectCommandType == SelectCommandTypeEnum.Query ? null : listForm.SelectCommand?.Split('.')[^1].Trim('[', ']', '"');
if (table is not null) if (table is not null)
tables.Add(table); tables.Add(table);
@ -226,7 +229,7 @@ public class McpSchemaAppService(
SourceKind = PendingSourceKind, SourceKind = PendingSourceKind,
KeyFieldName = listForm.KeyFieldName, KeyFieldName = listForm.KeyFieldName,
SearchText = string.Join(' ', listForm.ListFormCode, seed.Wizard?.TitleTr, seed.Wizard?.TitleEn, table), SearchText = string.Join(' ', listForm.ListFormCode, seed.Wizard?.TitleTr, seed.Wizard?.TitleEn, table),
Fields = [.. seed.Fields.Select(field => new McpDictionaryField Fields = [.. wizard.BuildFields().Select(field => new McpDictionaryField
{ {
Field = field.FieldName, Field = field.FieldName,
DisplayName = field.CaptionName, DisplayName = field.CaptionName,

View file

@ -90,16 +90,16 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService, IMc
=> McpToolResult.From(await developAppService.GetWizardsAsync(search)); => McpToolResult.From(await developAppService.GetWizardsAsync(search));
[McpServerTool(Name = McpToolNames.GetWizard, ReadOnly = true)] [McpServerTool(Name = McpToolNames.GetWizard, ReadOnly = true)]
[Description("Bir wizard seed dosyasini diskteki bicimiyle dondurur (Wizard / ListForm / Fields bolumleri). Var olan bir ekrani degistirmeden once oku.")] [Description("Bir wizard seed dosyasini diskteki bicimiyle dondurur: tek Wizard bolumu; Menu/Language/Data/Details/Layout basliklari, ekranin her sekmesi bir blok (Editing, Insert/Update/Delete, FormDefaults, FilterRow, HeaderFilter, Selection, ColumnOption, Pager, StateStoring, ChartOptions …) ve Groups altinda alanlarin butun detay sekmeleri (Filter, Header, Grouping, Summary, Join, ValidationRules, Styling, Permission, PivotSettings). Var olan bir ekrani degistirmeden once oku.")]
public async Task<CallToolResult> GetWizardAsync( public async Task<CallToolResult> GetWizardAsync(
[Description("list_wizards cikisindaki fileName degeri, ornegin 'Tags.json'.")] [Description("list_wizards cikisindaki fileName degeri, ornegin 'Tags.json'.")]
string fileName) string fileName)
=> McpToolResult.From(await developAppService.GetWizardAsync(fileName)); => McpToolResult.From(await developAppService.GetWizardAsync(fileName));
[McpServerTool(Name = McpToolNames.DeployWizard, Destructive = true, Idempotent = false)] [McpServerTool(Name = McpToolNames.DeployWizard, Destructive = true, Idempotent = false)]
[Description("Wizard'i deploy eder: ListForm ekrani (ComponentKind 0) ya da bir custom component'i menuye baglayan kayit (ComponentKind 1), yetkileri, dil anahtarlari ve menusu seed dosyalarina yazilir. Deploy veritabanina yazmaz: ekran, menu ve yetkiler kullanici Wizard Manager'da DB Migrate calistirinca olusur; cikistaki isPending true iken ekran henuz yoktur; migrate'i bekleme, isini bitir ve kullaniciya yalnizca DB Migrate calistirmasini soyle. Eksik modul, ust menu, izin grubu, yetki ve dil satirlari da deploy'da dosyalara yazilir ve migrate onlari dogru sirayla ekler; bunlarin veritabaninda olmamasi beklenen durumdur, uyari olarak raporlama ve kullanicidan elle eklemesini isteme. Ust menu zinciri istenen derinlikte kurulur: MenuParentCode ekranin dogrudan ust menusu, Menu.MenuParentPath onun ustundeki seviyeler (kokten asagi, her biri {Code, Icon, En, Tr}); eksik her seviye bir ustunun altina kurulur, var olana baglanilir. Liste ekraninda Grid, Card, Pivot ve Chart gorunumleri varsayilan aciktir (DefaultLayout grid); kullanici bir gorunumu istemedigini soylemedikce bu bayraklari false yazma. Hedef tablo onceden var olmalidir; yoksa once create_table ile olustur. Istek ornek/demo veri tasiyorsa (prompt'taki tablo/liste ya da ekran goruntusunde gorunen satirlar) SeedData true yaz ve satirlarin hepsini ayni cagrida seedRows ile gonder: satirlar ekranin data/{ListFormCode}.json dosyasina yazilir, migrate ekrani kurduktan sonra ekler. SeedData ekrandan girilen kayitlari dosyaya yansitmaz (SeedSyncInsert/Update/Delete kapali); bunu yalnizca istek acikca isterse SeedSync true yaz. Satirlar icin migrate'i bekleyip insert_row kullanma. replaceFileName verilirse o wizard'in dosyalari yeniden yazilir, onceki kayitlarin yerini migrate'te yenileri alir. Wizard cevaplarinin uretmedigi ekran ayarlari (FormFieldsDefaultValueDto/InsertFieldsDefaultValueDto gibi varsayilan degerler, ValidationRuleDto, Format, Alignment, Width, Visible, BandName, ozet/gruplama, ExtraFilter, Insert/Update/Delete komutlari, PageSize, CommandColumnDto …) ayni JSON'un ListForm ve Fields bolumlerine get_wizard ciktisindaki adlarla yazilir. Yeni kaydin form degeri (Aktif = true, Birim = Adet, Kur = TRY, Sure = 60) ayri yazilmaz: deploy kolonun tablodaki sabit DEFAULT degerini okur; tablodan farkli deger gerekiyorsa alan ogesinde DefaultValue yaz (Groups[].Items[].DefaultValue). StaticData lookup seceneklerinde (RadioGroup, SelectBox, TagBox) name dil anahtaridir (Sas.StaticLookup.{Key}); EN + TR metni bilinen secenegi Language.Texts'te ver, duz metin yazilirsa deploy onu anahtara cevirip TR'yi metinden yazar (get_dev_guide 4.3.4.1). EditorScript'i elle yazma: Script Builder kurallarini ({v:1, rules:[...]}, get_dev_guide 5.3) ver, deploy kodu Script Builder dialogu ile birebir ayni uretir; govde yazsan da atilir. Onay kutusu alanina setValue text 'true'/'false' (0/1 cevrilir). set ile yazilan alanin kurallari da sirayla calisir (derinlik siniri yok); A->B->A dongusu kuran kural yazma, calisma aninda hata olarak gosterilir. Wizard girdisinde taninmayan anahtar adiyla reddedilir, baska adla tekrar deneme; bolumler kismi olabilir (yalnizca degisen anahtarlar), Fields ogeleri FieldName ile eslesir, *Json adlari (FormFieldsDefaultValueJson) *Dto karsiligina cevrilir. Guncellemede get_wizard ciktisini butunuyle degistirip gonder: tasarimci ayarlari korunur; Wizard bolumunde yazdigin cevap ayni ayarin ListForm'daki eski degerine ustun gelir. Kimlik alanlari (ListFormCode, Name, Title, Description, PermissionDto, FieldName) wizard'dan gelir. Fields'taki her alan Wizard Groups'ta da bulunmalidir; tanınmayan ya da hesaplanan bir anahtar reddedilir. Liste ekraninda DescTr ve DescEn ayrintili olmali (en az 3 satir, 150 karakter): ekran basligindaki bilgi simgesinde gosterilir; ne ise yaradigi, menude nerede ya da hangi ekranin sekmesi oldugu, ana alanlar, otomatik hesaplar ve iliskili ekranlar yazilir (get_dev_guide 4.4.2); kisa aciklama reddedilir. Deploy modulun dokumanini (docs/modules) acar ya da envanterini yeniler; elle yazilan bolumleri save_module_doc ile doldur. Ne yazacagini get_dev_guide 4. bolumunden ogren.")] [Description("Wizard'i deploy eder: ListForm ekrani (ComponentKind 0) ya da bir custom component'i menuye baglayan kayit (ComponentKind 1), yetkileri, dil anahtarlari ve menusu seed dosyalarina yazilir. Deploy veritabanina yazmaz: ekran, menu ve yetkiler kullanici Wizard Manager'da DB Migrate calistirinca olusur; cikistaki isPending true iken ekran henuz yoktur; migrate'i bekleme, isini bitir ve kullaniciya yalnizca DB Migrate calistirmasini soyle. Eksik modul, ust menu, izin grubu, yetki ve dil satirlari da deploy'da dosyalara yazilir ve migrate onlari dogru sirayla ekler; bunlarin veritabaninda olmamasi beklenen durumdur, uyari olarak raporlama ve kullanicidan elle eklemesini isteme. Ust menu zinciri istenen derinlikte kurulur: MenuParentCode ekranin dogrudan ust menusu, Menu.MenuParentPath onun ustundeki seviyeler (kokten asagi, her biri {Code, Icon, En, Tr}); eksik her seviye bir ustunun altina kurulur, var olana baglanilir. Liste ekraninda Grid, Card, Pivot ve Chart gorunumleri varsayilan aciktir (DefaultLayout grid); kullanici bir gorunumu istemedigini soylemedikce bu bayraklari false yazma. Hedef tablo onceden var olmalidir; yoksa once create_table ile olustur. Istek ornek/demo veri tasiyorsa (prompt'taki tablo/liste ya da ekran goruntusunde gorunen satirlar) SeedData true yaz ve satirlarin hepsini ayni cagrida seedRows ile gonder: satirlar ekranin data/{ListFormCode}.json dosyasina yazilir, migrate ekrani kurduktan sonra ekler. SeedData ekrandan girilen kayitlari dosyaya yansitmaz (SeedSyncInsert/Update/Delete kapali); bunu yalnizca istek acikca isterse SeedSync true yaz. Satirlar icin migrate'i bekleyip insert_row kullanma. replaceFileName verilirse o wizard'in dosyalari yeniden yazilir, onceki kayitlarin yerini migrate'te yenileri alir. Ekranin her ayari Wizard bolumundedir ve adlari get_wizard ciktisindaki gibidir: liste ayarlari bloklarda (Details.PageSize, Editing, Insert/Update/Delete.FieldsDefaultValue, FormDefaults, ExtraFilters, CommandColumns, FilterRow … ChartOptions), alan ayarlari Groups[].Items[] ogesinde (Format, Alignment, Width, Visible, BandName, ListOrderNo, ValidationRules, TotalSummary/GroupSummary, Grouping, Join, Styling, PivotSettings …). Yazmadigin blok deploy'da varsayilaniyla dolar; ListForm/Fields bolumleri kabul edilmez. Yeni kaydin form degeri (Aktif = true, Birim = Adet, Kur = TRY, Sure = 60) ayri yazilmaz: deploy kolonun tablodaki sabit DEFAULT degerini okur; tablodan farkli deger gerekiyorsa alan ogesinde DefaultValue yaz (Groups[].Items[].DefaultValue). StaticData lookup seceneklerinde (RadioGroup, SelectBox, TagBox) name dil anahtaridir (Sas.StaticLookup.{Key}); EN + TR metni bilinen secenegi Language.Texts'te ver, duz metin yazilirsa deploy onu anahtara cevirip TR'yi metinden yazar (get_dev_guide 4.3.4.1). EditorScript'i elle yazma: Script Builder kurallarini ({v:1, rules:[...]}, get_dev_guide 5.3) ver, deploy kodu Script Builder dialogu ile birebir ayni uretir; govde yazsan da atilir. Onay kutusu alanina setValue text 'true'/'false' (0/1 cevrilir). set ile yazilan alanin kurallari da sirayla calisir (derinlik siniri yok); A->B->A dongusu kuran kural yazma, calisma aninda hata olarak gosterilir. Wizard girdisinde taninmayan anahtar adiyla reddedilir, baska adla tekrar deneme. Guncellemede get_wizard ciktisini butunuyle alip degistirerek gonder: dosyadaki her blok cevaptir, yazilmayan blok varsayilana doner. Kimlik ve yetki alanlari (WizardCode, Title/Desc anahtarlari, Permission adlari, popup basligi) ekran kodundan yeniden turetilir. Liste ekraninda DescTr ve DescEn ayrintili olmali (en az 3 satir, 150 karakter): ekran basligindaki bilgi simgesinde gosterilir; ne ise yaradigi, menude nerede ya da hangi ekranin sekmesi oldugu, ana alanlar, otomatik hesaplar ve iliskili ekranlar yazilir (get_dev_guide 4.4.2); kisa aciklama reddedilir. Deploy modulun dokumanini (docs/modules) acar ya da envanterini yeniler; elle yazilan bolumleri save_module_doc ile doldur. Ne yazacagini get_dev_guide 4. bolumunden ogren.")]
public async Task<CallToolResult> DeployWizardAsync( public async Task<CallToolResult> DeployWizardAsync(
[Description("Wizard JSON'u: seed dosyasi bicimi ({\"Wizard\": {...}, \"ListForm\": {...}, \"Fields\": [...]}, get_wizard cikisi gibi) ya da duz wizard cevaplari. ListForm/Fields istege baglidir ve kismi olabilir. Enum alanlari sayi olarak yazilir.")] [Description("Wizard JSON'u: seed dosyasi bicimi ({\"Wizard\": {...}}, get_wizard cikisi gibi; Menu/Language/Data/Details/Layout basliklari toplanmis) ya da duz wizard cevaplari. Bloklar istege baglidir; yazilmayan blok varsayilaniyla dolar. Enum alanlari sayi olarak yazilir.")]
string wizard, string wizard,
[Description("Guncellemede yerine gecilecek dosyanin adi (list_wizards cikisindaki fileName). Yeni wizard icin bos birak. Guncelleme dosyayi tamamen degistirir: wizard girdisi get_wizard ile okunan dosyanin degistirilmis tam halidir; Wizard bolumu (WizardCode, Menu, Language, Groups) eksiksiz gonderilir.")] [Description("Guncellemede yerine gecilecek dosyanin adi (list_wizards cikisindaki fileName). Yeni wizard icin bos birak. Guncelleme dosyayi tamamen degistirir: wizard girdisi get_wizard ile okunan dosyanin degistirilmis tam halidir; Wizard bolumu (WizardCode, Menu, Language, Groups) eksiksiz gonderilir.")]
string? replaceFileName = null, string? replaceFileName = null,

View file

@ -24,6 +24,12 @@ public interface IListFormWizardAppService
/// <summary>Izin gruplarini iki dilli gorunen adlariyla dondurur.</summary> /// <summary>Izin gruplarini iki dilli gorunen adlariyla dondurur.</summary>
Task<List<WizardPermissionGroupDto>> GetPermissionGroups(); Task<List<WizardPermissionGroupDto>> GetPermissionGroups();
/// <summary>
/// Cevapta bos birakilan bloklar icin deploy'un yazacagi varsayilanlar (<see cref="ListFormWizardDto.ApplyDefaults"/>);
/// wizard ekrani ListForm sekmelerini bu degerlerle acar, boylece ekrandan ve MCP'den uretilen ekran ayni baslar.
/// </summary>
Task<ListFormWizardDto> GetScreenDefaults();
/// <summary>Wizard dosyasini ve bagimlilik kapanisindaki tum seed dosyalarini zip olarak dondurur.</summary> /// <summary>Wizard dosyasini ve bagimlilik kapanisindaki tum seed dosyalarini zip olarak dondurur.</summary>
Task<IRemoteStreamContent> ExportFile(string fileName); Task<IRemoteStreamContent> ExportFile(string fileName);

View file

@ -6,5 +6,9 @@ public class WizardColumnGroupInputDto
{ {
public string Caption { get; set; } public string Caption { get; set; }
public int ColCount { get; set; } = 2; public int ColCount { get; set; } = 2;
/// <summary>Grubun formda kapladigi kolon; <c>null</c> ise <see cref="ColCount"/> (<c>EditingFormDto.ColSpan</c>).</summary>
public int? ColSpan { get; set; }
public List<WizardColumnItemInputDto> Items { get; set; } = new(); public List<WizardColumnItemInputDto> Items { get; set; } = new();
} }

View file

@ -1,8 +1,14 @@
using System.Data; using System.Collections.Generic;
using System.Data;
using Sozsoft.Platform.Enums; using Sozsoft.Platform.Enums;
namespace Sozsoft.Platform.ListForms; namespace Sozsoft.Platform.ListForms;
/// <summary>
/// Wizard alan ogesi: alan detayinin (ListForm alan editoru) her sekmesi burada bir alan ya da bloktur.
/// Lookup alanlari duz yazilir (MCP sozlesmesi); sekme bloklari (<see cref="Filter"/>, <see cref="Header"/> …)
/// <c>null</c> ise deploy varsayilani yazar (<see cref="ListFormWizardDto.ApplyDefaults"/>).
/// </summary>
public class WizardColumnItemInputDto public class WizardColumnItemInputDto
{ {
public string FieldName { get; set; } public string FieldName { get; set; }
@ -11,6 +17,11 @@ public class WizardColumnItemInputDto
public string EditorOptions { get; set; } public string EditorOptions { get; set; }
public string EditorScript { get; set; } public string EditorScript { get; set; }
public int ColSpan { get; set; } = 1; public int ColSpan { get; set; } = 1;
/// <summary>
/// Zorunluluk kisayolu: deploy <see cref="ValidationRules"/> listesine <c>required</c> kurali ekler ya da
/// cikarir. Dosyaya yazilmaz; okunurken kural listesinden turetilir.
/// </summary>
public bool IsRequired { get; set; } public bool IsRequired { get; set; }
public bool IncludeInEditingForm { get; set; } = true; public bool IncludeInEditingForm { get; set; } = true;
public DbType DbSourceType { get; set; } = DbType.String; public DbType DbSourceType { get; set; } = DbType.String;
@ -25,6 +36,7 @@ public class WizardColumnItemInputDto
/// Yeni kayit acilirken alanin dolu gelecegi deger (<c>FormFieldsDefaultValueJson</c>, sabit deger). /// Yeni kayit acilirken alanin dolu gelecegi deger (<c>FormFieldsDefaultValueJson</c>, sabit deger).
/// Lookup alaninda lookup'in anahtaridir. Bos ise deploy kolonun veritabanindaki sabit DEFAULT degerini /// Lookup alaninda lookup'in anahtaridir. Bos ise deploy kolonun veritabanindaki sabit DEFAULT degerini
/// yazar (IsActive DEFAULT 1 → true); deger koda degil tabloyu tanimlayan scripte aittir. /// yazar (IsActive DEFAULT 1 → true); deger koda degil tabloyu tanimlayan scripte aittir.
/// Sabit olmayan varsayilanlar (<c>@USERID</c>, sorgu, numarator) <see cref="ListFormWizardDto.FormDefaults"/>'tadir.
/// </summary> /// </summary>
public string DefaultValue { get; set; } public string DefaultValue { get; set; }
@ -39,4 +51,70 @@ public class WizardColumnItemInputDto
/// <summary>Ebeveyn alanda: bu alan degisince bosaltilacak cocuk alanlar, virgulle.</summary> /// <summary>Ebeveyn alanda: bu alan degisince bosaltilacak cocuk alanlar, virgulle.</summary>
public string CascadeEmptyFields { get; set; } public string CascadeEmptyFields { get; set; }
// ── Alan detayi › Detay ───────────────────────────────────────────────────
/// <summary>Alanin dil varyanti; bos ise platform varsayilan dili. Ayni alanin rol/kullanici/dil varyantlari ayri ogelerdir.</summary>
public string CultureName { get; set; }
/// <summary>Alan tanimi yalnizca bu role aitse rolun adi.</summary>
public string RoleId { get; set; }
/// <summary>Alan tanimi yalnizca bu kullaniciya aitse kullanici adi.</summary>
public string UserId { get; set; }
public string PlaceHolder { get; set; }
public string BandName { get; set; }
public bool ReadOnly { get; set; }
/// <summary>Listede gorunur mu; <c>null</c> ise anahtar alan gizli, digerleri gorunur.</summary>
public bool? Visible { get; set; }
/// <summary>Sorguya dahil mi; <c>null</c> ise dahil.</summary>
public bool? IsActive { get; set; }
/// <summary>Arama panelinde aranir mi; <c>null</c> ise aranir.</summary>
public bool? AllowSearch { get; set; }
public string Alignment { get; set; }
public string Format { get; set; }
// ── Alan detayi › Secenekler ───────────────────────────────────────────────
/// <summary>
/// Listedeki sutun sirasi. <c>null</c> ise sira gruplardaki yerinden gelir; tasarimcida liste sirasi
/// form sirasindan ayrildiginda deger acikca yazilir.
/// </summary>
public int? ListOrderNo { get; set; }
/// <summary>Sutun genisligi; <c>0</c>/<c>null</c> otomatik.</summary>
public int? Width { get; set; }
public int SortIndex { get; set; }
public string SortDirection { get; set; }
/// <summary>Satir duzenlemede degistirilebilir mi; <c>null</c> ise evet.</summary>
public bool? AllowEditing { get; set; }
/// <summary>Yeni kayitta girilebilir mi; <c>null</c> ise evet.</summary>
public bool? AllowAdding { get; set; }
// ── Alan detayi › CSS ─────────────────────────────────────────────────────
public string ColumnCssClass { get; set; }
public string ColumnCssValue { get; set; }
// ── Alan detayi › sekme bloklari ─────────────────────────────────────────
public ColumnFilterDto Filter { get; set; }
public ColumnHeaderDto Header { get; set; }
public ColumnGroupingDto Grouping { get; set; }
public ColumnCustomizationDto Customization { get; set; }
public ColumnTotalSummaryDto TotalSummary { get; set; }
public ColumnTotalSummaryDto GroupSummary { get; set; }
public JoinTableDto Join { get; set; }
public List<ValidationRuleDto> ValidationRules { get; set; }
public List<ColumnStylingDto> Styling { get; set; }
public ListFormFieldPermissionDto Permission { get; set; }
public ListFormFieldPivotSettingsDto PivotSettings { get; set; }
} }

View file

@ -0,0 +1,99 @@
using System.Collections.Generic;
using System.Data;
using Sozsoft.Platform.Enums;
namespace Sozsoft.Platform.ListForms;
/// <summary>
/// Wizard cevabinda bos birakilan bloklarin varsayilanlari (<see cref="ListFormWizardDto.ApplyDefaults"/>).
/// DTO tipleri Application.Contracts'ta oldugu icin burada; metin sabitleri <see cref="WizardConsts"/>'tadir.
/// </summary>
public static class WizardDefaults
{
public const string InsertServiceAddress = "list-form-data/insert";
public const string UpdateServiceAddress = "list-form-data/update";
public const string DeleteServiceAddress = "list-form-data/delete";
/// <summary>Durum anahtarinin oneki; anahtar <c>{onek}{ListFormCode}</c> olur (<c>StateStoringDto</c> ile ayni).</summary>
public const string StateStorageKeyPrefix = "listFormGridStorage-";
public static PermissionCrudDto Permission(string permissionName, bool hasNote) => new()
{
C = permissionName + ".Create",
R = permissionName,
U = permissionName + ".Update",
D = permissionName + ".Delete",
E = permissionName + ".Export",
I = permissionName + ".Import",
// Not yalnizca tablo kaynakli listede vardir: view/sorgu satirinin notu bagli oldugu kalici kaydi yoktur.
N = hasNote ? permissionName + ".Note" : null,
};
public static GridColumnOptionDto ColumnOption() => new()
{
ColumnFixingEnabled = true,
ColumnAutoWidth = true,
ColumnChooserEnabled = true,
AllowColumnResizing = true,
AllowColumnReordering = true,
ColumnResizingMode = "widget",
FocusedRowEnabled = true,
};
public static GridPagerOptionDto PagerOption() => new()
{
Visible = true,
AllowedPageSizes = "10,20,50,100",
ShowPageSizeSelector = true,
ShowNavigationButtons = true,
ShowInfo = false,
InfoText = "Page {0} of {1} ({2} items)",
DisplayMode = PlatformConsts.GridColumnOptions.PagerDisplayModeAdaptive,
ScrollingMode = PlatformConsts.GridColumnOptions.ScrollingModeStandard,
LoadPanelEnabled = "auto",
LoadPanelText = "Loading...",
};
public static ChartExportDto ChartExport() => new()
{
Enabled = true,
AllowExportSelectedData = false,
PrintingEnabled = true,
BackgroundColor = "#FFFFFF",
Margin = 10,
};
private static FieldsDefaultValueDto Key(string fieldName, DbType dbType, string value) => new()
{
FieldName = fieldName,
FieldDbType = dbType,
Value = value,
CustomValueType = FieldCustomValueTypeEnum.CustomKey,
};
/// <summary>Audit kolonlu tabloda ekleme: olusturma damgasi, olusturan, soft delete bayragi ve yeni anahtar.</summary>
public static List<FieldsDefaultValueDto> InsertFields(DbType keyType) =>
[
Key("CreationTime", DbType.DateTime, "@NOW"),
Key("CreatorId", DbType.Guid, "@USERID"),
new() { FieldName = "IsDeleted", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
Key("Id", keyType, "@NEWID"),
];
public static List<FieldsDefaultValueDto> InsertFieldsOnlyId(DbType keyType) => [Key("Id", keyType, "@NEWID")];
/// <summary>Degistirme audit kolonlu tabloda guncelleme; ekranin <c>*AfterCommand</c>'lari da <c>@LastModifierId</c> okur.</summary>
public static List<FieldsDefaultValueDto> UpdateFields() =>
[
Key("LastModificationTime", DbType.DateTime, "@NOW"),
Key("LastModifierId", DbType.Guid, "@USERID"),
];
public static List<FieldsDefaultValueDto> DeleteFields(DbType keyType) =>
[
Key("DeleterId", DbType.Guid, "@USERID"),
Key("Id", keyType, "@ID"),
];
public static List<FieldsDefaultValueDto> DeleteFieldsOnlyId(DbType keyType) => [Key("Id", keyType, "@ID")];
}

View file

@ -1,225 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace Sozsoft.Platform.ListForms;
/// <summary>
/// Deploy girdisindeki <c>ListForm</c>/<c>Fields</c> bolumleri. Wizard cevaplari ekranin yalnizca bir
/// kismini uretir; varsayilan degerler, dogrulama kurallari, bicim, komutlar, ek filtre gibi ayarlar
/// ListForm detayinda (tasarimci) yapilir. Bu katman ayni ayarlari deploy'da seed dosyasinin
/// bicimiyle (<c>get_wizard</c> ciktisi) kabul eder; boylece MCP tasarimcinin yapabildigi her seyi
/// wizard dosyasina yazabilir ve yeniden deploy tasarimci ayarlarini silmez.
/// <para>
/// Birlestirme uc yonludur: bolumdeki deger, bolumlerden geri kurulan wizard'in uretecegi degerle
/// (<see cref="Baseline"/>) ayniysa katman sayilmaz ve wizard cevabi gecerli olur; farkliysa
/// tasarimci ayaridir ve uretilen ekranin ustune yazilir. Kimlik alanlari (kod, ad, dil anahtarli
/// baslik, yetki) her zaman wizard'dan gelir.
/// </para>
/// </summary>
/// <summary>Katman hatasi; metin cagiranin localizer'iyla uretilir (Contracts katmaninda localizer yoktur).</summary>
public sealed record WizardOverlayError(string Key, params object[] Args);
public class WizardScreenOverlay
{
public JsonObject ListForm { get; set; }
public List<JsonObject> Fields { get; set; } = [];
/// <summary>Bolumlerden geri kurulan wizard; uc yonlu birlestirmenin tabani.</summary>
public ListFormWizardDto Baseline { get; set; }
/// <summary>Wizard'dan turedigi icin katmanla degistirilmeyen ekran alanlari.</summary>
private static readonly string[] ProtectedListFormKeys =
[
nameof(GridOptionsEditDto.Id), nameof(GridOptionsEditDto.ListFormCode), nameof(GridOptionsEditDto.Name),
nameof(GridOptionsEditDto.Title), nameof(GridOptionsEditDto.Description), nameof(GridOptionsEditDto.CultureName),
nameof(GridOptionsEditDto.PermissionDto), nameof(GridOptionsEditDto.EditType),
nameof(GridOptionsEditDto.CreationTime), nameof(GridOptionsEditDto.CreatorId),
nameof(GridOptionsEditDto.LastModificationTime), nameof(GridOptionsEditDto.LastModifierId),
];
private static readonly string[] ProtectedFieldKeys =
[
nameof(ColumnFormatEditDto.Id), nameof(ColumnFormatEditDto.ListFormCode), nameof(ColumnFormatEditDto.FieldName),
nameof(ColumnFormatEditDto.CultureName), nameof(ColumnFormatEditDto.PermissionDto), nameof(ColumnFormatEditDto.EditType),
nameof(ColumnFormatEditDto.CreationTime), nameof(ColumnFormatEditDto.CreatorId),
nameof(ColumnFormatEditDto.LastModificationTime), nameof(ColumnFormatEditDto.LastModifierId),
];
/// <summary>Wizard cevabindaki adla yazilan alan anahtarlari; alan sozlesmesindeki karsiliklari.</summary>
private static readonly Dictionary<string, string> FieldAliases = new(StringComparer.OrdinalIgnoreCase)
{
[nameof(WizardColumnItemInputDto.EditorType)] = nameof(ColumnFormatEditDto.EditorType2),
[nameof(WizardColumnItemInputDto.DbSourceType)] = nameof(ColumnFormatEditDto.SourceDbType),
};
/// <summary>
/// Katmani wizard'in urettigi ListForm'a uygular. <paramref name="baseline"/> ayni donusumun
/// <see cref="Baseline"/> wizard'i icin ciktisidir.
/// </summary>
public GridOptionsEditDto Apply(GridOptionsEditDto built, GridOptionsEditDto baseline, List<WizardOverlayError> errors)
{
if (ListForm == null)
return built;
return Merge(built, ListForm, baseline, ProtectedListFormKeys, [], "ListForm", errors);
}
/// <summary>
/// Katmani alanlara uygular; alanlar <c>FieldName</c> ile eslesir. Wizard gruplarinda olmayan bir
/// alan icin katman yazilamaz: alan kumesi gruplardan gelir.
/// </summary>
public List<ColumnFormatEditDto> Apply(List<ColumnFormatEditDto> built, List<ColumnFormatEditDto> baseline, List<WizardOverlayError> errors)
{
if (Fields.Count == 0)
return built;
var result = built.ToList();
foreach (var overlay in Fields)
{
var fieldName = Text(overlay, nameof(ColumnFormatEditDto.FieldName));
var index = result.FindIndex(f => string.Equals(f.FieldName, fieldName, StringComparison.OrdinalIgnoreCase));
if (index < 0)
{
errors.Add(new WizardOverlayError("Sas.ListForm.WizardError.OverlayFieldNotInGroups", fieldName));
continue;
}
var source = baseline.FirstOrDefault(f => string.Equals(f.FieldName, fieldName, StringComparison.OrdinalIgnoreCase));
result[index] = Merge(result[index], overlay, source, ProtectedFieldKeys, FieldAliases, $"Fields[{fieldName}]", errors);
}
return result;
}
private static T Merge<T>(
T built,
JsonObject overlay,
T baseline,
string[] protectedKeys,
Dictionary<string, string> aliases,
string path,
List<WizardOverlayError> errors)
{
var target = JsonSerializer.SerializeToNode(built) as JsonObject ?? [];
var baseNode = baseline == null ? null : JsonSerializer.SerializeToNode(baseline) as JsonObject;
var writable = WritableProperties(typeof(T));
var touched = new List<string>();
foreach (var (rawKey, rawValue) in overlay)
{
var (key, value) = Resolve(target, rawKey, rawValue, aliases);
if (key == null)
{
errors.Add(new WizardOverlayError("Sas.ListForm.WizardError.OverlayUnknownKey", path, rawKey));
continue;
}
if (protectedKeys.Contains(key, StringComparer.OrdinalIgnoreCase))
continue;
if (!writable.Contains(key))
{
if (!JsonNode.DeepEquals(value, target[key]))
errors.Add(new WizardOverlayError("Sas.ListForm.WizardError.OverlayComputedKey", path, rawKey));
continue;
}
var merged = MergeValue(target[key], value, baseNode?[key]);
if (merged.Changed)
{
target[key] = merged.Value;
touched.Add(key);
}
}
// Ayni Json kolonunu besleyen iki ozellik olabilir (ExtraFilterDto / ExtraFilterEditDto);
// degisen anahtar sona alinir ki okurken son yazilan o olsun.
foreach (var key in touched.Distinct())
{
target.Remove(key, out var value);
target[key] = value;
}
return target.Deserialize<T>(PlatformJsonOptions.CaseInsensitive);
}
/// <summary>
/// Nesneler alan alan birlestirilir; dizi ve tekil degerler tumden yazilir. Tabandaki degerle
/// ayni olan katman degeri wizard'in turettigi degeri ezmez.
/// </summary>
private static (bool Changed, JsonNode Value) MergeValue(JsonNode current, JsonNode overlay, JsonNode baseline)
{
if (baseline != null && JsonNode.DeepEquals(overlay, baseline))
return (false, current);
if (current is JsonObject currentObject && overlay is JsonObject overlayObject)
{
var result = (JsonObject)currentObject.DeepClone();
var changed = false;
foreach (var (key, value) in overlayObject)
{
var existing = result.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Key ?? key;
var merged = MergeValue(result[existing], value, (baseline as JsonObject)?[existing]);
if (merged.Changed)
{
result[existing] = merged.Value;
changed = true;
}
}
return (changed, result);
}
return (true, overlay?.DeepClone());
}
/// <summary>
/// Anahtari sozlesmedeki ada cevirir: buyuk/kucuk harf duyarsiz, wizard adlari (<c>EditorType</c>)
/// ve rehberdeki kolon adlari (<c>FormFieldsDefaultValueJson</c> → <c>FormFieldsDefaultValueDto</c>).
/// Json kolonu metin olarak verilmisse cozulur.
/// </summary>
private static (string Key, JsonNode Value) Resolve(JsonObject target, string key, JsonNode value, Dictionary<string, string> aliases)
{
if (aliases.TryGetValue(key, out var alias))
key = alias;
var found = Find(target, key);
if (found == null && key.EndsWith("Json", StringComparison.OrdinalIgnoreCase))
{
found = Find(target, key[..^4] + "Dto");
if (found != null && value is JsonValue text && text.TryGetValue<string>(out var json))
{
try
{
value = string.IsNullOrWhiteSpace(json) ? null : JsonNode.Parse(json);
}
catch (JsonException)
{
return (found, value);
}
}
}
return (found, value?.DeepClone());
}
private static string Find(JsonObject node, string key) =>
node.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Key;
private static string Text(JsonObject node, string key) =>
node.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Value is JsonValue value &&
value.TryGetValue<string>(out var text) ? text : null;
private static HashSet<string> WritableProperties(Type type) =>
[
.. type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite && p.SetMethod?.IsPublic == true && p.GetCustomAttribute<JsonIgnoreAttribute>() == null)
.Select(p => p.Name)
];
}

View file

@ -0,0 +1,45 @@
using System.Collections.Generic;
namespace Sozsoft.Platform.ListForms;
/// <summary>
/// Veritabani sekmesinin Insert / Update / Delete bloklari: servis adresi, komut, oncesi/sonrasi komutlari
/// ve komuta gonderilen varsayilan degerler (<c>{Insert|Update|Delete}FieldsDefaultValueJson</c>).
/// </summary>
public class WizardDataCommandDto
{
public string ServiceAddress { get; set; }
public string Command { get; set; }
public string BeforeCommand { get; set; }
public string AfterCommand { get; set; }
public List<FieldsDefaultValueDto> FieldsDefaultValue { get; set; }
}
/// <summary>
/// Grafik gorunumunun butun sekmeleri; ListForm'un <c>*Json</c> grafik kolonlariyla birebir.
/// Blok <c>null</c> ise deploy varsayilani yazar (<see cref="ListFormWizardDto.ApplyDefaults"/>).
/// </summary>
public class WizardChartDto
{
public ChartCommonDto Common { get; set; }
public ChartAdaptivelayoutDto AdaptiveLayout { get; set; }
public ChartAnimationDto Animation { get; set; }
public List<ChartAnnotationDto> Annotations { get; set; }
public ChartArgumentAxisDto ArgumentAxis { get; set; }
public ChartCommonAnnotationDto CommonAnnotationSettings { get; set; }
public ChartCommonAxisDto CommonAxisSettings { get; set; }
public ChartCommonPaneDto CommonPaneSettings { get; set; }
public ChartCommonSeriesSettingsDto CommonSeriesSettings { get; set; }
public ChartCrosshairDto Crosshair { get; set; }
public ChartExportDto Export { get; set; }
public ChartLegendDto Legend { get; set; }
public ChartMarginDto Margin { get; set; }
public List<ChartPanesDto> Panes { get; set; }
public ChartScrollBarDto ScrollBar { get; set; }
public List<ChartSeriesDto> Series { get; set; }
public ChartSizeDto Size { get; set; }
public ChartTitleDto Title { get; set; }
public ChartTooltipDto Tooltip { get; set; }
public List<ChartValueAxisDto> ValueAxis { get; set; }
public ChartZoomAndPanDto ZoomAndPan { get; set; }
}

View file

@ -5,7 +5,6 @@ using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Sozsoft.Platform.Enums; using Sozsoft.Platform.Enums;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.ListForms; namespace Sozsoft.Platform.ListForms;
@ -13,25 +12,24 @@ namespace Sozsoft.Platform.ListForms;
/// Wizard seed dosyası formatı. /// Wizard seed dosyası formatı.
/// WizardAppService tarafından Seeds/wizard/{wizardName}.json olarak kaydedilir. /// WizardAppService tarafından Seeds/wizard/{wizardName}.json olarak kaydedilir.
/// WizardDataSeeder (DbMigrator) bu dosyaları okuyarak veritabanını yeniden oluşturur. /// WizardDataSeeder (DbMigrator) bu dosyaları okuyarak veritabanını yeniden oluşturur.
/// <para>
/// Dosya ekrani yalnizca <see cref="Wizard"/> bolumuyle tasir: ListForm detayinin her sekmesi orada bir
/// bloktur, alan detayinin sekmeleri alan ogelerindedir. Seeder ekrani
/// <see cref="ListFormWizardDto.BuildListForm"/> / <see cref="ListFormWizardDto.BuildFields"/> ile kurar.
/// </para>
/// </summary> /// </summary>
public class WizardSeedFileDto public class WizardSeedFileDto
{ {
public ListFormWizardDto Wizard { get; set; } public ListFormWizardDto Wizard { get; set; }
/// <summary> /// <summary>
/// Wizard'in urettigi ekranin ListForm sozlesmesi. Tasarimci (ListForm detayi) uzerinde /// Eski bicimin ekran bolumu; yalnizca okunur. Dosyada bulunursa <see cref="Wizard"/> bolumune
/// yapilan her degisiklik buraya geri yazilir; seeder ekrani bu bolumden kurar. /// <see cref="ListFormWizardDto.RefreshFrom"/> ile aktarilir ve bir sonraki yazimda dosyadan duser.
/// </summary> /// </summary>
public GridOptionsEditDto ListForm { get; set; } public GridOptionsEditDto ListForm { get; set; }
/// <summary>Ekranin alanlari; ListForm detayindaki alan tanimlariyla ayni sozlesme.</summary> /// <summary>Eski bicimin alan bolumu; <see cref="ListForm"/> gibi yalnizca okunur.</summary>
public List<ColumnFormatEditDto> Fields { get; set; } = []; public List<ColumnFormatEditDto> Fields { get; set; }
/// <summary>Tabloda IsDeleted alanı olup olmadığını belirtir (soft delete desteği).</summary>
public bool IsDeletedField { get; set; }
/// <summary>Tabloda CreatorId alanı olup olmadığını belirtir (audit alanı).</summary>
public bool IsCreatedField { get; set; }
/// <summary> /// <summary>
/// Wizard'in sahip oldugu kayitlar: deploy sirasinda ne veritabaninda ne seed dosyalarinda /// Wizard'in sahip oldugu kayitlar: deploy sirasinda ne veritabaninda ne seed dosyalarinda
@ -54,65 +52,18 @@ public class WizardSeedFileDto
public WizardDevRequestDto DevRequest { get; set; } public WizardDevRequestDto DevRequest { get; set; }
/// <summary> /// <summary>
/// Dosyaya yazmadan once ortama ozgu alanlari temizler. Kayit kimlikleri ve audit damgalari /// Dosyaya yazilmayan kisayollar: sahibi olan bloktan turetilirler (<see cref="ListFormWizardDto.SyncDerivedAnswers"/>).
/// veritabanina aittir; dosyada tasinirlarsa her ortamda anlamsiz fark uretirler. EditType ise /// Izin bayraklari <see cref="ListFormWizardDto.Editing"/>'de, veri aynasi bayraklari <c>Data</c> bolumundedir.
/// yalnizca tasarimcinin hangi sekmeyi kaydettigini bildiren istek alanidir.
/// </summary> /// </summary>
private void ClearRuntimeFields() private static readonly string[] DerivedWizardKeys =
{
if (Wizard != null)
Wizard.EditFileName = null;
if (ListForm != null)
ListForm.EditType = null;
ClearAudit(ListForm);
foreach (var field in Fields ?? [])
{
field.EditType = null;
ClearAudit(field);
}
}
/// <summary>
/// Ekranin sozlesmesi <see cref="ListForm"/> ve <see cref="Fields"/> bolumlerindedir; wizard
/// bolumunde ayni degerlerin ikinci bir kopyasi tutulmaz. Bu anahtarlar dosyaya yazilmadan
/// once wizard bolumunden cikarilir, okurken <see cref="Rehydrate"/> ile geri doldurulur.
/// </summary>
private static readonly string[] ListFormOwnedWizardKeys =
[ [
nameof(ListFormWizardDto.IsTenant), nameof(ListFormWizardDto.IsBranch), nameof(ListFormWizardDto.IsOrganizationUnit), nameof(ListFormWizardDto.SeedData), nameof(ListFormWizardDto.SeedSync),
nameof(ListFormWizardDto.AllowAdding), nameof(ListFormWizardDto.AllowUpdating), nameof(ListFormWizardDto.AllowDeleting), nameof(ListFormWizardDto.AllowAdding), nameof(ListFormWizardDto.AllowUpdating), nameof(ListFormWizardDto.AllowDeleting),
nameof(ListFormWizardDto.AllowDetail), nameof(ListFormWizardDto.OpenDetailAfterInsert), nameof(ListFormWizardDto.ConfirmDelete), nameof(ListFormWizardDto.SeedData), nameof(ListFormWizardDto.SeedSync), nameof(ListFormWizardDto.AllowDetail), nameof(ListFormWizardDto.OpenDetailAfterInsert), nameof(ListFormWizardDto.ConfirmDelete),
nameof(ListFormWizardDto.DefaultLayout), nameof(ListFormWizardDto.Grid), nameof(ListFormWizardDto.Card),
nameof(ListFormWizardDto.Pivot), nameof(ListFormWizardDto.Tree), nameof(ListFormWizardDto.Chart),
nameof(ListFormWizardDto.Gantt), nameof(ListFormWizardDto.Scheduler), nameof(ListFormWizardDto.Todo),
nameof(ListFormWizardDto.DataSourceCode), nameof(ListFormWizardDto.SelectCommandType), nameof(ListFormWizardDto.SelectCommand),
nameof(ListFormWizardDto.KeyFieldName), nameof(ListFormWizardDto.KeyFieldDbSourceType),
nameof(ListFormWizardDto.TreeOptionDto), nameof(ListFormWizardDto.GanttOptionDto),
nameof(ListFormWizardDto.SchedulerOptionDto), nameof(ListFormWizardDto.TodoOptionDto),
nameof(ListFormWizardDto.WorkflowDto), nameof(ListFormWizardDto.SubForms), nameof(ListFormWizardDto.Widgets),
nameof(ListFormWizardDto.CommandColumns),
]; ];
/// <summary>Grup ve alan tanimlarinin <see cref="Fields"/> bolumunde karsiligi olan anahtarlari.</summary> /// <summary>Alan ogesinde dosyaya yazilmayan kisayol; <c>ValidationRules</c>'tan turetilir.</summary>
private static readonly string[] FieldsOwnedGroupKeys = private static readonly string[] DerivedItemKeys = [nameof(WizardColumnItemInputDto.IsRequired)];
[
nameof(WizardColumnGroupInputDto.Caption), nameof(WizardColumnGroupInputDto.ColCount),
];
private static readonly string[] FieldsOwnedItemKeys =
[
nameof(WizardColumnItemInputDto.EditorType), nameof(WizardColumnItemInputDto.EditorOptions),
nameof(WizardColumnItemInputDto.EditorScript), nameof(WizardColumnItemInputDto.ColSpan),
nameof(WizardColumnItemInputDto.IsRequired), nameof(WizardColumnItemInputDto.IncludeInEditingForm),
nameof(WizardColumnItemInputDto.DbSourceType), nameof(WizardColumnItemInputDto.LookupDataSourceType),
nameof(WizardColumnItemInputDto.ValueExpr), nameof(WizardColumnItemInputDto.DisplayExpr),
nameof(WizardColumnItemInputDto.LookupQuery), nameof(WizardColumnItemInputDto.CascadeParentFields),
nameof(WizardColumnItemInputDto.CascadeRelationField), nameof(WizardColumnItemInputDto.CascadeFilterOperator),
nameof(WizardColumnItemInputDto.CascadeEmptyFields), nameof(WizardColumnItemInputDto.DefaultValue),
];
/// <summary>Dosyada `Menu` basligi altinda toplanan wizard alanlari; adlari degismez.</summary> /// <summary>Dosyada `Menu` basligi altinda toplanan wizard alanlari; adlari degismez.</summary>
private static readonly (string Property, string SeedKey)[] MenuKeys = private static readonly (string Property, string SeedKey)[] MenuKeys =
@ -124,6 +75,80 @@ public class WizardSeedFileDto
Same(nameof(ListFormWizardDto.MenuIcon)), Same(nameof(ListFormWizardDto.MenuIcon)),
]; ];
/// <summary>Dosyada `Data` basligi: veri kaynagi, kayit kaynagi, anahtar, kapsam bayraklari, okuma kosulu ve veri aynasi.</summary>
private static readonly (string Property, string SeedKey)[] DataKeys =
[
Same(nameof(ListFormWizardDto.DataSourceCode)), Same(nameof(ListFormWizardDto.DataSourceConnectionString)),
Same(nameof(ListFormWizardDto.SelectCommandType)), Same(nameof(ListFormWizardDto.SelectCommand)),
Same(nameof(ListFormWizardDto.TableName)), Same(nameof(ListFormWizardDto.KeyFieldName)),
Same(nameof(ListFormWizardDto.KeyFieldDbSourceType)), Same(nameof(ListFormWizardDto.IsTenant)),
Same(nameof(ListFormWizardDto.IsBranch)), Same(nameof(ListFormWizardDto.IsOrganizationUnit)),
Same(nameof(ListFormWizardDto.DefaultFilter)), Same(nameof(ListFormWizardDto.SeedFilePath)),
Same(nameof(ListFormWizardDto.SeedSyncInsert)), Same(nameof(ListFormWizardDto.SeedSyncUpdate)),
Same(nameof(ListFormWizardDto.SeedSyncDelete)), Same(nameof(ListFormWizardDto.SelectFieldsDefaultValue)),
];
/// <summary>Dosyada `Details` basligi: Detay sekmesinin genel ayarlari ve siralama modu.</summary>
private static readonly (string Property, string SeedKey)[] DetailsKeys =
[
Same(nameof(ListFormWizardDto.ListFormType)), Same(nameof(ListFormWizardDto.PageSize)),
Same(nameof(ListFormWizardDto.Width)), Same(nameof(ListFormWizardDto.Height)),
Same(nameof(ListFormWizardDto.FullHeight)), Same(nameof(ListFormWizardDto.ShowNote)),
Same(nameof(ListFormWizardDto.IsSubForm)), Same(nameof(ListFormWizardDto.SubFormsListFormType)),
Same(nameof(ListFormWizardDto.SortMode)), Same(nameof(ListFormWizardDto.UserId)), Same(nameof(ListFormWizardDto.RoleId)),
Same(nameof(ListFormWizardDto.CustomJsSources)), Same(nameof(ListFormWizardDto.CustomStyleSources)),
];
/// <summary>Dosyada `Layout` basligi: acik gorunumler ve acilis gorunumu.</summary>
private static readonly (string Property, string SeedKey)[] LayoutKeys =
[
Same(nameof(ListFormWizardDto.DefaultLayout)), Same(nameof(ListFormWizardDto.Grid)), Same(nameof(ListFormWizardDto.Card)),
Same(nameof(ListFormWizardDto.Pivot)), Same(nameof(ListFormWizardDto.Chart)), Same(nameof(ListFormWizardDto.Tree)),
Same(nameof(ListFormWizardDto.Gantt)), Same(nameof(ListFormWizardDto.Scheduler)), Same(nameof(ListFormWizardDto.Todo)),
];
/// <summary>
/// Dosyada `Language` basligi altinda toplanan dil metinleri: menu, ekran ve izin grubunun
/// iki dilli gorunen adlari. ListForm yalnizca dil anahtarini tasir
/// (`Title = "{ListFormCode}.Title"`); metinlerin tek kaynagi burasidir.
/// </summary>
private static readonly (string Property, string SeedKey)[] LanguageKeys =
[
Same(nameof(ListFormWizardDto.MenuParentEn)), Same(nameof(ListFormWizardDto.MenuParentTr)),
Same(nameof(ListFormWizardDto.PermissionGroupEn)), Same(nameof(ListFormWizardDto.PermissionGroupTr)),
Same(nameof(ListFormWizardDto.MenuEn)), Same(nameof(ListFormWizardDto.MenuTr)),
Same(nameof(ListFormWizardDto.TitleEn)), Same(nameof(ListFormWizardDto.TitleTr)),
Same(nameof(ListFormWizardDto.DescEn)), Same(nameof(ListFormWizardDto.DescTr)),
(nameof(ListFormWizardDto.LanguageTexts), "Texts"),
];
private static readonly (string Section, (string Property, string SeedKey)[] Keys)[] Sections =
[
(MenuSection, MenuKeys), (LanguageSection, LanguageKeys),
(DataSection, DataKeys), (DetailsSection, DetailsKeys), (LayoutSection, LayoutKeys),
];
/// <summary>
/// Ekrana ait her anahtar; Custom yolunda ekran olmadigi icin dosyadan cikarilir. Bolumlere tasinanlar
/// bolum adiyla, geri kalanlar kendi adiyla.
/// </summary>
private static readonly string[] ScreenKeys =
[
DataSection, DetailsSection, LayoutSection,
.. DerivedWizardKeys,
nameof(ListFormWizardDto.Editing), nameof(ListFormWizardDto.Insert), nameof(ListFormWizardDto.Update), nameof(ListFormWizardDto.Delete),
nameof(ListFormWizardDto.FormDefaults), nameof(ListFormWizardDto.Permission),
nameof(ListFormWizardDto.FilterRow), nameof(ListFormWizardDto.HeaderFilter), nameof(ListFormWizardDto.FilterPanel),
nameof(ListFormWizardDto.SearchPanel), nameof(ListFormWizardDto.GroupPanel), nameof(ListFormWizardDto.Selection),
nameof(ListFormWizardDto.ColumnOption), nameof(ListFormWizardDto.Row), nameof(ListFormWizardDto.Pager),
nameof(ListFormWizardDto.StateStoring), nameof(ListFormWizardDto.PivotOption),
nameof(ListFormWizardDto.TreeOptionDto), nameof(ListFormWizardDto.GanttOptionDto),
nameof(ListFormWizardDto.SchedulerOptionDto), nameof(ListFormWizardDto.TodoOptionDto),
nameof(ListFormWizardDto.ExtraFilters), nameof(ListFormWizardDto.CommandColumns), nameof(ListFormWizardDto.SubForms),
nameof(ListFormWizardDto.Widgets), nameof(ListFormWizardDto.WorkflowDto), nameof(ListFormWizardDto.ChartOptions),
nameof(ListFormWizardDto.Groups),
];
private static (string Property, string SeedKey) Same(string key) => (key, key); private static (string Property, string SeedKey) Same(string key) => (key, key);
/// <summary> /// <summary>
@ -142,29 +167,14 @@ public class WizardSeedFileDto
/// </summary> /// </summary>
private const string WizardCodeKey = "WizardCode"; private const string WizardCodeKey = "WizardCode";
/// <summary>Dosyadaki okuma sirasi: kimlik, kapsam, sonra menu/dil/alan bloklari.</summary> /// <summary>Dosyadaki okuma sirasi: kimlik, kapsam, menu/dil, veri, ekran sekmeleri, alanlar.</summary>
private static readonly string[] WizardKeyOrder = private static readonly string[] WizardKeyOrder =
[ [
nameof(ListFormWizardDto.ComponentKind), nameof(ListFormWizardDto.WizardName), WizardCodeKey, nameof(ListFormWizardDto.ComponentKind), nameof(ListFormWizardDto.WizardName), WizardCodeKey,
nameof(ListFormWizardDto.SeededAt), nameof(ListFormWizardDto.CustomComponentName), nameof(ListFormWizardDto.SeededAt), nameof(ListFormWizardDto.CustomComponentName),
nameof(ListFormWizardDto.PermissionGroupName), "ModuleId", nameof(ListFormWizardDto.PermissionGroupName), "ModuleId",
nameof(ListFormWizardDto.DataSourceConnectionString), MenuSection, LanguageSection, DataSection, DetailsSection, LayoutSection,
MenuSection, LanguageSection, nameof(ListFormWizardDto.Groups), .. ScreenKeys.Where(k => k is not (DataSection or DetailsSection or LayoutSection) && !DerivedWizardKeys.Contains(k)),
];
/// <summary>
/// Dosyada `Language` basligi altinda toplanan dil metinleri: menu, ekran ve izin grubunun
/// iki dilli gorunen adlari. ListForm yalnizca dil anahtarini tasir
/// (`Title = "{ListFormCode}.Title"`); metinlerin tek kaynagi burasidir.
/// </summary>
private static readonly (string Property, string SeedKey)[] LanguageKeys =
[
Same(nameof(ListFormWizardDto.MenuParentEn)), Same(nameof(ListFormWizardDto.MenuParentTr)),
Same(nameof(ListFormWizardDto.PermissionGroupEn)), Same(nameof(ListFormWizardDto.PermissionGroupTr)),
Same(nameof(ListFormWizardDto.MenuEn)), Same(nameof(ListFormWizardDto.MenuTr)),
Same(nameof(ListFormWizardDto.TitleEn)), Same(nameof(ListFormWizardDto.TitleTr)),
Same(nameof(ListFormWizardDto.DescEn)), Same(nameof(ListFormWizardDto.DescTr)),
(nameof(ListFormWizardDto.LanguageTexts), "Texts"),
]; ];
/// <summary> /// <summary>
@ -177,8 +187,17 @@ public class WizardSeedFileDto
(nameof(WizardColumnItemInputDto.TurkishCaption), "Tr"), (nameof(WizardColumnItemInputDto.TurkishCaption), "Tr"),
]; ];
/// <summary>Alan ogesinin dosyadaki ilk anahtarlari; geri kalani sozlesme sirasiyla gelir.</summary>
private static readonly string[] ItemKeyOrder =
[
nameof(WizardColumnItemInputDto.FieldName), nameof(WizardColumnItemInputDto.CaptionName), "En", "Tr",
];
private const string MenuSection = "Menu"; private const string MenuSection = "Menu";
private const string LanguageSection = "Language"; private const string LanguageSection = "Language";
private const string DataSection = "Data";
private const string DetailsSection = "Details";
private const string LayoutSection = "Layout";
// Varsayilan kodlayici Turkce harfleri de kacirir ("Kullanıcı"); dosya elle okunur ve // Varsayilan kodlayici Turkce harfleri de kacirir ("Kullanıcı"); dosya elle okunur ve
// diff'lenir, bu yuzden veri dosyalariyla ayni kodlayici kullanilir. // diff'lenir, bu yuzden veri dosyalariyla ayni kodlayici kullanilir.
@ -187,48 +206,26 @@ public class WizardSeedFileDto
WriteIndented = true, WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
}; };
/// <summary> /// <summary>
/// Dosyaya yazilacak JSON'u uretir: once ortama ozgu alanlar temizlenir, sonra wizard /// Dosyaya yazilacak JSON'u uretir: eski bicimin <see cref="ListForm"/>/<see cref="Fields"/> bolumleri ve
/// bolumunden ekranin sozlesmesinde zaten bulunan alanlar cikarilir. Wizard bolumunde /// istek alanlari hic yazilmaz, sahibi olan bloktan turetilen kisayollar cikarilir, wizard alanlari
/// yalnizca ekrandan uretilemeyen bilgiler kalir: menu, yetki, dil metinleri ve alan /// `Menu`/`Language`/`Data`/`Details`/`Layout` basliklarina toplanir. Custom yolunda ekran anahtari yoktur.
/// basliklarinin iki dilli karsiliklari.
/// </summary> /// </summary>
public string ToSeedJson() public string ToSeedJson()
{ {
ClearRuntimeFields(); if (Wizard != null)
Wizard.EditFileName = null;
var node = JsonSerializer.SerializeToNode(this, SeedWriteOptions); var node = JsonSerializer.SerializeToNode(this, SeedWriteOptions);
// Custom yolunda ListForm bolumu yoktur; cikarilan alanlarin geri doldurulacagi bir
// kaynak olmadigi icin wizard bolumu oldugu gibi birakilir.
if (ListForm != null && node?[nameof(Wizard)] is JsonObject wizard)
{
foreach (var key in ListFormOwnedWizardKeys)
wizard.Remove(key);
foreach (var group in wizard[nameof(ListFormWizardDto.Groups)] as JsonArray ?? [])
{
if (group is not JsonObject groupObject)
continue;
foreach (var key in FieldsOwnedGroupKeys)
groupObject.Remove(key);
foreach (var item in groupObject[nameof(WizardColumnGroupInputDto.Items)] as JsonArray ?? [])
{
if (item is JsonObject itemObject)
{
foreach (var key in FieldsOwnedItemKeys)
itemObject.Remove(key);
}
}
}
}
// Bekleyen kurulum yoksa bolum dosyada hic yer almaz; kurulu bir wizard'in dosyasi
// migrate sonrasinda deploy oncesiyle birebir ayni kalir.
if (node is JsonObject root) if (node is JsonObject root)
{ {
root.Remove(nameof(ListForm));
root.Remove(nameof(Fields));
// Bekleyen kurulum yoksa bolum dosyada hic yer almaz; kurulu bir wizard'in dosyasi
// migrate sonrasinda deploy oncesiyle birebir ayni kalir.
if (Pending == null) if (Pending == null)
root.Remove(nameof(Pending)); root.Remove(nameof(Pending));
@ -238,6 +235,28 @@ public class WizardSeedFileDto
if (node?[nameof(Wizard)] is JsonObject wizardNode) if (node?[nameof(Wizard)] is JsonObject wizardNode)
{ {
foreach (var key in DerivedWizardKeys)
wizardNode.Remove(key);
foreach (var group in GroupNodes(wizardNode))
{
var items = group[nameof(WizardColumnGroupInputDto.Items)] as JsonArray ?? [];
for (var i = 0; i < items.Count; i++)
{
if (items[i] is not JsonObject item)
continue;
foreach (var key in DerivedItemKeys)
item.Remove(key);
foreach (var (property, seedKey) in CaptionKeys)
Rename(item, property, seedKey);
// Alan kimligi ve basliklari once; dosyaya bakan alanin ne oldugunu ilk satirlarda gorur.
items[i] = Reorder((JsonObject)item.DeepClone(), ItemKeyOrder);
}
}
// Baglanti cumlesi yalnizca yeni bir veri kaynagi tanimlanirken doludur; bos degeri // Baglanti cumlesi yalnizca yeni bir veri kaynagi tanimlanirken doludur; bos degeri
// dosyada tasimanin anlami yok. // dosyada tasimanin anlami yok.
if (string.IsNullOrWhiteSpace(Wizard?.DataSourceConnectionString)) if (string.IsNullOrWhiteSpace(Wizard?.DataSourceConnectionString))
@ -249,8 +268,7 @@ public class WizardSeedFileDto
if (Wizard?.CreateMenu != false) if (Wizard?.CreateMenu != false)
wizardNode.Remove(nameof(ListFormWizardDto.CreateMenu)); wizardNode.Remove(nameof(ListFormWizardDto.CreateMenu));
// Kok seviyedeki ust menu eski ve en yaygin durumdur; bos zincir dosyaya yazilmaz, // Kok seviyedeki ust menu eski ve en yaygin durumdur; bos zincir dosyaya yazilmaz.
// mevcut wizard dosyalari degismeden kalir.
if (Wizard?.MenuParentPath is not { Count: > 0 }) if (Wizard?.MenuParentPath is not { Count: > 0 })
wizardNode.Remove(nameof(ListFormWizardDto.MenuParentPath)); wizardNode.Remove(nameof(ListFormWizardDto.MenuParentPath));
@ -260,14 +278,15 @@ public class WizardSeedFileDto
foreach (var (property, seedKey) in RenamedKeys) foreach (var (property, seedKey) in RenamedKeys)
Rename(wizardNode, property, seedKey); Rename(wizardNode, property, seedKey);
foreach (var item in ItemNodes(wizardNode)) foreach (var (section, keys) in Sections)
{ MoveIntoSection(wizardNode, section, keys);
foreach (var (property, seedKey) in CaptionKeys)
Rename(item, property, seedKey);
}
MoveIntoSection(wizardNode, MenuSection, MenuKeys); // Custom yolunda ListForm ekosistemi uretilmez; ekran anahtarlari dosyada yer almaz.
MoveIntoSection(wizardNode, LanguageSection, LanguageKeys); if (Wizard?.ComponentKind == WizardComponentKindEnum.Custom)
{
foreach (var key in ScreenKeys)
wizardNode.Remove(key);
}
node[nameof(Wizard)] = Reorder(wizardNode, WizardKeyOrder); node[nameof(Wizard)] = Reorder(wizardNode, WizardKeyOrder);
} }
@ -276,8 +295,8 @@ public class WizardSeedFileDto
} }
/// <summary> /// <summary>
/// Seed dosyasini okur: once `Menu`/`Language` basliklari duzlestirilir (sozlesme duz /// Seed dosyasini okur: basliklar duzlestirilir (sozlesme duz oldugu icin), eski bicimin
/// oldugu icin), sonra wizard bolumu ekranin sozlesmesinden tamamlanir. /// ekran bolumleri wizard'a aktarilir, kisayollar bloklardan turetilir.
/// </summary> /// </summary>
public static WizardSeedFileDto FromSeedJson(string json) public static WizardSeedFileDto FromSeedJson(string json)
{ {
@ -289,17 +308,11 @@ public class WizardSeedFileDto
} }
/// <summary> /// <summary>
/// Deploy girdisini (MCP <c>deploy_wizard</c>) okur: seed dosyasi bicimi (<c>{"Wizard": ..., "ListForm": ..., "Fields": [...]}</c>) /// Deploy girdisini (MCP <c>deploy_wizard</c>) okur: seed dosyasi bicimi (<c>{"Wizard": {...}}</c>, basliklar
/// ya da duz wizard cevaplari. <see cref="FromSeedJson"/>'dan farki, bolumlerin eksik ya da /// toplanmis) ya da duz wizard cevaplari. Dosyadan farki, cevabin kismi olabilmesidir: yazilmayan blok
/// kismi olabilmesidir: /// deploy'da varsayilaniyla dolar (<see cref="ListFormWizardDto.ApplyDefaults"/>), yazilan kisayol
/// <list type="bullet"> /// (<c>AllowAdding</c>, <c>IsRequired</c>) bloktan ustundur. Eski bicimin <c>ListForm</c>/<c>Fields</c> bolumleri
/// <item>Wizard bolumunde yazilan cevap korunur; yazilmayan ekran alani (veri kaynagi, duzen, /// kabul edilmez; adlari <see cref="ListFormWizardDto.LegacySections"/>'a yazilir ve deploy reddeder.
/// alan editoru …) <c>ListForm</c>/<c>Fields</c> bolumunden tamamlanir. Boylece get_wizard ciktisina
/// yeni bir alan eklemek, <c>Fields</c>'ta karsiligi olmadigi icin sessizce dusmez.</item>
/// <item><c>ListForm</c>/<c>Fields</c> bolumleri ekranin sozlesmesine katman olarak uygulanir
/// (<see cref="WizardScreenOverlay"/>): wizard cevaplarinin uretmedigi her ayar (varsayilan
/// degerler, dogrulama, bicim, komutlar …) deploy'da ekranda kalir.</item>
/// </list>
/// </summary> /// </summary>
public static ListFormWizardDto FromDeployJson(string json) public static ListFormWizardDto FromDeployJson(string json)
{ {
@ -310,7 +323,6 @@ public class WizardSeedFileDto
if (FindKey(root, "Seed") is { } seedKey && root[seedKey] is JsonObject seed) if (FindKey(root, "Seed") is { } seedKey && root[seedKey] is JsonObject seed)
root = (JsonObject)seed.DeepClone(); root = (JsonObject)seed.DeepClone();
// Bolum adlari dosyadaki gibi yazilir; istemcinin harf buyuklugu farki bolumu dusurmesin.
foreach (var section in new[] { nameof(Wizard), nameof(ListForm), nameof(Fields) }) foreach (var section in new[] { nameof(Wizard), nameof(ListForm), nameof(Fields) })
{ {
if (FindKey(root, section) is { } key && key != section) if (FindKey(root, section) is { } key && key != section)
@ -323,66 +335,18 @@ public class WizardSeedFileDto
if (root[nameof(Wizard)] is not JsonObject wizardNode) if (root[nameof(Wizard)] is not JsonObject wizardNode)
return null; return null;
FlattenWizard(wizardNode); var leftovers = FlattenWizard(wizardNode);
var wizard = wizardNode.Deserialize<ListFormWizardDto>(PlatformJsonOptions.CaseInsensitive); var wizard = wizardNode.Deserialize<ListFormWizardDto>(PlatformJsonOptions.CaseInsensitive);
if (wizard == null) if (wizard == null)
return null; return null;
wizard.UnknownKeys = UnknownKeys(root, wizardNode); wizard.LegacySections = [.. new[] { nameof(ListForm), nameof(Fields) }.Where(root.ContainsKey)];
wizard.UnknownKeys = [.. UnknownKeys(root, wizardNode), .. leftovers];
var listForm = root[nameof(ListForm)] as JsonObject; wizard.SyncDerivedAnswers(wizardNode);
var fields = root[nameof(Fields)] as JsonArray;
if (listForm == null && fields == null)
return wizard;
// Bolumlerden geri kurulan wizard hem eksik cevaplarin kaynagi hem de katmanin karsilastirma
// tabanidir: bolumde wizard'in zaten urettigi deger katman sayilmaz.
var sections = new JsonObject
{
[nameof(Wizard)] = wizardNode.DeepClone(),
[nameof(ListForm)] = listForm?.DeepClone() ?? new JsonObject(),
[nameof(Fields)] = fields?.DeepClone() ?? new JsonArray(),
}.Deserialize<WizardSeedFileDto>(PlatformJsonOptions.CaseInsensitive).Rehydrate().Wizard;
if (listForm != null)
FillMissing(wizard, sections, wizardNode, ListFormOwnedWizardKeys);
if (fields != null)
FillMissingGroups(wizard, sections, wizardNode);
MoveFieldDefaultValues(wizard, fields);
wizard.ScreenOverlay = new WizardScreenOverlay
{
ListForm = listForm,
Fields = [.. (fields ?? []).OfType<JsonObject>()],
Baseline = sections,
};
return wizard; return wizard;
} }
/// <summary>
/// Alan kaydindaki <c>DefaultValue</c>/<c>DefaultValueType</c> select servisinin hesapladigi degerdir,
/// saklanmaz; form varsayilani <c>FormFieldsDefaultValueJson</c>'dadir. Fields'ta yazilan deger ayni
/// alanin wizard ogesine (<see cref="WizardColumnItemInputDto.DefaultValue"/>) tasinir ve oradaki degerin yerine gecer.
/// </summary>
private static void MoveFieldDefaultValues(ListFormWizardDto wizard, JsonArray fields)
{
var items = wizard.Groups.SelectMany(g => g.Items).ToList();
foreach (var field in (fields ?? []).OfType<JsonObject>())
{
field.Remove(FindKey(field, nameof(ColumnFormatEditDto.DefaultValueType)) ?? string.Empty);
if (FindKey(field, nameof(ColumnFormatEditDto.DefaultValue)) is not { } key || !field.Remove(key, out var value) || value is null)
continue;
var fieldName = field[FindKey(field, nameof(ColumnFormatEditDto.FieldName)) ?? string.Empty]?.ToString();
var item = items.FirstOrDefault(i => string.Equals(i.FieldName, fieldName, StringComparison.OrdinalIgnoreCase));
if (item != null)
item.DefaultValue = value is JsonValue text && text.TryGetValue<string>(out var s) ? s : value.ToJsonString();
}
}
/// <summary> /// <summary>
/// Sozlesmede karsiligi olmayan anahtarlar (<c>Wizard.X</c>, <c>Groups[0].Items[2].X</c>). Okuyucu /// Sozlesmede karsiligi olmayan anahtarlar (<c>Wizard.X</c>, <c>Groups[0].Items[2].X</c>). Okuyucu
/// bunlari sessizce atar; model yazdigi degerin gittigini sanip baska adla tekrar denerdi. /// bunlari sessizce atar; model yazdigi degerin gittigini sanip baska adla tekrar denerdi.
@ -417,63 +381,18 @@ public class WizardSeedFileDto
return unknown; return unknown;
} }
/// <summary>Bolumde yazilmamis wizard alanlarini <paramref name="source"/>'tan kopyalar.</summary>
private static void FillMissing<T>(T target, T source, JsonObject written, IEnumerable<string> keys)
{
foreach (var key in keys.Where(key => !ContainsKey(written, key)))
{
var property = typeof(T).GetProperty(key);
property?.SetValue(target, property.GetValue(source));
}
}
/// <summary>
/// Gruplar wizard bolumunde hic yoksa <c>Fields</c>'tan kurulur; varsa yazilan gruplar ve ogeler
/// esas alinir, ogenin yazilmamis editor/lookup alanlari ayni adli alandan tamamlanir.
/// </summary>
private static void FillMissingGroups(ListFormWizardDto wizard, ListFormWizardDto sections, JsonObject written)
{
if (written[nameof(ListFormWizardDto.Groups)] is not JsonArray groupNodes)
{
wizard.Groups = sections.Groups;
return;
}
var sectionItems = sections.Groups
.SelectMany(g => g.Items)
.Where(i => !string.IsNullOrWhiteSpace(i.FieldName))
.GroupBy(i => i.FieldName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
for (var gi = 0; gi < wizard.Groups.Count && gi < groupNodes.Count; gi++)
{
if (groupNodes[gi] is not JsonObject groupNode)
continue;
var group = wizard.Groups[gi];
if (gi < sections.Groups.Count)
FillMissing(group, sections.Groups[gi], groupNode, FieldsOwnedGroupKeys);
var itemNodes = groupNode[nameof(WizardColumnGroupInputDto.Items)] as JsonArray ?? [];
for (var ii = 0; ii < group.Items.Count && ii < itemNodes.Count; ii++)
{
var item = group.Items[ii];
if (itemNodes[ii] is JsonObject itemNode && sectionItems.TryGetValue(item.FieldName ?? string.Empty, out var source))
FillMissing(item, source, itemNode, FieldsOwnedItemKeys);
}
}
}
private static bool ContainsKey(JsonObject node, string key) => FindKey(node, key) != null;
private static string FindKey(JsonObject node, string key) => private static string FindKey(JsonObject node, string key) =>
node.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Key; node.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Key;
/// <summary>`Menu`/`Language` basliklarini duzlestirir ve dosyadaki adlari sozlesme adlarina cevirir.</summary> /// <summary>
private static void FlattenWizard(JsonObject wizardNode) /// Basliklari duzlestirir ve dosyadaki adlari sozlesme adlarina cevirir. Baslik altinda sozlesmede
/// olmayan anahtarlar (<c>Data.X</c>) donus degeridir; deploy onlari tanimadigini soyler.
/// </summary>
private static List<string> FlattenWizard(JsonObject wizardNode)
{ {
FlattenSection(wizardNode, MenuSection, MenuKeys); var leftovers = new List<string>();
FlattenSection(wizardNode, LanguageSection, LanguageKeys); foreach (var (section, keys) in Sections)
leftovers.AddRange(FlattenSection(wizardNode, section, keys));
foreach (var (property, seedKey) in RenamedKeys) foreach (var (property, seedKey) in RenamedKeys)
Rename(wizardNode, seedKey, property); Rename(wizardNode, seedKey, property);
@ -483,12 +402,16 @@ public class WizardSeedFileDto
foreach (var (property, seedKey) in CaptionKeys) foreach (var (property, seedKey) in CaptionKeys)
Rename(item, seedKey, property); Rename(item, seedKey, property);
} }
return leftovers;
} }
private static IEnumerable<JsonObject> GroupNodes(JsonObject wizard) =>
(wizard[nameof(ListFormWizardDto.Groups)] as JsonArray ?? []).OfType<JsonObject>();
/// <summary>Wizard bolumundeki tum grup ogeleri; ad donusumu her ogeye ayni sekilde uygulanir.</summary> /// <summary>Wizard bolumundeki tum grup ogeleri; ad donusumu her ogeye ayni sekilde uygulanir.</summary>
private static IEnumerable<JsonObject> ItemNodes(JsonObject wizard) => private static IEnumerable<JsonObject> ItemNodes(JsonObject wizard) =>
(wizard[nameof(ListFormWizardDto.Groups)] as JsonArray ?? []) GroupNodes(wizard)
.OfType<JsonObject>()
.SelectMany(g => g[nameof(WizardColumnGroupInputDto.Items)] as JsonArray ?? []) .SelectMany(g => g[nameof(WizardColumnGroupInputDto.Items)] as JsonArray ?? [])
.OfType<JsonObject>(); .OfType<JsonObject>();
@ -529,43 +452,39 @@ public class WizardSeedFileDto
wizard[section] = target; wizard[section] = target;
} }
/// <summary>Baslik altindaki alanlari wizard bolumune geri tasir.</summary> /// <summary>Baslik altindaki alanlari wizard bolumune geri tasir; taninmayan anahtarlari bildirir.</summary>
private static void FlattenSection(JsonObject wizard, string section, (string Property, string SeedKey)[] keys) private static List<string> FlattenSection(JsonObject wizard, string section, (string Property, string SeedKey)[] keys)
{ {
if (!wizard.Remove(section, out var value) || value is not JsonObject group) var key = FindKey(wizard, section);
return; if (key == null || !wizard.Remove(key, out var value) || value is not JsonObject group)
return [];
foreach (var (property, seedKey) in keys) foreach (var (property, seedKey) in keys)
{ {
if (group.Remove(seedKey, out var item)) var found = FindKey(group, seedKey);
if (found != null && group.Remove(found, out var item))
wizard[property] = item; wizard[property] = item;
} }
return [.. group.Select(x => $"{nameof(Wizard)}.{section}.{x.Key}")];
} }
/// <summary> /// <summary>
/// Dosyadan okunan wizard bolumunu ekranin sozlesmesinden tamamlar. Dosyada tekrar edilmeyen /// Dosyadan okunan nesneyi tamamlar: eski bicimin ekran bolumleri wizard'a aktarilir ve dusurulur,
/// alanlar (veri kaynagi, duzen, duzenleme izinleri, alan tanimlari) <see cref="ListForm"/> ve /// kisayollar bloklardan turetilir.
/// <see cref="Fields"/> uzerinden doldurulur; wizard'i okuyan her kod tam nesneyi gorur.
/// </summary> /// </summary>
private WizardSeedFileDto Rehydrate() private WizardSeedFileDto Rehydrate()
{ {
if (Wizard != null && ListForm != null) if (Wizard != null && ListForm != null)
{
Wizard.RefreshFrom(ListForm, Fields ?? []); Wizard.RefreshFrom(ListForm, Fields ?? []);
ListForm = null;
Fields = null;
}
Wizard?.SyncDerivedAnswers();
return this; return this;
} }
private static void ClearAudit(AuditedEntityDto<Guid> dto)
{
if (dto == null)
return;
dto.Id = default;
dto.CreationTime = default;
dto.CreatorId = null;
dto.LastModificationTime = null;
dto.LastModifierId = null;
}
} }
/// <summary> /// <summary>

View file

@ -114,6 +114,13 @@ public class ListFormWizardAppService(
throw new UserFriendlyException(L["Sas.ListForm.WizardError.UnknownInputKeys", string.Join(", ", input.UnknownKeys)]); throw new UserFriendlyException(L["Sas.ListForm.WizardError.UnknownInputKeys", string.Join(", ", input.UnknownKeys)]);
} }
// Ekranin butun ayarlari Wizard bolumundedir; eski bicimin ListForm/Fields bolumleri sessizce
// atilsaydi model ayarlarin yazildigini sanirdi.
if (input.LegacySections.Count > 0)
{
throw new UserFriendlyException(L["Sas.ListForm.WizardError.LegacySectionsNotAccepted", string.Join(", ", input.LegacySections)]);
}
var wizardName = input.WizardName?.Trim(); var wizardName = input.WizardName?.Trim();
if (string.IsNullOrWhiteSpace(wizardName)) if (string.IsNullOrWhiteSpace(wizardName))
throw new UserFriendlyException(L["Sas.ListForm.WizardError.NameRequired"]); throw new UserFriendlyException(L["Sas.ListForm.WizardError.NameRequired"]);
@ -294,12 +301,6 @@ public class ListFormWizardAppService(
if (input.CreateMenu) if (input.CreateMenu)
await WriteMenuRowsAsync(rows, input, code, owned, inserted); await WriteMenuRowsAsync(rows, input, code, owned, inserted);
GridOptionsEditDto? listFormDto = null;
List<ColumnFormatEditDto> fieldDtos = [];
var isDeleted = false;
var isCreated = false;
var isModified = false;
// Custom yolunda ListForm ekosistemi üretilmez: veri kaynağı, ListForm, alanlar, // Custom yolunda ListForm ekosistemi üretilmez: veri kaynağı, ListForm, alanlar,
// subform/widget/workflow kayıtlarının hiçbirine ihtiyaç yoktur. // subform/widget/workflow kayıtlarının hiçbirine ihtiyaç yoktur.
if (!isCustomComponent) if (!isCustomComponent)
@ -324,11 +325,11 @@ public class ListFormWizardAppService(
// Ekranin sozlesmesi tek fabrikadan uretilir; migrate ekrani bu dosyadan kurar. // Ekranin sozlesmesi tek fabrikadan uretilir; migrate ekrani bu dosyadan kurar.
var tableColumns = await GetTableColumnsAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand); var tableColumns = await GetTableColumnsAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand);
isDeleted = input.SelectCommandType == SelectCommandTypeEnum.Query var isDeleted = input.SelectCommandType == SelectCommandTypeEnum.Query
? await IsQueryMainTableSoftDeletedAsync(input) ? await IsQueryMainTableSoftDeletedAsync(input)
: tableColumns.ContainsKey("IsDeleted"); : tableColumns.ContainsKey("IsDeleted");
isCreated = tableColumns.ContainsKey("CreatorId"); var isCreated = tableColumns.ContainsKey("CreatorId");
isModified = tableColumns.ContainsKey("LastModifierId"); var isModified = tableColumns.ContainsKey("LastModifierId");
// Yeni kaydin form degeri, cevapta yazilmadiysa kolonun sabit DEFAULT'udur (IsActive DEFAULT 1 → // Yeni kaydin form degeri, cevapta yazilmadiysa kolonun sabit DEFAULT'udur (IsActive DEFAULT 1 →
// true, UomId DEFAULT 'Adet'); deger koda degil tabloyu tanimlayan scripte aittir. // true, UomId DEFAULT 'Adet'); deger koda degil tabloyu tanimlayan scripte aittir.
@ -339,16 +340,16 @@ public class ListFormWizardAppService(
} }
foreach (var item in input.Groups.SelectMany(g => g.Items)) foreach (var item in input.Groups.SelectMany(g => g.Items))
item.CaptionName = await ResolveLanguageKeyCasingAsync(rows, item.CaptionName); item.CaptionName = await ResolveLanguageKeyCasingAsync(rows, item.CaptionName);
listFormDto = input.BuildListForm(isDeleted, isCreated, isModified);
fieldDtos = input.BuildFields();
// Veri aynasinin yeri ekranin tanimidir; tasarimcida klasore cevrilmis bir hedef // Veri aynasinin yeri ekranin tanimidir; tasarimcida klasore cevrilmis bir hedef
// duzenlemede varsayilan tek dosyaya geri donmez. // duzenlemede varsayilan tek dosyaya geri donmez.
if (input.SeedData && !string.IsNullOrWhiteSpace(previous?.ListForm?.SeedFilePath)) if (input.SeedData && string.IsNullOrWhiteSpace(input.SeedFilePath) && !string.IsNullOrWhiteSpace(previous?.Wizard?.SeedFilePath))
listFormDto.SeedFilePath = previous.ListForm.SeedFilePath; input.SeedFilePath = previous.Wizard.SeedFilePath;
if (input.ScreenOverlay != null) // Cevapta bos birakilan her blok burada varsayilaniyla dolar; dosyaya tam sozlesme yazilir.
(listFormDto, fieldDtos) = await ApplyScreenOverlayAsync(input, listFormDto, fieldDtos, isDeleted, isCreated, isModified, rows); input.ApplyDefaults(isDeleted, isCreated, isModified);
var listFormDto = input.BuildListForm();
var fieldDtos = input.BuildFields();
ValidateImageColumnLengths(input, tableColumns); ValidateImageColumnLengths(input, tableColumns);
await LocalizeStaticLookupsAsync(input, fieldDtos, rows, owned, inserted); await LocalizeStaticLookupsAsync(input, fieldDtos, rows, owned, inserted);
@ -362,6 +363,10 @@ public class ListFormWizardAppService(
foreach (var field in fieldDtos) foreach (var field in fieldDtos)
field.EditorScript = Sozsoft.Platform.Editors.EditorScript.Rebuild(field.EditorScript, booleanFields.Contains); field.EditorScript = Sozsoft.Platform.Editors.EditorScript.Rebuild(field.EditorScript, booleanFields.Contains);
// Uretilen ekran cevaplara geri yazilir: dosya yalnizca cevaplari tasir ve deploy sonrasi
// duzeltmeler (dil anahtarina cevrilen secenekler, kurallardan uretilen script) orada kalmali.
input.RefreshFrom(listFormDto, fieldDtos);
foreach (var item in input.Groups.SelectMany(g => g.Items)) foreach (var item in input.Groups.SelectMany(g => g.Items))
await WriteLanguageRowsAsync(rows, item.CaptionName, item.EnglishCaption, item.TurkishCaption, owned, inserted); await WriteLanguageRowsAsync(rows, item.CaptionName, item.EnglishCaption, item.TurkishCaption, owned, inserted);
} }
@ -370,7 +375,7 @@ public class ListFormWizardAppService(
await rows.SaveAsync(); await rows.SaveAsync();
var pending = BuildPending(previous, listFormCode); var pending = BuildPending(previous, listFormCode);
await SaveWizardSeedFileAsync(input, listFormDto, fieldDtos, isDeleted, isCreated, inserted, pending, devRequest); await SaveWizardSeedFileAsync(input, inserted, pending, devRequest);
// Ad degisen duzenlemede eski dosya kalmaz; migrate onun kayitlarini yeni dosyanin // Ad degisen duzenlemede eski dosya kalmaz; migrate onun kayitlarini yeni dosyanin
// bekleyen kurulumundan (Replaced) bilir. // bekleyen kurulumundan (Replaced) bilir.
@ -493,54 +498,6 @@ public class ListFormWizardAppService(
private static readonly Regex StaticLookupWordRegex = new(@"^[A-Za-z][A-Za-z0-9]*$", RegexOptions.Compiled); private static readonly Regex StaticLookupWordRegex = new(@"^[A-Za-z][A-Za-z0-9]*$", RegexOptions.Compiled);
private static readonly Regex StaticLookupSuffixRegex = new(@"^[A-Za-z0-9]+$", RegexOptions.Compiled); private static readonly Regex StaticLookupSuffixRegex = new(@"^[A-Za-z0-9]+$", RegexOptions.Compiled);
/// <summary>
/// Deploy girdisindeki ListForm/Fields bolumlerini uretilen ekrana uygular
/// (<see cref="WizardScreenOverlay"/>) ve wizard cevaplarini son sozlesmeyle esitler; sonraki
/// adimlar (dil satirlari, widget ve gorsel kolonu dogrulamasi) katmanin getirdigi degerleri gorur.
/// </summary>
private async Task<(GridOptionsEditDto ListForm, List<ColumnFormatEditDto> Fields)> ApplyScreenOverlayAsync(
ListFormWizardDto input,
GridOptionsEditDto listForm,
List<ColumnFormatEditDto> fields,
bool isDeleted,
bool isCreated,
bool isModified,
WizardSeedRowStore rows)
{
var overlay = input.ScreenOverlay;
var errors = new List<WizardOverlayError>();
var messages = new List<string>();
try
{
listForm = overlay.Apply(listForm, overlay.Baseline.BuildListForm(isDeleted, isCreated, isModified), errors);
fields = overlay.Apply(fields, overlay.Baseline.BuildFields(), errors);
}
catch (JsonException ex)
{
messages.Add(ex.Message);
}
messages.InsertRange(0, errors.Select(e => L[e.Key, e.Args].Value));
if (messages.Count > 0)
throw new UserFriendlyException(L["Sas.ListForm.WizardError.OverlayFailed", string.Join(" ", messages)]);
// Katmanin is akisi ham gelir (oneksiz "N1", bos onayci); wizard'in urettigi gibi normalize edilir.
listForm.WorkflowDto = ListFormWizardDto.NormalizeWorkflow(listForm.WorkflowDto, listForm.ListFormCode);
foreach (var field in fields)
field.CaptionName = await ResolveLanguageKeyCasingAsync(rows, field.CaptionName);
// Menusuz ekranin widget'i cizilmez (Create); katman bu kurali asamaz.
if (!input.CreateMenu)
listForm.WidgetsJson = null;
input.RefreshFrom(listForm, fields);
await ValidateWidgetLanguageKeysAsync(input, rows);
return (listForm, fields);
}
/// <summary> /// <summary>
/// Bekleyen kurulum: onceki surumun sahip oldugu kayitlar ve kurdugu ekran migrate'te yenilenmek /// Bekleyen kurulum: onceki surumun sahip oldugu kayitlar ve kurdugu ekran migrate'te yenilenmek
/// uzere eklenir. Migrate edilmeden ust uste yapilan duzenlemeler ayni listeyi buyutur; ilk /// uzere eklenir. Migrate edilmeden ust uste yapilan duzenlemeler ayni listeyi buyutur; ilk
@ -967,17 +924,13 @@ public class ListFormWizardAppService(
/// Dosya, seeder'lar ve File Manager ile ortak olan seed klasörünün wizard dizinine yazılır; /// Dosya, seeder'lar ve File Manager ile ortak olan seed klasörünün wizard dizinine yazılır;
/// ekrani WizardDataSeeder bu dosyadan kurar. /// ekrani WizardDataSeeder bu dosyadan kurar.
/// <para> /// <para>
/// Dosya yalnizca wizard'in cevaplarini degil, urettigi ekranin ListForm sozlesmesini de tasir; /// Cevaplar ekranin tam sozlesmesidir (<see cref="ListFormWizardDto.RefreshFrom"/> ile uretilen ekrandan
/// tasarimci uzerinde yapilan degisiklikler ayni bolumlere geri yazildigi icin dosya her zaman /// doldurulmus); tasarimci uzerinde yapilan degisiklikler ayni cevaplara geri yazildigi icin dosya her
/// ekranin guncel halidir. Deploy'un tek ciktisi dosyalar oldugu icin yazma hatasi yutulmaz. /// zaman ekranin guncel halidir. Deploy'un tek ciktisi dosyalar oldugu icin yazma hatasi yutulmaz.
/// </para> /// </para>
/// </summary> /// </summary>
private async Task SaveWizardSeedFileAsync( private async Task SaveWizardSeedFileAsync(
ListFormWizardDto input, ListFormWizardDto input,
GridOptionsEditDto? listForm,
List<ColumnFormatEditDto> fields,
bool isDeletedField,
bool isCreatedField,
WizardInsertedRecordsDto inserted, WizardInsertedRecordsDto inserted,
WizardPendingDto pending, WizardPendingDto pending,
WizardDevRequestDto? devRequest) WizardDevRequestDto? devRequest)
@ -992,10 +945,6 @@ public class ListFormWizardAppService(
var seedData = new WizardSeedFileDto var seedData = new WizardSeedFileDto
{ {
Wizard = input, Wizard = input,
ListForm = listForm!,
Fields = fields ?? [],
IsDeletedField = isDeletedField,
IsCreatedField = isCreatedField,
InsertedRecords = inserted, InsertedRecords = inserted,
Pending = pending, Pending = pending,
DevRequest = devRequest!, DevRequest = devRequest!,
@ -1054,6 +1003,13 @@ public class ListFormWizardAppService(
/// Izin gruplarini iki dilli gorunen adlariyla dondurur. Gorunen ad, grup adiyla ayni olan /// Izin gruplarini iki dilli gorunen adlariyla dondurur. Gorunen ad, grup adiyla ayni olan
/// dil anahtarinin metnidir; wizard formu secim yapildiginda bu degerleri doldurur. /// dil anahtarinin metnidir; wizard formu secim yapildiginda bu degerleri doldurur.
/// </summary> /// </summary>
public Task<ListFormWizardDto> GetScreenDefaults()
{
var wizard = new ListFormWizardDto();
wizard.ApplyDefaults(isDeletedField: false, isCreatedField: false, isModifiedField: false);
return Task.FromResult(wizard);
}
public async Task<List<WizardPermissionGroupDto>> GetPermissionGroups() public async Task<List<WizardPermissionGroupDto>> GetPermissionGroups()
{ {
var res = PlatformConsts.AppName; var res = PlatformConsts.AppName;
@ -1262,7 +1218,7 @@ public class ListFormWizardAppService(
SubFormDto[] subForms; SubFormDto[] subForms;
try try
{ {
subForms = seed.ListForm?.SubFormsDto ?? [.. seed.Wizard.SubForms ?? []]; subForms = [.. seed.Wizard.SubForms ?? []];
} }
catch (JsonException) catch (JsonException)
{ {
@ -1628,7 +1584,7 @@ public class ListFormWizardAppService(
if (seed.Wizard.ComponentKind != WizardComponentKindEnum.Custom && if (seed.Wizard.ComponentKind != WizardComponentKindEnum.Custom &&
!await _installationChecker.IsInstalledAsync(false, seed.Wizard.ResolveMenuCode(), seed.Wizard.ResolveListFormCode())) !await _installationChecker.IsInstalledAsync(false, seed.Wizard.ResolveMenuCode(), seed.Wizard.ResolveListFormCode()))
{ {
_recordCleaner.DeleteSeedDataMirror(seed.ListForm?.SeedFilePath, mirror); _recordCleaner.DeleteSeedDataMirror(seed.Wizard.SeedFilePath, mirror);
} }
return [.. mirror, .. outcome.Select(r => r.Removed return [.. mirror, .. outcome.Select(r => r.Removed

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -111,7 +111,8 @@ public static class ModuleDocRenderer
private static void AppendWizard(StringBuilder sb, string fileName, WizardSeedFileDto seed) private static void AppendWizard(StringBuilder sb, string fileName, WizardSeedFileDto seed)
{ {
var wizard = seed.Wizard; var wizard = seed.Wizard;
var listForm = seed.ListForm; // Envanter ekranin sozlesmesini okur; dosya cevaplari tasidigi icin sozlesme buradan uretilir.
var listForm = wizard.ComponentKind == WizardComponentKindEnum.Custom ? null : wizard.BuildListForm();
var code = wizard.ResolveListFormCode(); var code = wizard.ResolveListFormCode();
var title = FirstText(wizard.MenuTr, wizard.TitleTr, wizard.MenuEn, code); var title = FirstText(wizard.MenuTr, wizard.TitleTr, wizard.MenuEn, code);
@ -203,7 +204,8 @@ public static class ModuleDocRenderer
Row(sb, "Veri aynası", $"`{listForm.SeedFilePath}`"); Row(sb, "Veri aynası", $"`{listForm.SeedFilePath}`");
} }
AppendFields(sb, wizard, seed.Fields ?? []); if (listForm != null)
AppendFields(sb, wizard, wizard.BuildFields());
} }
private static void AppendFields(StringBuilder sb, ListFormWizardDto wizard, List<ColumnFormatEditDto> fields) private static void AppendFields(StringBuilder sb, ListFormWizardDto wizard, List<ColumnFormatEditDto> fields)

View file

@ -137,9 +137,8 @@ public sealed class WizardExportCollector(string scopeRoot, IReadOnlyDictionary<
/// </summary> /// </summary>
private void CollectList(WizardSeedFileDto seed, Queue<(string FilePath, string Reason)> pending) private void CollectList(WizardSeedFileDto seed, Queue<(string FilePath, string Reason)> pending)
{ {
var listForm = seed.ListForm; var commandType = seed.Wizard.SelectCommandType;
var commandType = listForm?.SelectCommandType ?? seed.Wizard.SelectCommandType; var selectCommand = seed.Wizard.SelectCommand;
var selectCommand = listForm?.SelectCommand ?? seed.Wizard.SelectCommand;
var screen = seed.Wizard.WizardName; var screen = seed.Wizard.WizardName;
// Query tipinde SelectCommand bir nesne adi degil ham SQL'dir; icindeki nesneler taranir. // Query tipinde SelectCommand bir nesne adi degil ham SQL'dir; icindeki nesneler taranir.
@ -156,11 +155,11 @@ public sealed class WizardExportCollector(string scopeRoot, IReadOnlyDictionary<
} }
// Ekranin verisi seed dosyasina aynalaniyorsa arsiv onsuz eksiktir. // Ekranin verisi seed dosyasina aynalaniyorsa arsiv onsuz eksiktir.
var seedFilePath = listForm?.SeedFilePath; var seedFilePath = seed.Wizard.SeedFilePath;
if (!string.IsNullOrWhiteSpace(seedFilePath) && !AddSeedData(seedFilePath, localizer["Sas.ListForm.WizardExport.ReasonData", screen])) if (!string.IsNullOrWhiteSpace(seedFilePath) && !AddSeedData(seedFilePath, localizer["Sas.ListForm.WizardExport.ReasonData", screen]))
warnings.Add(localizer["Sas.ListForm.WizardExport.SeedFileNotFound", seedFilePath]); warnings.Add(localizer["Sas.ListForm.WizardExport.SeedFileNotFound", seedFilePath]);
foreach (var field in seed.Fields ?? []) foreach (var field in seed.Wizard.BuildFields())
{ {
LookupDto? lookup; LookupDto? lookup;
try try
@ -197,18 +196,7 @@ public sealed class WizardExportCollector(string scopeRoot, IReadOnlyDictionary<
} }
} }
private SubFormDto[] ReadSubForms(WizardSeedFileDto seed) private static SubFormDto[] ReadSubForms(WizardSeedFileDto seed) => [.. seed.Wizard.SubForms ?? []];
{
try
{
return seed.ListForm?.SubFormsDto ?? [];
}
catch (JsonException)
{
warnings.Add(localizer["Sas.ListForm.WizardExport.SubFormsUnresolved", seed.Wizard.WizardName]);
return [];
}
}
// ── Custom yolu ──────────────────────────────────────────────────────────────── // ── Custom yolu ────────────────────────────────────────────────────────────────

View file

@ -180,19 +180,19 @@ public sealed class WizardImportValidator(
return; return;
} }
if (seed.ListForm == null) if (string.IsNullOrWhiteSpace(wizard.SelectCommand) || string.IsNullOrWhiteSpace(wizard.KeyFieldName))
{ {
Add(WizardImportIssueSeverityEnum.Error, path, localizer["Sas.ListForm.WizardImport.ListFormSectionMissing", screen]); Add(WizardImportIssueSeverityEnum.Error, path, localizer["Sas.ListForm.WizardImport.ListFormSectionMissing", screen]);
return; return;
} }
var commandType = seed.ListForm.SelectCommandType; var commandType = wizard.SelectCommandType;
var dataSourceCode = seed.ListForm.DataSourceCode; var dataSourceCode = wizard.DataSourceCode;
// StoredProcedure ve ham sorgu icin tek bir nesne adi yoktur; nesne kontrolu yapilamaz. // StoredProcedure ve ham sorgu icin tek bir nesne adi yoktur; nesne kontrolu yapilamaz.
if (commandType == SelectCommandTypeEnum.Table || commandType == SelectCommandTypeEnum.View) if (commandType == SelectCommandTypeEnum.Table || commandType == SelectCommandTypeEnum.View)
{ {
var objectName = WizardExportCollector.NormalizeDatabaseObjectName(seed.ListForm.SelectCommand); var objectName = WizardExportCollector.NormalizeDatabaseObjectName(wizard.SelectCommand);
if (objectName == null) if (objectName == null)
{ {
Add(WizardImportIssueSeverityEnum.Error, path, localizer["Sas.ListForm.WizardImport.SelectCommandEmpty", screen]); Add(WizardImportIssueSeverityEnum.Error, path, localizer["Sas.ListForm.WizardImport.SelectCommandEmpty", screen]);
@ -206,7 +206,7 @@ public sealed class WizardImportValidator(
} }
} }
var seedFilePath = seed.ListForm.SeedFilePath; var seedFilePath = wizard.SeedFilePath;
var seedTarget = ListFormSeedTarget.Create(scopeRoot, seedFilePath); var seedTarget = ListFormSeedTarget.Create(scopeRoot, seedFilePath);
if (!string.IsNullOrWhiteSpace(seedFilePath) if (!string.IsNullOrWhiteSpace(seedFilePath)
&& (seedTarget == null || (!paths.Any(seedTarget.Contains) && !seedTarget.Exists))) && (seedTarget == null || (!paths.Any(seedTarget.Contains) && !seedTarget.Exists)))
@ -215,7 +215,7 @@ public sealed class WizardImportValidator(
localizer["Sas.ListForm.WizardImport.SeedFileMissing", screen, seedFilePath]); localizer["Sas.ListForm.WizardImport.SeedFileMissing", screen, seedFilePath]);
} }
foreach (var subForm in ReadSubForms(seed, path)) foreach (var subForm in ReadSubForms(seed))
{ {
var code = subForm?.Code?.Trim(); var code = subForm?.Code?.Trim();
if (string.IsNullOrWhiteSpace(code) || incomingListFormCodes.Contains(code)) if (string.IsNullOrWhiteSpace(code) || incomingListFormCodes.Contains(code))
@ -499,18 +499,7 @@ public sealed class WizardImportValidator(
return targetCustomComponents = names; return targetCustomComponents = names;
} }
private SubFormDto[] ReadSubForms(WizardSeedFileDto seed, string path) private static SubFormDto[] ReadSubForms(WizardSeedFileDto seed) => [.. seed.Wizard.SubForms ?? []];
{
try
{
return seed.ListForm?.SubFormsDto ?? [];
}
catch (JsonException)
{
Add(WizardImportIssueSeverityEnum.Error, path, localizer["Sas.ListForm.WizardImport.SubFormsUnreadable"]);
return [];
}
}
private WizardSeedFileDto? ReadWizard(WizardImportStagedEntry entry) private WizardSeedFileDto? ReadWizard(WizardImportStagedEntry entry)
{ {

View file

@ -21,8 +21,8 @@ namespace Sozsoft.Platform.ListForms;
public interface IWizardSeedSynchronizer public interface IWizardSeedSynchronizer
{ {
/// <summary> /// <summary>
/// Verilen ListForm koduna ait bir wizard seed dosyasi varsa dosyanin ekran ve alan /// Verilen ListForm koduna ait bir wizard seed dosyasi varsa dosyanin cevaplarini veritabanindaki
/// bolumlerini veritabanindaki guncel haliyle degistirir. Dosya yoksa hicbir sey yapmaz. /// guncel ekranla esitler. Dosya yoksa hicbir sey yapmaz.
/// </summary> /// </summary>
Task SyncAsync(string listFormCode); Task SyncAsync(string listFormCode);
} }
@ -68,14 +68,11 @@ public class WizardSeedSynchronizer(
var fields = await repoListFormField.GetListAsync(a => a.ListFormCode == listFormCode); var fields = await repoListFormField.GetListAsync(a => a.ListFormCode == listFormCode);
seed.ListForm = objectMapper.Map<ListForm, GridOptionsEditDto>(listForm); // Dosya yalnizca cevaplari tasir; ekranin guncel hali cevaplara geri yazilir. Aksi halde ayni
seed.Fields = [.. fields // wizard yeniden deploy edildiginde ya da migrate'te tasarimcidaki degisiklikler kaybolurdu.
.OrderBy(f => f.ListOrderNo) seed.Wizard.RefreshFrom(
.Select(objectMapper.Map<ListFormField, ColumnFormatEditDto>)]; objectMapper.Map<ListForm, GridOptionsEditDto>(listForm),
[.. fields.OrderBy(f => f.ListOrderNo).Select(objectMapper.Map<ListFormField, ColumnFormatEditDto>)]);
// Wizard bolumu de guncellenir; aksi halde ayni wizard yeniden deploy edildiginde
// tasarimcidaki degisiklikler eski cevaplarla ezilirdi.
seed.Wizard.RefreshFrom(seed.ListForm, seed.Fields);
await File.WriteAllTextAsync(filePath, seed.ToSeedJson()); await File.WriteAllTextAsync(filePath, seed.ToSeedJson());
logger.LogInformation("Wizard seed file synchronized: {FilePath}", filePath); logger.LogInformation("Wizard seed file synchronized: {FilePath}", filePath);

View file

@ -22,8 +22,9 @@ namespace Sozsoft.Platform.Data.Seeds;
/// Wizard ile uretilen ekranlari Seeds/wizard/*.json dosyalarindan kurar; host'ta ve kendi /// Wizard ile uretilen ekranlari Seeds/wizard/*.json dosyalarindan kurar; host'ta ve kendi
/// veritabani olan her tenant'ta ayni dosyalar kullanilir (<see cref="SeedFileScope"/>). /// veritabani olan her tenant'ta ayni dosyalar kullanilir (<see cref="SeedFileScope"/>).
/// <para> /// <para>
/// Deploy veritabanina yazmaz; bu seeder wizard'in kurulumunun tek yeridir. Dosya yalnizca ekrana /// Deploy veritabanina yazmaz; bu seeder wizard'in kurulumunun tek yeridir. Ekran dosyadaki wizard
/// ozgu kayitlari tasir: veri kaynagi, ListForm, alanlar ve is akisi. Yetkiler hicbir role atanmaz; /// cevaplarindan kurulur (<see cref="ListFormWizardDto.BuildListForm"/>): veri kaynagi, ListForm, alanlar
/// ve is akisi. Yetkiler hicbir role atanmaz;
/// hangi rolun ekrani gorecegi rol ekranindan secilir. /// hangi rolun ekrani gorecegi rol ekranindan secilir.
/// Menu, modul, izin grubu, yetki ve dil metni satirlari platform seed dosyalarindadir /// Menu, modul, izin grubu, yetki ve dil metni satirlari platform seed dosyalarindadir
/// (<c>data/*.json</c>) ve bu seeder'dan sonra <c>ListFormSeedDataApplier</c> tarafindan eklenir. /// (<c>data/*.json</c>) ve bu seeder'dan sonra <c>ListFormSeedDataApplier</c> tarafindan eklenir.
@ -111,9 +112,9 @@ public class WizardDataSeeder(
} }
var isCustomComponent = wizard.ComponentKind == WizardComponentKindEnum.Custom; var isCustomComponent = wizard.ComponentKind == WizardComponentKindEnum.Custom;
if (!isCustomComponent && seedFile.ListForm == null) if (!isCustomComponent && (string.IsNullOrWhiteSpace(wizard.SelectCommand) || string.IsNullOrWhiteSpace(wizard.KeyFieldName)))
{ {
logger.LogWarning("[{File}] ListForm section is missing, skipped.", fileName); logger.LogWarning("[{File}] Data section (SelectCommand/KeyFieldName) is missing, skipped.", fileName);
continue; continue;
} }
@ -153,7 +154,7 @@ public class WizardDataSeeder(
/// <summary> /// <summary>
/// Ekrana ozgu kayitlari kurar: veri kaynagi (yoksa), ListForm, alanlar ve is akisi dosyadaki /// Ekrana ozgu kayitlari kurar: veri kaynagi (yoksa), ListForm, alanlar ve is akisi dosyadaki
/// sozlesmeden. Menu/yetki/dil satirlari <c>ListFormSeedDataApplier</c>'a kalir. /// cevaplardan. Menu/yetki/dil satirlari <c>ListFormSeedDataApplier</c>'a kalir.
/// </summary> /// </summary>
private async Task InstallAsync(WizardSeedFileDto seedFile) private async Task InstallAsync(WizardSeedFileDto seedFile)
{ {
@ -179,18 +180,18 @@ public class WizardDataSeeder(
await DeleteListFormAsync(listFormCode); await DeleteListFormAsync(listFormCode);
// Ekran, seed dosyasinin tasidigi ListForm sozlesmesinden kurulur; boylece wizard // Ekran, dosyanin tasidigi cevaplardan kurulur: cevaplar deploy'da uretilen ekranla esitlendigi
// sonrasinda tasarimci uzerinde yapilan degisiklikler de geri gelir. // ve tasarimci degisiklikleri ayni cevaplara yazildigi icin tasarimcidaki is de geri gelir.
// Elle ya da eski bir surumle yazilmis dosyanin is akisi de kaydedilebilir hale getirilir. // Is akisi kurulumdan once kaydedilebilir hale getirilir (BuildListForm icinde normalize edilir).
seedFile.ListForm.WorkflowDto = ListFormWizardDto.NormalizeWorkflow(seedFile.ListForm.WorkflowDto, listFormCode); var listFormDto = input.BuildListForm();
var listForm = new ListForm(); var listForm = new ListForm();
seedFile.ListForm.ApplyTo(listForm); listFormDto.ApplyTo(listForm);
await repoListForm.InsertAsync(listForm, autoSave: true); await repoListForm.InsertAsync(listForm, autoSave: true);
// Alanlar ve is akisi tek SaveChanges ile yazilir; satir basina kayit, izlenen varlik sayisi // Alanlar ve is akisi tek SaveChanges ile yazilir; satir basina kayit, izlenen varlik sayisi
// buyudukce her kayitta degisiklik taramasini tekrarlar ve seed suresini katlar. // buyudukce her kayitta degisiklik taramasini tekrarlar ve seed suresini katlar.
var fields = seedFile.Fields.Select(fieldDto => var fields = input.BuildFields().Select(fieldDto =>
{ {
var field = new ListFormField(); var field = new ListFormField();
fieldDto.ApplyTo(field); fieldDto.ApplyTo(field);
@ -201,7 +202,7 @@ public class WizardDataSeeder(
await repoListFormField.InsertManyAsync(fields, autoSave: true); await repoListFormField.InsertManyAsync(fields, autoSave: true);
var workflows = new List<ListFormWorkflow>(); var workflows = new List<ListFormWorkflow>();
foreach (var criteria in seedFile.ListForm.WorkflowDto.Criteria) foreach (var criteria in listFormDto.WorkflowDto.Criteria)
{ {
if (string.IsNullOrWhiteSpace(criteria.Id)) if (string.IsNullOrWhiteSpace(criteria.Id))
{ {

View file

@ -1,12 +1,13 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Data;
using System.Text.Json; using System.Text.Json;
using Sozsoft.Platform.Enums; using Sozsoft.Platform.Enums;
using static Sozsoft.Platform.PlatformConsts;
namespace Sozsoft.Platform; namespace Sozsoft.Platform;
/// <summary>
/// Wizard'in metin sabitleri: anahtar/yol kurallari, popup olcusu, sorgu takma adi, varsayilan silme
/// komutu ve lookup JSON'u. DTO tipli varsayilanlar Application.Contracts'taki <c>WizardDefaults</c>'tadir.
/// </summary>
public static class WizardConsts public static class WizardConsts
{ {
public static int EditFormMaxWidth => 1200; public static int EditFormMaxWidth => 1200;
@ -35,15 +36,6 @@ public static class WizardConsts
public static string MenuUrl(string code) => $"/admin/list/{code}"; public static string MenuUrl(string code) => $"/admin/list/{code}";
public static string MenuIcon => "FcList"; public static string MenuIcon => "FcList";
public static readonly string DefaultExportJson = JsonSerializer.Serialize(new
{
Enabled = true,
AllowExportSelectedData = false,
PrintingEnabled = true,
BackgroundColor = "#FFFFFF",
Margin = 10
});
public static string DefaultLayoutJson(string DefaultLayout = "grid", bool Grid = true, bool Card = true, bool Pivot = true, bool Chart = true, bool Tree = false, bool Gantt = false, bool Scheduler = false, bool Todo = false) => JsonSerializer.Serialize(new public static string DefaultLayoutJson(string DefaultLayout = "grid", bool Grid = true, bool Card = true, bool Pivot = true, bool Chart = true, bool Tree = false, bool Gantt = false, bool Scheduler = false, bool Todo = false) => JsonSerializer.Serialize(new
{ {
Grid = Grid, Grid = Grid,
@ -58,82 +50,6 @@ public static class WizardConsts
}); });
public static readonly string DefaultFilterJson = "\"IsDeleted\" = 'false'"; public static readonly string DefaultFilterJson = "\"IsDeleted\" = 'false'";
public static string DefaultFilterRowJson(bool visible = true) => JsonSerializer.Serialize(new { Visible = visible });
public static string DefaultHeaderFilterJson(bool visible = true) => JsonSerializer.Serialize(new { Visible = visible });
public static string DefaultSearchPanelJson(bool visible = true) => JsonSerializer.Serialize(new { Visible = visible });
public static string DefaultGroupPanelJson(bool visible = true) => JsonSerializer.Serialize(new { Visible = visible });
public static string DefaultSelectionSingleJson(string Mode = "none") => JsonSerializer.Serialize(new
{
Mode = Mode,
AllowSelectAll = false
});
public static string DefaultColumnOptionJson(bool FocusedRowEnabled = true) => JsonSerializer.Serialize(new
{
ColumnFixingEnabled = true,
ColumnAutoWidth = true,
ColumnChooserEnabled = true,
AllowColumnResizing = true,
AllowColumnReordering = true,
ColumnResizingMode = "widget",
FocusedRowEnabled = FocusedRowEnabled,
});
/// <summary>Not yalnizca tablo kaynakli listede vardir: view/sorgu satirinin notu bagli oldugu kalici kaydi yoktur.</summary>
public static string DefaultPermissionJson(string permissionName, bool hasNote)
{
return JsonSerializer.Serialize(new
{
C = permissionName + ".Create",
R = permissionName,
U = permissionName + ".Update",
D = permissionName + ".Delete",
E = permissionName + ".Export",
I = permissionName + ".Import",
N = hasNote ? permissionName + ".Note" : null,
});
}
public static string DefaultFieldPermissionJson(string permissionName)
{
return JsonSerializer.Serialize(new
{
C = permissionName + ".Create",
R = permissionName,
U = permissionName + ".Update",
E = true,
I = true,
Deny = false
});
}
public static readonly string DefaultColumnCustomizationJson = JsonSerializer.Serialize(new
{
AllowReordering = true,
});
public static readonly string DefaultColumnFilteringJson = JsonSerializer.Serialize(new
{
AllowFiltering = true,
});
public static readonly string DefaultPivotSettingsJson = JsonSerializer.Serialize(new
{
IsPivot = true
});
public static readonly string DefaultValidationRuleRequiredJson = JsonSerializer.Serialize(new[]
{
new { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) }
});
public static readonly string DefaultValidationRuleEmailJson = JsonSerializer.Serialize(new[]
{
new { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.email), Reevaluate = true, IgnoreEmptyValue = true }
});
public static readonly string DefaultValidationRuleEmailRequiredJson = JsonSerializer.Serialize(new[]
{
new { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.email) },
new { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) }
});
/// <summary> /// <summary>
/// Cascade alanlari yalnizca doluysa yazilir; cascade'siz lookup'in JSON'u onceki bicimiyle /// Cascade alanlari yalnizca doluysa yazilir; cascade'siz lookup'in JSON'u onceki bicimiyle
@ -178,84 +94,6 @@ public static class WizardConsts
{ {
return $"UPDATE \"{tableName}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id"; return $"UPDATE \"{tableName}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id";
} }
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid)
{
return JsonSerializer.Serialize(new[]
{
new { FieldName = "CreationTime", FieldDbType = DbType.DateTime, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
new { FieldName = "CreatorId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
new { FieldName = "IsDeleted", FieldDbType = DbType.Boolean, Value = "false", CustomValueType = FieldCustomValueTypeEnum.Value },
new { FieldName = "Id", FieldDbType = dbType, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
});
}
public static string DefaultInsertFieldsJsonOnlyId(DbType dbType = DbType.Guid)
{
return JsonSerializer.Serialize(new[]
{
new { FieldName = "Id", FieldDbType = dbType, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
});
}
public static readonly string DefaultUpdateFieldsDefaultValueJson = JsonSerializer.Serialize(new[]
{
new { FieldName = "LastModificationTime", FieldDbType = DbType.DateTime, Value = "@NOW", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
new { FieldName = "LastModifierId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
});
public static string DefaultDeleteFieldsDefaultValueJson(DbType dbType = DbType.Guid)
{
return JsonSerializer.Serialize(new[]
{
new { FieldName = "DeleterId", FieldDbType = DbType.Guid, Value = "@USERID", CustomValueType = FieldCustomValueTypeEnum.CustomKey },
new { FieldName = "Id", FieldDbType = dbType, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
});
}
public static string DefaultDeleteFieldsJsonOnlyId(DbType dbType = DbType.Guid)
{
return JsonSerializer.Serialize(new[]
{
new { FieldName = "Id", FieldDbType = dbType, Value = "@ID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
});
}
public static readonly string DefaultPagerOptionJson = JsonSerializer.Serialize(new
{
Visible = true,
AllowedPageSizes = "10,20,50,100",
ShowPageSizeSelector = true,
ShowNavigationButtons = true,
ShowInfo = false,
InfoText = "Page {0} of {1} ({2} items)",
DisplayMode = GridColumnOptions.PagerDisplayModeAdaptive,
ScrollingMode = GridColumnOptions.ScrollingModeStandard,
LoadPanelEnabled = "auto",
LoadPanelText = "Loading..."
});
public static string DefaultEditingOptionJson(
string Title,
int Width,
int Height,
bool AllowDeleting,
bool AllowAdding,
bool AllowUpdating,
bool ConfirmDelete,
bool SendOnlyChangedFormValuesUpdate,
bool AllowDetail = false,
bool OpenDetailAfterInsert = false) => JsonSerializer.Serialize(new
{
Popup = new { Title = Title, Width = Width, Height = Height },
AllowDeleting = AllowDeleting,
AllowAdding = AllowAdding,
AllowUpdating = AllowUpdating,
ConfirmDelete = ConfirmDelete,
SendOnlyChangedFormValuesUpdate = SendOnlyChangedFormValuesUpdate,
AllowDetail = AllowDetail,
OpenDetailAfterInsert = OpenDetailAfterInsert
});
} }
/// <summary>Wizard alanindan lookup'a yazilan cascade ayarlari (<c>LookupDto.Cascade*</c>).</summary> /// <summary>Wizard alanindan lookup'a yazilan cascade ayarlari (<c>LookupDto.Cascade*</c>).</summary>

View file

@ -52056,6 +52056,132 @@
"Key": "Sas.WizardStep3.DragColumnsHere", "Key": "Sas.WizardStep3.DragColumnsHere",
"Value": "Sütunları buraya sürükleyin" "Value": "Sütunları buraya sürükleyin"
}, },
{
"Id": "f2f608d6-b03d-5d38-b913-a530706a00f9",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.StepList",
"Value": "List Settings"
},
{
"Id": "22687d66-8cdd-5eaa-80ba-2bbdc0260a03",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.StepList",
"Value": "Liste Ayarları"
},
{
"Id": "eb0f297f-f0ee-50f0-b116-bb6fef0d1c9c",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.StepEditing",
"Value": "Editing & Data"
},
{
"Id": "60efd483-3390-5d7a-848f-4a3d42339fa7",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.StepEditing",
"Value": "Düzenleme ve Veri"
},
{
"Id": "3f2e1e1f-1521-5da1-ac91-41727198c991",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.StepChart",
"Value": "Chart"
},
{
"Id": "66c12a38-07cd-59cd-bb4c-776670bf5703",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.StepChart",
"Value": "Grafik"
},
{
"Id": "fb7f1856-ed59-570e-a77d-dee133f5b341",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.DraftHint",
"Value": "Changes are kept in the wizard and written to the seed file on deploy; the screen is installed by DB Migrate."
},
{
"Id": "6ea9144a-e128-594a-a71a-3f2e13a791a6",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.DraftHint",
"Value": "Değişiklikler wizard içinde tutulur, deploy ile seed dosyasına yazılır; ekranı DB Migrate kurar."
},
{
"Id": "91d425a8-dd6b-5c4a-85b6-7630bbe99c4b",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.ScreenSettings",
"Value": "Screen settings"
},
{
"Id": "ce920499-1178-51fe-943b-333a76425f80",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListFormWizard.ScreenSettings",
"Value": "Ekran ayarları"
},
{
"Id": "79f8e441-e19e-507a-8521-7013ec3d15f1",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.FieldDetails",
"Value": "Field details"
},
{
"Id": "752710dc-c2c0-5a66-8d2e-71eead5a048e",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.FieldDetails",
"Value": "Alan ayrıntıları"
},
{
"Id": "940e2162-75f2-5181-bd90-858300cc89db",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.ColumnOrder",
"Value": "Column order"
},
{
"Id": "f0c6502a-2092-5c24-afb0-ec6c913ead7f",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.ColumnOrder",
"Value": "Sütun sırası"
},
{
"Id": "cf0cd6a3-3491-5708-b4e0-6885b889905c",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.ColumnOrderHint",
"Value": "Order of the grid columns; the form order follows the groups."
},
{
"Id": "b765287e-9d7a-59bb-a123-83e7f18696d8",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.ColumnOrderHint",
"Value": "Listedeki sütun sırası; form sırası grupları izler."
},
{
"Id": "53d0c094-9a0c-5597-a02c-977d46d1dd5f",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.AdvancedFields",
"Value": "More field settings"
},
{
"Id": "7794824e-70dc-54ae-808a-857a3178ee79",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.WizardStep3.AdvancedFields",
"Value": "Ek alan ayarları"
},
{ {
"Id": "0db2b2db-8433-50bd-9d18-d4ca502da4f2", "Id": "0db2b2db-8433-50bd-9d18-d4ca502da4f2",
"CultureName": "en", "CultureName": "en",
@ -94308,20 +94434,6 @@
"Key": "Sas.ListForm.WizardError.DataSourceNotFound", "Key": "Sas.ListForm.WizardError.DataSourceNotFound",
"Value": "'{0}' veri kaynağı bulunamadı. Yeni veri kaynağı için bağlantı cümlesi zorunludur." "Value": "'{0}' veri kaynağı bulunamadı. Yeni veri kaynağı için bağlantı cümlesi zorunludur."
}, },
{
"Id": "4311b902-fb1f-55ef-9a2f-b28384da2413",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayFailed",
"Value": "The ListForm/Fields section could not be applied: {0}"
},
{
"Id": "b771f1e1-170b-590e-a9f1-92c4ba7a2683",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayFailed",
"Value": "ListForm/Fields bölümü uygulanamadı: {0}"
},
{ {
"Id": "d5ed251c-61dd-56d5-851d-ea105234d77e", "Id": "d5ed251c-61dd-56d5-851d-ea105234d77e",
"CultureName": "en", "CultureName": "en",
@ -96203,14 +96315,14 @@
"CultureName": "en", "CultureName": "en",
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "Sas.ListForm.WizardImport.ListFormSectionMissing", "Key": "Sas.ListForm.WizardImport.ListFormSectionMissing",
"Value": "{0}: the ListForm section is missing; the screen cannot be installed." "Value": "{0}: the Data section (SelectCommand / KeyFieldName) is missing; the screen cannot be installed."
}, },
{ {
"Id": "0a646bc1-0114-5d46-b382-118d6110a562", "Id": "0a646bc1-0114-5d46-b382-118d6110a562",
"CultureName": "tr", "CultureName": "tr",
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "Sas.ListForm.WizardImport.ListFormSectionMissing", "Key": "Sas.ListForm.WizardImport.ListFormSectionMissing",
"Value": "{0}: ListForm bölümü eksik, ekran kurulamaz." "Value": "{0}: Data bölümü (SelectCommand / KeyFieldName) eksik, ekran kurulamaz."
}, },
{ {
"Id": "41e969dc-49a9-5cd9-854f-ec977b6042a3", "Id": "41e969dc-49a9-5cd9-854f-ec977b6042a3",
@ -96478,20 +96590,6 @@
"Key": "Sas.ListForm.WizardImport.ObjectMissingRepairHint", "Key": "Sas.ListForm.WizardImport.ObjectMissingRepairHint",
"Value": "Tabloyu tablo tasarımcısından oluşturup import'a devam edebilirsiniz." "Value": "Tabloyu tablo tasarımcısından oluşturup import'a devam edebilirsiniz."
}, },
{
"Id": "4479198c-205f-55d3-8816-bde9f8e0701e",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardImport.SubFormsUnreadable",
"Value": "The sub form definition (SubFormsJson) could not be parsed."
},
{
"Id": "163caef7-6780-5b6f-b16e-86d6400f9811",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardImport.SubFormsUnreadable",
"Value": "Alt form tanımı (SubFormsJson) çözülemedi."
},
{ {
"Id": "6dcf6b76-3ede-521a-8917-52fb5b42b762", "Id": "6dcf6b76-3ede-521a-8917-52fb5b42b762",
"CultureName": "en", "CultureName": "en",
@ -97589,14 +97687,28 @@
"CultureName": "en", "CultureName": "en",
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.UnknownInputKeys", "Key": "Sas.ListForm.WizardError.UnknownInputKeys",
"Value": "The wizard input contains unknown fields: {0}. Use DefaultValue on the field item for new-record defaults, and the ListForm/Fields section (with the names from the get_wizard output) for screen settings that have no wizard answer." "Value": "The wizard input contains unknown fields: {0}. Every screen setting lives in the Wizard section (Data, Details, Layout, Editing, Insert/Update/Delete, FilterRow… Chart, Groups items) with the names from the get_wizard output; use DefaultValue on the field item for new-record defaults."
}, },
{ {
"Id": "ae67b931-b089-5306-9197-30ecfe240eb0", "Id": "ae67b931-b089-5306-9197-30ecfe240eb0",
"CultureName": "tr", "CultureName": "tr",
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.UnknownInputKeys", "Key": "Sas.ListForm.WizardError.UnknownInputKeys",
"Value": "Wizard girdisinde tanınmayan alanlar var: {0}. Yeni kayıt varsayılanı için alan öğesindeki DefaultValue, wizard cevabında olmayan ekran ayarları için ListForm/Fields bölümü (get_wizard çıktısındaki adlarla) kullanılır." "Value": "Wizard girdisinde tanınmayan alanlar var: {0}. Ekranın her ayarı Wizard bölümündedir (Data, Details, Layout, Editing, Insert/Update/Delete, FilterRow… Chart, Groups öğeleri) ve adları get_wizard çıktısındaki gibidir; yeni kayıt varsayılanı için alan öğesindeki DefaultValue kullanılır."
},
{
"Id": "16af1361-0be4-58df-8212-946d57dc3395",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.LegacySectionsNotAccepted",
"Value": "The sections {0} are no longer part of the wizard file. Every screen setting lives in the Wizard section; read the current file with get_wizard, change the Wizard section and send it back."
},
{
"Id": "9c813e4b-462d-5646-bc23-364a7ca6fcb0",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.LegacySectionsNotAccepted",
"Value": "{0} bölümleri artık wizard dosyasında yok. Ekranın her ayarı Wizard bölümündedir; dosyayı get_wizard ile oku, Wizard bölümünü değiştirip geri gönder."
}, },
{ {
"Id": "b4d81614-8344-5db4-af55-3a218396eeca", "Id": "b4d81614-8344-5db4-af55-3a218396eeca",
@ -97724,48 +97836,6 @@
"Key": "Sas.ListForm.WizardError.ImageColumnLengthLimited", "Key": "Sas.ListForm.WizardError.ImageColumnLengthLimited",
"Value": "Görsel yükleme alanları base64 saklar, kolon boyu sınırsız olmalı: {0}. Kolonu nvarchar(MAX) yapın ya da editöre uploadUrl verin." "Value": "Görsel yükleme alanları base64 saklar, kolon boyu sınırsız olmalı: {0}. Kolonu nvarchar(MAX) yapın ya da editöre uploadUrl verin."
}, },
{
"Id": "3ba2a0c0-7f21-5c18-b09b-c3209b54f047",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayFieldNotInGroups",
"Value": "Fields: the field '{0}' is not in the wizard groups (Groups); add it to a group or remove it from Fields."
},
{
"Id": "0f1a8c37-c6c6-5461-8ea9-32ebfc537c06",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayFieldNotInGroups",
"Value": "Fields: '{0}' alanı wizard gruplarında (Groups) yok; alanı bir gruba ekleyin ya da Fields'tan çıkarın."
},
{
"Id": "cfa6fb62-53da-51ad-a886-2e94aff72229",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayUnknownKey",
"Value": "{0}: '{1}' is not a known field."
},
{
"Id": "9e3ce3cf-f805-56ed-8d84-98940697849c",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayUnknownKey",
"Value": "{0}: '{1}' tanınmayan bir alan."
},
{
"Id": "df57571e-492e-5491-a410-d7a170801d9d",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayComputedKey",
"Value": "{0}: '{1}' is a computed field and cannot be written."
},
{
"Id": "34ffc6a0-258d-5fa3-9b23-3c64532b9f43",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardError.OverlayComputedKey",
"Value": "{0}: '{1}' hesaplanan bir alandır, yazılamaz."
},
{ {
"Id": "70db086b-0ef0-5b43-8122-ffff5437e0f7", "Id": "70db086b-0ef0-5b43-8122-ffff5437e0f7",
"CultureName": "en", "CultureName": "en",
@ -97836,20 +97906,6 @@
"Key": "Sas.ListForm.WizardExport.SubFormFileNotFound", "Key": "Sas.ListForm.WizardExport.SubFormFileNotFound",
"Value": "{0}: alt formun wizard dosyası bulunamadı, arşive eklenemedi." "Value": "{0}: alt formun wizard dosyası bulunamadı, arşive eklenemedi."
}, },
{
"Id": "9ac9ffdb-4351-598f-b2e4-d050d2cb71de",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardExport.SubFormsUnresolved",
"Value": "{0}: the sub form definition could not be resolved; sub forms were not added to the archive."
},
{
"Id": "baf4097b-099a-5384-9434-1f913fd5d1f4",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "Sas.ListForm.WizardExport.SubFormsUnresolved",
"Value": "{0}: alt form tanımı çözülemedi, alt formlar arşive eklenemedi."
},
{ {
"Id": "741c3a7c-8947-57c6-97ee-f472a020f62e", "Id": "741c3a7c-8947-57c6-97ee-f472a020f62e",
"CultureName": "en", "CultureName": "en",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more