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
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
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
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
@ -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
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
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.
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**
- 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
kolonlar kaçışlanmış metin değil, tipli karşılıkları (`SchedulerOptionJson` →
`SchedulerOptionDto`) olarak yer alır; kaçışlama yalnızca `EditorOptions`, `EditorScript`,
`LookupQuery` gibi gerçekten metin olan alanlar içindir.
dosyasının `Wizard` bölümü ekranın DTO sözleşmesini taşıdığı için orada bu kolonlar kaçışlanmış
metin değil, tipli bloklar (`SchedulerOptionJson` → `SchedulerOptionDto`, `EditingOptionJson` →
`Editing`, alan `ValidationRuleJson` → öğenin `ValidationRules`'ı) olarak yer alır; kaçışlama
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.
- `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
@ -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 |
| `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) |
| `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) |
| `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 |

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 |
| 2 | CRUD uçları (gerekiyorsa) | `crud/{Entity}.json` | Custom Component'in data source'ları buna bakar |
| 3 | Custom Component (gerekiyorsa) | `data/Sas.DeveloperKit.CustomComponents/{Id}.json` | Wizard `Custom` yolunda bileşen adını arar |
| 4 | Wizard | `wizard/{Ad}.json` + menü/yetki/dil satırları (`data/Sas.Menus.*`, `data/Abp.Identity.Permission*`, `data/Sas.Languages.LanguageText.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 |
| 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) |
@ -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:
`list_lists` / `describe_list` / `search_schema` geliştiriciye (host + `Sas.Listforms.Wizard`) veri
sözlüğünde henüz olmayan kaynakları da döner — migrate bekleyen wizard ekranları (`sourceKind: pending`,
alanlar wizard dosyasının `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.
| 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) |
| `generate_crud_endpoints` | Tablo/view için SQL Query Manager CRUD diyaloğuyla aynı endpoint kümesini yalnızca `crud/{Entity}.json` dosyasına yazar (`CrudEndpointSeedFileDto.Generate`); kayıtları DB Migrate ekler (`CrudEndpointSeedApplier`) | `Sas.SqlQueryManager.CrudEndpoints` ister. Custom component'in okuyup yazacağı tablo içindir; ListForm ekranı gerekmez. View/procedure/function'da yalnızca `GetList`/`GetById` |
| `list_wizards` · `get_wizard` | Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner | Var olan menü kökünü/izin grubunu bulmak için önce `list_wizards` |
| `deploy_wizard` | Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) | Girdi §4.4'teki düz cevap ya da `get_wizard` çıktısıyla aynı dosya biçimi; enum'lar sayı. `replaceFileName` = `EditFileName` (§4.5). List ekranında `Widgets` dolu gelir — alt form olarak üretilen ekran hariç (`Widgets: []`) — en az 3 kart, kiracı tablosunda `"TenantId" = '@TENANTID'` (§4.6.1); kartlardaki her dil anahtarı var olmalı ya da `Language.Texts`'te EN + TR verilmeli, yoksa deploy reddeder. `MenuOrder` yazılmaz — sunucu yeni ekranı kardeşlerinin sonuna koyar, güncellemede yerini korur. Talep bağı (`DevRequest`) da yazılmaz — sunucu etkin talepten doldurur (§4.0). İstek demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1). Satır/toolbar butonları `CommandColumns` (§4.9). Bağımlı lookup öğedeki `Cascade*` alanları (§4.3.4). 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) |
| `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 |
@ -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
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**.
### 4.0 Deploy dosya yazar, migrate kurar
@ -1161,7 +1162,7 @@ hiçbir şey silmez**. Tek çıktısı seed dosyalarıdır:
| Dosya | İçerik |
| --- | --- |
| `wizard/{WizardName}.json` | Ekranın `ListForm`/`Fields` sözleşmesi, wizard cevapları, `InsertedRecords`, `Pending`, `DevRequest` |
| `wizard/{WizardName}.json` | `Wizard` bölümü (wizard cevapları = ekranın bütün sözleşmesi, §4.1), `InsertedRecords`, `Pending`, `DevRequest` |
| `data/Sas.Menus.Module.json`, `data/Sas.Menus.Menu.json` | Modül, üst menü, ekranın menüsü |
| `data/Abp.Identity.PermissionGroups.json`, `data/Abp.Identity.Permissions.json` | İzin grubu, okuma yetkisi ve alt yetkileri |
| `data/Sas.Languages.LanguageText.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
kalır, `Pending` silinmez), sonraki dosyalar etkilenmez. İş akışı kurulumdan önce
`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
seçilir. Tek istisna yeni kiracının yöneticisidir: ilk seed'de, o anda tanımlı olan kiracı tarafı
yetkilerin hepsini alır (`PlatformIdentityDataSeeder`).
**Deploy hattı** (`ListFormWizardAppService.Create`): önce `ListFormWizardDto.ApplyDefaults(isDeletedField,
isCreatedField, isModifiedField)` cevapta boş bırakılan her bloğu wizard varsayılanıyla doldurur
(`WizardDefaults` + `WizardConsts`; üç bayrak hedef tablonun `IsDeleted`/`CreatorId`/`LastModifierId`
kolonlarını taşıyıp taşımadığıdır — katalogdan ya da seed scriptinden okunur, dosyaya yazılmaz; ekran
kodundan türeyen yetki adları, popup başlığı ve durum anahtarı her deploy'da yeniden yazılır), sonra
`BuildListForm()` / `BuildFields()` ekranı üretir (salt eşleme, parametresiz), ardından
`LocalizeStaticLookupsAsync` ve `EditorScript.Rebuild` çalışır, üretilen ekran `RefreshFrom(listForm, fields)`
ile cevaplara geri yazılır ve dosyaya **yalnızca `Wizard` bölümü** kaydedilir (`WizardSeedFileDto.ToSeedJson`).
Migrate'te `WizardDataSeeder` aynı `BuildListForm()/BuildFields()` ile dosyadan ekranı kurar; ListForm
detayından yapılan değişikliği `WizardSeedSynchronizer` `RefreshFrom` ile dosyaya yazar. Üç yolun çıktısı bu
yüzden birbirinden ayrışamaz.
Deploy'dan migrate'e kadar wizard'ın hiçbir kaydı veritabanında yoktur ve bu beklenen durumdur.
Yeni modül, kök/üst menü, izin grubu, yetki ya da dil anahtarı veritabanında bulunmuyorsa
deploy onu dosyaya yazar, migrate `Order` sırasıyla (route → modül → menü, grup → yetki) ekler.
@ -1210,22 +1223,42 @@ yoksa bölüm dosyaya yazılmaz. Migrate, senkronizasyon ve import bölümü old
```jsonc
{
"Wizard": { // yalnızca ekrandan üretilemeyen bilgiler — 4.2
"ComponentKind": 0, "WizardName": "Tags", "WizardCode": "Prj.Tags",
"Wizard": { // ekranın BÜTÜN sözleşmesi — 4.2, 4.3
"ComponentKind": 0, "WizardName": "Prj_Tags", "WizardCode": "Prj.Tags",
"SeededAt": "2026-09-01T09:03:00", "CustomComponentName": null,
"PermissionGroupName": "Prj", "ModuleId": "Prj", // boş bağlantı metni yazılmaz
"Menu": { "MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…",
"MenuParentPath": [ { "Code": "…", "Icon": "…", "En": "…", "Tr": "…" } ], // yalnızca derin yolda
"MenuParentCode": "…", "MenuParentIcon": "…" }, // "CreateMenu": false ise eklenir
"Language": { "MenuEn": "…", /* Menu/Title/Desc/MenuParent/PermissionGroup · EN + TR */
"PermissionGroupName": "Prj", "ModuleId": "Prj",
"Menu": { "MenuParentPath": [ { "Code": "…", "Icon": "…", "En": "…", "Tr": "…" } ], // yalnızca derin yolda
"MenuParentCode": "…", "MenuParentIcon": "…", // "CreateMenu": false ise eklenir
"MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…" },
"Language": { "MenuParentEn": "…", /* MenuParent/PermissionGroup/Menu/Title/Desc · EN + TR */
"Texts": [ { "Key": "…", "En": "…", "Tr": "…" } ] }, // Texts yalnızca doluysa
"Groups": [ { "Items": [ { "FieldName": "…", "CaptionName": "…",
"En": "…", "Tr": "…" } ] } ] // grup Caption/ColCount yok
"Data": { "DataSourceCode": "Default", /* "DataSourceConnectionString" yalnızca doluysa */
"SelectCommandType": 1, "SelectCommand": "Prj_T_Tag", "TableName": null,
"KeyFieldName": "Id", "KeyFieldDbSourceType": 9,
"IsTenant": true, "IsBranch": false, "IsOrganizationUnit": false,
"DefaultFilter": "\"IsDeleted\" = 'false'",
"SeedFilePath": null, "SeedSyncInsert": false, "SeedSyncUpdate": false, "SeedSyncDelete": false,
"SelectFieldsDefaultValue": [] },
"Details": { "ListFormType": "List", "PageSize": 10, "Width": 0, "Height": 0, "FullHeight": false,
"ShowNote": false, "IsSubForm": false, "SubFormsListFormType": "Form", "SortMode": "single",
"UserId": null, "RoleId": null, "CustomJsSources": [], "CustomStyleSources": [] },
"Layout": { "DefaultLayout": "grid", "Grid": true, "Card": true, "Pivot": true, "Chart": true,
"Tree": false, "Gantt": false, "Scheduler": false, "Todo": false },
"Editing": { /* GridEditingDto — mod, popup, izin bayrakları … */ },
"Insert": { "ServiceAddress": "list-form-data/insert", "Command": null, "BeforeCommand": null,
"AfterCommand": null, "FieldsDefaultValue": [ /* FieldsDefaultValueDto */ ] },
"Update": { /* aynı yapı */ }, "Delete": { /* aynı yapı */ },
"FormDefaults": [ /* FieldsDefaultValueDto — öğenin sabit DefaultValue'suna sığmayanlar */ ],
"Permission": { "C": "…", "R": "…", "U": "…", "D": "…", "E": "…", "I": "…", "N": "…" },
"FilterRow": {}, "HeaderFilter": {}, "FilterPanel": {}, "SearchPanel": {}, "GroupPanel": {},
"Selection": {}, "ColumnOption": {}, "Row": {}, "Pager": {}, "StateStoring": {}, "PivotOption": {},
"TreeOptionDto": {}, "GanttOptionDto": {}, "SchedulerOptionDto": {}, "TodoOptionDto": {},
"ExtraFilters": [], "CommandColumns": [], "SubForms": [], "Widgets": [],
"WorkflowDto": {}, "ChartOptions": { /* WizardChartDto — grafik sekmeleri */ },
"Groups": [ { "Caption": "", "ColCount": 1, "ColSpan": 1,
"Items": [ { "FieldName": "…", "CaptionName": "…", "En": "…", "Tr": "…",
/* alan detayının bütün alanları — 4.3 */ } ] } ]
},
"ListForm": { /* GridOptionsEditDto — ekranın kendisi, ListForm detayıyla aynı sözleşme */ },
"Fields": [ /* ColumnFormatEditDto — alanlar, alan detayıyla aynı sözleşme */ ],
"IsDeletedField": true, // tabloda IsDeleted var mı (soft delete)
"IsCreatedField": true, // tabloda CreatorId var mı (audit)
"InsertedRecords": { // bu çalıştırmanın GERÇEKTEN yarattığı kayıtlar
"LanguageKeys": [], "PermissionGroupNames": [], "PermissionNames": [],
"MenuCodes": [], "DataSourceCodes": [], "ModuleIds": []
@ -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,
bileşen yolu, alan başlıklarının dil karşılıkları); `ListForm`/`Fields` içinde karşılığı olan
hiçbir alan orada tekrar edilmez (§4.2). Anahtar sırası sabittir: `ComponentKind`, `WizardName`,
`WizardCode`, `SeededAt`, `CustomComponentName`, `PermissionGroupName`, `ModuleId`,
`DataSourceConnectionString`, `Menu`, `Language`, `Groups`. `ListForm`/`Fields` içindeki
`Id`/`CreationTime`/`EditType` gibi çalışma zamanı alanları sıfırlanmış yazılır.
Dosyada **yalnızca `Wizard` bölümü** vardır ve ekranın bütün sözleşmesi oradadır: ListForm detayının
her sekmesi bir blok (`Editing`, `Insert`/`Update`/`Delete`, `FilterRow` … `ChartOptions`), alan
detayının her sekmesi `Groups[].Items[]` öğesinin bir alanıdır (§4.2, §4.3). Anahtar sırası sabittir:
`ComponentKind`, `WizardName`, `WizardCode`, `SeededAt`, `CustomComponentName`, `PermissionGroupName`,
`ModuleId`, ardından `Menu`, `Language`, `Data`, `Details`, `Layout` başlıkları, ardından bloklar
`Editing`, `Insert`, `Update`, `Delete`, `FormDefaults`, `Permission`, `FilterRow`, `HeaderFilter`,
`FilterPanel`, `SearchPanel`, `GroupPanel`, `Selection`, `ColumnOption`, `Row`, `Pager`, `StateStoring`,
`PivotOption`, `TreeOptionDto`, `GanttOptionDto`, `SchedulerOptionDto`, `TodoOptionDto`, `ExtraFilters`,
`CommandColumns`, `SubForms`, `Widgets`, `WorkflowDto`, `ChartOptions`, `Groups`. Kök seviyede
`ListForm`/`Fields` bölümü ve `IsDeletedField`/`IsCreatedField` **yoktur**; eski biçimdeki bir dosya
okunursa (`FromSeedJson`) iki bölüm `RefreshFrom` ile `Wizard`'a aktarılır ve bir sonraki yazımda
düşer. Dosyaya yazılmayan kısayollar: `AllowAdding/AllowUpdating/AllowDeleting/AllowDetail/
OpenDetailAfterInsert/ConfirmDelete` (`Editing`'den), `SeedData`/`SeedSync` (`Data.SeedFilePath`/
`SeedSync*`'tan), öğedeki `IsRequired` (`ValidationRules`'tan) — okurken türetilir
(`SyncDerivedAnswers`), deploy girdisinde yazılırsa bloktan üstündür.
**Custom yolunda** (`ComponentKind: 1`) `ListForm` `null`, `Fields` `[]` yazılır ve kırpma
yapılmaz: `Wizard` bloğu List'e ait anahtarları (`Grid`/`Card`/…, `SelectCommand`, option
blokları) varsayılan değerleriyle taşımaya devam eder; bunlar kullanılmaz, temizlemeye çalışma.
**Custom yolunda** (`ComponentKind: 1`) ekrana ait anahtarların hiçbiri (`Data`, `Details`, `Layout`,
bloklar, `Groups`) dosyaya yazılmaz; `Wizard` yalnızca kimlik, `Menu` ve `Language` taşır.
Bu yolun dosyası elle yazılabilir — seeder yalnızca menü, yetki ve dil kaydı üretir.
**`ListForm` + `Fields` ekranın tek gerçeğidir.** Seeder ekranı bu iki bölümden kurar; `Wizard`
bölümünden yalnızca menü, yetki, dil anahtarları ve veri kaynağı üretilir. Ekran **ListForm
detayından** (liste ayarları, alan ayarları, JSON satır editörleri) düzenlendiğinde sunucu aynı
dosyanın `ListForm`/`Fields` bölümlerini ve `Wizard` bölümündeki karşılıklarını geri yazar
(`WizardSeedSynchronizer`); böylece migrate ya da import ekranı dosyadan kurarken tasarımcıdaki iş
kaybolmaz (`EditFileName` ile yeniden deploy ise ekranı cevaptan üretir, §4.5).
Elle bu dosyayı yazarken `ListForm`/`Fields` bölümlerini uydurma — wizard'ı deploy et, dosyayı
sunucu üretsin.
**`Wizard` bölümü ekranın tek gerçeğidir.** Seeder ekranı `Wizard.BuildListForm()` /
`Wizard.BuildFields()` ile kurar (§4.0 deploy hattı). Ekran **ListForm detayından** (liste ayarları,
alan ayarları, JSON satır editörleri) düzenlendiğinde sunucu aynı dosyanın `Wizard` bölümünü
`RefreshFrom` ile geri yazar (`WizardSeedSynchronizer`); böylece migrate ya da import ekranı dosyadan
kurarken tasarımcıdaki iş kaybolmaz. Bunun şartı eşliktir: `GridOptionsEditDto`/`ColumnFormatEditDto`'ya
eklenen her alanın `ListFormWizardDto`/`WizardColumnItemInputDto`'da karşılığı + `RefreshFrom` +
`BuildListForm/BuildFields` eşlemesi olmalıdır, yoksa tasarımcı değişikliği migrate'te kaybolur.
Elle bu dosyayı yazarken blokları uydurma — wizard'ı deploy et, dosyayı sunucu üretsin; yazmadığın
blok deploy'da varsayılanıyla dolar.
`InsertedRecords` **silme sözleşmesidir**: wizard silindiğinde yalnızca burada listelenen
kayıtlar silinmeye **aday** olur, önceden var olan kayıtlara (var olan izin grubu, var olan üst
@ -1262,23 +1304,22 @@ Elle dosya yazarken buraya var olan bir kaydı koyma — kullanılmıyorsa silme
### 4.2 `Wizard` bloğu — alan alan
`Wizard` bloğu **yalnızca ekranın sözleşmesinden üretilemeyen** bilgileri taşır: menü, yetki,
dil metinleri, bileşen yolu ve alan başlıklarının iki dilli karşılıkları. Ekrana ait olan her
şey (veri kaynağı, select komutu, anahtar alan, kırılım bayrakları, düzen/görünüm bayrakları,
düzenleme izinleri, görünüm option blokları, alt formlar, widget'lar, iş akışı, alan tanımları)
`ListForm`/`Fields` bölümlerindedir ve dosyaya **ikinci kez yazılmaz**.
`Wizard` bloğu ekranın **bütün** sözleşmesidir: menü, yetki, dil metinleri, bileşen yolu, veri
kaynağı, ekran ayarları, ListForm detayının her sekmesi (blok olarak) ve alan tanımları (öğe
olarak). Ekrana ait hiçbir bilgi dosyada başka bir bölümde değildir.
Dosyada wizard alanları üç başlık altında toplanır — `Menu` (menü kaydı), `Language` (dil
metinleri), `Groups` (alan başlıklarının dil karşılıkları); geri kalanı kök seviyededir.
Gruplama ile `ListFormCode → WizardCode`, `MenuParentModuleId → ModuleId` ve `LanguageTexts → Language.Texts` adlandırmaları
yalnızca **dosya biçimidir**; C#
sözleşmesi (`ListFormWizardDto`) düzdür ve dosya okunurken düzleştirilir
(`WizardSeedFileDto.FromSeedJson` ↔ `ToSeedJson`). Aşağıdaki tablolar C# alan adlarını kullanır.
Dosyada wizard alanları beş başlık altında toplanır — `Menu` (menü kaydı), `Language` (dil
metinleri), `Data` (veri kaynağı, kayıt kaynağı, anahtar, kırılım bayrakları, okuma koşulu, veri
aynası), `Details` (Detay sekmesinin genel ayarları), `Layout` (görünüm bayrakları); bloklar ve
`Groups` kök seviyededir. Gruplama ile `ListFormCode → WizardCode`, `MenuParentModuleId → ModuleId`
ve `LanguageTexts → Language.Texts` adlandırmaları yalnızca **dosya biçimidir**; C# sözleşmesi
(`ListFormWizardDto`) düzdür ve dosya okunurken düzleştirilir (`WizardSeedFileDto.FromSeedJson` ↔
`ToSeedJson`). Aşağıdaki tablolar C# alan adlarını kullanır.
Aşağıdaki tablolarda `[ListForm]` işaretli alanlar **istek girdisidir**: sihirbaz bunları POST
eder, sunucu ekranı bunlardan üretir, ama seed dosyasında `ListForm` bölümündeki karşılıkları
saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldurulur
(`WizardSeedFileDto.FromSeedJson`), dolayısıyla wizard'ı okuyan kod tam nesneyi görür.
Tablolardaki köşeli işaret alanın dosyadaki yerini söyler: `[Data]`/`[Details]`/`[Layout]` o başlığın
altında, `[Blok]` kök seviyede kendi adıyla, `[Item]` `Groups[].Items[]` öğesinde. `[Kısayol]` işaretli
alanlar **yalnızca istek girdisidir**: dosyaya yazılmazlar, sahibi olan bloktan türetilirler
(`SyncDerivedAnswers`) ve deploy girdisinde yazıldıklarında bloğun aynı adlı alanını ezerler.
**Kimlik ve yol**
@ -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. |
| `MenuUrl` | List'te `/admin/list/{MenuCode}` hesaplanır; Custom'da bileşenin `RoutePath`'i yazılır. |
| `CustomComponentName` | Yalnızca Custom yolunda; bağlanacak bileşenin adı. |
| `EditFileName` | Yalnızca **istek girdisi** (MCP'de `replaceFileName`): doluysa güncellemedir — `.Update` yetkisi ister, `SeededAt` korunur, dosyalar cevaptan yeniden yazılır ve ekran migrate'te **cevaptan yeniden kurulur** (§4.0). 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ü**
@ -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
> 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.
| Alan | Anlam |
@ -1335,26 +1376,50 @@ atanmaz (admin dahil); ekranı görecek roller yetkiyi rol ekranından alır.
| Alan | Anlam |
| --- | --- |
| `DataSourceCode` | `[ListForm]` Bağlantı kodu; varsayılan `"Default"`. |
| `DataSourceConnectionString` | Yalnızca **yeni** bir bağlantı tanımlanıyorsa doldurulur; boşsa dosyaya hiç yazılmaz. **Asla gerçek kimlik bilgisi yazma.** |
| `SelectCommandType` | `[ListForm]` Bkz. §3. |
| `SelectCommand` | `[ListForm]` Tablo/view adı, fonksiyon çağrısı ya da sorgu metni. |
| `KeyFieldName` / `KeyFieldDbSourceType` | `[ListForm]` Birincil anahtar ve tipi. Wizard ekranının başlangıç değeri `11` (Int32), C# DTO'nun varsayılanı yok — her zaman açıkça yaz; `Guid` anahtarda `9`. |
| `DataSourceCode` | `[Data]` Bağlantı kodu; varsayılan `"Default"`. |
| `DataSourceConnectionString` | `[Data]` Yalnızca **yeni** bir bağlantı tanımlanıyorsa doldurulur; boşsa dosyaya hiç yazılmaz. **Asla gerçek kimlik bilgisi yazma.** |
| `SelectCommandType` | `[Data]` Bkz. §3. |
| `SelectCommand` | `[Data]` Tablo/view adı, fonksiyon çağrısı ya da sorgu metni. |
| `TableName` | `[Data]` Sorgu kaynağında türetilmiş tablonun takma adı; boşsa deploy `WizardConsts.QuerySourceAlias` yazar. |
| `KeyFieldName` / `KeyFieldDbSourceType` | `[Data]` Birincil anahtar ve tipi. Wizard ekranının başlangıç değeri `11` (Int32), C# DTO'nun varsayılanı yok — her zaman açıkça yaz; `Guid` anahtarda `9`. |
| `DefaultFilter` | `[Data]` Okuma koşulu; boşsa soft delete tablosunda deploy `"IsDeleted" = 'false'` yazar. |
| `SeedFilePath` / `SeedSyncInsert` / `SeedSyncUpdate` / `SeedSyncDelete` | `[Data]` Veri aynası (§9.15); kısayolları `SeedData`/`SeedSync` aşağıda. |
| `SelectFieldsDefaultValue` | `[Data]` Veritabanı › Select sekmesindeki varsayılanlar (`SelectFieldsDefaultValueJson`); çalışma anında okunmaz (§4.3.5.3). |
**Davranış**
| Alan | Anlam |
| --- | --- |
| `IsTenant` / `IsBranch` / `IsOrganizationUnit` | `[ListForm]` Otomatik kırılım filtresi. Tabloda `TenantId` varsa `IsTenant: true` **olmalı**. |
| `AllowAdding` / `AllowUpdating` / `AllowDeleting` / `ConfirmDelete` | `[ListForm]` Toolbar ve satır aksiyonları (`EditingOptionDto`). |
| `SeedData` | `[ListForm]` `true` ise `SeedFilePath = data/{ListFormCode}.json` ü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`. |
| `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. |
| `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. |
| `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). |
| `DefaultLayout` | `[ListForm]` `"grid"`, `"card"`, `"pivot"`, `"chart"`, `"tree"`, `"gantt"`, `"scheduler"`, `"todo"`. |
| `Grid` `Card` `Pivot` `Chart` `Tree` `Gantt` `Scheduler` `Todo` | `[ListForm]` Hangi görünümlerin açık olduğu (`LayoutDto`). **Liste ekranında `Grid`/`Card`/`Pivot`/`Chart` varsayılan `true`**, diğer dördü `false` (§9.8). Açtığın her görünümün option bloğunu da doldur. |
| `IsTenant` / `IsBranch` / `IsOrganizationUnit` | `[Data]` Otomatik kırılım filtresi. Tabloda `TenantId` varsa `IsTenant: true` **olmalı**. |
| `AllowAdding` / `AllowUpdating` / `AllowDeleting` / `ConfirmDelete` | `[Kısayol]` Toolbar ve satır aksiyonları; `Editing` bloğunun aynı adlı alanlarına yazılır. |
| `SeedData` | `[Kısayol]` `true` ise `Data.SeedFilePath = data/{ListFormCode}.json` üretilir; migrate dosyadaki satırları yükler. Wizard ekranında sorulmaz; demo veri taşıyan MCP üretimi için (§0.10.1). Varsayılan `false`; okurken `SeedFilePath` doluysa `true`. |
| `SeedSync` | `[Kısayol]` `SeedData` açıkken `Data.SeedSyncInsert/Update/Delete`'i açar: ekrandan eklenen/değiştirilen/silinen kayıt dosyaya yansır. **Varsayılan `false`**; demo veri bunu açmaz, yalnızca istekte açıkça istenirse `true` yazılır. Tek tek ayar `Data` başlığında yapılır. |
| `AllowDetail` | `[Kısayol]` Satırda **Detay** düğmesi çıkar; `/admin/form/{ListFormCode}/{anahtar}` adresini yeni sekmede açar — aynı ekranın tek kayıtlık form görünümüdür. `KeyFieldName` boşsa çalışmaz. **`SubForms` doluysa cevaptan bağımsız `true` üretilir** (`ListFormWizardDto.BuildListForm`): wizard alt formları `Form` tipinde kurar ve sekmeler yalnızca Detay görünümünde açılır. Elle yazılan wizard dosyasında da alt formu olan ekranın `EditingOptionDto.AllowDetail` değeri `true` yazılır. **Alt formu olmayan listede `false` kalır** — MCP/AI üretiminde Detay düğmesi yalnızca `SubForms` taşıyan listeye açılır; kullanıcı açıkça istemedikçe tek tablolu liste ya da alt form ekranının kendisi (`PerformancePeriodSkill` gibi) Detay almaz. |
| `OpenDetailAfterInsert` | `[Kısayol]` Popup'tan **yeni** kayıt kaydedilince listede kalınmaz, aynı sekmede kaydın Detay formuna (`/admin/form/{ListFormCode}/{anahtar}`) geçilir (`EditingOptionDto.OpenDetailAfterInsert`; Grid, Tree, Card, Scheduler). Varsayılan `false`. **Analizde karar verilir:** alt form sekmesinde satır girilen başlık + kalem ekranında (talep + talep satırları, sipariş + sipariş kalemleri, masraf + masraf kalemleri) `true` yazılır — kalemler yalnız Detay'da girildiği için kullanıcı başlığı kaydedip doğrudan kalem girişine geçer; kullanıcı listede kalmak istediğini söylerse `false`. Alt formu olmayan listede ve alt form ekranının kendisinde `false`. `AllowDetail` ile birlikte anlamlıdır; `KeyFieldName` ve insert'in anahtarı döndürmesi şarttır (varsayılan insert döndürür). |
| `DefaultLayout` | `[Layout]` `"grid"`, `"card"`, `"pivot"`, `"chart"`, `"tree"`, `"gantt"`, `"scheduler"`, `"todo"`. |
| `Grid` `Card` `Pivot` `Chart` `Tree` `Gantt` `Scheduler` `Todo` | `[Layout]` Hangi görünümlerin açık olduğu (`LayoutDto`). **Liste ekranında `Grid`/`Card`/`Pivot`/`Chart` varsayılan `true`**, diğer dördü `false` (§9.8). Açtığın her görünümün option bloğunu da doldur. |
**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 |
| --- | --- |
@ -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`
öğesi bire bir bir **`ListFormField` kaydına** dönüşür (§4.8); ekranda görünen sütunların tek
kaynağı odur. Grup yapısı yalnızca düzenleme formunun yerleşimini belirler — grid sütun sırası
`Items` sırasından gelir.
kaynağı odur. Grup yapısı düzenleme formunun yerleşimini belirler (form sırası = öğenin gruptaki
yeri); grid sütun sırası öğenin `ListOrderNo`'sudur — yazılmamışsa grup sırası, yazılmışsa olduğu
gibi korunur ve `ListOrderNo` taşımayan öğeler en büyük değerin ardına eklenir (`ResolveListOrders`).
```jsonc
"Groups": [
{
"Caption": "Genel",
"ColCount": 2,
"ColSpan": null, // grubun formda kapladığı kolon; null ise ColCount
"Items": [ /* WizardColumnItemInputDto */ ]
}
]
@ -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:
düzenleme formuna giren tek grup varsa `Caption` boşaltılır (`McpDevelopAppService.DeployWizardAsync`).
Seed dosyasında `Groups` **kırpılmış** hâlde durur: grup başlığı/kolon sayısı ve alanların
editör bilgileri `ListForm.EditingFormDto` ve `Fields` içinde olduğu için tekrarlanmaz; dosyada
her öğe yalnızca `FieldName`, `CaptionName` ve `En`/`Tr` taşır — çünkü
alan başlıklarının dil metinlerinin başka kaynağı yoktur. Okurken tümü geri doldurulur.
Seed dosyasında her öğe **alan detayının tamamını** taşır: alan editörünün her sekmesi öğede bir
alan ya da bloktur (`WizardColumnItemInputDto`); öğe kırpılmaz. Dosyada ilk dört anahtar
`FieldName`, `CaptionName`, `En`, `Tr`'dir (C#'ta `EnglishCaption`/`TurkishCaption`), geri kalanı
sözleşme sırasıyla gelir. Aynı alanın dil/rol/kullanıcı varyantları (`CultureName`/`RoleId`/`UserId`)
ayrı öğelerdir. `null` bırakılan blok (`Filter`, `Header` …) deploy'da varsayılanıyla dolar.
Her `Items` öğesi:
Her `Items` öğesi (`[Item]`):
| Alan | Anlam |
| --- | --- |
| `FieldName` | Veri kolonunun adı (SQL'deki adıyla birebir). |
| `CaptionName` | Dil anahtarı ya da düz başlık. |
| `En` / `Tr` | Alan başlığının dil metinleri; ikisi de doldurulur. |
| `EditorType` | `[Fields]` Bkz. §3. |
| `EditorOptions` | `[Fields]` DevExtreme editör JSON'u — §6. |
| `EditorScript` | `[Fields]` Alan davranış script'i — §5. |
| `DbSourceType` | `[Fields]` Bkz. §3. |
| `IsRequired` | `[Fields]` Zorunluluk (`ValidationRuleDto`). |
| `IncludeInEditingForm` | `[Fields]` `false` ise yalnızca grid sütunu olur, forma girmez (`EditGroupOrderNo`). |
| `ColSpan` | `[Fields]` Formda kaç kolon kaplar (grubun `ColCount` değeri içinde). |
| `LookupDataSourceType` | `[Fields]` `1/2/3` — bkz. §3. 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. |
| `ValueExpr` / `DisplayExpr` | `[Fields]` 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). |
| `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. |
| `EditorType` | Bkz. §3. |
| `EditorOptions` | DevExtreme editör JSON'u — §6. |
| `EditorScript` | Alan davranış script'i — §5. |
| `DbSourceType` | Bkz. §3. |
| `IsRequired` | `[Kısayol]` Zorunluluk: deploy `ValidationRules`'a `required` kuralı ekler/çıkarır; dosyaya yazılmaz, okurken kural listesinden türetilir. |
| `IncludeInEditingForm` | `false` ise yalnızca grid sütunu olur, forma girmez (`EditGroupOrderNo`). |
| `ColSpan` | Formda kaç kolon kaplar (grubun `ColCount` değeri içinde). |
| `LookupDataSourceType` | `1/2/3` — bkz. §3. Lookup yalnızca `LookupQuery` doluysa üretilir; wizard ekranı tipi `1` ile açar. |
| `LookupQuery` | `Query` tipinde SQL; `StaticData` tipinde JSON dizisi; `WebService` tipinde URL. |
| `ValueExpr` / `DisplayExpr` | Lookup'ın değer ve etiket kolonları (açıkça `null` verilirse `null` yazılır). |
| `DefaultValue` | Yeni kayıt **sabit** varsayılanı; `FormFieldsDefaultValueJson`'a `CustomValueType: 1` olarak yazılır (§4.3.5.1). Sabit olmayan türler `FormDefaults` bloğundadır. |
| `CascadeParentFields` · `CascadeRelationField` · `CascadeFilterOperator` · `CascadeEmptyFields` | Bağımlı liste (§4.3.4). Wizard ekranında `DefaultValue` ve `Cascade*` için giriş yoktur; MCP/seed girdisiyle gelir, düzenlemede korunur. |
| `CultureName` · `RoleId` · `UserId` | Alan varyantı: dil (boşsa platform varsayılan dili), rol adı, kullanıcı adı. |
| `PlaceHolder` · `BandName` · `ReadOnly` | Editör yer tutucusu, sütun bandı, salt okunur. |
| `Visible` · `IsActive` · `AllowSearch` | Listede görünür (`null` ise anahtar alan gizli, diğerleri görünür) · sorguya dahil · arama panelinde aranır. |
| `Alignment` · `Format` · `Width` | Hizalama, görüntü biçimi (adlı DevExtreme biçimi), sütun genişliği (`0`/`null` otomatik). |
| `ListOrderNo` | Grid sütun sırası (yukarıda); formdaki sıradan bağımsızdır. |
| `SortIndex` · `SortDirection` | Açılış sıralaması. |
| `AllowEditing` · `AllowAdding` | Satır düzenlemede değiştirilebilir · yeni kayıtta girilebilir (`null` ise evet). |
| `ColumnCssClass` · `ColumnCssValue` | CSS sekmesi. |
| `Filter` · `Header` · `Grouping` · `Customization` | Filtreleme (`ColumnFilterDto`), başlık filtresi (`ColumnHeaderDto`), gruplama (`ColumnGroupingDto`), sabitleme/sıralama (`ColumnCustomizationDto`). |
| `TotalSummary` · `GroupSummary` | Özetler (`ColumnTotalSummaryDto`). |
| `Join` | Join sekmesi (`JoinTableDto`). |
| `ValidationRules` | Doğrulama kuralları (`ValidationRuleDto[]`, §4.3.6). |
| `Styling` | Koşullu stil (`ColumnStylingDto[]`). |
| `Permission` | Alan yetkisi (`ListFormFieldPermissionDto`); ekran kodundan türetilir. |
| `PivotSettings` | Pivot sekmesi (`ListFormFieldPivotSettingsDto`). |
### 4.3.1 Sütunlar nasıl otomatik üretilir
@ -1567,7 +1650,7 @@ yazılır (`WizardConsts.DefaultLookupJson`): `CascadeParentFields` ve `CascadeE
`CascadeRelationField` ile `CascadeFilterOperator` yalnızca `CascadeParentFields` doluysa yazılır ve
operatör boşsa da `"="` olarak yazılır. `LookupJson` yalnızca `LookupQuery` doluysa üretilir — lookup'ı
olmayan bir ebeveyn alana wizard girdisiyle `CascadeEmptyFields` yazılamaz; böyle bir alanın `LookupJson`'ı
`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;
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"`.
Alana özel değer (tablo `DEFAULT`'undan farklı) wizard öğesinin **`DefaultValue`** cevabıyla verilir
(`Groups[].Items[].DefaultValue`); dosyada `ListForm.FormFieldsDefaultValueDto`'da durur ve okurken
öğeye geri döner. Alan kaydındaki `DefaultValue`/`DefaultValueType` select servisinin hesapladığı
değerdir, saklanmaz: MCP'de `Fields[].DefaultValue` yazılırsa deploy onu öğenin `DefaultValue`'suna
taşır. `CustomKey`, `DbQuery`, `Sequence` gibi diğer türler `ListForm.FormFieldsDefaultValueDto`
katmanıyla yazılır (§0.10). Deploy girdisinde sözleşmede karşılığı olmayan bir anahtar adıyla
(`Groups[].Items[].DefaultValue`); dosyada da öğede durur. Alan kaydındaki
`DefaultValue`/`DefaultValueType` select servisinin hesapladığı değerdir, saklanmaz. `CustomKey`,
`DbQuery`, `Sequence` gibi sabit olmayan türler `Wizard.FormDefaults` listesine yazılır (§4.2); deploy
önce öğelerin sabitlerini grup sırasıyla, sonra `FormDefaults`'u birleştirir, aynı alan için açık
liste kazanır (`BuildFormDefaults`). Deploy girdisinde sözleşmede karşılığı olmayan bir anahtar adıyla
reddedilir — sessizce yok sayılmaz.
#### 4.3.5.2 Wizard / MCP ekranında otomatik numara — `Adm.Definitions.Sequence`
@ -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
türetilmiş sabit GUID, `LastPeriodKey` yazılmaz). Aynı numarayı üreten uygun bir numaratör zaten
varsa (`query_list Adm.Definitions.Sequence`) o kullanılır, ikincisi açılmaz.
2. Alanın varsayılanı `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:
```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
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` /
`UpdateFieldsDefaultValueDto` / `DeleteFieldsDefaultValueDto` adıyla durur; MCP `deploy_wizard`'da komutla aynı
çağrının `ListForm` bölümünde verilir — komutu yazıp listeyi sonraya bırakmak yoktur.
parametre varsa ekran teslim edilmez. Wizard dosyasında listeler `Insert.FieldsDefaultValue` /
`Update.FieldsDefaultValue` / `Delete.FieldsDefaultValue` adıyla, komutla (`Command`/`BeforeCommand`/`AfterCommand`)
aynı blokta durur; MCP `deploy_wizard`'da da komutla aynı çağrının aynı bloğunda verilir — komutu yazıp
listeyi sonraya bırakmak yoktur.
### 4.3.6 Doğrulama — `ValidationRuleJson`
@ -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
(`ListFormWizardDto`; `Groups[].Items[]` = `WizardColumnItemInputDto`, `En`/`Tr` = C#'taki
`EnglishCaption`/`TurkishCaption`). Okuyucu `FromSeedJson` olduğu için `Menu`/`Language` blokları
ya da `WizardCode`/`ModuleId` adları da kabul edilir. Deploy bunu okuyup ekranı kurar ve §4.1
biçimindeki dosyayı yazar; `IsDeletedField`/`IsCreatedField` tablodaki `IsDeleted`/`CreatorId`
kolonlarından, `InsertedRecords` gerçekten yaratılan kayıtlardan **sunucu tarafından** doldurulur —
`EnglishCaption`/`TurkishCaption`). Okuyucu `FromDeployJson` olduğu için seed dosyası biçimi
(`{"Wizard": {...}}`, `Menu`/`Language`/`Data`/`Details`/`Layout` başlıkları, `WizardCode`/`ModuleId`
adları) da kabul edilir; `ListForm`/`Fields` bölümü reddedilir. Yazılmayan blok deploy'da varsayılanıyla
dolar (`ApplyDefaults`). Deploy bunu okuyup §4.1 biçimindeki dosyayı yazar; tablonun
`IsDeleted`/`CreatorId`/`LastModifierId` kolonlarını taşıyıp taşımadığı katalogdan ya da seed
scriptinden, `InsertedRecords` gerçekten yaratılan kayıtlardan **sunucu tarafından** okunur —
cevaba yazılmaz.
```jsonc
@ -2056,9 +2142,10 @@ cevaba yazılmaz.
}
```
Deploy sonrası dosyada `Wizard` bölümü `Menu`/`Language`/`Groups` başlıklarına toplanır ve
yalnızca `FieldName`/`CaptionName`/`En`/`Tr` kalır; ekranın kendisi `ListForm` + `Fields`
bölümlerine düşer (§4.1). `SeededAt` ilk deploy anında sunucu tarafından yazılır.
Deploy sonrası dosyada `Wizard` bölümü `Menu`/`Language`/`Data`/`Details`/`Layout` başlıklarına
toplanır, boş bırakılan bloklar varsayılanlarıyla dolu yazılır ve her öğe alan detayının tamamını
taşır (§4.1); kısayollar (`AllowAdding` …, `IsRequired`) dosyada yer almaz. `SeededAt` ilk deploy
anında sunucu tarafından yazılır.
> `EditorScript` alanındaki `// @builder` başlığı ile onu izleyen satırlar **birebir
> tutarlı** olmalıdır: açılışta çalışan bir kural varsa (hesap tarifleri varsayılan `both`)
@ -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
Aşağıdakileri deploy wizard cevabından **kendisi** üretir (`ListFormWizardDto.BuildListForm` /
`BuildFields`; menü, yetki ve dil satırları `ListFormWizardAppService` → `WizardSeedRowStore`).
Ekrana ait olanlar dosyanın `ListForm`/`Fields` bölümlerine düşer; `WizardDataSeeder` migrate'te
ekranı oradan olduğu gibi kurar (`GridOptionsEditDto.ApplyTo`), yeniden türetmez. Menü, yetki ve
Aşağıdakileri deploy wizard cevabından **kendisi** üretir (`ListFormWizardDto.ApplyDefaults` →
`BuildListForm` / `BuildFields`; menü, yetki ve dil satırları `ListFormWizardAppService` →
`WizardSeedRowStore`). Üretilen ekran `RefreshFrom` ile `Wizard` bölümüne geri yazılır;
`WizardDataSeeder` migrate'te ekranı dosyadaki bloklardan `BuildListForm/BuildFields` +
`GridOptionsEditDto.ApplyTo` ile kurar, cevaptan yeniden türetmez. Menü, yetki ve
dil kayıtları platform seed dosyalarına satır olarak yazılır, migrate `ListFormSeedDataApplier`
ile ekler (§4.0). Sıra değerleri veritabanı ile dosyadaki satırların birleşiminden hesaplanır:
@ -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ç |
| Form genişlik/yükseklik | Grup kolon sayısı ve satır sayısından hesaplanır (en fazla 3 kolon) |
| `FilterRowJson`, `HeaderFilterJson`, `SearchPanelJson`, `GroupPanelJson`, `ColumnOptionJson`, `PagerOptionJson`, `ExportJson` | Varsayılanlar |
| `DeleteCommand` | `IsDeletedField: true` ise `WizardConsts.DefaultDeleteCommand(SelectCommand)` (soft delete `UPDATE`); `false` ise `null` |
| `DefaultFilter` | `IsDeletedField: true` ise `"IsDeleted" = 'false'` |
| `InsertFieldsDefaultValueJson` | `IsCreatedField: true` ise `CreationTime=@NOW`, `CreatorId=@USERID`, `IsDeleted=false`, `Id=@NEWID`; değilse yalnızca `Id=@NEWID` |
| `DeleteFieldsDefaultValueJson` | `IsDeletedField: true` ise `DeleterId=@USERID`, `Id=@ID`; değilse yalnızca `Id=@ID` |
| `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 |
| `Delete.Command` | Tabloda `IsDeleted` varsa `WizardConsts.DefaultDeleteCommand(SelectCommand)` (soft delete `UPDATE`); yoksa `null` |
| `Data.DefaultFilter` | Tabloda `IsDeleted` varsa `"IsDeleted" = 'false'` |
| `Insert.FieldsDefaultValue` | Tabloda `CreatorId` varsa `CreationTime=@NOW`, `CreatorId=@USERID`, `IsDeleted=false`, `Id=@NEWID`; değilse yalnızca `Id=@NEWID` |
| `Delete.FieldsDefaultValue` | Tabloda `IsDeleted` varsa `DeleterId=@USERID`, `Id=@ID`; değilse yalnızca `Id=@ID` |
| `Update.FieldsDefaultValue` | Tabloda `LastModifierId` varsa `LastModificationTime=@NOW`, `LastModifierId=@USERID`; yoksa boş. `*AfterCommand`/`*BeforeCommand` içindeki `@LastModifierId` bu satırdan gelir. Özel komut eklenen ekranda (view kaynağı dahil) bu üç liste komutun parametrelerine göre tamamlanır — §4.3.5.3 |
| `ValidationRuleJson` | `IsRequired: true` olan alanlara required kuralı |
| `LookupJson` | `LookupQuery` doluysa `LookupDataSourceType` + `DisplayExpr` + `ValueExpr` ile |
| `ShowNote` | Kayıt kaynağı Table ise ve alt form veya iş akışı varsa açılır; Table dışı kaynakta hep kapalı |
@ -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,
silme, detay ve alt form ilişkileri çalışmaz. Yazdığında sütun otomatik gizlenir (`Visible =
false`) ve forma girmez; tip bilgisi de oradan okunur.
- `IsDeletedField: false` verirsen ekranda **kalıcı silme** olmaz (`DeleteCommand` null kalır);
soft delete kolonları olmayan tabloda silme istiyorsan ListForm editöründen komut yazmalısın.
- Tabloda `IsDeleted` kolonu yoksa ekranda **kalıcı silme** olmaz (`Delete.Command` null kalır);
soft delete kolonları olmayan tabloda silme istiyorsan `Delete.Command`'ı cevapta ya da ListForm
editöründen yazmalısın.
- Dil anahtarı zaten varsa metni **ezilmez**. Yanlış metin görüyorsan anahtar başka bir yerde
tanımlanmıştır. Anahtar yalnızca veritabanında varsa (seed dosyasında satırı yoksa) deploy onu
veritabanındaki metniyle `Sas.Languages.LanguageText.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ı:
1. **ListForm editörü** (`/admin/listform/edit/{kod}`) — alan ekleme, JSON kolonları, komutlar,
görünümler. Her kayıt `WizardSeedSynchronizer` ile aynı dosyanın `ListForm`/`Fields`/`Wizard`
bölümlerine geri yazılır; **ince ayar için tercih edilen yol** budur.
görünümler. Her kayıt `WizardSeedSynchronizer` ile aynı dosyanın `Wizard` bölümüne
(`RefreshFrom`) geri yazılır; **ince ayar için tercih edilen yol** budur.
2. **`EditFileName` ile yeniden deploy** (wizard ekranı ya da MCP `replaceFileName`) — menü,
yetki grubu, veri kaynağı gibi wizard cevabına ait değişiklikler için. Dosyalar yeniden yazılır,
ekran DB Migrate'te cevaptan yeniden kurulur (§4.0); editörde yapılmış, wizard'da karşılığı
olmayan ayarlar kaybolur — MCP'de `get_wizard` çıktısının `ListForm`/`Fields` bölümleri de
gönderilirse korunur (§0.10).
yetki grubu, veri kaynağı gibi değişiklikler için. Dosyalar yeniden yazılır, ekran DB Migrate'te
cevaptan yeniden kurulur (§4.0). Ekranın her ayarı cevabın bir bloğu olduğu için editör ayarları
korunur: wizard ekranı dosyayı yükler, MCP'de `get_wizard` çıktısı düzenlenip gönderilir; cevapta
hiç yazılmayan blok varsayılana döner (§0.10).
3. İlgili kayıtları elle sil, sonra seed'i yeniden çalıştır.
> `List` yolunda seeder ekranı uygularken `ListForm`, `ListFormField` ve `ListFormWorkflow`
@ -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
ana ekrandan eklendiği için bu sekmelerde yalnızca `AllowUpdating` açıktır — `AllowAdding` ve
`AllowDeleting` kapalı yazılır (eklemek tabloya ikinci bir ana kayıt açardı). Taşınan alanlar ana
ekranın `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
(`lowcode-reference` §3.2).
- **`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
`deploy_wizard` girdisinde aynı dizi **`CommandColumns`** alanıdır; deploy onu
`CommandColumnJson`'a yazar, seed dosyasında `ListForm` bölümünde durur ve düzenlemede korunur.
Wizard ekranında bu alanın adımı yoktur; buton ekranın detayındaki **Komutlar** sekmesinden ya da
`deploy_wizard` ile yazılır.
`CommandColumnJson`'a yazar, seed dosyasında `Wizard.CommandColumns` olarak durur ve düzenlemede
korunur. Wizard ekranında **Düzenleme** adımının Komutlar sekmesinden, ekranın detayındaki
**Komutlar** sekmesinden ya da `deploy_wizard` ile yazılır.
```jsonc
[
@ -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) |
| 1 | Custom Component | yalnız Custom yolu | `CustomComponentName`, `MenuUrl` (bileşenin `RoutePath`'i) |
| 2 | Menü | her zaman | `WizardName`, `CreateMenu`, `MenuParentCode` (ekranda eksik seviyeler menü diyaloğuyla kurulur; MCP `MenuParentPath` yazar), `MenuCode`, `Language.MenuEn/Tr`, `PermissionGroupName` (+ görünen adlar), `MenuIcon`, `MenuOrder` |
| 3 | Ayarlar | yalnız List yolu | `ListFormCode`, `DataSourceCode` (+`DataSourceConnectionString`), `SelectCommandType`, `SelectCommand`, `KeyFieldName`, `KeyFieldDbSourceType`, `IsTenant/IsBranch/IsOrganizationUnit`, `AllowAdding/Updating/Deleting`, `ConfirmDelete`, `AllowDetail`, `OpenDetailAfterInsert`, `DefaultLayout` + 8 görünüm bayrağı (`SeedData` adımda sorulmaz, düzenlemede korunur), `Language.TitleEn/Tr`, `Language.DescEn/Tr` |
| 4 | Alanlar | yalnız List | `Groups[]` (aşağıda) |
| 5 | Alt Formlar | yalnız List | `SubForms[]` |
| 6 | Widget'lar | yalnız List | `Widgets[]` |
| 7 | İş Akışı | yalnız List | `WorkflowDto` |
| 8–11 | Todo / Tree / Gantt / Scheduler | List + ilgili bayrak açıksa | `TodoOptionDto` / `TreeOptionDto` / `GanttOptionDto` / `SchedulerOptionDto` |
| 12 | Deploy | her zaman | — (özet + üretim; dosyaya alan yazmaz) |
| 3 | Ayarlar | yalnız List yolu | `ListFormCode`, `Data` (`DataSourceCode` (+`DataSourceConnectionString`), `SelectCommandType`, `SelectCommand`, `KeyFieldName`, `KeyFieldDbSourceType`, `IsTenant/IsBranch/IsOrganizationUnit`; `SeedData` adımda sorulmaz, düzenlemede korunur), `AllowAdding/Updating/Deleting`, `ConfirmDelete`, `AllowDetail`, `OpenDetailAfterInsert`, `Layout` (`DefaultLayout` + 8 görünüm bayrağı), `Language.TitleEn/Tr`, `Language.DescEn/Tr` |
| 4 | Alanlar | yalnız List | `Groups[]` (aşağıda); karttaki ek ayarlar `Visible`/`IsActive`/`ReadOnly`/`AllowSearch`/`AllowEditing`/`AllowAdding`, `Width`, `Alignment`, `Format`, `SortDirection`/`SortIndex`, `DefaultValue`, `PlaceHolder`, `BandName`; alan kartından açılan detay sekmeleri öğenin bloklarını (`Filter`, `Header`, `Grouping`, `TotalSummary`/`GroupSummary`, `Join`, `ValidationRules`, `Styling`, `ColumnCss*`, `Permission`, `PivotSettings` …), sütun sırası paneli `ListOrderNo`'yu doldurur |
| 5 | Liste Ayarları | yalnız List | ListForm editörünün sekmeleri taslak modunda: Detay (`Details`: tip, sayfa boyu, boyutlar, not, alt form, kaynak dosyaları, rol/kullanıcı varyantı), Sütunlar (`ColumnOption`), Sıralama (`Details.SortMode`), Filtreleme (`FilterRow`, `HeaderFilter`, `FilterPanel`), Satır (`Row`), Arama (`SearchPanel`), Gruplama (`GroupPanel`), Seçim (`Selection`), Sayfalama (`Pager`), Durum (`StateStoring`), Pivot (`PivotOption`; yalnız `Pivot` açıksa) |
| 6 | Düzenleme ve Veri | yalnız List | Düzenleme (`Editing`; izin bayrakları Ayarlar adımıyla iki yönlü eşlenir), Veritabanı Select/Insert/Update/Delete (`Data.SelectFieldsDefaultValue`, `Data.SeedFilePath`/`SeedSync*`, `Insert`/`Update`/`Delete`), Butonlar (`CommandColumns`), Ek Filtreler (`ExtraFilters`) |
| 7 | Alt Formlar | yalnız List | `SubForms[]` |
| 8 | Widget'lar | yalnız List | `Widgets[]` |
| 9 | İş Akışı | yalnız List | `WorkflowDto` |
| 10–13 | Todo / Tree / Gantt / Scheduler | List + ilgili bayrak açıksa | `TodoOptionDto` / `TreeOptionDto` / `GanttOptionDto` / `SchedulerOptionDto` |
| 14 | Grafik | List + `Chart` açıksa | `ChartOptions` (grafik sekmeleri: Genel, Seriler, Eksen, Paneller, Animasyon, Açıklamalar, Yakınlaştırma, Gösterge, Crosshair, Dışa Aktarma) |
| 15 | Deploy | her zaman | — (özet + "Ekran ayarları" bölümü + üretim; dosyaya alan yazmaz) |
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
adıma döner.
@ -2839,15 +2939,23 @@ taşır. Sihirbazın kendi otomatikleri — dosya yazarken aynısını uygula:
| `ValueExpr`/`DisplayExpr` | `Key` / `Name` |
Alan kartındaki **Seçenekler** düğmesi Editor Options Builder'ı (§6), **Script** düğmesi Script
Builder'ı (§5) açar — çıktıları `EditorOptions`/`EditorScript` string'leridir.
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`
(liste yalnız sıradaki onaycıya düşsün), `ApprovalIsResetWorkflow` (toolbar'a Taslak
düğmesi; otomatik sıfırlama yok), `ApprovalIsManualStart` (yeni kayıt taslak kalsın, akış başlat düğmesiyle başlasın). Onay düğümleri (`Criteria`) görsel akış tasarımcısında kurulur ve `ListFormWorkflow`
kayıtlarına gider (§4.7).
**Adım 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ı,
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",
"MenuIcon": "FcKanban", "MenuOrder": 2
},
"IsDeletedField": false, "IsCreatedField": false,
"InsertedRecords": {
"LanguageKeys": ["Sas.Mrp.OrderBoard"],
"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,
`ListForm: null`, `Fields: []` yazılır. Custom yolunda `ListForm` **üretilmez**; `Title`/`Desc`
dil anahtarları da üretilmez.
ekrana ait anahtarlar (`Data`, `Details`, `Layout`, bloklar, `Groups`) hiç yazılmaz. Custom yolunda
ListForm kaydı **üretilmez**; `Title`/`Desc` dil anahtarları da üretilmez.
`Export`/`Import`/`Note` yetkileri bu yolda yoktur.
> **Güvenlik — route yetkisiz üretilir.** Custom component route'ları `RoutePath` değerinden
@ -3826,7 +3933,7 @@ ileri geçiş koşulludur: çözülmemiş çakışma varken Kontroller'e, engell
Yapılacaklar'a geçilmez.
Engelleyici bulgular kaynağına göre ikiye ayrılır (`WizardImportIssueDto.RequiresReexport`):
arşivin kendi eksikliği/bozukluğu — bozuk JSON, eksik `Wizard`/`ListForm` bölümü, karşılığı olmayan
arşivin kendi eksikliği/bozukluğu — bozuk JSON, eksik `Wizard` bölümü ya da `Data` başlığı, karşılığı olmayan
alt form / bağımlılık / component / script — hedef ortamda **giderilemez**, eksik olan veri arşivde
yoktur; tek çözüm kaynak ortamda export'u tekrarlamaktır ve UI bunları ayrı başlıkta, düzeltme
düğmesi olmadan gösterir. `WizardImportValidator.Add` üzerinden geçen her `Error` bu sınıftadır;
@ -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
`Error` varken import başlatılamaz; sunucu oturuma `blocked` işaret dosyası bırakır, dolayısıyla
`ApplyImport` doğrudan çağrılsa bile reddeder. Error: bozuk JSON; `Wizard`/`ListForm` bölümü eksik
wizard dosyası; ekranın ya da bir `crud/{Entity}.json`'un tablosu hedefte yok **ve** zip onu
`ApplyImport` doğrudan çağrılsa bile reddeder. Error: bozuk JSON; `Wizard` bölümü ya da List yolunda
`Data` başlığı (`SelectCommand`/`KeyFieldName`) eksik wizard dosyası; ekranın ya da bir `crud/{Entity}.json`'un tablosu hedefte yok **ve** zip onu
oluşturan scripti taşımıyor; `data/{code}.json`'un ekranı ne zip'te ne hedefte var; custom
component'in `Dependencies`/`DataSources[].SeedFile` bağımlılığı bulunamıyor; Custom wizard'da
bileşen adı ya da rota boş; alt form referansı karşılıksız. Warning: nesne varlığı sorgulanamadı,
@ -4268,7 +4375,7 @@ içindir.
ile tabloya bağlanır. Custom Component kullanacaksa `crud/{Entity}.json` de yaz (§9).
4. Wizard dosyasında `SelectCommand` = tablo adı, `KeyFieldName` = `Id`,
`KeyFieldDbSourceType` = tablodaki `Id` tipinin sayısı, `IsTenant` = tabloda `TenantId` varsa
`true`, `IsDeletedField`/`IsCreatedField` = ilgili kolonlar varsa `true`. Anahtar alanı
`true` (soft delete / audit kolonlarını deploy tablodan okur, cevaba yazılmaz). Anahtar alanı
`Groups[0].Items[0]` olarak `IncludeInEditingForm: false` ile de yaz (§4.4.1).
5. DB Migrate çalıştır; log'da `Executing: CREATE TABLE …` satırını gör.
@ -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… |
| --- | --- |
| 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 |
| `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 |
@ -5289,7 +5396,8 @@ isteniyorsa geçilir.
(`ApprovalUserName`, `ApprovalStatus`, `ApprovalDate`, `ApprovalDescription`; ayrı `Status` yok).
Talep sahibi için de bir kolon (`EmployeeId`) bulunmalıdır.
2. **Wizard dosyası** — §4.4'teki iskelet:
- `IsTenant: true` (tabloda `TenantId` var), `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`
- `PermissionGroupName` = `Hr`, EN + TR görünen adlarıyla
- 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.
- [ ] Menü ve route elle eklenmedi; wizard dosyasından geliyor.
- [ ] List ekranı deploy ile üretildi (wizard ekranı / MCP `deploy_wizard`) ya da elle yazılan
dosya `ListForm` + `Fields` bölümlerini taşıyor (§4.1).
dosya yalnızca `Wizard` bölümünü taşıyor ve `Data` başlığı dolu (§4.1); `ListForm`/`Fields`
bölümü yok.
- [ ] Kullanıcıya hangi dosyaların üretildiği ve DB Migrate gerektiği söylendi.
- [ ] Keşif turu işletildi (§0.6): Tur 1 soruları soruldu, yol için gerekçe verildi ve tarif
üretimden **önce** onaylatıldı — kullanıcı "sorma / sen seç" demedikçe.

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
`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.
- **Wizard seed dosyasında tekrar yok** → `Wizard` bölümü yalnızca ekrandan üretilemeyen bilgileri
taşır. `ListForm`/`Fields` içinde karşılığı olan her alan (veri kaynağı, select, anahtar alan,
kırılım/düzen/düzenleme bayrakları, görünüm option blokları, alt form, widget, iş akışı, alan
tanımları) dosyaya yazılmadan çıkarılır (`WizardSeedFileDto.ToSeedJson`) ve okurken geri
doldurulur (`FromSeedJson`). Wizard'a yeni bir alan eklerken karşılığı ListForm'da varsa listeye ekle (`CommandColumns` →
`CommandColumnJson` bu yolla taşınır; wizard ekranında adımı yoktur, UI modeli düzenlemede korur).
Dosyada kalan wizard alanları üç başlık altında toplanır — `Menu`, `Language`, `Groups` — ve
`ListFormCode` → `WizardCode`, `MenuParentModuleId` → `ModuleId`, `LanguageTexts` → `Language.Texts`
adıyla yazılır; varsayılanı olan alanlar (`CreateMenu: true`, boş `DataSourceConnectionString`, boş
`MenuParentPath` / `LanguageTexts`) hiç yazılmaz. Bu yalnızca dosya biçimidir; C#
sözleşmesi düzdür, dönüşüm `WizardSeedFileDto.ToSeedJson` / `FromSeedJson` içindedir. **Wizard
seed dosyası her zaman bu iki metotla okunup yazılır**; düz `JsonSerializer` çağrısı bloklu
bölümleri sessizce düşürür.
- **Wizard ile üretilen ekran** → seed dosyası (`configs/seeds/wizard/{WizardName}.json`)
wizard cevaplarının yanında ekranın `GridOptionsEditDto` (`ListForm`) ve alanların
`ColumnFormatEditDto` (`Fields`) sözleşmesini taşır. ListForm detayından yapılan her kayıt
(`ListFormsAppService`, `ListFormFieldsAppService`, `ListFormJsonRowAppService`) bu bölümleri
ve `Wizard` bölümündeki karşılıklarını geri yazar (`WizardSeedSynchronizer`). Bu üçlüden birine
yeni bir kayıt yolu eklenirse senkronizasyon çağrısı da eklenir; yoksa dosya bayatlar.
- **Wizard girdisinden ekran üretimi** → tek kaynak `ListFormWizardDto.BuildListForm/BuildFields`,
entity'ye yazma `GridOptionsEditDto.ApplyTo` / `ColumnFormatEditDto.ApplyTo`. Deploy, seeder ve
senkronizasyon aynı metotları çağırır; ikinci bir üretim kopyası yazma.
- **Wizard seed dosyası tek `Wizard` bölümüdür** → dosyada `ListForm`/`Fields` bölümü yoktur; ekranın
bütün sözleşmesi `Wizard` bölümündedir. ListForm detayının her sekmesi orada bir bloktur (`Editing`,
`Insert`/`Update`/`Delete`, `FormDefaults`, `Permission`, `FilterRow` … `PivotOption`, görünüm option
blokları, `ExtraFilters`, `CommandColumns`, `SubForms`, `Widgets`, `WorkflowDto`, `ChartOptions`), alan
detayının her sekmesi `Groups[].Items[]` öğesinin bir alanıdır (`WizardColumnItemInputDto`: `Filter`,
`Header`, `Grouping`, `TotalSummary`/`GroupSummary`, `Join`, `ValidationRules`, `Styling`, `Permission`,
`PivotSettings`, `ListOrderNo` …). Wizard alanları `Menu`, `Language`, `Data`, `Details`, `Layout`
başlıklarına toplanır ve `ListFormCode` → `WizardCode`, `MenuParentModuleId` → `ModuleId`,
`LanguageTexts` → `Language.Texts` adıyla yazılır; varsayılanı olan alanlar (`CreateMenu: true`, boş
`DataSourceConnectionString`, boş `MenuParentPath` / `LanguageTexts`) hiç yazılmaz. Kısayol cevaplar
(`AllowAdding/AllowUpdating/AllowDeleting/AllowDetail/OpenDetailAfterInsert/ConfirmDelete`,
`SeedData`/`SeedSync`, öğedeki `IsRequired`) **dosyaya yazılmaz**: `Editing`, `Data.SeedFilePath`/`SeedSync*`
ve `ValidationRules`'tan türetilir (`SyncDerivedAnswers`); deploy girdisinde yazılırsa bloğu ezer. Bu yalnızca
dosya biçimidir; C# sözleşmesi düzdür, dönüşüm `WizardSeedFileDto.ToSeedJson` / `FromSeedJson` içindedir.
**Wizard seed dosyası her zaman bu iki metotla okunup yazılır**; düz `JsonSerializer` çağrısı başlıkları
ve türetilen kısayolları sessizce bozar. `FromSeedJson` eski biçimin `ListForm`/`Fields` bölümlerini
okuyup `RefreshFrom` ile `Wizard`'a aktarır; bir sonraki yazımda düşerler.
- **Wizard ile üretilen ekran** → ListForm detayından yapılan her kayıt (`ListFormsAppService`,
`ListFormFieldsAppService`, `ListFormJsonRowAppService`) seed dosyasının `Wizard` bölümünü
`ListFormWizardDto.RefreshFrom(listForm, fields)` ile geri yazar (`WizardSeedSynchronizer`). Bu üçlüden
birine yeni bir kayıt yolu eklenirse senkronizasyon çağrısı da eklenir; yoksa dosya bayatlar.
**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
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.
@ -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.
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`
girdisindeki `ListForm`/`Fields` bölümleri ekrana katman olarak uygulanır (`WizardSeedFileDto.FromDeployJson`
+ `WizardScreenOverlay`); `ListFormWizardDto`'ya editörde karşılığı olan alan eklenirse katman onu
kendiliğinden taşır.
girdisi dosya biçimi (`{"Wizard": {...}}`, başlıklı) ya da düz cevaptır (`WizardSeedFileDto.FromDeployJson`);
bloklar isteğe bağlıdır (yazılmayan blok `ApplyDefaults` ile dolar), kısayol cevap bloktan üstündü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
ekranın sorgusundan (`IListFormSelectAppService`/`IListFormDataAppService`) geçer; liste
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`
→ `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;
dosyadaki `ListForm`/`Fields` sözleşmesi olduğu gibi kullanılır, wizard cevaplarından yeniden
türetilmez — türetmek tasarımcı değişikliklerini silerdi. Wizard'ın menü/yetki/dil satırlarını
dosyanın `Wizard` bölümündeki bloklar olduğu gibi uygulanır (`BuildListForm/BuildFields`), varsayılanlar
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`).
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ı**

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)
6. **Dil anahtarları** (menü, başlık, açıklama; EN + TR)
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ı
**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 |
| 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 |
| Widget'lar | `WizardStepWidgets.tsx` | KPI kartları |
| İş 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 |
| 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
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
`GridOptionsEditDto` sözleşmesini (`ListForm`) ve alanların `ColumnFormatEditDto` sözleşmesini
(`Fields`) tutar; `WizardDataSeeder` ekranı bu iki bölümden kurar. **Deploy veritabanına yazmaz:**
**Seed dosyası ekranın kendisini taşır — tek `Wizard` bölümüyle.** ListForm detayının her sekmesi
`Wizard` bölümünde bir bloktur (`Editing`, `Insert`/`Update`/`Delete`, `FormDefaults`, `FilterRow` …
`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
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
@ -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
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.
`Wizard` bölümü yalnızca ekrandan üretilemeyen bilgileri (menü, yetki, dil metinleri, bileşen
yolu, alan başlıklarının dil karşılıkları) taşır; `ListForm`/`Fields` içinde karşılığı olan alanlar
dosyaya yazılmaz, okuma sırasında geri doldurulur. Kalanlar dosyada `Menu`, `Language` ve `Groups`
başlıkları altında toplanır (`WizardSeedFileDto.ToSeedJson` / `FromSeedJson`). Dosya adı `{Ad}.json`'dır (zaman damgası yok);
Wizard alanları dosyada `Menu`, `Language`, `Data`, `Details`, `Layout` başlıkları altında toplanır,
bloklar ve `Groups` kök seviyededir (`WizardSeedFileDto.ToSeedJson` / `FromSeedJson`); kısayol cevaplar
(`AllowAdding` …, `SeedData`/`SeedSync`, öğedeki `IsRequired`) dosyaya yazılmaz, sahibi olan bloktan
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
`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,
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
üretilmiş `ListForm`/`Fields` sözleşmesini taşıdığı için bunlar wizard cevaplarından **yeniden
türetilmez**. Wizard'ın menü/yetki/dil satırlarını taşıyan platform dosyaları 4. adımda
`WizardDataSeeder` tarafından kurulur (hedefte kurulu olsa da dosyadaki hâliyle). Ekranın bütün
ayarları dosyanın `Wizard` bölümündeki bloklardadır; seeder onları olduğu gibi uygular
(`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.
**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 |
| --- | --- |
| 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 |
| `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 |
@ -1587,7 +1601,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| 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. |
| `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`. |
| `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. |
@ -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. |
| `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. |
| `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). |
| `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. |
@ -2230,8 +2244,8 @@ configs/seeds/
> 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
> 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
> `Wizard` bölümündeki karşılıkları veritabanındaki güncel haliyle değiştirilir.
> de aynı dosya güncellenir (`WizardSeedSynchronizer`): `Wizard` bölümü veritabanındaki güncel
> 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))
continue;
var found = columns
.Where(column => (seed.Fields ?? []).Any(field => string.Equals(field.FieldName, column, StringComparison.OrdinalIgnoreCase)))
.ToList();
var fieldNames = (seed.Wizard?.Groups ?? [])
.SelectMany(group => group.Items ?? [])
.Select(item => item.FieldName)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var found = columns.Where(fieldNames.Contains).ToList();
if (found.Count > 0)
owners[seed.ListForm?.ListFormCode ?? file.ListFormCode ?? file.WizardName] = found;
owners[seed.Wizard?.ResolveListFormCode() ?? file.ListFormCode ?? file.WizardName] = found;
}
return owners;
}
/// <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
/// <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
@ -486,12 +488,12 @@ public class McpDevelopAppService(
/// </summary>
private async Task<McpProblem?> WriteSeedRowsAsync(string fileName, List<Dictionary<string, JsonElement>> rows)
{
var listForm = (await wizardAppService.GetFile(fileName)).ListForm;
var target = ListFormSeedTarget.Create(SeedPathResolver.GetRootPath(configuration), listForm?.SeedFilePath);
if (listForm == null || target == null)
var wizard = (await wizardAppService.GetFile(fileName)).Wizard;
var target = ListFormSeedTarget.Create(SeedPathResolver.GetRootPath(configuration), wizard?.SeedFilePath);
if (wizard == null || target == null)
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 added = new List<(object Key, Dictionary<string, object> Row)>();
@ -501,14 +503,14 @@ public class McpDevelopAppService(
if (!existing.Add(RowContent(values, keyFieldName)))
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();
added.Add((values.GetValueOrDefault(keyFieldName) ?? string.Empty, values));
}
// 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)
: null;
}
@ -1100,10 +1102,9 @@ public class McpDevelopAppService(
}
/// <summary>
/// Wizard'i seed dosyasi biciminden (<c>{"Wizard": {...}, "ListForm": {...}, "Fields": [...]}</c>)
/// ya da duz cevaplardan okur; Menu/Language bloklari ve WizardCode/ModuleId adlari dosyadakiyle
/// ayni anlama gelir. ListForm/Fields bolumleri ekrana katman olarak uygulanir
/// (<see cref="WizardSeedFileDto.FromDeployJson"/>).
/// Wizard'i seed dosyasi biciminden (<c>{"Wizard": {...}}</c>, basliklar toplanmis) ya da duz
/// cevaplardan okur; Menu/Language/Data/Details/Layout bloklari ve WizardCode/ModuleId adlari
/// dosyadakiyle ayni anlama gelir (<see cref="WizardSeedFileDto.FromDeployJson"/>).
/// </summary>
private McpOutcome<ListFormWizardDto> ParseWizard(string? json)
{

View file

@ -192,7 +192,7 @@ public class McpSchemaAppService(
/// <summary>
/// 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:
/// kolon adlari dogrudur ama query_list migrate'ten once calismaz.
/// </summary>
@ -208,9 +208,12 @@ public class McpSchemaAppService(
continue;
var seed = await wizardAppService.GetFile(file.FileName);
if (seed.ListForm is not { } listForm)
if (seed.Wizard is not { } wizard || string.IsNullOrWhiteSpace(wizard.SelectCommand))
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('[', ']', '"');
if (table is not null)
tables.Add(table);
@ -226,7 +229,7 @@ public class McpSchemaAppService(
SourceKind = PendingSourceKind,
KeyFieldName = listForm.KeyFieldName,
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,
DisplayName = field.CaptionName,

View file

@ -90,16 +90,16 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService, IMc
=> McpToolResult.From(await developAppService.GetWizardsAsync(search));
[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(
[Description("list_wizards cikisindaki fileName degeri, ornegin 'Tags.json'.")]
string fileName)
=> McpToolResult.From(await developAppService.GetWizardAsync(fileName));
[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(
[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,
[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,

View file

@ -24,6 +24,12 @@ public interface IListFormWizardAppService
/// <summary>Izin gruplarini iki dilli gorunen adlariyla dondurur.</summary>
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>
Task<IRemoteStreamContent> ExportFile(string fileName);

View file

@ -6,5 +6,9 @@ public class WizardColumnGroupInputDto
{
public string Caption { get; set; }
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();
}

View file

@ -1,8 +1,14 @@
using System.Data;
using System.Collections.Generic;
using System.Data;
using Sozsoft.Platform.Enums;
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 string FieldName { get; set; }
@ -11,6 +17,11 @@ public class WizardColumnItemInputDto
public string EditorOptions { get; set; }
public string EditorScript { get; set; }
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 IncludeInEditingForm { get; set; } = true;
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).
/// 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.
/// Sabit olmayan varsayilanlar (<c>@USERID</c>, sorgu, numarator) <see cref="ListFormWizardDto.FormDefaults"/>'tadir.
/// </summary>
public string DefaultValue { get; set; }
@ -39,4 +51,70 @@ public class WizardColumnItemInputDto
/// <summary>Ebeveyn alanda: bu alan degisince bosaltilacak cocuk alanlar, virgulle.</summary>
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.Serialization;
using Sozsoft.Platform.Enums;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.ListForms;
@ -13,25 +12,24 @@ namespace Sozsoft.Platform.ListForms;
/// Wizard seed dosyası formatı.
/// WizardAppService tarafından Seeds/wizard/{wizardName}.json olarak kaydedilir.
/// 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>
public class WizardSeedFileDto
{
public ListFormWizardDto Wizard { get; set; }
/// <summary>
/// Wizard'in urettigi ekranin ListForm sozlesmesi. Tasarimci (ListForm detayi) uzerinde
/// yapilan her degisiklik buraya geri yazilir; seeder ekrani bu bolumden kurar.
/// Eski bicimin ekran bolumu; yalnizca okunur. Dosyada bulunursa <see cref="Wizard"/> bolumune
/// <see cref="ListFormWizardDto.RefreshFrom"/> ile aktarilir ve bir sonraki yazimda dosyadan duser.
/// </summary>
public GridOptionsEditDto ListForm { get; set; }
/// <summary>Ekranin alanlari; ListForm detayindaki alan tanimlariyla ayni sozlesme.</summary>
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>Eski bicimin alan bolumu; <see cref="ListForm"/> gibi yalnizca okunur.</summary>
public List<ColumnFormatEditDto> Fields { get; set; }
/// <summary>
/// Wizard'in sahip oldugu kayitlar: deploy sirasinda ne veritabaninda ne seed dosyalarinda
@ -54,65 +52,18 @@ public class WizardSeedFileDto
public WizardDevRequestDto DevRequest { get; set; }
/// <summary>
/// Dosyaya yazmadan once ortama ozgu alanlari temizler. Kayit kimlikleri ve audit damgalari
/// veritabanina aittir; dosyada tasinirlarsa her ortamda anlamsiz fark uretirler. EditType ise
/// yalnizca tasarimcinin hangi sekmeyi kaydettigini bildiren istek alanidir.
/// Dosyaya yazilmayan kisayollar: sahibi olan bloktan turetilirler (<see cref="ListFormWizardDto.SyncDerivedAnswers"/>).
/// Izin bayraklari <see cref="ListFormWizardDto.Editing"/>'de, veri aynasi bayraklari <c>Data</c> bolumundedir.
/// </summary>
private void ClearRuntimeFields()
{
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 =
private static readonly string[] DerivedWizardKeys =
[
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.AllowDetail), nameof(ListFormWizardDto.OpenDetailAfterInsert), nameof(ListFormWizardDto.ConfirmDelete), nameof(ListFormWizardDto.SeedData), nameof(ListFormWizardDto.SeedSync),
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),
nameof(ListFormWizardDto.AllowDetail), nameof(ListFormWizardDto.OpenDetailAfterInsert), nameof(ListFormWizardDto.ConfirmDelete),
];
/// <summary>Grup ve alan tanimlarinin <see cref="Fields"/> bolumunde karsiligi olan anahtarlari.</summary>
private static readonly string[] FieldsOwnedGroupKeys =
[
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>Alan ogesinde dosyaya yazilmayan kisayol; <c>ValidationRules</c>'tan turetilir.</summary>
private static readonly string[] DerivedItemKeys = [nameof(WizardColumnItemInputDto.IsRequired)];
/// <summary>Dosyada `Menu` basligi altinda toplanan wizard alanlari; adlari degismez.</summary>
private static readonly (string Property, string SeedKey)[] MenuKeys =
@ -124,6 +75,80 @@ public class WizardSeedFileDto
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);
/// <summary>
@ -142,29 +167,14 @@ public class WizardSeedFileDto
/// </summary>
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 =
[
nameof(ListFormWizardDto.ComponentKind), nameof(ListFormWizardDto.WizardName), WizardCodeKey,
nameof(ListFormWizardDto.SeededAt), nameof(ListFormWizardDto.CustomComponentName),
nameof(ListFormWizardDto.PermissionGroupName), "ModuleId",
nameof(ListFormWizardDto.DataSourceConnectionString),
MenuSection, LanguageSection, nameof(ListFormWizardDto.Groups),
];
/// <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"),
MenuSection, LanguageSection, DataSection, DetailsSection, LayoutSection,
.. ScreenKeys.Where(k => k is not (DataSection or DetailsSection or LayoutSection) && !DerivedWizardKeys.Contains(k)),
];
/// <summary>
@ -177,8 +187,17 @@ public class WizardSeedFileDto
(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 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
// diff'lenir, bu yuzden veri dosyalariyla ayni kodlayici kullanilir.
@ -187,48 +206,26 @@ public class WizardSeedFileDto
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
/// <summary>
/// Dosyaya yazilacak JSON'u uretir: once ortama ozgu alanlar temizlenir, sonra wizard
/// bolumunden ekranin sozlesmesinde zaten bulunan alanlar cikarilir. Wizard bolumunde
/// yalnizca ekrandan uretilemeyen bilgiler kalir: menu, yetki, dil metinleri ve alan
/// basliklarinin iki dilli karsiliklari.
/// Dosyaya yazilacak JSON'u uretir: eski bicimin <see cref="ListForm"/>/<see cref="Fields"/> bolumleri ve
/// istek alanlari hic yazilmaz, sahibi olan bloktan turetilen kisayollar cikarilir, wizard alanlari
/// `Menu`/`Language`/`Data`/`Details`/`Layout` basliklarina toplanir. Custom yolunda ekran anahtari yoktur.
/// </summary>
public string ToSeedJson()
{
ClearRuntimeFields();
if (Wizard != null)
Wizard.EditFileName = null;
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)
{
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)
root.Remove(nameof(Pending));
@ -238,6 +235,28 @@ public class WizardSeedFileDto
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
// dosyada tasimanin anlami yok.
if (string.IsNullOrWhiteSpace(Wizard?.DataSourceConnectionString))
@ -249,8 +268,7 @@ public class WizardSeedFileDto
if (Wizard?.CreateMenu != false)
wizardNode.Remove(nameof(ListFormWizardDto.CreateMenu));
// Kok seviyedeki ust menu eski ve en yaygin durumdur; bos zincir dosyaya yazilmaz,
// mevcut wizard dosyalari degismeden kalir.
// Kok seviyedeki ust menu eski ve en yaygin durumdur; bos zincir dosyaya yazilmaz.
if (Wizard?.MenuParentPath is not { Count: > 0 })
wizardNode.Remove(nameof(ListFormWizardDto.MenuParentPath));
@ -260,14 +278,15 @@ public class WizardSeedFileDto
foreach (var (property, seedKey) in RenamedKeys)
Rename(wizardNode, property, seedKey);
foreach (var item in ItemNodes(wizardNode))
{
foreach (var (property, seedKey) in CaptionKeys)
Rename(item, property, seedKey);
}
foreach (var (section, keys) in Sections)
MoveIntoSection(wizardNode, section, keys);
MoveIntoSection(wizardNode, MenuSection, MenuKeys);
MoveIntoSection(wizardNode, LanguageSection, LanguageKeys);
// Custom yolunda ListForm ekosistemi uretilmez; ekran anahtarlari dosyada yer almaz.
if (Wizard?.ComponentKind == WizardComponentKindEnum.Custom)
{
foreach (var key in ScreenKeys)
wizardNode.Remove(key);
}
node[nameof(Wizard)] = Reorder(wizardNode, WizardKeyOrder);
}
@ -276,8 +295,8 @@ public class WizardSeedFileDto
}
/// <summary>
/// Seed dosyasini okur: once `Menu`/`Language` basliklari duzlestirilir (sozlesme duz
/// oldugu icin), sonra wizard bolumu ekranin sozlesmesinden tamamlanir.
/// Seed dosyasini okur: basliklar duzlestirilir (sozlesme duz oldugu icin), eski bicimin
/// ekran bolumleri wizard'a aktarilir, kisayollar bloklardan turetilir.
/// </summary>
public static WizardSeedFileDto FromSeedJson(string json)
{
@ -289,17 +308,11 @@ public class WizardSeedFileDto
}
/// <summary>
/// Deploy girdisini (MCP <c>deploy_wizard</c>) okur: seed dosyasi bicimi (<c>{"Wizard": ..., "ListForm": ..., "Fields": [...]}</c>)
/// ya da duz wizard cevaplari. <see cref="FromSeedJson"/>'dan farki, bolumlerin eksik ya da
/// kismi olabilmesidir:
/// <list type="bullet">
/// <item>Wizard bolumunde yazilan cevap korunur; yazilmayan ekran alani (veri kaynagi, duzen,
/// 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>
/// Deploy girdisini (MCP <c>deploy_wizard</c>) okur: seed dosyasi bicimi (<c>{"Wizard": {...}}</c>, basliklar
/// toplanmis) ya da duz wizard cevaplari. Dosyadan farki, cevabin kismi olabilmesidir: yazilmayan blok
/// deploy'da varsayilaniyla dolar (<see cref="ListFormWizardDto.ApplyDefaults"/>), yazilan kisayol
/// (<c>AllowAdding</c>, <c>IsRequired</c>) bloktan ustundur. Eski bicimin <c>ListForm</c>/<c>Fields</c> bolumleri
/// kabul edilmez; adlari <see cref="ListFormWizardDto.LegacySections"/>'a yazilir ve deploy reddeder.
/// </summary>
public static ListFormWizardDto FromDeployJson(string json)
{
@ -310,7 +323,6 @@ public class WizardSeedFileDto
if (FindKey(root, "Seed") is { } seedKey && root[seedKey] is JsonObject seed)
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) })
{
if (FindKey(root, section) is { } key && key != section)
@ -323,66 +335,18 @@ public class WizardSeedFileDto
if (root[nameof(Wizard)] is not JsonObject wizardNode)
return null;
FlattenWizard(wizardNode);
var leftovers = FlattenWizard(wizardNode);
var wizard = wizardNode.Deserialize<ListFormWizardDto>(PlatformJsonOptions.CaseInsensitive);
if (wizard == null)
return null;
wizard.UnknownKeys = UnknownKeys(root, wizardNode);
var listForm = root[nameof(ListForm)] as JsonObject;
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,
};
wizard.LegacySections = [.. new[] { nameof(ListForm), nameof(Fields) }.Where(root.ContainsKey)];
wizard.UnknownKeys = [.. UnknownKeys(root, wizardNode), .. leftovers];
wizard.SyncDerivedAnswers(wizardNode);
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>
/// 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.
@ -417,63 +381,18 @@ public class WizardSeedFileDto
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) =>
node.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.OrdinalIgnoreCase)).Key;
/// <summary>`Menu`/`Language` basliklarini duzlestirir ve dosyadaki adlari sozlesme adlarina cevirir.</summary>
private static void FlattenWizard(JsonObject wizardNode)
/// <summary>
/// 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);
FlattenSection(wizardNode, LanguageSection, LanguageKeys);
var leftovers = new List<string>();
foreach (var (section, keys) in Sections)
leftovers.AddRange(FlattenSection(wizardNode, section, keys));
foreach (var (property, seedKey) in RenamedKeys)
Rename(wizardNode, seedKey, property);
@ -483,12 +402,16 @@ public class WizardSeedFileDto
foreach (var (property, seedKey) in CaptionKeys)
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>
private static IEnumerable<JsonObject> ItemNodes(JsonObject wizard) =>
(wizard[nameof(ListFormWizardDto.Groups)] as JsonArray ?? [])
.OfType<JsonObject>()
GroupNodes(wizard)
.SelectMany(g => g[nameof(WizardColumnGroupInputDto.Items)] as JsonArray ?? [])
.OfType<JsonObject>();
@ -529,43 +452,39 @@ public class WizardSeedFileDto
wizard[section] = target;
}
/// <summary>Baslik altindaki alanlari wizard bolumune geri tasir.</summary>
private static void FlattenSection(JsonObject wizard, string section, (string Property, string SeedKey)[] keys)
/// <summary>Baslik altindaki alanlari wizard bolumune geri tasir; taninmayan anahtarlari bildirir.</summary>
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)
return;
var key = FindKey(wizard, section);
if (key == null || !wizard.Remove(key, out var value) || value is not JsonObject group)
return [];
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;
}
return [.. group.Select(x => $"{nameof(Wizard)}.{section}.{x.Key}")];
}
/// <summary>
/// Dosyadan okunan wizard bolumunu ekranin sozlesmesinden tamamlar. Dosyada tekrar edilmeyen
/// alanlar (veri kaynagi, duzen, duzenleme izinleri, alan tanimlari) <see cref="ListForm"/> ve
/// <see cref="Fields"/> uzerinden doldurulur; wizard'i okuyan her kod tam nesneyi gorur.
/// Dosyadan okunan nesneyi tamamlar: eski bicimin ekran bolumleri wizard'a aktarilir ve dusurulur,
/// kisayollar bloklardan turetilir.
/// </summary>
private WizardSeedFileDto Rehydrate()
{
if (Wizard != null && ListForm != null)
{
Wizard.RefreshFrom(ListForm, Fields ?? []);
ListForm = null;
Fields = null;
}
Wizard?.SyncDerivedAnswers();
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>

View file

@ -114,6 +114,13 @@ public class ListFormWizardAppService(
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();
if (string.IsNullOrWhiteSpace(wizardName))
throw new UserFriendlyException(L["Sas.ListForm.WizardError.NameRequired"]);
@ -294,12 +301,6 @@ public class ListFormWizardAppService(
if (input.CreateMenu)
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,
// subform/widget/workflow kayıtlarının hiçbirine ihtiyaç yoktur.
if (!isCustomComponent)
@ -324,11 +325,11 @@ public class ListFormWizardAppService(
// Ekranin sozlesmesi tek fabrikadan uretilir; migrate ekrani bu dosyadan kurar.
var tableColumns = await GetTableColumnsAsync(input.DataSourceCode, input.SelectCommandType, input.SelectCommand);
isDeleted = input.SelectCommandType == SelectCommandTypeEnum.Query
var isDeleted = input.SelectCommandType == SelectCommandTypeEnum.Query
? await IsQueryMainTableSoftDeletedAsync(input)
: tableColumns.ContainsKey("IsDeleted");
isCreated = tableColumns.ContainsKey("CreatorId");
isModified = tableColumns.ContainsKey("LastModifierId");
var isCreated = tableColumns.ContainsKey("CreatorId");
var isModified = tableColumns.ContainsKey("LastModifierId");
// Yeni kaydin form degeri, cevapta yazilmadiysa kolonun sabit DEFAULT'udur (IsActive DEFAULT 1 →
// 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))
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
// duzenlemede varsayilan tek dosyaya geri donmez.
if (input.SeedData && !string.IsNullOrWhiteSpace(previous?.ListForm?.SeedFilePath))
listFormDto.SeedFilePath = previous.ListForm.SeedFilePath;
if (input.SeedData && string.IsNullOrWhiteSpace(input.SeedFilePath) && !string.IsNullOrWhiteSpace(previous?.Wizard?.SeedFilePath))
input.SeedFilePath = previous.Wizard.SeedFilePath;
if (input.ScreenOverlay != null)
(listFormDto, fieldDtos) = await ApplyScreenOverlayAsync(input, listFormDto, fieldDtos, isDeleted, isCreated, isModified, rows);
// Cevapta bos birakilan her blok burada varsayilaniyla dolar; dosyaya tam sozlesme yazilir.
input.ApplyDefaults(isDeleted, isCreated, isModified);
var listFormDto = input.BuildListForm();
var fieldDtos = input.BuildFields();
ValidateImageColumnLengths(input, tableColumns);
await LocalizeStaticLookupsAsync(input, fieldDtos, rows, owned, inserted);
@ -362,6 +363,10 @@ public class ListFormWizardAppService(
foreach (var field in fieldDtos)
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))
await WriteLanguageRowsAsync(rows, item.CaptionName, item.EnglishCaption, item.TurkishCaption, owned, inserted);
}
@ -370,7 +375,7 @@ public class ListFormWizardAppService(
await rows.SaveAsync();
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
// 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 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>
/// 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
@ -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;
/// ekrani WizardDataSeeder bu dosyadan kurar.
/// <para>
/// Dosya yalnizca wizard'in cevaplarini degil, urettigi ekranin ListForm sozlesmesini de tasir;
/// tasarimci uzerinde yapilan degisiklikler ayni bolumlere geri yazildigi icin dosya her zaman
/// ekranin guncel halidir. Deploy'un tek ciktisi dosyalar oldugu icin yazma hatasi yutulmaz.
/// Cevaplar ekranin tam sozlesmesidir (<see cref="ListFormWizardDto.RefreshFrom"/> ile uretilen ekrandan
/// doldurulmus); tasarimci uzerinde yapilan degisiklikler ayni cevaplara geri yazildigi icin dosya her
/// zaman ekranin guncel halidir. Deploy'un tek ciktisi dosyalar oldugu icin yazma hatasi yutulmaz.
/// </para>
/// </summary>
private async Task SaveWizardSeedFileAsync(
ListFormWizardDto input,
GridOptionsEditDto? listForm,
List<ColumnFormatEditDto> fields,
bool isDeletedField,
bool isCreatedField,
WizardInsertedRecordsDto inserted,
WizardPendingDto pending,
WizardDevRequestDto? devRequest)
@ -992,10 +945,6 @@ public class ListFormWizardAppService(
var seedData = new WizardSeedFileDto
{
Wizard = input,
ListForm = listForm!,
Fields = fields ?? [],
IsDeletedField = isDeletedField,
IsCreatedField = isCreatedField,
InsertedRecords = inserted,
Pending = pending,
DevRequest = devRequest!,
@ -1054,6 +1003,13 @@ public class ListFormWizardAppService(
/// Izin gruplarini iki dilli gorunen adlariyla dondurur. Gorunen ad, grup adiyla ayni olan
/// dil anahtarinin metnidir; wizard formu secim yapildiginda bu degerleri doldurur.
/// </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()
{
var res = PlatformConsts.AppName;
@ -1262,7 +1218,7 @@ public class ListFormWizardAppService(
SubFormDto[] subForms;
try
{
subForms = seed.ListForm?.SubFormsDto ?? [.. seed.Wizard.SubForms ?? []];
subForms = [.. seed.Wizard.SubForms ?? []];
}
catch (JsonException)
{
@ -1628,7 +1584,7 @@ public class ListFormWizardAppService(
if (seed.Wizard.ComponentKind != WizardComponentKindEnum.Custom &&
!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

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -111,7 +111,8 @@ public static class ModuleDocRenderer
private static void AppendWizard(StringBuilder sb, string fileName, WizardSeedFileDto seed)
{
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 title = FirstText(wizard.MenuTr, wizard.TitleTr, wizard.MenuEn, code);
@ -203,7 +204,8 @@ public static class ModuleDocRenderer
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)

View file

@ -137,9 +137,8 @@ public sealed class WizardExportCollector(string scopeRoot, IReadOnlyDictionary<
/// </summary>
private void CollectList(WizardSeedFileDto seed, Queue<(string FilePath, string Reason)> pending)
{
var listForm = seed.ListForm;
var commandType = listForm?.SelectCommandType ?? seed.Wizard.SelectCommandType;
var selectCommand = listForm?.SelectCommand ?? seed.Wizard.SelectCommand;
var commandType = seed.Wizard.SelectCommandType;
var selectCommand = seed.Wizard.SelectCommand;
var screen = seed.Wizard.WizardName;
// 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.
var seedFilePath = listForm?.SeedFilePath;
var seedFilePath = seed.Wizard.SeedFilePath;
if (!string.IsNullOrWhiteSpace(seedFilePath) && !AddSeedData(seedFilePath, localizer["Sas.ListForm.WizardExport.ReasonData", screen]))
warnings.Add(localizer["Sas.ListForm.WizardExport.SeedFileNotFound", seedFilePath]);
foreach (var field in seed.Fields ?? [])
foreach (var field in seed.Wizard.BuildFields())
{
LookupDto? lookup;
try
@ -197,18 +196,7 @@ public sealed class WizardExportCollector(string scopeRoot, IReadOnlyDictionary<
}
}
private SubFormDto[] ReadSubForms(WizardSeedFileDto seed)
{
try
{
return seed.ListForm?.SubFormsDto ?? [];
}
catch (JsonException)
{
warnings.Add(localizer["Sas.ListForm.WizardExport.SubFormsUnresolved", seed.Wizard.WizardName]);
return [];
}
}
private static SubFormDto[] ReadSubForms(WizardSeedFileDto seed) => [.. seed.Wizard.SubForms ?? []];
// ── Custom yolu ────────────────────────────────────────────────────────────────

View file

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

View file

@ -21,8 +21,8 @@ namespace Sozsoft.Platform.ListForms;
public interface IWizardSeedSynchronizer
{
/// <summary>
/// Verilen ListForm koduna ait bir wizard seed dosyasi varsa dosyanin ekran ve alan
/// bolumlerini veritabanindaki guncel haliyle degistirir. Dosya yoksa hicbir sey yapmaz.
/// Verilen ListForm koduna ait bir wizard seed dosyasi varsa dosyanin cevaplarini veritabanindaki
/// guncel ekranla esitler. Dosya yoksa hicbir sey yapmaz.
/// </summary>
Task SyncAsync(string listFormCode);
}
@ -68,14 +68,11 @@ public class WizardSeedSynchronizer(
var fields = await repoListFormField.GetListAsync(a => a.ListFormCode == listFormCode);
seed.ListForm = objectMapper.Map<ListForm, GridOptionsEditDto>(listForm);
seed.Fields = [.. 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);
// Dosya yalnizca cevaplari tasir; ekranin guncel hali cevaplara geri yazilir. Aksi halde ayni
// wizard yeniden deploy edildiginde ya da migrate'te tasarimcidaki degisiklikler kaybolurdu.
seed.Wizard.RefreshFrom(
objectMapper.Map<ListForm, GridOptionsEditDto>(listForm),
[.. fields.OrderBy(f => f.ListOrderNo).Select(objectMapper.Map<ListFormField, ColumnFormatEditDto>)]);
await File.WriteAllTextAsync(filePath, seed.ToSeedJson());
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
/// veritabani olan her tenant'ta ayni dosyalar kullanilir (<see cref="SeedFileScope"/>).
/// <para>
/// Deploy veritabanina yazmaz; bu seeder wizard'in kurulumunun tek yeridir. Dosya yalnizca ekrana
/// ozgu kayitlari tasir: veri kaynagi, ListForm, alanlar ve is akisi. Yetkiler hicbir role atanmaz;
/// Deploy veritabanina yazmaz; bu seeder wizard'in kurulumunun tek yeridir. Ekran dosyadaki wizard
/// cevaplarindan kurulur (<see cref="ListFormWizardDto.BuildListForm"/>): veri kaynagi, ListForm, alanlar
/// ve is akisi. Yetkiler hicbir role atanmaz;
/// hangi rolun ekrani gorecegi rol ekranindan secilir.
/// 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.
@ -111,9 +112,9 @@ public class WizardDataSeeder(
}
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;
}
@ -153,7 +154,7 @@ public class WizardDataSeeder(
/// <summary>
/// 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>
private async Task InstallAsync(WizardSeedFileDto seedFile)
{
@ -179,18 +180,18 @@ public class WizardDataSeeder(
await DeleteListFormAsync(listFormCode);
// Ekran, seed dosyasinin tasidigi ListForm sozlesmesinden kurulur; boylece wizard
// sonrasinda tasarimci uzerinde yapilan degisiklikler de geri gelir.
// Elle ya da eski bir surumle yazilmis dosyanin is akisi de kaydedilebilir hale getirilir.
seedFile.ListForm.WorkflowDto = ListFormWizardDto.NormalizeWorkflow(seedFile.ListForm.WorkflowDto, listFormCode);
// Ekran, dosyanin tasidigi cevaplardan kurulur: cevaplar deploy'da uretilen ekranla esitlendigi
// ve tasarimci degisiklikleri ayni cevaplara yazildigi icin tasarimcidaki is de geri gelir.
// Is akisi kurulumdan once kaydedilebilir hale getirilir (BuildListForm icinde normalize edilir).
var listFormDto = input.BuildListForm();
var listForm = new ListForm();
seedFile.ListForm.ApplyTo(listForm);
listFormDto.ApplyTo(listForm);
await repoListForm.InsertAsync(listForm, autoSave: true);
// Alanlar ve is akisi tek SaveChanges ile yazilir; satir basina kayit, izlenen varlik sayisi
// 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();
fieldDto.ApplyTo(field);
@ -201,7 +202,7 @@ public class WizardDataSeeder(
await repoListFormField.InsertManyAsync(fields, autoSave: true);
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))
{

View file

@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Collections.Generic;
using System.Text.Json;
using Sozsoft.Platform.Enums;
using static Sozsoft.Platform.PlatformConsts;
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 int EditFormMaxWidth => 1200;
@ -35,15 +36,6 @@ public static class WizardConsts
public static string MenuUrl(string code) => $"/admin/list/{code}";
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
{
Grid = Grid,
@ -58,82 +50,6 @@ public static class WizardConsts
});
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>
/// 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";
}
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>

View file

@ -52056,6 +52056,132 @@
"Key": "Sas.WizardStep3.DragColumnsHere",
"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",
"CultureName": "en",
@ -94308,20 +94434,6 @@
"Key": "Sas.ListForm.WizardError.DataSourceNotFound",
"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",
"CultureName": "en",
@ -96203,14 +96315,14 @@
"CultureName": "en",
"ResourceName": "Platform",
"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",
"CultureName": "tr",
"ResourceName": "Platform",
"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",
@ -96478,20 +96590,6 @@
"Key": "Sas.ListForm.WizardImport.ObjectMissingRepairHint",
"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",
"CultureName": "en",
@ -97589,14 +97687,28 @@
"CultureName": "en",
"ResourceName": "Platform",
"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",
"CultureName": "tr",
"ResourceName": "Platform",
"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",
@ -97724,48 +97836,6 @@
"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."
},
{
"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",
"CultureName": "en",
@ -97836,20 +97906,6 @@
"Key": "Sas.ListForm.WizardExport.SubFormFileNotFound",
"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",
"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