From a0f359df670b86f15a3a3fa6db1a00fe0d1f7577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:52:06 +0300 Subject: [PATCH] =?UTF-8?q?Wizard=20Mcp=20kurallar=C4=B1=20de=C4=9Fi=C5=9F?= =?UTF-8?q?tirildi.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lowcode-reference.instructions.md | 2 +- .github/instructions/lowcode.instructions.md | 56 ++++++----- README.md | 4 +- .../Dtos/McpDevelopDtos.cs | 6 ++ .../McpDevelopAppService.cs | 99 ++++++++++++++++++- .../McpErrorCodes.cs | 3 + .../McpServerInfoDefaults.cs | 1 + .../Tools/McpDevelopPrompts.cs | 9 +- .../Tools/McpDevelopTools.cs | 12 ++- .../ListForms/ListFormSeedDataSynchronizer.cs | 7 +- .../ListForms/ListFormWizardAppService.cs | 12 ++- .../ListForms/WizardRecordCleaner.cs | 2 +- .../ListForms/WizardSeedRowStore.cs | 1 - .../Migrations/ListFormSeeder_Saas.cs | 13 +++ .../Data/ListFormSeedDataFile.cs | 15 ++- claude.md | 12 ++- .../host/data/App.Languages.LanguageText.json | 42 ++++++++ ui/src/views/list/TodoBoard.tsx | 34 +++++++ 18 files changed, 272 insertions(+), 58 deletions(-) diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md index 5f081dde..df158788 100644 --- a/.github/instructions/lowcode-reference.instructions.md +++ b/.github/instructions/lowcode-reference.instructions.md @@ -1249,7 +1249,7 @@ soneklerini alır. | **Public › Sales Orders** | `App.Orders.SalesOrders` (alt form Sales Order Item) | `Sas_H_Order` + `Sas_H_OrderItem` | host | **Onay akışı + alt kalem + toplam + diyalog** birlikte kullanılan tam örnek (§12.6). | | **Public › Blog Category / Posts** | `App.BlogManagement.Category` · `.Posts` | `Sas_H_BlogCategory` · `Sas_H_BlogPost` | host | Çok dilli içerik (`ContentTr`/`ContentEn`), slug, yayın durumu, sayaç alanları; public `/blog` bu tablolardan okur, yayınlanmış yazılar sitemap'e girer. | | **Public › Demos** | `App.Demos` | `Sas_H_Demo` | host | Public `/demo` formundan gelen talepler — public form → tablo akışının örneği. | -| **Dev Requests** | `App.DevRequests` | `Sas_H_DevRequest` | host | **Kanban/Todo düzeninin** referansı (`DefaultLayout: todo`): `Status`, `Priority`, `SortOrder`, `Tags`, `Assignees`, `SubTasks`, `Timesheets`. MCP geliştirme döngüsünün kuyruğudur (`Ready to Start` → `Staging`; yalnızca `start_dev_request` başlatır; açıklamadaki base64 görseller MCP'ye resim içeriği olarak gider, not `append_dev_request_note` ile eklenir; `lowcode.instructions.md` §0.11). | +| **Dev Requests** | `App.DevRequests` | `Sas_H_DevRequest` | host | **Kanban/Todo düzeninin** referansı (`DefaultLayout: todo`): `Status`, `Priority`, `SortOrder`, `Tags`, `Assignees`, `SubTasks`, `Timesheets`. MCP geliştirme döngüsünün kuyruğudur (`Ready to Start` → `Testing`, `Staging`'e kullanıcı alır; araç çubuğunda DB Migrate butonu vardır — `UiEvalService.ApiDbMigrate()`, `App.Setup.Migrate`; yalnızca `start_dev_request` başlatır; açıklamadaki base64 görseller MCP'ye resim içeriği olarak gider, not `append_dev_request_note` ile eklenir; `lowcode.instructions.md` §0.11). | | **Tenants** | `AbpTenantManagement.Tenants` | `AbpTenants` | host | Kiracı kaydı, bağlantı dizesi diyaloğu (`TenantsConnectionString`), alt form olarak şubeler; yazma `list-form-dynamic-api/tenant-*`. Ekranların `IsTenant` davranışının kaynağı. | | **Branches** | `App.Branches` | `Sas_T_Branch` | tenant | Şube listesi. Ekranı `IsBranch = true` yaparsan filtre bu tablodan gelir; `BranchSeed` diyaloğu şube verisini kurar. | | **Setting Definitions** | `App.SettingDefinitions` | `Sas_H_SettingDefinition` | host | Yeni uygulama ayarı: `Code`, `MainGroupKey`/`SubGroupKey`, `DataType`, `SelectOptions`, `DefaultValue`, `Providers`, `RequiredPermissionName`, `IsEncrypted`, `IsVisibleToClients`. Hazır tanımlar `data/App.SettingDefinitions.json`'dan gelir. Ayarlanabilir olması gereken değeri koda gömme. | diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index d599201b..f80e99c8 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -111,7 +111,7 @@ Uydurma. Şu üçünü mutlaka kontrol et: | JSON alan adları | **PascalCase** (`Wizard`, `ListFormCode`, `Groups`) — seeder `PropertyNameCaseInsensitive` okur ama dosyalar PascalCase üretilir | | Custom component dosyası | Kök alanlar PascalCase; `Props` **içindeki** designer dokümanı camelCase (`visualDesigner`, `nodes`, `sourceMode`) | | Enum'lar | Sayı olarak yazılır (§3) | -| `GeneratedAt` | ISO-8601 UTC (`2026-08-26T10:15:00Z`) — yalnızca bilgi amaçlı. **Yalnızca `crud/` ve `data/` dosyalarında vardır**; wizard seed dosyasında karşılığı `Wizard.SeededAt` alanıdır | +| `GeneratedAt` | ISO-8601 UTC (`2026-08-26T10:15:00Z`). **Yalnızca `crud/` ve `data/` dosyalarında vardır**; wizard seed dosyasında karşılığı `Wizard.SeededAt` alanıdır. `data/`: dosyanın oluşturulma anı, yalnızca bilgi amaçlı — ilk yazımda dolar, satır ekleme/silme onu değiştirmez (ekleyip geri kaldıran işlem git'te iz bırakmaz). `crud/`: üretim damgası, migrate kararında kullanılır (§9.1) | | İç içe JSON | `EditorOptions`, `EditorScript`, `LookupQuery` → JSON **string** olarak kaçışlanır | | Satır sonu | LF | @@ -503,13 +503,13 @@ ister ve kiracı kullanıcısına hiçbiri açılmaz. | `get_dev_guide` | Bu dosyanın (`lowcode`) ya da `reference`'ın başlık listesini / bir bölümünü döner | Bölüm numarayla istenir (`4.3`); aynı numarayı iki başlık taşımamalıdır | | `create_table` | Yeni SQL Server tablosu kurar, `sql/object/{Tablo}.sql` dosyasını yazar | Ek olarak `App.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz | | `list_wizards` · `get_wizard` | Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner | Var olan menü kökünü/izin grubunu bulmak için önce `list_wizards` | -| `deploy_wizard` | Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) | Girdi §4.4'teki düz cevap ya da `get_wizard` çıktısıyla aynı dosya biçimi; enum'lar sayı. `replaceFileName` = `EditFileName` (§4.5). İstek demo veri taşıyorsa `SeedData: true` (§0.10.1). Çıkıştaki `isPending: true` ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır | +| `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). İstek demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1). Çıkıştaki `isPending: true` ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır; model migrate'i beklemez | | `delete_wizard` | Wizard'ı ve yalnızca onun ürettiği kayıtları siler; `dropTables: true` ekranın tablosunu da düşürür | Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; `App.SqlQueryManager` ister (§4.5) | | `start_dev_request` | `App.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `App.DevRequests.Update` ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11) | | `get_dev_request` | Talebi durumuna dokunmadan okur (aynı metin + resim içerikleri) | Yarım kalan talebe dönmek için; `App.DevRequests` ister | | `append_dev_request_note` | Talep açıklamasının sonuna HTML ekler | Sonuç notu için; `Description`'ı `update_row` ile yazma (§0.11) | | `add_dev_request_subtasks` | `SubTasks` listesine tamamlanmış satırlar ekler | Atanan = bağlanan kullanıcı, bitiş = bugün (sunucu yazar); `SubTasks`'ı `update_row` ile yazma (§0.11) | -| `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | `passed` yalnızca engelleyici bir kontrol kalırsa `false`; `seed_closure` engellemez ama rapora yazılır (§0.11) | +| `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. `passed` yalnızca engelleyici bir kontrol kalırsa `false` | | `list_components` · `get_component` · `save_component` · `delete_component` | Custom component okuma/yazma (ada göre upsert) | `save_component` visual designer bileşenini reddeder — yalnız Code modu (§7.1.1) | **Yetkiler.** MCP'nin iki yetkisi vardır: `.Invoke` (bağlantı + okuma) ve `.Write` (her @@ -524,25 +524,24 @@ araçla yapmaya çalışma, eksik yetkiyi kullanıcıya olduğu gibi bildir ve d İstek örnek/demo satır taşıyorsa (prompt'taki tablo ya da liste, ekran görüntüsündeki satırlar, eklenen dosya) bu satırlar ekranla birlikte seed edilir; migrate çalıştığında liste boş gelmez. -1. `deploy_wizard` cevabına `SeedData: true` yaz. Ekranın `SeedFilePath`'i - `data/{ListFormCode}.json` olur, `SeedSyncInsert/Update/Delete` açılır (§9.15). -2. Deploy ekranı kurmaz; kullanıcıdan Wizard Manager'da **DB Migrate** çalıştırmasını iste. - `list_wizards`'ta dosyanın `isPending` değeri `false` olunca (ya da `verify_wizard` geçince) - her satırı `insert_row` ile ekle. Satır ekranın insert sözleşmesinden geçer - ve `ListFormSeedDataSynchronizer` onu `configs/seeds/host/data/{ListFormCode}.json` dosyasına - yazar (kiracı bağlamında `tenants/{TenantId}/data/`). -3. Migrate sırasında wizard seeder ekranı kurar, ardından `ListFormSeedDataApplier` dosyadaki - satırları ekler (anahtarı olmayanı ekler, var olana dokunmaz). +1. `deploy_wizard` cevabına `SeedData: true` yaz, satırların hepsini aynı çağrıda `seedRows` ile + gönder (alan adından değere nesne dizisi). Ekranın `SeedFilePath`'i `data/{ListFormCode}.json` + olur, `SeedSyncInsert/Update/Delete` açılır (§9.15); satırlar deploy anında bu dosyaya yazılır. + `Guid` anahtarı sunucu üretir; `TenantId` ve audit alanlarını migrate doldurur — yazma. Aynı + içerikli satır dosyada varsa (tekrar deploy) yeniden eklenmez. +2. DB Migrate'i bekleme. Migrate wizard seeder ile ekranı kurar, ardından `ListFormSeedDataApplier` + dosyadaki satırları ekler (anahtarı olmayanı ekler, var olana dokunmaz). +3. Migrate'ten sonra ekrandan girilen satırlar `ListFormSeedDataSynchronizer` ile aynı dosyaya yazılır + (kiracı bağlamında `tenants/{TenantId}/data/`). Kurallar: -- Dosyayı elle yazma; satır ekranın sorgusundan okunduğu için anahtar, `TenantId` ve audit - alanları doğru biçimde gelir. +- Dosyayı elle yazma; `seedRows` alan adlarını ekranın alanlarına göre doğrular, bilinmeyen alanı reddeder. - Görsel yalnızca ekranın şablonu değildir: içinde görünen **her satır** okunur ve demo veri olarak eklenir (standing default 6'daki "piksel sözleşmesi değildir" kuralı düzen içindir, veri için değil). - Görselden okunamayan değer uydurulmaz: zorunlu alan boşsa kullanıcıya sor. - Lookup alanının değeri kaynak tablodaki anahtardır; kaynakta yoksa önce o ekrana satır eklenir (o ekranın `SeedFilePath`'i yoksa satır yalnızca bu veritabanında kalır — raporda belirt). -- İstek demo veri taşımıyorsa `SeedData` yazılmaz; `verify_wizard` testi için satır eklenmez. +- İstek demo veri taşımıyorsa `SeedData` ve `seedRows` yazılmaz; test için satır eklenmez. Okuma araçları (`list_lists`, `describe_list`, `search_schema`, `query_list`, `run_select`, `list_queries`, `run_query`) keşif turunda (§0.3) tablo/kolon/lookup kaynağını bulmak içindir; @@ -582,18 +581,17 @@ talebin metnini döndürür. Başlık birden fazla talebe uyuyorsa Id ister. `St | --- | --- | --- | | 1. Kuyruğu oku (yalnızca toplu) | `query_list` — `ListFormCode: App.DevRequests`, filtre `["Status","=","Ready to Start"]`, sıralama `SortOrder` | — | | 2. Talebi üstlen | `start_dev_request` — Id ya da tam başlık. Yarım kalmış talebe dönüşte `get_dev_request` | In Progress | -| 3. Üret | §0.3 keşif → `create_table` / `deploy_wizard` / `save_component`; talep demo veri taşıyorsa `SeedData: true` + `insert_row` (§0.10.1); bu dosyanın kuralları aynen geçerlidir | — | -| 4. Test et | `update_row` → `Testing`, ardından üretilen her wizard için `verify_wizard` | Testing | -| 5a. Geçti | `update_row` → `Staging` + sonuç kaydı | Staging | -| 5b. Kaldı | `update_row` → `In Progress` + sonuç kaydı; düzelt ve 4'e dön ya da bırak | In Progress | +| 3. Üret | §0.3 keşif (depodan/araçlardan; kullanıcıya soru yok) → `create_table` / `deploy_wizard` / `save_component`; talep demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1); bu dosyanın kuralları aynen geçerlidir | — | +| 4. Teslim | Sonuç kaydı + `update_row` → `Testing`. DB Migrate beklenmez; talep burada biter | Testing | +| 5. Test | Kullanıcı DB Migrate çalıştırır, ekranı elle test eder ve talebi kendisi `Staging`'e alır (geri dönüşte `In Progress`). MCP bu adımı yapmaz | Staging | **Sonuç kaydı** şema değiştirmeden mevcut alanlara yazılır: - `SubTasks` — her üretilen artefakt bir satır, `add_dev_request_subtasks` ile yalnızca başlık verilerek (`"deploy_wizard: Mrp.Orders.json"`). Araç satırı tamamlanmış yazar, atanan kişiyi bağlanan kullanıcı, bitiş tarihini bugün yapar ve var olan satırları korur. `SubTasks` `update_row` ile yazılmaz. -- `Description` — `append_dev_request_note` ile sonuna `

MCP

` başlığıyla `verify_wizard` - özeti: geçen/kalan kontroller, `seed_closure` uyarıları. `Description` hiçbir zaman `update_row` +- `Description` — `append_dev_request_note` ile sonuna `

MCP

` başlığıyla kısa özet: + üretilenler ve DB Migrate bekleyen wizard'lar. `Description` hiçbir zaman `update_row` ile yazılmaz — base64 görselleri taşır ve yeniden yazmak onları siler. **Görseller.** Talebe yapıştırılan ekran görüntüleri açıklamada base64 durur; araç onları metinden @@ -602,18 +600,19 @@ görseli yeniden istemezsin. Görsel talebin parçasıdır: ekran düzenini (§0 değildir) ve içindeki satırları taşır; satırlar demo veridir (§0.10.1). **Kurallar** +- Talep tarifin kendisidir: kullanıcıya soru sorulmaz, belirsiz noktada §0.6'nın önerilen + varsayılanı seçilir ve sonuç notuna yazılır. Döngü DB Migrate'i beklemez. - Talep §0.6 keşif turunu cevaplayacak kadar net değilse üretme; `Backlog`'a geri al ve eksik soruları `Description` sonuna yaz. Tabloya yazma hedefi, onaycı ve hesap formülü talepten okunamıyorsa varsayılmaz. - Çerçeve dışı bir ihtiyaç (§0.8) çıkarsa talebi `In Progress`'te bırak ve kullanıcıya sor. -- `verify_wizard` veri yazmaz; test için ekrana satır ekleme — SeedSync açık ekranda seed dosyasına iz bırakır. Talebin kendi demo verisi test satırı değildir, §0.10.1 ile eklenir. -- `Staging` son durumdur: ekran bu ortamda o anda kullanılabilir, çünkü deploy canlı veritabanına yazar. +- Test için ekrana satır ekleme — SeedSync açık ekranda seed dosyasına iz bırakır. Talebin kendi demo verisi test satırı değildir, §0.10.1 ile eklenir. +- MCP talebi `Staging`'e almaz: deploy yalnızca dosya yazar, ekran migrate'ten sonra vardır ve testi kullanıcı yapar. **Migrate** döngünün parçası değildir ve MCP'den tetiklenmez. Parti bitince yetkili host -kullanıcısı `/api/setup/migrate` akışını elle çalıştırır (`Setup.Migrate`; Redis'i temizler ve -bütün kiracıları dolaşır). Gerekli olduğu durumlar: ayrı veritabanı kullanan kiracılara -tablo/script taşımak, seed dosyalarından başka bir ortam ya da sıfırlanmış veritabanı kurmak. -Parti raporunda `seed_closure` uyarısı veren ekranlar ayrıca belirtilir; migrate onları kuramaz. +kullanıcısı Wizard Manager'daki DB Migrate'i (`/api/setup/migrate`, `Setup.Migrate`) çalıştırır; +migrate ekranları, menü/yetki/dil satırlarını ve demo verisini kurar, Redis'i temizler ve bütün +kiracıları dolaşır. Rapor bu adımı tek satırla belirtir. ## 1. Hangi istek → hangi artefakt @@ -1839,6 +1838,9 @@ Kurallar: olmayan bir ad da yazılabilir. Form ekranının araç çubuğu da aynı ikon adlarını kullanır. - Toolbar butonu **seçili satırlarla** çalışır; `SelectionJson` uygun modda (`single`/`multiple`) olmalıdır. İş akışı varsa wizard bunu zaten `single` yapar. +- Toolbar butonu Grid, Card ve Tree'nin yanında **Todo panosunda** da çizilir; pano satır seçimi + taşımadığı için orada yalnızca satırdan bağımsız `Url` ve `OnClick` butonları görünür + (`DialogName` görünmez). - Satır bazlı aksiyon istiyorsan `ButtonPosition: 0` kullan; buton komut sütununda görünür. - **`IsVisible` ve `VisibleExpression` yalnızca satır butonlarında (`ButtonPosition: 0`) çalışır**; toolbar butonu her zaman görünür (görünürlüğü `AuthName` belirler). diff --git a/README.md b/README.md index 4a7efdad..23a32e6a 100644 --- a/README.md +++ b/README.md @@ -1406,9 +1406,9 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır. | `get_dev_guide` | Üretim sözleşmesi: `lowcode` / `reference` belgelerinin başlık listesi ya da istenen bölümü (numarayla, örn. `4.2`). Çok büyük bir üst bölüm yalnızca giriş metni + alt başlıklarıyla döner. | | `create_table` | Yeni tabloyu SQL Query Manager tablo tasarımcısıyla aynı script biçimiyle kurar (`McpTableCreator`): `Id` + `TenantId` + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. Script `{kapsam}/sql/object/{Tablo}.sql` dosyasına yazılır. Yalnızca oluşturur — tablo varsa reddeder; yalnızca SQL Server. `.Write` + `App.SqlQueryManager` 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": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (`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 ve `SeedSync*` bayrakları açılır — migrate sonrasında satırlar `insert_row` ile eklenir; `ListFormSeedDataSynchronizer` onları `configs/seeds/host/data/` altına yazar, migrate ekranla birlikte geri yükler (`lowcode.instructions.md` §0.10.1). | +| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. 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 ve `SeedSync*` bayrakları açılır — 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). | | `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). `App.DevRequests` geliştirme döngüsünün test adımıdır (`lowcode.instructions.md` §0.11). | +| `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `App.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. `.Write` + `App.DevRequests.Update` ister. | | `get_dev_request` | Talebi durumuna dokunmadan, `start_dev_request` ile aynı biçimde (metin + resim içerikleri) okur; yarım kalan talebe dönmek için. `App.DevRequests` ister. | | `add_dev_request_subtasks` | Talebin `SubTasks` listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (`userName`), bitiş tarihi bugün (`yyyy-MM-dd`) olarak sunucuda yazılır; var olan satırlar korunur. `.Write` + `App.DevRequests.Update` ister. | diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs index 8f18f6fe..a38b181d 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Dtos/McpDevelopDtos.cs @@ -50,6 +50,12 @@ public class McpWizardDeployRequestDto /// Doluysa guncellemedir: bu dosya ve urettigi kayitlar silinip yeniden uretilir. public string? ReplaceFileName { get; set; } + + /// + /// Demo satirlar: alan adindan degere JSON nesne dizisi. Ekranin veri dosyasina + /// (data/{ListFormCode}.json) deploy aninda yazilir, migrate ekranla birlikte ekler. + /// + public string? SeedRows { get; set; } } /// diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs index 225b921a..2e8020b5 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs @@ -1,14 +1,18 @@ using System; using System.Collections.Generic; +using System.Data; using System.Globalization; +using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; using Sozsoft.Mcp.Dtos; using Sozsoft.Mcp.Services; using Sozsoft.Platform; +using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.DeveloperKit; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; @@ -37,7 +41,8 @@ public class McpDevelopAppService( IListFormSelectAppService listFormSelectAppService, IRepository menuRepository, IRepository permissionRepository, - IRepository devRequestRepository) + IRepository devRequestRepository, + IConfiguration configuration) : McpAppService(serverConfigProvider), IMcpDevelopAppService { private const string DefaultGuideDocument = "lowcode"; @@ -160,13 +165,103 @@ public class McpDevelopAppService( var wizard = ParseWizard(input.Wizard); wizard.EditFileName = string.IsNullOrWhiteSpace(input.ReplaceFileName) ? null : input.ReplaceFileName.Trim(); + // Satirlar deploy'dan once dogrulanir; gecersiz girdi yarim bir deploy birakmaz. + var seedRows = ParseSeedRows(input.SeedRows, wizard); + await wizardAppService.Create(wizard); var files = await wizardAppService.GetFiles(); + var file = files.FirstOrDefault(file => string.Equals(file.WizardName, wizard.WizardName?.Trim(), StringComparison.OrdinalIgnoreCase)); - return files.FirstOrDefault(file => string.Equals(file.WizardName, wizard.WizardName?.Trim(), StringComparison.OrdinalIgnoreCase)); + if (file != null && seedRows.Count > 0) + await WriteSeedRowsAsync(file.FileName, seedRows); + + return file; } + /// + /// Demo satirlarini okur ve ekranin alanlarina gore dogrular. Satirlar yalnizca SeedData acik + /// liste ekraninda kabul edilir: migrate onlari ekranin veri dosyasindan ekler. + /// + private List> ParseSeedRows(string? json, ListFormWizardDto wizard) + { + if (string.IsNullOrWhiteSpace(json)) + return []; + + if (wizard.ComponentKind == WizardComponentKindEnum.Custom || !wizard.SeedData) + throw new UserFriendlyException(L[McpErrorCodes.SeedRowsNotAllowed]); + + List>? rows; + try + { + rows = JsonSerializer.Deserialize>>(json); + } + catch (JsonException) + { + rows = null; + } + + if (rows is null || rows.Any(row => row is null)) + throw new UserFriendlyException(L[McpErrorCodes.SeedRowsInvalid]); + + var fields = wizard.Groups.SelectMany(group => group.Items).Select(item => item.FieldName) + .Append(wizard.KeyFieldName) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + var unknown = rows.SelectMany(row => row.Keys).Where(key => !fields.Contains(key)) + .Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + if (unknown.Count > 0) + throw new UserFriendlyException(L[McpErrorCodes.SeedRowsUnknownFields, string.Join(", ", unknown), string.Join(", ", fields)]); + + return rows; + } + + /// + /// Demo satirlarini ekranin veri dosyasina (ListForm.SeedFilePath) yazar; veritabanina + /// dokunmaz. Migrate ekrani kurduktan sonra ListFormSeedDataApplier satirlari ekler ve + /// kiraci/audit alanlarini doldurur. Guid anahtar burada uretilir: dosya her uygulandiginda ayni + /// kayda karsilik gelir. Dosyada ayni icerikli satir varsa (tekrar deploy) yeniden eklenmez. + /// + private async Task WriteSeedRowsAsync(string fileName, List> rows) + { + var listForm = (await wizardAppService.GetFile(fileName)).ListForm; + var relativePath = listForm?.SeedFilePath?.Replace('\\', '/').Trim('/'); + if (listForm == null || string.IsNullOrWhiteSpace(relativePath) || SeedPathResolver.IsSeedFolderPath(relativePath)) + throw new UserFriendlyException(L[McpErrorCodes.SeedRowsNotAllowed]); + + var path = SeedPathResolver.GetScopePath(configuration, CurrentTenant.Id, relativePath.Split('/')); + var seedFile = File.Exists(path) + ? JsonSerializer.Deserialize(await File.ReadAllTextAsync(path), ListFormSeedDataFileDto.JsonOptions) + : null; + seedFile ??= new ListFormSeedDataFileDto { ListFormCode = listForm.ListFormCode, KeyFieldName = listForm.KeyFieldName }; + + var keyFieldName = seedFile.KeyFieldName; + var existing = seedFile.Rows.Select(row => RowContent(row, keyFieldName)).ToHashSet(StringComparer.Ordinal); + + foreach (var row in rows) + { + var values = row.ToDictionary(item => item.Key, item => (object)item.Value); + if (!existing.Add(RowContent(values, keyFieldName))) + continue; + + if (listForm.KeyFieldDbSourceType == DbType.Guid && !values.Keys.Contains(keyFieldName, StringComparer.OrdinalIgnoreCase)) + values[keyFieldName] = GuidGenerator.Create(); + + seedFile.Rows.Add(values); + } + + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + await File.WriteAllTextAsync(path, ListFormSeedDataFileDto.Serialize(seedFile)); + } + + /// Satirin anahtar disindaki icerigi; tekrar deploy'da ayni satiri tanimak icin. + private static string RowContent(Dictionary row, string keyFieldName) => + string.Join('\u001f', row + .Where(item => !string.Equals(item.Key, keyFieldName, StringComparison.OrdinalIgnoreCase)) + .OrderBy(item => item.Key, StringComparer.OrdinalIgnoreCase) + .Select(item => $"{item.Key.ToLowerInvariant()}={JsonSerializer.Serialize(item.Value)}")); + /// /// Ekranin tablosu ancak acikken ve Wizard Manager'daki onayla ayni /// kosullarda (secilebilir aday) dusurulur; onayi kullanicidan almak istemcinin isidir. diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs index d2b3b75b..3ed3539c 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs @@ -32,6 +32,9 @@ public static class McpErrorCodes public const string DevGuideUnavailable = Prefix + "DevGuideUnavailable"; public const string DevGuideSectionNotFound = Prefix + "DevGuideSectionNotFound"; public const string InvalidWizard = Prefix + "InvalidWizard"; + public const string SeedRowsInvalid = Prefix + "SeedRowsInvalid"; + public const string SeedRowsNotAllowed = Prefix + "SeedRowsNotAllowed"; + public const string SeedRowsUnknownFields = Prefix + "SeedRowsUnknownFields"; public const string ComponentNameRequired = Prefix + "ComponentNameRequired"; public const string ComponentCodeRequired = Prefix + "ComponentCodeRequired"; public const string ComponentNotFound = Prefix + "ComponentNotFound"; diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs index 9da57ab8..8794f3aa 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpServerInfoDefaults.cs @@ -25,6 +25,7 @@ internal static class McpServerInfoDefaults "Agree on screen type, data source, module and permissions with the user before deploying. " + "Development requests (App.DevRequests) follow get_dev_guide section 0.11: claim each one with " + "start_dev_request, which only accepts requests in Ready to Start; images pasted into the request arrive as image content and are part of the request (layout and demo rows). " + + "A request is its own brief: do not ask questions and do not wait for DB Migrate; deploy (demo rows go in deploy_wizard seedRows), move the request to Testing and finish. " + "When a tool reports a missing permission, tell the user which permission is missing and stop; " + "do not try to reach the same result through another tool."; } diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopPrompts.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopPrompts.cs index b02bc1a2..3c49c2e4 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopPrompts.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopPrompts.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using ModelContextProtocol.Server; namespace Sozsoft.Mcp.Tools; @@ -20,7 +20,8 @@ public static class McpDevelopPrompts "Once get_dev_guide ile lowcode 0.11 bolumunu oku ve oradaki adimlari uygula. " + "Talebi start_dev_request ile ustlen. Arac reddederse (talep Ready to Start degil, bulunamadi ya da " + "birden fazla eslesme var) hicbir sey uretme, baska bir talebe gecme: nedenini bildir ve dur. " + - "Sonunda rapor ver: uretilen artefaktlar, verify_wizard sonucu, talebin son durumu."; + "Talep tarifin kendisidir: soru sorma, DB Migrate'i bekleme; talebi Testing'e alip bitir. " + + "Sonunda rapor ver: uretilen artefaktlar, talebin son durumu ve DB Migrate gerektigi."; [McpServerPrompt(Name = McpPromptNames.ProcessReadyRequests)] [Description("Ready to Start durumundaki tum gelistirme taleplerini SortOrder sirasiyla isler.")] @@ -28,6 +29,6 @@ public static class McpDevelopPrompts => "App.DevRequests kuyrugundaki Ready to Start taleplerini query_list ile SortOrder sirasiyla oku ve tek tek isle. " + "Once get_dev_guide ile lowcode 0.11 bolumunu oku ve oradaki adimlari uygula. " + "Her talebi start_dev_request ile ustlen; reddedilen talebi atla ve rapora yaz. " + - "Sonunda parti raporu ver: talep basina uretilen artefaktlar, verify_wizard sonucu, son durum; " + - "seed_closure uyarisi olan ekranlari ve migrate gerekip gerekmedigini ayrica belirt."; + "Talep tarifin kendisidir: soru sorma, DB Migrate'i bekleme; her talebi Testing'e alip digerine gec. " + + "Sonunda parti raporu ver: talep basina uretilen artefaktlar ve son durum; DB Migrate gerektigini tek satirda belirt."; } diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs index 855ad883..d70ffa56 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs @@ -64,13 +64,15 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService) => 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; 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.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; DB Migrate calistiktan sonra (isPending false, verify_wizard gecer) her satiri insert_row ile ekle: satirlar seed klasorundeki data/{ListFormCode}.json dosyasina yazilir ve migrate ile ekranla birlikte geri yuklenir; SeedData olmadan eklenen satir yalnizca bu veritabaninda kalir. replaceFileName verilirse o wizard'in dosyalari yeniden yazilir, onceki kayitlarin yerini migrate'te yenileri alir; ekran wizard cevaplarindan yeniden turetildigi icin tasarimcida yapilmis ve wizard'da karsiligi olmayan ayarlar kaybolur. 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. 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. Satirlar icin migrate'i bekleyip insert_row kullanma. replaceFileName verilirse o wizard'in dosyalari yeniden yazilir, onceki kayitlarin yerini migrate'te yenileri alir; ekran wizard cevaplarindan yeniden turetildigi icin tasarimcida yapilmis ve wizard'da karsiligi olmayan ayarlar kaybolur. Ne yazacagini get_dev_guide 4. bolumunden ogren.")] public async Task DeployWizardAsync( [Description("Wizard JSON'u: seed dosyasi bicimi ({\"Wizard\": {...}}, get_wizard cikisi gibi) ya da duz wizard cevaplari. Enum alanlari sayi olarak yazilir.")] string wizard, [Description("Guncellemede yerine gecilecek dosyanin adi (list_wizards cikisindaki fileName). Yeni wizard icin bos birak.")] - string? replaceFileName = null) - => await developAppService.DeployWizardAsync(new McpWizardDeployRequestDto { Wizard = wizard, ReplaceFileName = replaceFileName }); + string? replaceFileName = null, + [Description("Demo satirlar: JSON nesne dizisi, anahtarlar ekranin alan adlari ([{\"Name\": \"...\"}]). Guid anahtar, TenantId ve audit alanlarini yazma; sunucu ve migrate doldurur. Yalnizca SeedData true olan liste ekraninda.")] + string? seedRows = null) + => await developAppService.DeployWizardAsync(new McpWizardDeployRequestDto { Wizard = wizard, ReplaceFileName = replaceFileName, SeedRows = seedRows }); [McpServerTool(Name = McpToolNames.DeleteWizard, Destructive = true, Idempotent = true)] [Description("Bir wizard'i ve yalnizca onun urettigi kayitlari siler; baska yerde kullanilan kayitlar korunur ve raporda gosterilir. Ekranin veri aynasi (data/{ListFormCode}.json) ekranla birlikte gider. Bagimli wizard'i olan bir wizard silinemez, once bagimlilar silinir. Ekranin tablosu varsayilan olarak kalir; dropTables true ise tablo DROP edilir, CRUD endpoint'leri ve sql/object + crud seed dosyalari da silinir. Tablo yalnizca create_table ya da tablo tasarimcisiyla olusturulmussa (sql/object scripti varsa) ve baska ekran, Custom Endpoint ya da bilesen kullanmiyorsa dusurulur; aksi halde raporda neden korundugu yazar. Tablo silmek geri alinamaz ve App.SqlQueryManager yetkisi ister: dropTables true gondermeden once kullaniciya tablonun ve icindeki verinin silinecegini soyle ve acik onay al.")] @@ -82,7 +84,7 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService) => await developAppService.DeleteWizardAsync(fileName, dropTables); [McpServerTool(Name = McpToolNames.VerifyWizard, ReadOnly = true)] - [Description("Deploy edilip DB Migrate ile kurulmus bir wizard'i veri yazmadan dogrular (migrate bekleyen wizard'da install kontrolu basarisiz doner ve diger kontroller yapilmaz): menu kaydi (var mi, etkin mi, URL'si wizard'la ayni mi), menunun istedigi yetki tanimi, liste ekraninda ekranin sorgusu (cagiranin yetkisiyle tek satir), Custom yolunda bilesen (var mi, etkin mi, rotasi menuyle ayni mi) ve seed kapanisi (migrate ya da baska ortamda eksik kalacak bagimlilik). passed yalnizca blocking kontrollerden biri kalirsa false olur; seed_closure engellemez ama raporlanmalidir. deploy_wizard'dan sonra ve bir gelistirme talebini Staging durumuna almadan once cagir.")] + [Description("Deploy edilip DB Migrate ile kurulmus bir wizard'i veri yazmadan dogrular (migrate bekleyen wizard'da install kontrolu basarisiz doner ve diger kontroller yapilmaz): menu kaydi (var mi, etkin mi, URL'si wizard'la ayni mi), menunun istedigi yetki tanimi, liste ekraninda ekranin sorgusu (cagiranin yetkisiyle tek satir), Custom yolunda bilesen (var mi, etkin mi, rotasi menuyle ayni mi) ve seed kapanisi (migrate ya da baska ortamda eksik kalacak bagimlilik). passed yalnizca blocking kontrollerden biri kalirsa false olur; seed_closure engellemez ama raporlanmalidir. Istege baglidir: kullanici migrate sonrasi dogrulama isterse cagir; gelistirme talebi dongusunun adimi degildir.")] public async Task VerifyWizardAsync( [Description("Dogrulanacak wizard dosyasinin adi (list_wizards ya da deploy_wizard cikisindaki fileName).")] string fileName) @@ -103,7 +105,7 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService) => ToContent(await developAppService.GetDevRequestAsync(request)); [McpServerTool(Name = McpToolNames.AppendDevRequestNote, Destructive = false, Idempotent = false)] - [Description("Talebin HTML aciklamasinin sonuna ekleme yapar; var olan metne ve gorsellere dokunmaz. Aciklama gomulu gorsel tasidigi icin Description'i update_row ile yeniden yazma, sonuc notunu (

MCP

+ verify_wizard ozeti) bu aracla ekle.")] + [Description("Talebin HTML aciklamasinin sonuna ekleme yapar; var olan metne ve gorsellere dokunmaz. Aciklama gomulu gorsel tasidigi icin Description'i update_row ile yeniden yazma, sonuc notunu (

MCP

+ uretilenler ve DB Migrate bekleyen wizard'lar) bu aracla ekle.")] public async Task AppendDevRequestNoteAsync( [Description("Talebin Id'si (GUID) ya da tam basligi.")] string request, diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs index 165e8c79..fc2c4ad7 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs @@ -454,9 +454,6 @@ public class ListFormSeedDataSynchronizer( return seedFile; } - private static async Task WriteSeedFileAsync(string filePath, ListFormSeedDataFileDto seedFile) - { - seedFile.GeneratedAt = DateTime.UtcNow; - await File.WriteAllTextAsync(filePath, ListFormSeedDataFileDto.Serialize(seedFile)); - } + private static Task WriteSeedFileAsync(string filePath, ListFormSeedDataFileDto seedFile) => + File.WriteAllTextAsync(filePath, ListFormSeedDataFileDto.Serialize(seedFile)); } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 276f898c..8b344ad0 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -1029,7 +1029,16 @@ public class ListFormWizardAppService( var outcome = rows.RemoveOwned(owned, (type, name) => usages.ContainsKey((type, name.ToLowerInvariant()))); await rows.SaveAsync(); - return [.. outcome.Select(r => r.Removed + // MCP demo satirlari deploy aninda ekranin veri aynasina yazilir; hic kurulmamis ekranin + // aynasi onunla gider. Kurulu ekranin aynasi veritabanindaki satirlarin kaydidir, kalir. + var mirror = new List(); + if (seed.Wizard.ComponentKind != WizardComponentKindEnum.Custom && + !await _installationChecker.IsInstalledAsync(false, seed.Wizard.ResolveMenuCode(), seed.Wizard.ResolveListFormCode())) + { + _recordCleaner.DeleteSeedDataMirror(seed.ListForm?.SeedFilePath, mirror); + } + + return [.. mirror, .. outcome.Select(r => r.Removed ? new WizardDeleteLogItemDto { RecordType = r.Type, Name = r.Name, Status = WizardDeleteStatusEnum.Deleted } : new WizardDeleteLogItemDto { @@ -2223,7 +2232,6 @@ public class ListFormWizardAppService( } existingFile.Rows = merged; - existingFile.GeneratedAt = Clock.Now.ToUniversalTime(); return ImportEncoding.GetBytes(ListFormSeedDataFileDto.Serialize(existingFile)); } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs index 6f5d49a2..28035e21 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs @@ -176,7 +176,7 @@ public class WizardRecordCleaner( /// Ekranin veri aynasi (ListForm.SeedFilePath) ekransiz uygulanamaz; ekranla birlikte /// gider. ///
- private void DeleteSeedDataMirror(string? seedFilePath, List report) + public void DeleteSeedDataMirror(string? seedFilePath, List report) { var relativePath = seedFilePath?.Replace('\\', '/').Trim('/'); if (string.IsNullOrWhiteSpace(relativePath) || diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs index 4ca220fe..e1b9af9c 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs @@ -208,7 +208,6 @@ public sealed class WizardSeedRowStore foreach (var listFormCode in dirty) { var (path, file) = files[listFormCode]; - file.GeneratedAt = DateTime.UtcNow; Directory.CreateDirectory(Path.GetDirectoryName(path)!); await File.WriteAllTextAsync(path, ListFormSeedDataFileDto.Serialize(file)); diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs index d7a0bd9d..c96b8c04 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs @@ -13,6 +13,7 @@ using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; using Sozsoft.Platform.ListForms; using Sozsoft.Platform.Localization; +using Sozsoft.Platform.Permissions; using Sozsoft.Platform.Queries; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -11725,6 +11726,18 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency GroupPanelJson = DefaultGroupPanelJson(), SelectionJson = DefaultSelectionSingleJson, ColumnOptionJson = DefaultColumnOptionJson(), + // MCP talepleri yalnizca seed dosyasi yazar; kurulum icin kuyruktan cikmadan migrate calistirilir. + CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] { + new() { + ButtonPosition = UiCommandButtonPositionTypeEnum.Toolbar, + Hint = "App.Platform.StartMessage", + Text = "App.ListForm.DbMigrate", + AuthName = PlatformPermissions.Setup.Migrate, + OnClick = "UiEvalService.ApiDbMigrate();", + Icon = "import", + IsVisible = true, + }, + }), PermissionJson = DefaultPermissionJson(listFormName), DeleteCommand = DefaultDeleteCommand(nameof(TableNameEnum.DevRequest)), DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(), diff --git a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs index 7b9460dd..346d67c0 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs @@ -22,7 +22,11 @@ public class ListFormSeedDataFileDto /// Satirlarin eslestigi anahtar alan (ListForm.KeyFieldName). public string KeyFieldName { get; set; } = string.Empty; - /// Dosyanin en son yazildigi an (UTC) — yalnizca bilgi amaclidir. + /// + /// Dosyanin olusturuldugu an (UTC) — yalnizca bilgi amaclidir. Ilk yazimda + /// doldurur, sonraki yazimlar degistirmez: satir ekleyip ayni satiri kaldiran bir islem (wizard + /// deploy + silme) dosyayi birebir onceki haline dondurur, git'te iz birakmaz. + /// public DateTime GeneratedAt { get; set; } /// @@ -48,6 +52,11 @@ public class ListFormSeedDataFileDto /// kacirilmadan yazilir () ve dosya depodaki digerleri gibi satir sonuyla /// biter — dosya bastan yazildiginda sahte fark olusmaz. /// - public static string Serialize(ListFormSeedDataFileDto file) => - SeedJsonText.RestoreSurrogatePairs(JsonSerializer.Serialize(file, JsonOptions)) + "\n"; + public static string Serialize(ListFormSeedDataFileDto file) + { + if (file.GeneratedAt == default) + file.GeneratedAt = DateTime.UtcNow; + + return SeedJsonText.RestoreSurrogatePairs(JsonSerializer.Serialize(file, JsonOptions)) + "\n"; + } } diff --git a/claude.md b/claude.md index dea5aa84..6cc55d6c 100644 --- a/claude.md +++ b/claude.md @@ -241,10 +241,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → adım ekleme. Ayrıntı: `lowcode.instructions.md` §4.0. - **MCP ile demo veri** → geliştirme isteği örnek satır taşıyorsa (prompt'taki tablo/liste, ekran görüntüsü) ekran `deploy_wizard`'a `SeedData: true` ile gider (`SeedFilePath = data/{ListFormCode}.json` - + `SeedSync*` açık, `ListFormWizardDto.BuildListForm`); kullanıcı DB Migrate çalıştırıp ekran - kurulduktan sonra (`isPending: false`) satırlar `insert_row` ile eklenir ve - senkronizasyon onları `configs/seeds/host/data/{ListFormCode}.json` dosyasına yazar; migrate - ekranla birlikte seed eder. Dosya elle yazılmaz, görselden okunamayan değer uydurulmaz. + + `SeedSync*` açık, `ListFormWizardDto.BuildListForm`) ve satırlar aynı çağrıda `seedRows` ile + `configs/seeds/host/data/{ListFormCode}.json` dosyasına yazılır (`McpDevelopAppService`; alan adları + ekrana göre doğrulanır, `Guid` anahtarı sunucu üretir). Migrate ekranı kurar, ardından satırları + ekler; MCP migrate'i beklemez. Talep döngüsü `Testing`'de biter, `Staging`'e kullanıcı alır. Dosya elle yazılmaz, görselden okunamayan değer uydurulmaz. `SeedData` wizard ekranında sorulmaz ama düzenlemede korunur (`RefreshFrom` + UI modeli). Ayrıntı: `lowcode.instructions.md` §0.10.1. - **Talepteki görsel** → `App.DevRequests` açıklamasına yapıştırılan görsel base64 `data:` URI olarak @@ -365,7 +365,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → `WizardSeedFileDto.ToSeedJson` ile yazılır; düz `JsonSerializer.Serialize` + `File.WriteAllText` kullanılmaz. System.Text.Json emojiyi her zaman vekil çift kaçışına çevirir; dosya baştan yazıldığında bütün emojiler sahte fark üretir. İkisi de `SeedJsonText.RestoreSurrogatePairs`'ten - geçer. + geçer. Veri dosyasının `GeneratedAt` değeri oluşturma anıdır: `Serialize` yalnızca boşsa doldurur, + hiçbir yazma yolu onu güncellemez — ekleyip geri kaldıran işlem (wizard deploy + silme) dosyayı + birebir önceki hâline döndürür. `crud/` dosyalarının damgası ayrıdır ve karar verir (aşağıda). - **`crud/{Entity}.json`** → uygulama kuralı `CrudEndpointSeedApplier`'dadır ve damga karşılaştırmalıdır: eksik endpoint eklenir, mevcut kayıt yalnızca dosyanın `GeneratedAt` değeri kaydın `LastModificationTime`/`CreationTime` değerinden yeniyse güncellenir/silinir. Migrate diff --git a/configs/seeds/host/data/App.Languages.LanguageText.json b/configs/seeds/host/data/App.Languages.LanguageText.json index dfb6cd93..3ebd8528 100644 --- a/configs/seeds/host/data/App.Languages.LanguageText.json +++ b/configs/seeds/host/data/App.Languages.LanguageText.json @@ -66910,6 +66910,48 @@ "Key": "App.Mcp.Error.InvalidWizard", "Value": "Wizard JSON okunamadı. Seed dosyası biçimini ({\"Wizard\": {...}}) ya da düz wizard cevaplarını gönderin." }, + { + "Id": "9221bf6b-577c-5d08-a95e-7a22d9fd9c3b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Mcp.Error.SeedRowsInvalid", + "Value": "seedRows could not be read. Send a JSON array of objects that map field names to values." + }, + { + "Id": "bcee973d-f521-5fe6-8fa0-817bc70329e8", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Mcp.Error.SeedRowsInvalid", + "Value": "seedRows okunamadı. Alan adından değere nesnelerden oluşan bir JSON dizisi gönderin." + }, + { + "Id": "8ad5b888-cbbb-5085-838b-5e32f5ac8cef", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Mcp.Error.SeedRowsNotAllowed", + "Value": "seedRows can only be used on a list screen (ComponentKind 0) with SeedData true." + }, + { + "Id": "61428bfd-3dad-5c2d-b060-e77b62372685", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Mcp.Error.SeedRowsNotAllowed", + "Value": "seedRows yalnızca SeedData true olan liste ekranında (ComponentKind 0) kullanılabilir." + }, + { + "Id": "412674a3-8458-5b26-b9f3-236cd3ec0f76", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Mcp.Error.SeedRowsUnknownFields", + "Value": "seedRows contains fields that are not on the screen: {0}. Screen fields: {1}." + }, + { + "Id": "bb733499-5c0e-5693-aab9-f290dfef9a4e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Mcp.Error.SeedRowsUnknownFields", + "Value": "seedRows ekranda olmayan alanlar içeriyor: {0}. Ekranın alanları: {1}." + }, { "Id": "fe038e1e-68b6-5cfe-ad9e-42c8e58f01bb", "CultureName": "en", diff --git a/ui/src/views/list/TodoBoard.tsx b/ui/src/views/list/TodoBoard.tsx index 61c0c115..354bdfe7 100644 --- a/ui/src/views/list/TodoBoard.tsx +++ b/ui/src/views/list/TodoBoard.tsx @@ -1,4 +1,5 @@ import type { GridDto, TodoOptionDto } from '@/proxy/form/models' +import { UiCommandButtonPositionTypeEnum } from '@/proxy/form/models' import type { IdentityUserDto } from '@/proxy/admin/models' import type { DataGridRef } from 'devextreme-react/data-grid' import type CustomStore from 'devextreme/data/custom_store' @@ -310,6 +311,16 @@ const TodoBoardContent = ({ const { translate } = useLocalization() const { checkPermission } = usePermission() const isPwaMode = usePWA() + const toolbarCommands = useMemo( + () => + (gridDto.gridOptions.commandColumnDto ?? []).filter( + (action) => + action.buttonPosition === UiCommandButtonPositionTypeEnum.Toolbar && + (action.url || action.onClick) && + checkPermission(action.authName), + ), + [gridDto.gridOptions.commandColumnDto, checkPermission], + ) const uncategorized = translate('::App.ListFormTodoBoard.Uncategorized') const lookupValuesForField = useCallback( @@ -513,6 +524,29 @@ const TodoBoardContent = ({ onClick: refreshData, }} /> + {/* Ekranin CommandColumnJson'daki Toolbar butonlari (orn. DB Migrate); Grid/Card ile ayni tanim. + Pano satir secimi tasimadigi icin yalnizca satirdan bagimsiz eylemler (Url, OnClick) calisir. */} + {toolbarCommands.map((action, index) => ( + { + if (action.url) { + window.open(action.url, isPwaMode ? '_self' : action.urlTarget) + } else if (action.onClick) { + eval(action.onClick) + } + }, + }} + /> + ))} {checkPermission('App.Listforms.Listform.Update') && (