diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index 772e8c4a..7249800c 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -68,7 +68,7 @@ kurulur; host veritabanını paylaşan tenant'ta dosyalar yeniden uygulanmaz (`S | # | Adım | Dosya | Neden bu sırada | | --- | --- | --- | --- | -| 1 | Tablo / view | `sql/object/…` | Ekranın bağlanacağı nesne önce var olmalı. MCP'de yeni tablo `create_table` ile kurulur (script ve dosya tasarımcıyla aynı); view MCP'den kurulamaz | +| 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 MCP'den kurulamaz | | 2 | CRUD uçları (gerekiyorsa) | `crud/{Entity}.json` | Custom Component'in data source'ları buna bakar | | 3 | Custom Component (gerekiyorsa) | `data/App.DeveloperKit.CustomComponents/{Id}.json` | Wizard `Custom` yolunda bileşen adını arar | | 4 | Wizard | `wizard/{Ad}.json` + menü/yetki/dil satırları (`data/App.Menus.*`, `data/Abp.Identity.Permission*`, `data/App.Languages.LanguageText.json`) | Deploy (MCP `deploy_wizard` ya da wizard ekranı, §0.10) **yalnızca bu dosyaları yazar**; ekran, menü ve yetkiler DB Migrate ile kurulur (§4.0). Dosyaları sunucu yazar. Elle yazılan List dosyası `ListForm` + `Fields` taşımak zorundadır, yoksa seeder atlar (§4.1) | @@ -509,13 +509,13 @@ ister ve kiracı kullanıcısına hiçbiri açılmaz. | Araç | Ne yapar | Not | | --- | --- | --- | | `get_dev_guide` | Bu dosyanın (`lowcode`) ya da `reference`'ın başlık listesini / bir bölümünü döner | Bölüm numarayla (`4.3`) ya da başlık metniyle istenir; aynı numarayı iki başlık taşımamalıdır. Bölüm önce istenen belgede, sonra diğerinde aranır; başlık olmayan bir terim (`dxImageUpload`) terimin geçtiği bölümlerin listesini döndürür | -| `create_table` | Yeni tabloyu kurar (SQL Server ya da PostgreSQL veri kaynağı), `sql/object/{Tablo}.sql` ve PG eşi `postgres/object/{Tablo}.sql` dosyaları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 | +| `create_table` | Yeni tablonun `sql/object/{Tablo}.sql` ve PG eşi `postgres/object/{Tablo}.sql` dosyalarını yazar; veritabanında çalıştırmaz, tabloyu DB Migrate kurar (deploy kolonları o zamana kadar scriptten okur) | Ek olarak `App.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz | | `list_wizards` · `get_wizard` | Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner | Var olan menü kökünü/izin grubunu bulmak için önce `list_wizards` | | `deploy_wizard` | Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) | Girdi §4.4'teki düz cevap ya da `get_wizard` çıktısıyla aynı dosya biçimi; enum'lar sayı. `replaceFileName` = `EditFileName` (§4.5). List ekranında `Widgets` dolu gelir — alt form olarak üretilen ekran hariç (`Widgets: []`) — en az 3 kart, kiracı tablosunda `"TenantId" = '@TENANTID'` (§4.6.1); kartlardaki her dil anahtarı var olmalı ya da `Language.Texts`'te EN + TR verilmeli, yoksa deploy reddeder. `MenuOrder` yazılmaz — sunucu yeni ekranı kardeşlerinin sonuna koyar, güncellemede yerini korur. Talep bağı (`DevRequest`) da yazılmaz — sunucu etkin talepten doldurur (§4.0). İstek demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1). Satır/toolbar butonları `CommandColumns` (§4.9). Bağımlı lookup öğedeki `Cascade*` alanları (§4.3.4). Çıkıştaki `isPending: true` ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır; model migrate'i beklemez | | `delete_wizard` | Wizard'ı ve yalnızca onun ürettiği kayıtları siler; `dropTables: true` ekranın tablosunu da düşürür | Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; `App.SqlQueryManager` ister (§4.5) | | `start_dev_request` | `App.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `App.DevRequests.Update` ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11) | | `get_dev_request` | Talebi durumuna dokunmadan okur (aynı metin + analiz + resim içerikleri) | Analizden önce ya da yarım kalan talebe dönmek için; `App.DevRequests` ister | -| `save_dev_request_analysis` | `Backlog`/`Analysis` durumundaki talebin analizini `Analysis` kolonuna yazar (var olanı değiştirir), talebi `Analysis` durumuna alır; diğer durumları reddeder. `menuCode` dalının modül dokümanı bölümüne de yazar, dosya/bölüm yoksa açar (§0.13) | Ek olarak `App.DevRequests.Update` ve `App.Listforms.Wizard` ister. Standart §0.12; `Description`'a dokunmaz | +| `save_dev_request_analysis` | `Backlog`/`Analysis` durumundaki talebin analizini `Analysis` kolonuna yazar (var olanı değiştirir), talebi `Analysis` durumuna alır; diğer durumları reddeder. Modül dokümanına yazmaz; doküman üretimde açılır (§0.13) | Ek olarak `App.DevRequests.Update` ister. Standart §0.12; `Description`'a dokunmaz | | `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler | Üretim sonuç notu için; `Description` ve `Analysis` `update_row` ile yazılmaz (§0.11) | | `add_dev_request_subtasks` | `SubTasks` listesine tamamlanmış satırlar ekler | Atanan = bağlanan kullanıcı, bitiş = bugün (sunucu yazar); `SubTasks`'ı `update_row` ile yazma (§0.11) | | `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. `passed` yalnızca engelleyici bir kontrol kalırsa `false` | @@ -673,7 +673,7 @@ bileşen, seed dosyası yazılmaz; yalnızca okuma araçları ve `save_dev_reque | 1. Kuyruğu oku (yalnızca toplu) | `query_list` — `ListFormCode: App.DevRequests`, filtre `["Status","=","Backlog"]`, sıralama `SortOrder` | — | | 2. Talebi oku | `get_dev_request` — metin + görseller | — | | 3. Keşif | `list_wizards` (menü kökleri, modüller, izin grupları), `list_lists` / `describe_list` / `search_schema` (var olan ekran, tablo, lookup kaynağı), `list_components`, `get_dev_guide` (§0.6, §0.7, §0.8, §1) | — | -| 4. Yaz | `save_dev_request_analysis` — analizin tamamı, HTML + `menuCode` (menü yerindeki dalın ya da ekranın kodu). Analiz o dalın modül dokümanı bölümüne ("2. Analiz ve kararlar", §0.13) de yazılır; kök menünün ilk talebiyse dosya ve bölüm burada açılır (Timesheets satırını sunucu yazar, §0.11) | Analysis | +| 4. Yaz | `save_dev_request_analysis` — analizin tamamı, HTML. Modül dokümanına yazılmaz; dosya ve dal bölümü üretimde deploy ile açılır, analiz "2. Analiz ve kararlar" alt bölümüne üretimde `save_module_doc` ile taşınır (§0.13) (Timesheets satırını sunucu yazar, §0.11) | Analysis | **Durum kuralı sunucudadır:** araç yalnızca `Backlog` ya da `Analysis` durumundaki talebi kabul eder; `Analysis`'teki talebe yeniden çağrılırsa analiz değiştirilir. `Ready to Start` ve sonrası diff --git a/README.md b/README.md index 5fb74d46..f5b871f7 100644 --- a/README.md +++ b/README.md @@ -231,9 +231,9 @@ Uygulama `https://localhost:44344` adresinde açılır. Swagger UI kök adreste - UI tarafında `/setup` route'u (`views/setup/DatabaseSetup.tsx`) migration'ı tetikleyen ekranı gösterir. - Migration için `Setup:MigrationPassword` yapılandırması veya `App.Setup.Migrate` yetkisi gerekir. - Ekran DbMigrator'ü `SetupMigrationRunner` ile çalıştırır. `Setup:MigratorPath` publish çıktısını - gösteriyorsa DLL doğrudan çalışır; kaynak proje klasöründe (geliştirme) `dotnet run` kullanılmaz — - `bin/Debug` altındaki DLL `api/` kaynaklarından (ve son başarılı derlemenin `obj/setup-migrator.stamp` - damgasından) yeniyse derleme atlanır, değilse `dotnet build` çıktısı ekrandaki loga akar. + gösteriyorsa DLL doğrudan çalışır; kaynak proje klasöründe (geliştirme) `bin/Debug` altındaki var olan + DLL çalışır. Migrate restore/build yapmaz: DLL yoksa hata verir, `api/` kaynaklarından eskiyse yalnızca + uyarı yazar — kod değişikliğinin migrate'e girmesi için DbMigrator elle derlenir. Alternatif olarak migration + seed'i doğrudan çalıştırabilirsiniz: @@ -1555,14 +1555,14 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır. | `run_query` | Kayıtlı bir nesneyi ya da Custom Endpoint'i parametreleriyle çağırır; yetkiler `list_queries` ile aynı. | | `insert_row` / `update_row` / `delete_row` | ListForm ekranının insert/update/delete sözleşmesinden geçer. | | `get_dev_guide` | Üretim sözleşmesi: `lowcode` / `reference` belgelerinin başlık listesi ya da istenen bölümü (numarayla, örn. `4.2`, ya da başlık metniyle). Bölüm önce istenen, sonra diğer belgede aranır; başlıkla eşleşmeyen terimde (örn. `dxImageUpload`) terimin geçtiği bölümler (en çok 15, `DevGuideSearchMatches`), hiç geçmiyorsa başlık listesi döner — hata atılmaz. Ç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 `sql/object/{Tablo}.sql` dosyasına, PostgreSQL eşi (`PostgreSqlTableScript`) `postgres/object/{Tablo}.sql` dosyasına yazılır; PG veri kaynağında çalıştırılan da o eştir. Yalnızca oluşturur — tablo varsa reddeder. `.Write` + `App.SqlQueryManager` ister. | +| `create_table` | Yeni tablonun scriptini SQL Query Manager tablo tasarımcısıyla aynı biçimde üretir (`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 `sql/object/{Tablo}.sql` dosyasına, PostgreSQL eşi (`PostgreSqlTableScript`) `postgres/object/{Tablo}.sql` dosyasına yazılır. Veritabanında çalıştırılmaz: tabloyu kullanıcının çalıştırdığı DB Migrate kurar; o zamana kadar `deploy_wizard` kolonları scriptten okur (`PostgreSqlTableScript.ReadSeedColumns`). Yalnızca oluşturur — tablo ya da scripti varsa reddeder. `.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. 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 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). 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`). `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; `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. 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` + `App.DevRequests.Update` ister. | | `get_dev_request` | Talebi durumuna dokunmadan, `start_dev_request` ile aynı biçimde (metin + analiz + resim içerikleri) okur; analizden önce ya da yarım kalan talebe dönmek için. `App.DevRequests` ister. | -| `save_dev_request_analysis` | `Backlog` ya da `Analysis` durumundaki talebin analizini (HTML) `Analysis` kolonuna yazar — var olanı değiştirir — ve talebi `Analysis` durumuna alır; diğer durumları reddeder. `Description`'a dokunmaz. Zorunlu `menuCode` (dalın ya da ekranın kodu) ile analizi modül dokümanındaki dal bölümüne ("2. Analiz ve kararlar") de yazar; kök menünün ilk talebiyse dosya (`App.Inventory.md`) ve bölüm burada açılır, elle yazılmış alt bölüme dokunulmaz. Analizin standardı `lowcode.instructions.md` §0.12. `.Write` + `App.DevRequests.Update` + `App.Listforms.Wizard` ister. | +| `save_dev_request_analysis` | `Backlog` ya da `Analysis` durumundaki talebin analizini (HTML) `Analysis` kolonuna yazar — var olanı değiştirir — ve talebi `Analysis` durumuna alır; diğer durumları reddeder. `Description`'a dokunmaz. Modül dokümanına yazmaz; doküman üretimde açılır. Analizin standardı `lowcode.instructions.md` §0.12. `.Write` + `App.DevRequests.Update` 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. | | `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler (üretim sonuç notu); `Description` talebin yazıldığı hâliyle kalır. İkisi de `update_row` ile yazılmaz — açıklama base64 görsel taşır. `.Write` + `App.DevRequests.Update` ister. | | `list_components` / `get_component` / `save_component` / `delete_component` | Custom component'ler `ICustomComponentAppService` üzerinden; seed senkronu ve `DataSources` çözümü aynen çalışır. `save_component` ada göre ekler/günceller, tasarımcı belgesi (`Props.visualDesigner`, `sourceMode ≠ code`) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi. | @@ -1656,7 +1656,7 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay | `delete_report_template` | — | `.Write` | `App.Reports.ReportTemplates.Delete` | | `start_dev_request` | — | `.Write` | `App.DevRequests.Update` | | `get_dev_request` | — | — | `App.DevRequests` | -| `save_dev_request_analysis` | — | `.Write` | `App.DevRequests.Update`, `App.Listforms.Wizard` (modül dokümanı) | +| `save_dev_request_analysis` | — | `.Write` | `App.DevRequests.Update` | | `append_dev_request_note` | — | `.Write` | `App.DevRequests.Update` | | `add_dev_request_subtasks` | — | `.Write` | `App.DevRequests.Update` | diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs index b88d93e7..40b379d8 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs @@ -39,10 +39,9 @@ public interface IMcpDevelopAppService : IApplicationService /// /// Talebin analizini yazar (var olani degistirir) ve talebi Analysis durumuna alir; yalnizca - /// Backlog ya da Analysis durumundaki talep analiz edilir. Analiz - /// dalinin modul dokumani bolumune de yazilir; dosya ya da bolum yoksa acilir. + /// Backlog ya da Analysis durumundaki talep analiz edilir. /// - Task SaveDevRequestAnalysisAsync(string request, string? menuCode, string html); + Task SaveDevRequestAnalysisAsync(string request, string html); /// /// Analizin sonuna HTML ekler. Aciklama talebin yazildigi haliyle kalir; uretim notlari diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs index fdae9f59..26c8ff15 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs @@ -491,10 +491,9 @@ public class McpDevelopAppService( /// /// Durum kurali modelin dikkatine birakilmaz: yalnizca Backlog ya da Analysis durumundaki talep /// analiz edilir. Analysis'teki talebin analizi begenilmediyse yeniden yazilir; Ready to Start'a - /// kullanici alir. Analiz modulun dokumanina da tasinir: talep ilk kez bir menude yapiliyorsa - /// dokuman ve dalin bolumu burada acilir, uretim beklenmez. + /// kullanici alir. /// - public async Task SaveDevRequestAnalysisAsync(string request, string? menuCode, string html) + public async Task SaveDevRequestAnalysisAsync(string request, string html) { await EnsureToolAllowedAsync(McpToolNames.SaveDevRequestAnalysis); await AuthorizationService.CheckAsync(DevRequestUpdatePermission); @@ -504,12 +503,6 @@ public class McpDevelopAppService( throw new UserFriendlyException(L[McpErrorCodes.DevRequestAnalysisRequired]); } - // Arac listesini onbellekten okuyan istemci parametreyi hic gondermeyebilir; marshaller'in - // ArgumentException'i modele ulasmaz, bu mesaj ise ulasir ve model cagriyi kodla tekrarlar. - if (string.IsNullOrWhiteSpace(menuCode)) - { - throw new UserFriendlyException(L[McpErrorCodes.DevRequestMenuCodeRequired]); - } var devRequest = await FindDevRequestAsync(request); @@ -518,15 +511,6 @@ public class McpDevelopAppService( throw new UserFriendlyException(L[McpErrorCodes.DevRequestNotAnalyzable, devRequest.Title ?? devRequest.Id.ToString(), devRequest.Status ?? string.Empty]); } - // Dokuman once yazilir: menu kodu gecersizse analiz de kaydedilmez, model kodu duzeltip tekrar dener. - await moduleDocAppService.SaveAnalysisAsync(new ModuleDocAnalysisInput - { - MenuCode = menuCode, - DevRequestId = devRequest.Id, - DevRequestTitle = devRequest.Title, - Html = html, - }); - devRequest.Analysis = html.Trim(); devRequest.Status = McpConsts.DevRequestStatuses.Analysis; await devRequestRepository.UpdateAsync(devRequest, autoSave: true); 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 2f5f2e8c..c7bc315e 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs @@ -57,7 +57,6 @@ public static class McpErrorCodes public const string DevRequestNotReady = Prefix + "DevRequestNotReady"; public const string DevRequestNotAnalyzable = Prefix + "DevRequestNotAnalyzable"; public const string DevRequestAnalysisRequired = Prefix + "DevRequestAnalysisRequired"; - public const string DevRequestMenuCodeRequired = Prefix + "DevRequestMenuCodeRequired"; public const string DevRequestNoteRequired = Prefix + "DevRequestNoteRequired"; public const string DevRequestSubTasksRequired = Prefix + "DevRequestSubTasksRequired"; public const string DevRequestSubTasksInvalid = Prefix + "DevRequestSubTasksInvalid"; diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs index 30bd8a66..287ba648 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpToolNames.cs @@ -149,7 +149,6 @@ public static class McpToolNames [DeleteWizard] = [PlatformConsts.AppCodes.Listforms.Wizard, PlatformConsts.AppCodes.Listforms.Wizards.Delete, PlatformConsts.AppCodes.SqlQueryManagers.Default], [GetModuleDoc] = [PlatformConsts.AppCodes.Listforms.Wizard], [SaveModuleDoc] = [PlatformConsts.AppCodes.Listforms.Wizard, PlatformConsts.AppCodes.Listforms.Wizards.Update], - [SaveDevRequestAnalysis] = [PlatformConsts.AppCodes.Listforms.Wizard], }; /// diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs index 0a536dbc..9e423b7c 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs @@ -20,6 +20,10 @@ namespace Sozsoft.Mcp.Services; /// sonrasi yazdigi yerlere (sql/object/{Tablo}.sql ve PostgreSQL esi postgres/object/{Tablo}.sql) yazilir; boylece tablo /// tasarimcida acilip duzenlenebilir ve veritabani sifirlandiginda seed ile geri gelir. /// +/// Veritabaninda calistirilmaz: tabloyu, wizard deploy'unun ekranini kurdugu gibi kullanicinin +/// calistirdigi DB Migrate kurar (SqlDataSeeder). +/// +/// /// Yalnizca olusturma yapar. Var olan tabloya dokunulmaz — kolon ekleme/degistirme tasarimcinin /// isidir ve sema degisikligi kurallarina (kayipsiz ekleme, degistirme/dusurme yok) tabidir. /// Kolon tipi ve varsayilan deger serbest metin degil, beyaz listeden gecer; istemci SQL yazamaz. @@ -102,24 +106,22 @@ public class McpTableCreator( context.ConnectionString, new Dictionary { ["name"] = isPostgreSql ? $"public.\"{tableName}\"" : $"{SchemaName}.{tableName}" }); - if (existing is not null and not DBNull) + // Henuz migrate edilmemis bir create_table da tabloyu sahiplenmis sayilir; ikinci cagri onun + // scriptinin ustune yazmaz. + if (existing is not null and not DBNull || PostgreSqlTableScript.ReadSeedColumns(configuration, tableName).Count > 0) { throw new UserFriendlyException(localizer[McpErrorCodes.TableAlreadyExists, tableName]); } - // Dosya biçimi tasarımcınınkiyle aynı T-SQL'dir; PostgreSQL eşi ondan üretilir ve PG veri - // kaynağında çalıştırılan da o eştir. + // Dosya biçimi tasarımcınınkiyle aynı T-SQL'dir; PostgreSQL eşi ondan üretilir. var script = BuildScript(tableName, columns); - if (!PostgreSqlTableScript.TryConvert(script, out var postgreSqlScript)) + if (!PostgreSqlTableScript.TryConvert(script, out _)) { throw new InvalidOperationException($"PostgreSQL script could not be generated for {tableName}."); } - // GO yalnizca SSMS ayiricidir; tek batch oldugu icin calistirirken atilir, dosyada kalir. - await context.Repository.ExecuteAsync( - isPostgreSql ? postgreSqlScript : string.Join('\n', script.Split('\n').Where(line => line.Trim() != "GO")), - context.ConnectionString); - + // Tablo veritabaninda kurulmaz: wizard deploy'u gibi yalnizca seed yazilir, kurulum kullanicinin + // calistirdigi DB Migrate'tedir (SqlDataSeeder). var (seedFilePath, _) = PostgreSqlTableScript.WriteSeedFiles(configuration, tableName, script); return new McpCreatedTable(tableName, script, seedFilePath); 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 5ee1d34f..9c7e47c5 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs @@ -38,7 +38,7 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService, IMc => await developAppService.GetDevGuideAsync(document, section); [McpServerTool(Name = McpToolNames.CreateTable, Destructive = false, Idempotent = false)] - [Description("Ekranin baglanacagi yeni tabloyu SQL Query Manager tablo tasarimcisiyla ayni bicimde olusturur ve script'i seed dosyasina (sql/object) yazar. Id (uniqueidentifier, PK), TenantId ve tam audit kolonlari (CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId) otomatik eklenir; columns icine yalnizca is kolonlarini yaz. Yalnizca yeni tablo olusturur: tablo varsa reddeder, kolon ekleme/degistirme tablo tasarimcisindan yapilir. SQL Server veri kaynaginda calisir. Ardindan deploy_wizard ile ekrani kur.")] + [Description("Ekranin baglanacagi yeni tablonun script'ini SQL Query Manager tablo tasarimcisiyla ayni bicimde uretir ve seed dosyasina (sql/object + postgres/object) yazar; veritabaninda calistirmaz, tabloyu kullanicinin calistirdigi DB Migrate kurar. DB Migrate'i bekleme ve calistirmaya calisma; deploy_wizard kolonlari script'ten okur. Id (uniqueidentifier, PK), TenantId ve tam audit kolonlari (CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId) otomatik eklenir; columns icine yalnizca is kolonlarini yaz. Yalnizca yeni tablo icindir: tablo ya da script'i varsa reddeder, kolon ekleme/degistirme tablo tasarimcisindan yapilir. Ardindan deploy_wizard ile ekrani kur.")] public async Task CreateTableAsync( [Description("Tablo adi; harf/rakam/alt cizgi. Adlandirma icin get_dev_guide'daki tablo onek kuralina uy (ornegin Adm_T_...).")] string tableName, @@ -117,10 +117,8 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService, IMc [Description("Talebin Id'si (GUID) ya da tam basligi.")] string request, [Description("Analizin tamami, HTML.")] - string html, - [Description("Zorunlu. Talebin menu yeri: kok menunun altindaki dalin ya da ekranin kodu (App.Inventory.Definitions, App.Hr.LeaveRequest); analizdeki Menu yeri ile ayni. Menude henuz olmayan yeni kod da verilir. Analiz bu dalin modul dokumani bolumune ('2. Analiz ve kararlar') yazilir; kok menunun dosyasi (App.Inventory.md) ya da dalin bolumu yoksa acilir.")] - string? menuCode = null) - => ToContent(await developAppService.SaveDevRequestAnalysisAsync(request, menuCode, html)); + string html) + => ToContent(await developAppService.SaveDevRequestAnalysisAsync(request, html)); [McpServerTool(Name = McpToolNames.AppendDevRequestNote, Destructive = false, Idempotent = false)] [Description("Talebin Analysis alaninin sonuna HTML ekler; var olan analize ve Description'a dokunmaz. Description talebin yazildigi haliyle kalir; uretim sonuc notunu (

MCP

+ uretilenler, varsayimlar ve DB Migrate bekleyen wizard'lar) bu aracla ekle. Description'i ya da Analysis'i update_row ile yazma.")] diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs index 46780a0a..c963cffd 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs @@ -34,12 +34,6 @@ public interface IModuleDocAppService : IApplicationService /// bolum var). Bos liste talebin dokumantasyonunun tamam oldugunu gosterir. ///
Task> GetIncompleteForDevRequestAsync(Guid devRequestId); - - /// - /// Talep analizini dalin bolumune ("2. Analiz ve kararlar") yazar; bolum ya da dosya yoksa acar. - /// Elle yazilmis alt bolume dokunulmaz. - /// - Task SaveAnalysisAsync(ModuleDocAnalysisInput input); } public class ModuleDocDto @@ -80,16 +74,3 @@ public class ModuleDocSaveInput /// Markdown. Otomatik envanter bolumu varsa yok sayilir. public string Content { get; set; } = string.Empty; } - -public class ModuleDocAnalysisInput -{ - /// Talebin menu yeri: dalin ya da ekranin kodu (App.Inventory.Definitions). - public string MenuCode { get; set; } = string.Empty; - - public Guid DevRequestId { get; set; } - - public string? DevRequestTitle { get; set; } - - /// Analiz, HTML. - public string Html { get; set; } = string.Empty; -} diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 58b7a96f..f8f90f06 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -3075,12 +3075,14 @@ public class ListFormWizardAppService( : null; } } - return columns; + // MCP create_table tabloyu yalnizca seed scripti olarak yazar; DB Migrate kurana kadar + // kolonlar oradan okunur, yoksa ekran audit/soft delete bayraklari olmadan uretilirdi. + return columns.Count > 0 ? columns : PostgreSqlTableScript.ReadSeedColumns(_configuration, tableName); } catch (Exception ex) { logger.LogWarning(ex, "Table columns could not be read for {DataSourceCode}/{SelectCommand}", dataSourceCode, selectCommand); - return []; + return PostgreSqlTableScript.ReadSeedColumns(_configuration, tableName); } } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocAppService.cs index 2023ddbe..9cac3bd4 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocAppService.cs @@ -28,13 +28,4 @@ public class ModuleDocAppService(ModuleDocManager manager) : PlatformAppService, public Task> GetIncompleteForDevRequestAsync(Guid devRequestId) => manager.GetIncompleteForDevRequestAsync(devRequestId); - /// - /// Talep analizinin parcasidir; talebi analiz edebilen (Wizard ekranini okuyabilen) kullanici - /// dokumanin iskeletini ve analiz alt bolumunu yazabilir, diger bolumler icin gerekir. - /// - public Task SaveAnalysisAsync(ModuleDocAnalysisInput input) - { - Check.NotNull(input, nameof(input)); - return manager.SaveAnalysisAsync(input.MenuCode, input.DevRequestId, input.DevRequestTitle, input.Html); - } } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs index cfff928e..25c683a2 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs @@ -24,9 +24,6 @@ public class ModuleDocManager( { private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false); - /// Analizin yazdigi alt bolumun isareti; isaret tasiyan govde sonraki analizde yenilenir. - private const string AnalysisMarker = "\n_Talep: {title}_\n\n{html.Trim()}\n"; - var updated = ReplaceSubSection(section.Text, ModuleDocRenderer.Sections[1], body); - if (updated != null) - manual = manual[..section.Start] + updated + (section.End < manual.Length ? "\n" + manual[section.End..].TrimStart('\n') : string.Empty); - - Directory.CreateDirectory(catalog.DocsPath); - await File.WriteAllTextAsync(path, manual.TrimEnd() + "\n", Utf8NoBom); - - catalog = await LoadCatalogAsync(); - var groups = catalog.GroupByDoc(); - await RefreshAsync(catalog, name, groups.GetValueOrDefault(name)); - - return await ToDtoAsync(catalog, name, sectionKey, groups, includeContent: false); - } - - /// - /// Bolumdeki ### {heading} alt bolumunun govdesini degistirir. Govde doldurulmamissa ya da - /// bir analizle yazilmissa yenilenir, elle yazilmissa null doner (dokunulmaz). - /// - private static string? ReplaceSubSection(string sectionText, string heading, string body) - { - var lines = sectionText.TrimEnd('\n').Split('\n').ToList(); - var start = lines.FindIndex(l => l.StartsWith("### ", StringComparison.Ordinal) && l[4..].Trim() == heading); - if (start < 0) - return sectionText.TrimEnd() + $"\n\n### {heading}\n\n{body}"; - - var end = lines.FindIndex(start + 1, l => l.StartsWith("### ", StringComparison.Ordinal)); - if (end < 0) - end = lines.Count; - - var current = string.Join('\n', lines.Skip(start + 1).Take(end - start - 1)); - if (!current.Contains(ModuleDocCatalog.TodoMarker, StringComparison.Ordinal) && !current.Contains(AnalysisMarker, StringComparison.Ordinal)) - return null; - - lines.RemoveRange(start + 1, end - start - 1); - lines.Insert(start + 1, string.Empty); - lines.Insert(start + 2, body.TrimEnd('\n')); - if (start + 3 < lines.Count) - lines.Insert(start + 3, string.Empty); - - return string.Join('\n', lines) + "\n"; - } - private static string RootTitle(ModuleDocCatalog catalog, ModuleDocCatalog.DocGroup group) => ModuleDocRenderer.Title(catalog, group.All.Select(w => w.Seed), group.RootCode); diff --git a/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs b/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs index f293d671..5d422d64 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs @@ -49,6 +49,60 @@ public static partial class PostgreSqlTableScript return (sqlServerPath, $"{SeedPathResolver.PostgresFolder}/{SeedPathResolver.ObjectFolder}/{fileName}"); } + /// + /// Henüz DB Migrate'te kurulmamış tablonun kolonlarını SQL Server seed scriptinden + /// (sql/object/{Tablo}.sql) okur: ad ve metin kolonunun karakter sınırı (MAX için -1, + /// INFORMATION_SCHEMA ile aynı). Dosya yoksa ya da CREATE TABLE tanınmazsa boş döner. + /// + public static Dictionary ReadSeedColumns(IConfiguration? configuration, string tableName) + { + var columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + var path = Path.Combine( + SeedPathResolver.GetPath(configuration, SeedPathResolver.SqlFolder, SeedPathResolver.ObjectFolder), + $"{tableName}.sql"); + + if (!File.Exists(path)) + return columns; + + var inCreate = false; + foreach (var line in File.ReadLines(path)) + { + if (!inCreate) + { + inCreate = CreateTableLineRegex().IsMatch(line); + continue; + } + + // Kolon listesi ilk constraint ya da kapanan parantezle biter; PK'nin [Id] ASC satiri kolon degildir. + if (SeedColumnEndRegex().IsMatch(line)) + break; + + var match = SeedColumnLineRegex().Match(line); + if (!match.Success) + continue; + + var size = match.Groups["size"].Value.Trim(); + columns[match.Groups["name"].Value] = size.Length == 0 + ? null + : size.Equals("MAX", StringComparison.OrdinalIgnoreCase) + ? -1 + : int.TryParse(size, out var length) && match.Groups["type"].Value.Contains("char", StringComparison.OrdinalIgnoreCase) + ? length + : null; + } + + return columns; + } + + [GeneratedRegex(@"^\s*CREATE\s+TABLE\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex CreateTableLineRegex(); + + [GeneratedRegex(@"^\s*(CONSTRAINT\b|\))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex SeedColumnEndRegex(); + + [GeneratedRegex(@"^\s*\[(?[^\]]+)\]\s+\[?(?[A-Za-z0-9]+)\]?\s*(?:\(\s*(?[^)]*)\))?", RegexOptions.CultureInvariant)] + private static partial Regex SeedColumnLineRegex(); + public static bool TryConvert(string sqlServerScript, out string postgreSqlScript) { postgreSqlScript = string.Empty; diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs index 06c32623..5568b58d 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs @@ -39,8 +39,6 @@ public static class SetupMigrationRunner /// Alt surece seed klasorunu bildiren ortam degiskeni (App:SeedsPath karsiligi). private const string SeedsPathEnvironmentVariable = "App__SeedsPath"; - private const string BuildStampFileName = "setup-migrator.stamp"; - /// /// Kaynak projeden çalışırken derleme çıktısını eskiten dosyalar. Seed klasörü çalışma anında /// okunduğu için burada değildir. @@ -115,7 +113,7 @@ public static class SetupMigrationRunner { if (command!.ProjectPath is not null) { - var migratorDll = await PrepareSourceMigratorAsync(command.ProjectPath, stream, token); + var migratorDll = await FindSourceMigratorAsync(command.ProjectPath, stream); if (migratorDll is null) { await stream.DoneAsync("Failed."); @@ -183,61 +181,28 @@ public static class SetupMigrationRunner } /// - /// Kaynak projeden çalışırken migrator DLL'ini hazırlar. dotnet run her seferinde - /// çıktısını gizleyerek derler; değişiklik yokken bile bu ~25 sn sessiz bekleme demektir. - /// DLL kaynaklardan yeniyse derleme atlanır, değilse derleme çıktısı kurulum loguna akar. + /// Kaynak projeden çalışırken var olan derleme çıktısını kullanır. Migrate derleme yapmaz: istek + /// sırasında restore/build hem süreyi uzatır hem de çalışan Host'un kilitlediği dosyalarla + /// çakışır. Derleme geliştiricinin işidir; DLL kaynaklardan eskiyse yalnızca uyarılır. /// - private static async Task PrepareSourceMigratorAsync( - string projectPath, - SetupEventStream stream, - CancellationToken token) + private static async Task FindSourceMigratorAsync(string projectPath, SetupEventStream stream) { - // MSBuild değişmeyen çıktıyı yeniden yazmaz; derlemeye girmeyen bir dosya değiştiğinde DLL'in - // tarihi eski kalır ve her çalıştırma yeniden derlerdi. Son başarılı derlemenin anı damgadadır. - var stampPath = Path.Combine(projectPath, "obj", BuildStampFileName); - var migratorDll = FindBuiltMigrator(projectPath); - if (migratorDll is not null && IsUpToDate(projectPath, LastBuildTimeUtc(migratorDll, stampPath))) - { - await stream.InfoAsync("DbMigrator is up to date, build skipped."); - return migratorDll; - } - - await stream.InfoAsync("Building DbMigrator..."); - - var exitCode = await RunProcessAsync( - new MigratorCommand("dotnet", $"build \"{projectPath}\" -nologo -v:m", projectPath), - null, - stream, - token); - - if (exitCode != 0) - { - await stream.ErrorAsync($"DbMigrator build failed. Exit code: {exitCode}"); - return null; - } - - migratorDll = FindBuiltMigrator(projectPath); if (migratorDll is null) { - await stream.ErrorAsync($"DbMigrator build output not found under {projectPath}."); + await stream.ErrorAsync($"DbMigrator build output not found. Build it first: dotnet build \"{projectPath}\""); return null; } - await File.WriteAllTextAsync(stampPath, string.Empty, token); + if (!IsUpToDate(projectPath, File.GetLastWriteTimeUtc(migratorDll))) + { + await stream.SendAsync(SetupEventLevels.Warn, + "DbMigrator sources changed after its last build; running the existing build. Rebuild DbMigrator to apply code changes."); + } return migratorDll; } - private static DateTime LastBuildTimeUtc(string migratorDll, string stampPath) - { - var builtAt = File.GetLastWriteTimeUtc(migratorDll); - - return File.Exists(stampPath) && File.GetLastWriteTimeUtc(stampPath) > builtAt - ? File.GetLastWriteTimeUtc(stampPath) - : builtAt; - } - /// /// Debug derleme çıktısındaki migrator DLL'i; birden çok hedef çerçeve klasörü varsa en yenisi /// (eski çerçevenin kalıntısı seçilmez). @@ -421,7 +386,7 @@ public static class SetupMigrationRunner command = dllFiles.Length > 0 // Publish çıktısı — SDK gerekmez. ? new MigratorCommand("dotnet", $"\"{dllFiles[0]}\" {extraArgs}", migratorPath) - // Kaynak proje klasörü — DLL çalıştırmadan önce PrepareSourceMigratorAsync ile hazırlanır. + // Kaynak proje klasörü — derlenmiş DLL FindSourceMigratorAsync ile bulunur, derlenmez. : new MigratorCommand("dotnet", extraArgs, migratorPath, migratorPath); return true; } diff --git a/claude.md b/claude.md index f885cce4..e28d3388 100644 --- a/claude.md +++ b/claude.md @@ -338,9 +338,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → - **Talep analizi** → `Description` talebin yazıldığı hâliyle kalır; MCP ona yazmaz. Analiz ve üretim notları `Analysis` kolonundadır (Todo panosunda `AnalysisExpr`, Açıklama yanında sekme). Durum sırası `Backlog → Analysis → Ready to Start → …`: "Analiz et" `save_dev_request_analysis` - ile yalnızca `Backlog`/`Analysis` talebini analiz edip `Analysis`'e alır (ekran/tablo üretmez), - analizi `menuCode` dalının modül dokümanı bölümüne ("2. Analiz ve kararlar") de yazar — menüde ilk - talepse kök menünün dosyası (`App.Inventory.md`) ve dal bölümü burada açılır, elle yazılmış alt bölüme dokunulmaz; + ile yalnızca `Backlog`/`Analysis` talebini analiz edip `Analysis`'e alır (ekran/tablo üretmez, + modül dokümanına yazmaz — dosya ve dal bölümü üretimde, deploy ile açılır; onaylanan analiz bölümün + "2. Analiz ve kararlar" alt bölümüne üretimde `save_module_doc` ile yazılır); `Ready to Start`'a kullanıcı taşır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir. Analiz standardı tek yerdedir: `lowcode.instructions.md` §0.12; prompt'lar oraya yönlendirir. - **MCP şema araçları** → tablo/sütun bilgisi canlı katalogdan değil `Sas_H_DbTableColumn` @@ -362,7 +362,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → yetkisini aşan bir yetenek (ham SQL gibi) `McpToolNames.RequiredPermissions`'a uygulamadaki karşılık gelen yetkiyle eklenir. `create_table` bu yüzden `.Write`'a ek olarak `App.SqlQueryManager` ister; tablo scriptini tasarımcının `generateCreateTableSql` çıktısıyla aynı biçimde üretir - (`McpTableCreator`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir. Tasarımcı + (`McpTableCreator`) ve **veritabanında çalıştırmaz** — MCP veritabanı kurmaz, tabloyu da ekran gibi + kullanıcının çalıştırdığı DB Migrate kurar; wizard deploy'u kurulmamış tablonun kolonlarını seed + scriptinden okur (`PostgreSqlTableScript.ReadSeedColumns`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir. Tasarımcı (`SaveTableScriptAsync`) ve `create_table` T-SQL dosyanın PostgreSQL eşini `PostgreSqlTableScript` ile üretip `postgres/object/` altına yazar; script biçimine yeni bir deyim eklenirse çevirisi de eklenir, yoksa PG eşi yazılmaz (log uyarısı). Düşürülen nesnenin dosyası iki klasörden de silinir. diff --git a/configs/seeds/data/App.DevRequests/53294e77-a4fb-45e4-a80e-acf07b5d75be.json b/configs/seeds/data/App.DevRequests/53294e77-a4fb-45e4-a80e-acf07b5d75be.json index 7e9770ce..d1f51f79 100644 --- a/configs/seeds/data/App.DevRequests/53294e77-a4fb-45e4-a80e-acf07b5d75be.json +++ b/configs/seeds/data/App.DevRequests/53294e77-a4fb-45e4-a80e-acf07b5d75be.json @@ -17,7 +17,7 @@ "Assignees": "system@sozsoft.com", "Priority": null, "Completed": false, - "SortOrder": 1, + "SortOrder": 0, "Timesheets": null, "SubTasks": null, "Analysis": null diff --git a/configs/seeds/data/App.DevRequests/5d11851e-5f00-4a0b-b5a3-b8211e56c8b5.json b/configs/seeds/data/App.DevRequests/5d11851e-5f00-4a0b-b5a3-b8211e56c8b5.json index 09a8b5cb..1cb49726 100644 --- a/configs/seeds/data/App.DevRequests/5d11851e-5f00-4a0b-b5a3-b8211e56c8b5.json +++ b/configs/seeds/data/App.DevRequests/5d11851e-5f00-4a0b-b5a3-b8211e56c8b5.json @@ -9,9 +9,9 @@ "TenantId": null, "UserName": "system@sozsoft.com", "RequestDate": null, - "Status": "Backlog", + "Status": "Testing", "Title": "Inventory -> Tanımlamalar", - "Description": "

Ürünler : Ürün listesi ve detayı aşağıdaki örneklendirilmiştir. Ürünler Satış, Masraflar, Satınalma olarak 3 seçeneği var. Satış seçerse sadece Satış siparişlerinde görünür. Satınalma seçerse Satınalınabilir anlamına gelir. Ürün Türü Mal veya Hizmet olabilir. Envanter Takibi yapılıp yapılmayacağı seçilebilir. Eğer takip edilecekse Seri No, Lot Numarası veya Miktara göre seklinden seçilecek.


Nitelikler ve Varyantlar seçeneğinde yazılabilecek değerler Nitelik olarak tanımlanmalı. İçerisindeki değerleri de tanımlanabilir. Nitelik seçilip değerleri birden fazla şekilde yapılabilir. Eğer nitelikli bir ürün tanımlanırsa o varyantlı ürün listeler de seçilecektir.


Satınalma kısmında o ürünü hangi tedarikçiden hangi bedele aldığımızı belirtiyoruz.







Reçeteler : Ürünlerin hammadde ve yarı mamülerin ve tüketim miktarlarının belirlendiği kısımdır. Ayrıca tanımlanan operasyonların tanımlandığı listedir. Hangi bileşenlerin hangi operasyon ile tüketildiği belirlenecektir.

Operasyonlar : Inventory -> Tanımlamalar menüsünde yer alacaktır.

İş Merkezi tanımlamaları : Inventory -> Tanımlamalar menüsünde yer alacaktır.


Eğer nitelikli bir ürün tanımlanırsa o zaman Varyantlı liste oluşur ve tüm sistem varyantlı seçeneker çalışır. Eğer nitelik tanımlamazsa ana ürün seçilecektir.

Ürün reçetesine genel bakış raporu bu ERP ssitemlerdeki BOM ile aynı




", + "Description": "

Ürünler : Ürün listesi ve detayı aşağıdaki örneklendirilmiştir. Ürünler Satış, Masraflar, Satınalma olarak 3 seçeneği var. Satış seçerse sadece Satış siparişlerinde görünür. Satınalma seçerse Satınalınabilir anlamına gelir. Ürün Türü Mal veya Hizmet olabilir. Envanter Takibi yapılıp yapılmayacağı seçilebilir. Eğer takip edilecekse Seri No, Lot Numarası veya Miktara göre seklinden seçilecek.


Nitelikler ve Varyantlar seçeneğinde yazılabilecek değerler Nitelik olarak tanımlanmalı. İçerisindeki değerleri de tanımlanabilir. Nitelik seçilip değerleri birden fazla şekilde yapılabilir. Eğer nitelikli bir ürün tanımlanırsa o varyantlı ürün listeler de seçilecektir.


Satınalma kısmında o ürünü hangi tedarikçiden hangi bedele aldığımızı belirtiyoruz. Ürün ve reçetede kullanılan Birim sütunun Adm_T_Uom tablosu ile ilişkili olmalı. Adm_T_Uom tablosundaki Id bilgisi nvarchar(64)







Reçeteler : Ürünlerin hammadde ve yarı mamülerin ve tüketim miktarlarının belirlendiği kısımdır. Ayrıca tanımlanan operasyonların tanımlandığı listedir. Hangi bileşenlerin hangi operasyon ile tüketildiği belirlenecektir.

Operasyonlar : Inventory -> Tanımlamalar menüsünde yer alacaktır.

İş Merkezi tanımlamaları : Inventory -> Tanımlamalar menüsünde yer alacaktır.


Eğer nitelikli bir ürün tanımlanırsa o zaman Varyantlı liste oluşur ve tüm sistem varyantlı seçeneker çalışır. Eğer nitelik tanımlamazsa ana ürün seçilecektir.

Ürün reçetesine genel bakış raporu bu ERP ssitemlerdeki BOM ile aynı. Reçetelerin malzemelerinde recursive şekilde iç içe kullanılan diğer malzemeler ve miktarların hesaplanması gerekiyor.




", "DueDate": "2026-10-06T21:02:00", "Tags": "IN", "Assignees": "system@sozsoft.com", @@ -20,7 +20,7 @@ "SortOrder": 0, "Timesheets": null, "SubTasks": null, - "Analysis": null + "Analysis": "" } ] } diff --git a/configs/seeds/data/App.DevRequests/94639b65-1bb7-454a-ae73-3e1c5c9fe23a.json b/configs/seeds/data/App.DevRequests/94639b65-1bb7-454a-ae73-3e1c5c9fe23a.json index 32219684..b2822d8e 100644 --- a/configs/seeds/data/App.DevRequests/94639b65-1bb7-454a-ae73-3e1c5c9fe23a.json +++ b/configs/seeds/data/App.DevRequests/94639b65-1bb7-454a-ae73-3e1c5c9fe23a.json @@ -17,7 +17,7 @@ "Assignees": null, "Priority": null, "Completed": false, - "SortOrder": 2, + "SortOrder": 1, "Timesheets": null, "SubTasks": null, "Analysis": null diff --git a/ui/src/views/list/TodoBoard.tsx b/ui/src/views/list/TodoBoard.tsx index dd951687..485d807f 100644 --- a/ui/src/views/list/TodoBoard.tsx +++ b/ui/src/views/list/TodoBoard.tsx @@ -211,9 +211,7 @@ const TodoBoard = (props: TodoBoardProps) => { // Alt form olarak açıldığında tanım dışarıdan gelmez; kendisi yükler. const gridDto = useListFormGridDto(listFormCode, extGridDto) - return gridDto ? ( - - ) : null + return gridDto ? : null } const TodoBoardContent = ({ @@ -262,11 +260,14 @@ const TodoBoardContent = ({ const loadLookupDisplayValuesRef = useRef(loadLookupDisplayValues) getBandedColumnsRef.current = getBandedColumns loadLookupDisplayValuesRef.current = loadLookupDisplayValues - const listFormColumnsCacheRef = useRef<{ - gridDto?: GridDto - listFormCode: string - columns: GridColumnData[] - } | undefined>(undefined) + const listFormColumnsCacheRef = useRef< + | { + gridDto?: GridDto + listFormCode: string + columns: GridColumnData[] + } + | undefined + >(undefined) if ( !listFormColumnsCacheRef.current || listFormColumnsCacheRef.current.gridDto !== gridDto || @@ -998,6 +999,26 @@ const TodoBoardContent = ({ } } + // Dinleyici kayit acikken bir kez baglanir; taslagin guncel halini ref uzerinden okur. + const saveTodoRef = useRef(saveTodo) + saveTodoRef.current = saveTodo + const canSaveDraft = + canUpdate && !saving && Boolean(String(draft[options.titleExpr ?? ''] ?? '').trim()) + const canSaveDraftRef = useRef(canSaveDraft) + canSaveDraftRef.current = canSaveDraft + const hasSelected = Boolean(selected) + + useEffect(() => { + if (!hasSelected || !canUpdate) return + const handleKeyDown = (event: KeyboardEvent) => { + if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== 's') return + event.preventDefault() + if (canSaveDraftRef.current) void saveTodoRef.current() + } + window.addEventListener('keydown', handleKeyDown, true) + return () => window.removeEventListener('keydown', handleKeyDown, true) + }, [hasSelected, canUpdate]) + const updateTodoInline = async (row: TodoRow, values: Record) => { if (!canUpdate) return const updated = { ...row, ...values } @@ -1055,7 +1076,9 @@ const TodoBoardContent = ({ ? { key: 'subtask' as DetailTab, label: translate('::App.ListFormTodoBoard.SubTasks') } : undefined, ].filter(Boolean) as Array<{ key: DetailTab; label: string }> - const activeTab = detailTabs.some((tab) => tab.key === detailTab) ? detailTab : detailTabs[0]?.key + const activeTab = detailTabs.some((tab) => tab.key === detailTab) + ? detailTab + : detailTabs[0]?.key const timesheetRows = parseJsonRows(fieldValue(draft, options.timesheetExpr)) const subTaskRows = parseJsonRows(fieldValue(draft, options.subTaskExpr)) const timesheetTotal = timesheetRows.reduce( @@ -1066,39 +1089,18 @@ const TodoBoardContent = ({ return (
{renderToolbar(true)} -
- {editingOptions?.allowUpdating === true && !gridDto.gridOptions.updateServiceAddress && ( - - {translate('::App.ListFormTodoBoard.EditRequiresUpdateService')} - - )} - {options.statusExpr && ( -
- {statuses.map((status) => { - const isActiveStatus = String(draft[options.statusExpr!] ?? '') === status - return ( - - ) - })} -
- )} -
+ {editingOptions?.allowUpdating === true && !gridDto.gridOptions.updateServiceAddress && ( +
+ {translate('::App.ListFormTodoBoard.EditRequiresUpdateService')} +
+ )}
{options.titleExpr && (
- + - + {translate('::App.ListFormTodoBoard.NoRows')} @@ -1399,7 +1404,9 @@ const TodoBoardContent = ({ type="date" className={cellInputClass} disabled={!canUpdate} - value={row.dueDate ? dayjs(row.dueDate).format('YYYY-MM-DD') : ''} + value={ + row.dueDate ? dayjs(row.dueDate).format('YYYY-MM-DD') : '' + } onChange={(event) => updateJsonRow(options.subTaskExpr, subTaskRows, index, { dueDate: event.target.value || null, @@ -1428,7 +1435,10 @@ const TodoBoardContent = ({ ))} {!subTaskRows.length && ( - + {translate('::App.ListFormTodoBoard.NoRows')} @@ -1460,205 +1470,237 @@ const TodoBoardContent = ({
)}
- + {(canUpdate || canDelete) && ( +
+
+ {canDelete && ( + + )} +
+ {canUpdate && ( +
+ + + +
+ )}
)}
- )} +
) } @@ -1715,180 +1757,182 @@ const TodoBoardContent = ({ ) }} > -
-
- { - event.stopPropagation() - event.dataTransfer.effectAllowed = 'move' - dragActiveRef.current = true - setDraggedKey(undefined) - setDraggedColumn(status) - }} - onDragEnd={clearDragState} - > - - - {editingStatus === status ? ( -
{ - event.preventDefault() - renameStatus(status) +
+
+ { + event.stopPropagation() + event.dataTransfer.effectAllowed = 'move' + dragActiveRef.current = true + setDraggedKey(undefined) + setDraggedColumn(status) }} + onDragEnd={clearDragState} > - { - setStatusName(event.target.value) - setStatusError('') + + + {editingStatus === status ? ( + { + event.preventDefault() + renameStatus(status) }} - onKeyDown={(event) => { - if (event.key === 'Escape') { - setEditingStatus(undefined) + > + { + setStatusName(event.target.value) setStatusError('') - } - }} - /> - - - - ) : ( - <> -

- {statusText(status)} -

- {canUpdate && ( + }} + onKeyDown={(event) => { + if (event.key === 'Escape') { + setEditingStatus(undefined) + setStatusError('') + } + }} + /> + - )} - - )} - - {items.length} - - {canAdd && ( - - )} - {canDelete && ( - - )} -
- {editingStatus === status && statusError && ( -

{statusError}

- )} - {statusNotice?.status === status && ( -
- {statusNotice.message} - -
- )} -
-
-
- {isAdding && ( -
-
- setNewTodoTitle(event.target.value)} - onKeyDown={(event) => { - if (event.key === 'Enter') addTodo(status) - if (event.key === 'Escape') setNewTodoStatus(undefined) + + ) : ( + <> +

+ {statusText(status)} +

+ {canUpdate && ( + + )} + + )} + + {items.length} + + {canAdd && ( + + )} + {canDelete && ( + )} +
+ {editingStatus === status && statusError && ( +

{statusError}

+ )} + {statusNotice?.status === status && ( +
+ {statusNotice.message} +
+ )} +
+
- )} + {isAdding && ( +
+
+ setNewTodoTitle(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') addTodo(status) + if (event.key === 'Escape') setNewTodoStatus(undefined) + }} + /> + + +
+
+ )}
{items.map((row, index) => {