TodoBoard kısmına Analiz sütunu eklendi

This commit is contained in:
Sedat ÖZTÜRK 2026-09-28 12:28:56 +03:00
parent a0f359df67
commit a2c3655942
33 changed files with 1065 additions and 138 deletions

View file

@ -414,12 +414,20 @@ SELECT 'Onaylı', COUNT(*), 'green', 'FaCheck' FROM Mrp_T_Order WHERE Status = 2
Sorgu ekranın bağlantısında (`IsTenant` + `DataSourceCode` ile çözülen, `lowcode` §9.3.3), Sorgu ekranın bağlantısında (`IsTenant` + `DataSourceCode` ile çözülen, `lowcode` §9.3.3),
**olduğu gibi** çalıştırılır: **olduğu gibi** çalıştırılır:
- Ekranın otomatik tenant/şube/OU filtresi **uygulanmaz**. - Ekranın otomatik tenant/şube/OU filtresi **uygulanmaz**; kırılım sorguya yazılır.
- `@USERID` / `@TENANTID` gibi token'lar **çözülmez** — bunlar `*FieldsDefaultValueJson` ve - Sorgu `DefaultFilter` ile aynı token çözümünden geçer (`DefaultValueHelper.GetDefaultValue`):
`DefaultFilter` yolunun sözleşmesidir. Widget sorgusuna yazılırsa bağlanmamış parametre `@USERID`, `@USERNAME`, `@DATE`, `@NOW` ve `@TENANTID` metin olarak yerine konur.
hatası verir. Kiracı kırılımı gerekiyorsa ya kiracıya özel bir `DataSource` kullan, ya da Kiracı tablosunda (`IsTenant: true`) her karta `"TenantId" = '@TENANTID'` yaz; host'ta bu
kırılımı sorgunun okuduğu view'ın içine göm. `"TenantId" IS NULL`'a döner, yani kartlar liste ekranıyla aynı kümeyi sayar. Yazılmazsa ortak
- Soft delete'li tabloda `"IsDeleted" = 'false'` koşulunu sorguya kendin yaz. veritabanında kartlar bütün kiracıların toplamını gösterir.
- `IsDeleted` koşulu widget sorgusuna yazılmaz (platform varsayılanı).
- Wizard / MCP ile üretilen List ekranı sorulmadan en az 3 kartla gelir; kart seçimi, istisnalar
ve metin/renk kuralı: `lowcode` §4.6.1.
- Seeder'da şerit `DefaultWidgetsJson(tablo, listFormName, tenant, params WidgetCard[])` ile
kurulur (`ListFormSeeder_DefaultJsons`): kart başına `Title` (dil anahtarı), renk, ikon, ek koşul
ve isteğe bağlı `Value` ifadesi; alt başlık ekranın adıdır, değer tip çakışmasın diye metne
çevrilir. Gruplamalı sorgu için `DefaultWidgetsJson(sql, kartSayısı)`. Genel kart başlıkları
`App.Widget.*` dil anahtarlarındadır.
### 5.4 `LayoutJson` (`LayoutDto`) ### 5.4 `LayoutJson` (`LayoutDto`)
@ -547,7 +555,7 @@ yazılırsa düşer), `AllowTaskAdding`, `AllowTaskUpdating`, `AllowTaskResource
### 7.4 `TodoOptionJson` (`TodoOptionDto`) — Kanban ### 7.4 `TodoOptionJson` (`TodoOptionDto`) — Kanban
`TitleExpr`*, `StatusExpr`*, `DescriptionExpr`, `DueDateExpr`, `TagExpr`, `AssigneeExpr`, `TitleExpr`*, `StatusExpr`*, `DescriptionExpr`, `AnalysisExpr` (detayda Açıklama yanında HTML sekmesi), `DueDateExpr`, `TagExpr`, `AssigneeExpr`,
`PriorityExpr`, `CompletedExpr`, `OrderExpr`, `TimesheetExpr`, `SubTaskExpr`, `PriorityExpr`, `CompletedExpr`, `OrderExpr`, `TimesheetExpr`, `SubTaskExpr`,
`StatusOrder` (kolon sırası, virgüllü), `AllowDragging` (varsayılan `true`). `StatusOrder` (kolon sırası, virgüllü), `AllowDragging` (varsayılan `true`).
@ -1249,7 +1257,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 › 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 › 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. | | **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` → `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). | | **Dev Requests** | `App.DevRequests` | `Sas_H_DevRequest` | host | **Kanban/Todo düzeninin** referansı (`DefaultLayout: todo`): `Status`, `Priority`, `SortOrder`, `Tags`, `Assignees`, `SubTasks`, `Timesheets`; detayda `Description` yanında `Analysis` sekmesi (`AnalysisExpr`). MCP geliştirme döngüsünün kuyruğudur (`Backlog` → `Analysis` MCP analiz eder, `Ready to Start`'a kullanıcı alır; `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, analiz `save_dev_request_analysis` ile yazılır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir — `Description` talebin yazıldığı hâliyle kalır; `lowcode.instructions.md` §0.11–§0.12). |
| **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ğı. | | **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. | | **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. | | **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. |
@ -1446,7 +1454,7 @@ içinde ilgili `#region` altındadır.
| Tek kayıtlık form ekranı | `Licence` (`ListFormType = Form` + alt kalem listesi) | | Tek kayıtlık form ekranı | `Licence` (`ListFormType = Form` + alt kalem listesi) |
| Onay akışı | `Sales Orders` — `WorkflowJson` (`ApprovalUserFieldName`, `ApprovalStatusFieldName`, `ApprovalDateFieldName`, `ApprovalDescriptionFieldName`) + `ListFormWorkflow` düğümleri (Start → Approval → End/End) | | Onay akışı | `Sales Orders` — `WorkflowJson` (`ApprovalUserFieldName`, `ApprovalStatusFieldName`, `ApprovalDateFieldName`, `ApprovalDescriptionFieldName`) + `ListFormWorkflow` düğümleri (Start → Approval → End/End) |
| Alt form (master-detail) | `Tenant → Branches`, `Sales Orders → Sales Order Item`, `Survey → Question → Option` | | Alt form (master-detail) | `Tenant → Branches`, `Sales Orders → Sales Order Item`, `Survey → Question → Option` |
| KPI widget | `User`, `Dynamic Services`, `Custom Components` — `WidgetsJson`; tek sorgu `UNION ALL` ile birden çok kart döndürür (`Title`, `Value`, `Color`, `SubTitle`, `Icon`) | | KPI widget | Saas ve Administration'daki yönetim ekranlarının çoğu (Users, Tenants, Dynamic Services, Custom Components, Audit Logs, Bildirimler, Duyuru/Anket/Etkinlik, Forum…; basit tanım listeleri hariç) — `WidgetsJson`; tek sorgu `UNION ALL` ile birden çok kart döndürür (`Title`, `Value`, `Color`, `SubTitle`, `Icon`) |
| Toolbar'dan diyalog açma | `Tenant` (`TenantsConnectionString`), `Role` (`RolesPermission`), `User` (`UsersPermission`), `Audit Logs` (`AuditLogDetail`), `Notification Rule` (`CreateNotification`), `Branches` (`BranchSeed`), `Sales Orders` (`CreateTenantFromOrderDialog`), `Dynamic Services` (`DynamicServiceEditor` — hem toolbar hem satır) | | Toolbar'dan diyalog açma | `Tenant` (`TenantsConnectionString`), `Role` (`RolesPermission`), `User` (`UsersPermission`), `Audit Logs` (`AuditLogDetail`), `Notification Rule` (`CreateNotification`), `Branches` (`BranchSeed`), `Sales Orders` (`CreateTenantFromOrderDialog`), `Dynamic Services` (`DynamicServiceEditor` — hem toolbar hem satır) |
| Yazmayı servise devretme | `Tenant`, `Role`, `User` — `*ServiceAddress = "list-form-dynamic-api/{tenant\|role\|user}-{insert\|update}"`; `Notification` — `notification-insert` (çoklu değer `MultiValueStringJsonConverter`) | | Yazmayı servise devretme | `Tenant`, `Role`, `User` — `*ServiceAddress = "list-form-dynamic-api/{tenant\|role\|user}-{insert\|update}"`; `Notification` — `notification-insert` (çoklu değer `MultiValueStringJsonConverter`) |
| Ekle düğmesini diyaloğa bağlama | `Dynamic Services` — `EditingOptionJson.AddDialogName = "DynamicServiceEditor"` | | Ekle düğmesini diyaloğa bağlama | `Dynamic Services` — `EditingOptionJson.AddDialogName = "DynamicServiceEditor"` |

View file

@ -251,6 +251,7 @@ bildirilir:
| Konu | Neden sorulmaz | | Konu | Neden sorulmaz |
| --- | --- | | --- | --- |
| Tenant + audit kolonları (`Id`, `TenantId`, `CreationTime`, `CreatorId`, `LastModificationTime`, `LastModifierId`, `IsDeleted`, `DeletionTime`, `DeleterId`) | Yeni tabloda varsayılan; kullanıcı "audit yok" demedikçe eklenir | | Tenant + audit kolonları (`Id`, `TenantId`, `CreationTime`, `CreatorId`, `LastModificationTime`, `LastModifierId`, `IsDeleted`, `DeletionTime`, `DeleterId`) | Yeni tabloda varsayılan; kullanıcı "audit yok" demedikçe eklenir |
| Tanım tablosunda `IsActive` | Yeni **tanım** tablosunda varsayılan (aşağıda); kullanıcı "aktif/pasif yok" demedikçe eklenir |
| Metinlerin EN + TR olması, dil anahtarı üzerinden verilmesi | Platform sözleşmesi | | Metinlerin EN + TR olması, dil anahtarı üzerinden verilmesi | Platform sözleşmesi |
| İsimlendirme (`ListFormCode`, menü kodu, yetki adları, dosya adları) | §2'den türer | | İsimlendirme (`ListFormCode`, menü kodu, yetki adları, dosya adları) | §2'den türer |
| Seed dosyası yazılıp yazılmayacağı | Her artefakt seed'lenir (§0.2) | | Seed dosyası yazılıp yazılmayacağı | Her artefakt seed'lenir (§0.2) |
@ -258,6 +259,7 @@ bildirilir:
| Parametreli SQL kullanımı, yetki kontrolü, tenant izolasyonu | Tartışmaya açık değil | | Parametreli SQL kullanımı, yetki kontrolü, tenant izolasyonu | Tartışmaya açık değil |
| Depoda okunabilen tablo/kolon/menü/yetki adları | Önce ara, bulduğunu **doğrulat**, sıfırdan sorma | | Depoda okunabilen tablo/kolon/menü/yetki adları | Önce ara, bulduğunu **doğrulat**, sıfırdan sorma |
| Var olan bir tanım tablosunun (ülke, para birimi, departman, ünvan, birim) yeniden üretilip üretilmeyeceği | Üretilmez; mevcut tabloya lookup bağlanır (reference §12.5) | | Var olan bir tanım tablosunun (ülke, para birimi, departman, ünvan, birim) yeniden üretilip üretilmeyeceği | Üretilmez; mevcut tabloya lookup bağlanır (reference §12.5) |
| Liste ekranının üstündeki KPI kartları (`Widgets`) | List ekranı en az 3 kartla gelir, kartlar tablonun durum/bayrak/tarih/tutar kolonlarından seçilir (§4.6.1) |
Buna karşılık **her zaman sorulacak** üç şey vardır, çünkü yanlış varsayım geri dönülmez sonuç Buna karşılık **her zaman sorulacak** üç şey vardır, çünkü yanlış varsayım geri dönülmez sonuç
üretir: **hangi tabloya yazılacağı**, **kimin onaylayacağı** (onay süreci varsa) ve üretir: **hangi tabloya yazılacağı**, **kimin onaylayacağı** (onay süreci varsa) ve
@ -503,11 +505,12 @@ 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 | | `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 | | `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` | | `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` + `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 | | `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 — en az 3 kart, kiracı tablosunda `"TenantId" = '@TENANTID'` (§4.6.1). İ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) | | `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) | | `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 | | `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 |
| `append_dev_request_note` | Talep açıklamasının sonuna HTML ekler | Sonuç notu için; `Description`'ı `update_row` ile yazma (§0.11) | | `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 | 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) | | `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` | | `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. `passed` yalnızca engelleyici bir kontrol kalırsa `false` |
| `list_components` · `get_component` · `save_component` · `delete_component` | Custom component okuma/yazma (ada göre upsert) | `save_component` visual designer bileşenini reddeder — yalnız Code modu (§7.1.1) | | `list_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) |
@ -554,10 +557,17 @@ talep girer (`App.DevRequest`). Döngüyü `/mcp`'ye bağlı bir **host** kullan
kiracı bağlamında geliştirme araçları kapalıdır. Gereken yetkiler: `App.DevRequests.Update`, kiracı bağlamında geliştirme araçları kapalıdır. Gereken yetkiler: `App.DevRequests.Update`,
`App.DeveloperKit.McpServers.Invoke` + `.Write` ve üretimin kendi yetkileri (§0.10). `App.DeveloperKit.McpServers.Invoke` + `.Write` ve üretimin kendi yetkileri (§0.10).
İki giriş vardır; ikisi de istemcide hazır prompt (komut) olarak görünür (`McpPromptNames`): Talep yaşam döngüsü: `Backlog` → **`Analysis`** (MCP analiz eder, §0.12) → `Ready to Start`
(analizi beğenen kullanıcı taşır; beğenmezse analiz yeniden yazılır ya da elle düzeltilir) →
`In Progress` → `Testing` → `Staging`. Üretim `Ready to Start`'tan başlar ve talebin
`Description`'ı ile onaylanmış `Analysis`'i birlikte tarif sayılır.
Girişler istemcide hazır prompt (komut) olarak görünür (`McpPromptNames`):
| Prompt | Kapsam | | Prompt | Kapsam |
| --- | --- | | --- | --- |
| `analyze_dev_request` (`request`: Id ya da tam başlık) | Yalnızca o talebi analiz eder (§0.12); hiçbir şey üretmez |
| `analyze_backlog_requests` | `Backlog` durumundaki bütün talepler, `SortOrder` sırasıyla analiz edilir |
| `process_dev_request` (`request`: Id ya da tam başlık) | Yalnızca o talep. Talep başlatılamazsa hiçbir şey üretilmez, başka talebe geçilmez | | `process_dev_request` (`request`: Id ya da tam başlık) | Yalnızca o talep. Talep başlatılamazsa hiçbir şey üretilmez, başka talebe geçilmez |
| `process_ready_requests` | `Ready to Start` durumundaki bütün talepler, `SortOrder` sırasıyla; başlatılamayan talep atlanır | | `process_ready_requests` | `Ready to Start` durumundaki bütün talepler, `SortOrder` sırasıyla; başlatılamayan talep atlanır |
@ -565,6 +575,8 @@ Kullanıcı prompt yerine düz metin de yazabilir; aynı anlama gelir:
| Kullanıcı yazar | Karşılığı | | Kullanıcı yazar | Karşılığı |
| --- | --- | | --- | --- |
| `<Id ya da "başlık"> talebini analiz et` | `analyze_dev_request` — yalnızca o talep |
| `Analiz et` · `Backlog'daki talepleri analiz et` | `analyze_backlog_requests` |
| `<Id ya da "başlık"> talebini işle` | `process_dev_request` — yalnızca o talep | | `<Id ya da "başlık"> talebini işle` | `process_dev_request` — yalnızca o talep |
| `Ready to Start durumundaki talepleri sırayla işle` · `Ready olanları işle` | `process_ready_requests` | | `Ready to Start durumundaki talepleri sırayla işle` · `Ready olanları işle` | `process_ready_requests` |
| `Ready to Start durumundaki talepleri Id'leriyle listele` | Yalnızca adım 1 (`query_list`); hiçbir şey üstlenilmez, üretilmez | | `Ready to Start durumundaki talepleri Id'leriyle listele` | Yalnızca adım 1 (`query_list`); hiçbir şey üstlenilmez, üretilmez |
@ -581,7 +593,7 @@ 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` | — | | 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 | | 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 (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 | — | | 3. Üret | Analizin **Üretim planı** sırayla, aynen uygulanır (aşağıdaki kurallar) → `create_table` / `deploy_wizard` / `save_component`; demo veri varsa `SeedData: true` + `seedRows` (§0.10.1); bu dosyanın kuralları aynen geçerlidir | — |
| 4. Teslim | Sonuç kaydı + `update_row` → `Testing`. DB Migrate beklenmez; talep burada biter | Testing | | 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 | | 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 |
@ -590,9 +602,12 @@ talebin metnini döndürür. Başlık birden fazla talebe uyuyorsa Id ister. `St
verilerek (`"deploy_wizard: Mrp.Orders.json"`). Araç satırı tamamlanmış yazar, atanan kişiyi 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` bağlanan kullanıcı, bitiş tarihini bugün yapar ve var olan satırları korur. `SubTasks`
`update_row` ile yazılmaz. `update_row` ile yazılmaz.
- `Description` — `append_dev_request_note` ile sonuna `<h4>MCP</h4>` başlığıyla kısa özet: - `Analysis` — `append_dev_request_note` ile sonuna `<h4>MCP</h4>` başlığıyla kısa özet:
üretilenler ve DB Migrate bekleyen wizard'lar. `Description` hiçbir zaman `update_row` tablo, ekran, alan, demo veri, menü/yetki, analize eklenen varsayılanlar ve DB Migrate bekleyen
ile yazılmaz — base64 görselleri taşır ve yeniden yazmak onları siler. wizard'lar. Tekrar üretimde yeni bir `<h4>MCP</h4>` bloğu eklenir, eskisi silinmez.
- `Description` **talebin yazıldığı hâliyle kalır**; MCP ona hiçbir şey yazmaz. `Description` ve
`Analysis` hiçbir zaman `update_row` ile yazılmaz — açıklama base64 görselleri taşır ve yeniden
yazmak onları siler.
**Görseller.** Talebe yapıştırılan ekran görüntüleri açıklamada base64 durur; araç onları metinden **Görseller.** Talebe yapıştırılan ekran görüntüleri açıklamada base64 durur; araç onları metinden
ayıklar ve ayrı resim içeriği olarak verir, açıklamada yerlerinde `[image-N]` kalır. Kullanıcıdan ayıklar ve ayrı resim içeriği olarak verir, açıklamada yerlerinde `[image-N]` kalır. Kullanıcıdan
@ -600,12 +615,14 @@ 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). değildir) ve içindeki satırları taşır; satırlar demo veridir (§0.10.1).
**Kurallar** **Kurallar**
- Talep tarifin kendisidir: kullanıcıya soru sorulmaz, belirsiz noktada §0.6'nın önerilen - **Onaylanmış analiz tarifin kendisidir; üretim karar vermez.** Soru sorulmaz, seçim yapılmaz,
varsayılanı seçilir ve sonuç notuna yazılır. Döngü DB Migrate'i beklemez. analizin yazdığı hiçbir şey değiştirilmez ya da daraltılmaz. Bu dosyadaki "kullanıcıya sor"
- Talep §0.6 keşif turunu cevaplayacak kadar net değilse üretme; `Backlog`'a geri al ve eksik kuralları (onaycı, görselden okunamayan zorunlu alan, çerçeve dışı ihtiyaç) bu döngüde analizin
soruları `Description` sonuna yaz. Tabloya yazma hedefi, onaycı ve hesap formülü talepten **(onay)** kararlarıyla çözülmüştür. Analizde karşılığı olmayan küçük bir ayrıntı (§0.6
okunamıyorsa varsayılmaz. varsayılanı olan türden) varsayılanla tamamlanır ve sonuç notuna yazılır.
- Çerçeve dışı bir ihtiyaç (§0.8) çıkarsa talebi `In Progress`'te bırak ve kullanıcıya sor. - **Üretim tek seferde biter:** `In Progress` → `Testing` tek geçiştir; talep durdurulmaz,
`Analysis`'e geri alınmaz, kısmi ekran üretilmez. Üretilebilirlik analizde doğrulanmıştır
(§0.12). Döngü DB Migrate'i beklemez.
- Test için ekrana satır ekleme — SeedSync açık ekranda seed dosyasına iz bırakır. Talebin kendi demo verisi test satırı değildir, §0.10.1 ile eklenir. - 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. - MCP talebi `Staging`'e almaz: deploy yalnızca dosya yazar, ekran migrate'ten sonra vardır ve testi kullanıcı yapar.
@ -614,6 +631,77 @@ kullanıcısı Wizard Manager'daki DB Migrate'i (`/api/setup/migrate`, `Setup.Mi
migrate ekranları, menü/yetki/dil satırlarını ve demo verisini kurar, Redis'i temizler ve bütün 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. kiracıları dolaşır. Rapor bu adımı tek satırla belirtir.
### 0.12 Talep analizi — `Backlog` → `Analysis`
"Analiz et" dendiğinde `Backlog` talepleri üretimden önce analiz edilir; kullanıcı analizi okuyup
beğenirse talebi `Ready to Start`'a kendisi taşır, beğenmezse analiz değişir (MCP'ye yeniden
analiz ettirir ya da `Analysis` sekmesinde elle düzeltir). Analiz **üretim yapmaz**: tablo, wizard,
bileşen, seed dosyası yazılmaz; yalnızca okuma araçları ve `save_dev_request_analysis` kullanılır.
| Adım | Araç | Durum |
| --- | --- | --- |
| 1. Kuyruğu oku (yalnızca toplu) | `query_list` — `ListFormCode: 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 | Analysis |
**Durum kuralı sunucudadır:** araç yalnızca `Backlog` ya da `Analysis` durumundaki talebi kabul eder;
`Analysis`'teki talebe yeniden çağrılırsa analiz değiştirilir. `Ready to Start` ve sonrası
reddedilir. `Description`'a hiçbir koşulda yazılmaz.
**Analiz standardı.** HTML, aşağıdaki başlıklar bu sırayla (`<h4>`), boş kalan başlık yazılmaz.
Kısa ve karar odaklı yaz; talebi tekrar anlatma.
**Analiz kapalı uçludur ve üretilebilirdir.** Üretim analizi tek seferde, karar vermeden uygular
(§0.11); bu yüzden:
- Her konu tek bir kararla yazılır. Açık soru, seçenek listesi ("(a) … (b) …"), "istenir mi?",
"gerekirse", "ileride" bırakılmaz.
- Talebin açıkça yazdığı şey (menü yolu, alan, kapsam) **girdidir**: değiştirilmez, daraltılmaz,
alternatifi sunulmaz.
- Talepte olmayan bilgi için analiz karar verir (**Kararlar**, madde 8). Tabloya yazma hedefi,
onaycı ve hesap formülü de boş bırakılmaz: somut karar **(onay)** etiketiyle yazılır; talebi
`Ready to Start`'a alan kullanıcı onları onaylamış olur.
- Planın her adımının mevcut araçlarla (`create_table`, `deploy_wizard`, `save_component` ve
sözleşmeleri, §4.4) yapılabildiği keşifte doğrulanır (madde 10).
1. **Amaç** — talep neden istenmiş, kim kullanacak, hangi işi çözecek (bir-iki cümle).
2. **Görsel analizi** — talepte görsel varsa her biri için (`[image-N]`): ekran türü (liste, form,
kanban, grafik, public sayfa…), görünen alanlar ve tipleri, gruplar/sekmeler, butonlar/aksiyonlar,
filtreler, görünen satırlar (demo veri adayı, §0.10.1). Görsel yoksa başlık yazılmaz.
3. **Çözüm yolu** — karar sırasındaki adım (Wizard List / Form / Custom component / SQL + Custom
Endpoint / Dynamic Service) ve neden.
4. **Menü yeri** — ekranın menüde nerede duracağı, tek bakışta okunur biçimde. Önce tam yol tek
satırda (`Kök › Ara › Ekran`), sonra kökten ekrana her seviye bir satır:
| Seviye | Kod | Ad (TR / EN) | İkon | Durum |
| --- | --- | --- | --- | --- |
| Kök | … | … / … | … | Var / Yeni |
| Ara (varsa, her biri) | … | … / … | … | Var / Yeni |
| Ekran | `ListFormCode` | … / … | … | Yeni |
Altında `ModuleId`, izin grubu ve route yazılır. Var olan seviyeler `list_wizards` ve menü
seed'inden okunur, kodları oradaki gibi yazılır; sıra (`Order`) deploy'da hesaplanır, analize
yazılmaz. Talep menü yolunu yazıyorsa (başlıkta ya da açıklamada) o yol girdidir ve seviye
seviye aynen alınır.
5. **Veri modeli** — tablo adı (onek kuralı §2), yeni mi var olan mı; kolonlar (ad, tip, zorunluluk,
uzunluk), lookup kaynakları, kiracı + audit (standing default 1). Yeni her tablonun ilk satırı
**Tanım tablosu: Evet / Hayır** kararıdır, talepteki dayanağıyla (§8.1); atlanmaz. Evet ise
kolon tablosunda `IsActive` (bit, varsayılan 1, zorunlu) yer alır — talepte ya da görselde
olmasa da — ve madde 7 (form alanı + Aktif/Pasif widget'ı) ile madde 9 (`create_table` kolonu,
demo veride `IsActive: true`) bunu taşır.
6. **Etkilenen kısımlar** — uygulamada dokunulacak her yer: yeni/değişen ekranlar, alt formlar,
menü, yetkiler (`C/R/U/D` + varsa özel), dil anahtarları, widget'lar, iş akışı/bildirim, SEO (public
yol varsa), var olan ekranlara etkiler.
7. **Görünümler ve davranış** — Grid/Card/Pivot/Chart (standing default 12), widget kartları (§4.6.1),
`EditorScript` / `EditorOptions` ihtiyaçları, hesaplar ve doğrulamalar.
8. **Kararlar** — talepten okunamayıp analizde verilen her karar, tek satır gerekçesiyle (§0.6);
varsayılamayan konular **(onay)** etiketi taşır. "Açık sorular" başlığı yoktur.
9. **Üretim planı** — sıralı artefakt listesi (`create_table: …`, `deploy_wizard: ….json`,
`save_component: …`); üretimdeki `add_dev_request_subtasks` başlıkları bununla aynı olur.
10. **Üretime hazır** — tek satır: **Evet**, ya da **Hayır — eksik yetenek: …** (çerçeve dışı
ihtiyaç, §0.8, adıyla). Talebi daraltan geçici çözüm önerilmez; "Hayır" taşıyan talep yetenek
eklenene kadar `Ready to Start`'a alınmaz.
## 1. Hangi istek → hangi artefakt ## 1. Hangi istek → hangi artefakt
| İstek | Üretilecek artefakt | Bölüm | | İstek | Üretilecek artefakt | Bölüm |
@ -963,7 +1051,7 @@ atanmaz (admin dahil); ekranı görecek roller yetkiyi rol ekranından alır.
| `TreeOptionDto` | `KeyExpr`, `ParentIdExpr`; opsiyonel `HasItemsExpr`, `RootValue`, `AutoExpandAll`, `RecursiveSelection` | | `TreeOptionDto` | `KeyExpr`, `ParentIdExpr`; opsiyonel `HasItemsExpr`, `RootValue`, `AutoExpandAll`, `RecursiveSelection` |
| `GanttOptionDto` | `KeyExpr`, `ParentIdExpr`, `TitleExpr`, `StartExpr`, `EndExpr`; `ProgressExpr`, `ScaleType` (`auto`/`minutes`/`hours`/`sixHours`/`days`/`weeks`/`months`/`quarters`/`years`, varsayılan `weeks`), `Allow*` bayrakları | | `GanttOptionDto` | `KeyExpr`, `ParentIdExpr`, `TitleExpr`, `StartExpr`, `EndExpr`; `ProgressExpr`, `ScaleType` (`auto`/`minutes`/`hours`/`sixHours`/`days`/`weeks`/`months`/`quarters`/`years`, varsayılan `weeks`), `Allow*` bayrakları |
| `SchedulerOptionDto` | `TextExpr`, `StartDateExpr`, `EndDateExpr`; `AllDayExpr`, `RecurrenceRuleExpr`, `RecurrenceExceptionExpr`, `StartDayHour`, `EndDayHour`, `DefaultView` (`day`/`week`/`workWeek`/`month`/`timelineDay`/`timelineWeek`/`timelineMonth`/`agenda`, varsayılan `week`), `CellDuration`, `FirstDayOfWeek`, `Allow*` | | `SchedulerOptionDto` | `TextExpr`, `StartDateExpr`, `EndDateExpr`; `AllDayExpr`, `RecurrenceRuleExpr`, `RecurrenceExceptionExpr`, `StartDayHour`, `EndDayHour`, `DefaultView` (`day`/`week`/`workWeek`/`month`/`timelineDay`/`timelineWeek`/`timelineMonth`/`agenda`, varsayılan `week`), `CellDuration`, `FirstDayOfWeek`, `Allow*` |
| `TodoOptionDto` | `TitleExpr`, `StatusExpr`; `DescriptionExpr`, `DueDateExpr`, `TagExpr`, `AssigneeExpr`, `PriorityExpr`, `CompletedExpr`, `OrderExpr`, `StatusOrder` (virgülle ayrılmış kolon sırası), `AllowDragging` | | `TodoOptionDto` | `TitleExpr`, `StatusExpr`; `DescriptionExpr`, `AnalysisExpr`, `DueDateExpr`, `TagExpr`, `AssigneeExpr`, `PriorityExpr`, `CompletedExpr`, `OrderExpr`, `StatusOrder` (virgülle ayrılmış kolon sırası), `AllowDragging` |
### 4.3 `Groups` — alanlar ve düzenleme formu ### 4.3 `Groups` — alanlar ve düzenleme formu
@ -1408,7 +1496,15 @@ cevaba yazılmaz.
] ]
} }
], ],
"SubForms": [], "Widgets": [], "SubForms": [],
// List ekranı widget'la gelir (§4.6.1): kiracı tablosu → her kartta TenantId koşulu.
"Widgets": [
{
"SqlQuery": "SELECT 'App.Widget.Total' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'blue' AS \"Color\", 'App.Mrp.Orders' AS \"SubTitle\", 'FaShoppingCart' AS \"Icon\" FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID' UNION ALL SELECT 'App.Widget.Today', CAST(COUNT(*) AS VARCHAR(32)), 'green', 'App.Mrp.Orders', 'FaCalendarDay' FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID' AND CAST(\"CreationTime\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE) UNION ALL SELECT 'App.Widget.TotalRevenue', CAST(COALESCE(SUM(\"Total\"), 0) AS VARCHAR(32)), 'purple', 'App.Mrp.Orders', 'FaMoneyBillWave' FROM \"Mrp_T_Order\" WHERE \"TenantId\" = '@TENANTID'",
"Title": "Title", "Value": "Value", "Color": "Color", "Icon": "Icon", "SubTitle": "SubTitle",
"ValueClassName": "text-3xl", "ColSpan": 4, "ColGap": 3, "ClassName": "mb-3", "IsActive": true
}
],
"WorkflowDto": { "ApprovalUserFieldName": "", "ApprovalDateFieldName": "", "WorkflowDto": { "ApprovalUserFieldName": "", "ApprovalDateFieldName": "",
"ApprovalStatusFieldName": "", "ApprovalDescriptionFieldName": "", "ApprovalStatusFieldName": "", "ApprovalDescriptionFieldName": "",
"ApprovalIsFilterUserName": false, "ApprovalIsResetWorkflow": false, "Criteria": [] } "ApprovalIsFilterUserName": false, "ApprovalIsResetWorkflow": false, "Criteria": [] }
@ -1591,7 +1687,7 @@ widget, sorgunun döndürdüğü **her satır için bir kart** çizer:
```jsonc ```jsonc
"Widgets": [ "Widgets": [
{ {
"SqlQuery": "SELECT 'App.Mrp.Widget.OpenOrders' AS Baslik, COUNT(*) AS Deger, 'blue' AS Renk, 'FcClock' AS Ikon FROM Mrp_T_Order WHERE IsDeleted = 'false' AND Status = 1", "SqlQuery": "SELECT 'App.Mrp.Widget.OpenOrders' AS Baslik, COUNT(*) AS Deger, 'blue' AS Renk, 'FcClock' AS Ikon FROM Mrp_T_Order WHERE TenantId = '@TENANTID' AND Status = 1",
"Title": "Baslik", // sorgudaki kolon adı "Title": "Baslik", // sorgudaki kolon adı
"Value": "Deger", // sorgudaki kolon adı "Value": "Deger", // sorgudaki kolon adı
"Color": "Renk", "Color": "Renk",
@ -1612,11 +1708,57 @@ widget, sorgunun döndürdüğü **her satır için bir kart** çizer:
çevirir, karşılığı yoksa metni aynen basar. Sorguya düz metin değil anahtar yaz. çevirir, karşılığı yoksa metni aynen basar. Sorguya düz metin değil anahtar yaz.
- Tek sorguyla birden çok kart üretmek için `UNION ALL` kullan — üç durum için üç widget kaydı - Tek sorguyla birden çok kart üretmek için `UNION ALL` kullan — üç durum için üç widget kaydı
açmana gerek yok. açmana gerek yok.
- Widget sorgusu ekranın otomatik tenant/şube filtresine tabi değildir ve `@TENANTID`/`@USERID` - Widget sorgusu ekranın otomatik tenant/şube filtresine tabi değildir; kırılımı sorgu kendisi
gibi token'lar burada **çözülmez** (bağlanmamış parametre hatası verir). Kırılım gerekiyorsa yazar. Sorgu `DefaultFilter` ile aynı token çözümünden geçer (`DefaultValueHelper`):
kiracıya özel bir `DataSource` kullan ya da kırılımı view'ın içine göm. kiracı tablosunda `"TenantId" = '@TENANTID'` yaz — host'ta `IS NULL`'a çevrilir, liste ekranıyla
aynı kırılımı verir. `@USERID`, `@DATE` vb. de çözülür.
Ayrıntı: `lowcode-reference.instructions.md` §5.3. Ayrıntı: `lowcode-reference.instructions.md` §5.3.
#### 4.6.1 Otomatik widget — Wizard ve MCP ile üretilen her liste ekranı
Wizard ekranından ya da MCP `deploy_wizard` ile üretilen her **List** ekranı `Widgets` ile
gelir; kullanıcı "widget/KPI" demese de, sorulmadan eklenir ve tarifte tek satırla bildirilir.
İstek widget'ı açıkça dışarıda bırakırsa ya da ekran aşağıdaki istisnalardan biriyse eklenmez.
- **En az 3 kart**, tek widget kaydında `UNION ALL` ile. `ColSpan = 12 / kartSayısı`
(3 kart → 4, 4 kart → 3, 6 kart → 2), `ColGap: 3`, `ClassName: "mb-3"`,
`ValueClassName: "text-3xl"`, `IsActive: true`.
- **Kartları tablonun anlamlı kolonlarından seç**, sırayla:
1. Durum/statü kolonu (`Status`, `ApprovalStatus`, lookup'lı `dxSelectBox`) → değer başına bir
kart; başlık lookup'taki dil anahtarı.
2. Boolean bayraklar (`IsActive`, `IsPublished`, `IsPinned`, `IsSent`…) → Aktif/Pasif,
Yayında/Taslak gibi çift.
3. Tarih kolonu → "Bugün" (`CAST("Kolon" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE)`),
"Yaklaşan" (`"Kolon" >= CURRENT_TIMESTAMP`), "Süresi dolmuş".
4. Sayısal/tutar kolonu → `COALESCE(SUM("Kolon"), 0)` (Toplam tutar, toplam görüntülenme).
5. Hiçbiri 3 kartı doldurmuyorsa `App.Widget.Total` ile başla.
- **Sorgu kuralları:**
- Tablo `TenantId` taşıyorsa (`IsTenant: true`) her karta `"TenantId" = '@TENANTID'` yazılır
(§4.6, reference §5.3). Unutulursa ortak veritabanında kartlar bütün kiracıları sayar.
- `IsDeleted` koşulu widget'a **yazılmaz**.
- Değer `CAST(... AS VARCHAR(32))` ile metne çevrilir: sayı ve tutar kartları aynı
`UNION ALL`'da tip çakışmasına düşmez.
- Tanımlayıcılar çift tırnaklı yazılır, sağlayıcıya özgü fonksiyon (`DATEADD`, `GETDATE`,
`NOW()`) kullanılmaz; sorgu hem SQL Server hem PostgreSQL'de çalışmalıdır.
- **Metin:** `Title` bir dil anahtarıdır. Önce var olan anahtarlar kullanılır: genel
`App.Widget.*` (`Total`, `Active`, `Passive`, `Published`, `Draft`, `Today`, `Upcoming`,
`Pending`, `Sent`, `TotalViews`, `TotalRevenue`…), lookup'ın `App.StaticLookup.*` anahtarları,
alanın `App.Listform.ListformField.*` başlığı. Karşılığı yoksa yeni anahtar EN + TR yazılır.
`SubTitle` ekranın kendi başlık anahtarıdır (`ListFormCode`).
- **Renk ve ikon:** `blue` toplam, `green` olumlu/aktif, `gray` pasif/taslak, `red` hata/iptal,
`yellow`/`orange` bekleyen, `purple` tutar/özet. İkon `react-icons/fa` adıdır (`FaCheckCircle`,
`FaTimesCircle`, `FaCalendarDay`, `FaEye`, `FaMoneyBillWave`…).
- **İstisna — widget eklenmez:** Form ve Chart ekranları; alt form olarak kullanılan menüsüz
ekranlar (`CreateMenu: false`, beğeni/yorum/seçenek gibi detay tabloları); `IsActive` de
taşımayan, yalnızca kod + ad tutan eski tanım listeleri. Bunlarda anlamlı bir kırılım yoktur,
şerit yalnızca kayıt sayısını tekrarlar. Yeni tanım tablosu `IsActive` ile gelir (§8.1) ve
Toplam · Aktif · Pasif kartlarını alır.
- Kartların sorgusunu seed dosyasına yazmadan önce tablo kolonları doğrulanır
(MCP'de `search_schema`, ekranda tablo tasarımcısı). Var olmayan kolona yazılmış kart ekranı hatayla açar.
Seeder'da (`ListFormSeeder_*`) aynı kural `DefaultWidgetsJson(tablo, listFormName, tenant,
params WidgetCard[])` ile uygulanır.
Alt form olarak kullanılacak ekranı **menüsüz wizard** (`CreateMenu: false`) ile üret; menüde Alt form olarak kullanılacak ekranı **menüsüz wizard** (`CreateMenu: false`) ile üret; menüde
görünmesin ama kendi yetkileri ve alan tanımları olsun. `Wizard.SeededAt` damgası ana görünmesin ama kendi yetkileri ve alan tanımları olsun. `Wizard.SeededAt` damgası ana
wizard'ınkinden **küçük** olmalıdır (§0.2). wizard'ınkinden **küçük** olmalıdır (§0.2).
@ -3065,6 +3207,26 @@ tabloları için yalnız `T`/`D` üretir.)
Kullanıcı açıkça "tenant yok / audit yok" demedikçe Multi-Tenant + Full Audited setleri dahil Kullanıcı açıkça "tenant yok / audit yok" demedikçe Multi-Tenant + Full Audited setleri dahil
edilir; her seferinde sorma. edilir; her seferinde sorma.
**Tanım tablosu → `IsActive` otomatik.** Tanım tablosu, başka ekranların seçtiği kod/ad/parametre
listesidir. Bir ekranın tanım olup olmadığına sabit bir ad listesiyle değil **talebin içeriğine
bakılarak** karar verilir: talep ekranı tanımlamaların altına koyuyorsa (başlıktaki ya da
açıklamadaki menü yolu, "tanım/parametre listesi" ifadesi) ya da amaç ekranın başka ekranlara
seçim kaynağı olmasıysa ekran tanımdır. Karar analizde verilir ve gerekçesiyle yazılır; üretim
kararı yeniden vermez. Tanım olan yeni tabloya iş kolonlarının sonuna, audit kolonlarından önce
sorulmadan eklenir:
| Katman | `IsActive` |
| --- | --- |
| Tablo | `bit NOT NULL DEFAULT 1` (`create_table`'da iş kolonu olarak, `defaultValue: 1`) |
| Liste | Görünür kolon, `dxCheckBox` |
| Form | `dxCheckBox`, yeni kayıtta `true` (`InsertFieldsDefaultValueJson`) |
| Widget | Toplam · Aktif · Pasif kartları (§4.6.1 madde 2) |
| Demo veri | Satırlar `IsActive: true` ile yazılır |
| Dil | `App.Listform.ListformField.IsActive` (var olan anahtar) |
Pasife alma silmenin yerine geçmez; silme açık kalır. Analiz bunu **Kararlar**'a değil **Veri
modeli**'ne yazar (§0.12 madde 5); tanım tablosu olduğunu da orada belirtir. Var olan tabloya kolon eklenmez (şema değişikliği ayrı iştir).
### 8.2 View tasarımcısı (`SqlViewDesignerDialog`) — çalışma mantığı ### 8.2 View tasarımcısı (`SqlViewDesignerDialog`) — çalışma mantığı
Bir ekran birden fazla tablodan besleniyorsa, `SelectCommandType: 4` (Query) yerine **tasarlanmış Bir ekran birden fazla tablodan besleniyorsa, `SelectCommandType: 4` (Query) yerine **tasarlanmış
@ -4169,6 +4331,8 @@ Prompt ile üretim yaptıysan ek olarak:
- [ ] Liste ekranında `Grid`/`Card`/`Pivot`/`Chart` açık, `DefaultLayout: "grid"`; kapalı olan - [ ] Liste ekranında `Grid`/`Card`/`Pivot`/`Chart` açık, `DefaultLayout: "grid"`; kapalı olan
görünüm yalnızca istek onu açıkça dışarıda bıraktığı için kapalı (§9.8). görünüm yalnızca istek onu açıkça dışarıda bıraktığı için kapalı (§9.8).
- [ ] Açılan her görünümün zorunlu option alanı dolu (§9.8). - [ ] Açılan her görünümün zorunlu option alanı dolu (§9.8).
- [ ] List ekranında en az 3 kartlık `Widgets` var (istisna değilse); kiracı tablosunda her kart
`"TenantId" = '@TENANTID'` taşıyor, başlıklar dil anahtarı, kolonlar doğrulandı (§4.6.1).
- [ ] Custom component'te route yetkisiz üretildiği için koruma bileşenin içinde - [ ] Custom component'te route yetkisiz üretildiği için koruma bileşenin içinde
(`designerPermission` / `*Permission` / `checkPermission`). (`designerPermission` / `*Permission` / `checkPermission`).
- [ ] Custom component visual modda ise `Props.visualDesigner` ile `Code` marker'ı aynı dokümanı - [ ] Custom component visual modda ise `Props.visualDesigner` ile `Code` marker'ı aynı dokümanı

View file

@ -1410,14 +1410,16 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). | | `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). |
| `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `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). | | `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. | | `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. | | `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. 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. | | `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` | Talep açıklamasının sonuna HTML ekler, var olan metne ve görsellere dokunmaz. Açıklama base64 görsel taşıdığı için `update_row` ile yeniden yazılmaz. `.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. |
**Prompt'lar** (`McpPromptNames`, `McpDevelopPrompts`). İstemcide komut olarak görünür; yalnızca **Prompt'lar** (`McpPromptNames`, `McpDevelopPrompts`). İstemcide komut olarak görünür; yalnızca
metindir, yetki taşımaz. `process_dev_request` tek talebi, `process_ready_requests` bütün metindir, yetki taşımaz. `analyze_dev_request` tek talebi, `analyze_backlog_requests` bütün
`Ready to Start` kuyruğunu işler. İkisi de adımları tekrar etmez, `get_dev_guide` ile `Backlog` kuyruğunu analiz edip `Analysis` durumuna alır (§0.12); `process_dev_request` tek talebi,
`lowcode.instructions.md` §0.11'e yönlendirir. `process_ready_requests` bütün `Ready to Start` kuyruğunu işler (§0.11). Hiçbiri adımları tekrar
etmez, `get_dev_guide` ile `lowcode.instructions.md` ilgili bölümüne yönlendirir.
**Geliştirme taleplerini çalıştırma.** Komutun adı istemcideki sunucu adına göre değişir: aşağıdaki **Geliştirme taleplerini çalıştırma.** Komutun adı istemcideki sunucu adına göre değişir: aşağıdaki
örnekler `claude mcp add sozsoft ...` ile eklenmiş sunucu içindir (`sozsoft-local` ile eklediysen örnekler `claude mcp add sozsoft ...` ile eklenmiş sunucu içindir (`sozsoft-local` ile eklediysen
@ -1426,6 +1428,8 @@ değilse sunucu reddeder ve hiçbir şey üretilmez.
| Ne istiyorsun | Claude Code | Claude.ai / Claude Desktop | Düz metin (her istemci) | | Ne istiyorsun | Claude Code | Claude.ai / Claude Desktop | Düz metin (her istemci) |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Tek talebi analiz et | `/mcp__sozsoft__analyze_dev_request <Id>` | `+` menüsü → `sozsoft` → `analyze_dev_request` | `<Id ya da "başlık"> talebini analiz et` |
| Backlog'u analiz et | `/mcp__sozsoft__analyze_backlog_requests` | `+` menüsü → `sozsoft` → `analyze_backlog_requests` | `Backlog'daki talepleri analiz et` |
| Tek talep | `/mcp__sozsoft__process_dev_request <Id>` | Mesaj kutusundaki `+` menüsü → `sozsoft` → `process_dev_request`, `request` alanına Id ya da tam başlık | `<Id ya da "başlık"> talebini işle` | | Tek talep | `/mcp__sozsoft__process_dev_request <Id>` | Mesaj kutusundaki `+` menüsü → `sozsoft` → `process_dev_request`, `request` alanına Id ya da tam başlık | `<Id ya da "başlık"> talebini işle` |
| Bütün kuyruk | `/mcp__sozsoft__process_ready_requests` | `+` menüsü → `sozsoft` → `process_ready_requests` | `Ready to Start durumundaki talepleri sırayla işle` | | Bütün kuyruk | `/mcp__sozsoft__process_ready_requests` | `+` menüsü → `sozsoft` → `process_ready_requests` | `Ready to Start durumundaki talepleri sırayla işle` |
| Önce kuyruğu görmek | — | — | `Ready to Start durumundaki talepleri Id'leriyle listele` | | Önce kuyruğu görmek | — | — | `Ready to Start durumundaki talepleri Id'leriyle listele` |
@ -1459,7 +1463,7 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `App.DeveloperKit.McpServers` | Host, kiracı | MCP Servers ekranını görme (`.Create/.Update/.Delete/.Export/.Import` ekran işlemleri) | `/mcp` ucuna erişim vermez | | `App.DeveloperKit.McpServers` | Host, kiracı | MCP Servers ekranını görme (`.Create/.Update/.Delete/.Export/.Import` ekran işlemleri) | `/mcp` ucuna erişim vermez |
| `App.DeveloperKit.McpServers.Invoke` | Host, kiracı | `/mcp` bağlantısı ve bütün okuma araçları: `server_info`, `list_lists`, `describe_list`, `search_schema`, `query_list`; host'ta ayrıca `get_dev_guide`, `list_wizards`, `get_wizard`, `verify_wizard`, `list_components`, `get_component` | Hiçbir şey yazmaz. Okuma da ekran yetkisine bağlıdır: `R` yetkisi olmayan liste görünmez, sorgulanamaz. Ham SQL (`run_select`, `list_queries`, `run_query`) ayrıca `App.SqlQueryManager` ister | | `App.DeveloperKit.McpServers.Invoke` | Host, kiracı | `/mcp` bağlantısı ve bütün okuma araçları: `server_info`, `list_lists`, `describe_list`, `search_schema`, `query_list`; host'ta ayrıca `get_dev_guide`, `list_wizards`, `get_wizard`, `verify_wizard`, `list_components`, `get_component` | Hiçbir şey yazmaz. Okuma da ekran yetkisine bağlıdır: `R` yetkisi olmayan liste görünmez, sorgulanamaz. Ham SQL (`run_select`, `list_queries`, `run_query`) ayrıca `App.SqlQueryManager` ister |
| `App.DeveloperKit.McpServers.Write` | Host, kiracı | MCP üzerinden her değişiklik. **Kiracıda:** `insert_row`, `update_row`, `delete_row`. **Host'ta:** bunlara ek olarak `create_table`, `deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, `start_dev_request`, `append_dev_request_note`, `add_dev_request_subtasks` | Kendi başına bir şey yazdırmaz: satır araçları ekranın `C`/`U`/`D` yetkisini, geliştirme araçları Wizard / Custom Components / SQL Query Manager / `App.DevRequests.Update` yetkisini ayrıca ister. Kiracıda geliştirme araçlarını **açmaz** — kiracı bağlamında bu araçlar yetkiden bağımsız kapalıdır (`McpToolNames.TenantTools`). `Invoke` olmadan işe yaramaz | | `App.DeveloperKit.McpServers.Write` | Host, kiracı | MCP üzerinden her değişiklik. **Kiracıda:** `insert_row`, `update_row`, `delete_row`. **Host'ta:** bunlara ek olarak `create_table`, `deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, `start_dev_request`, `save_dev_request_analysis`, `append_dev_request_note`, `add_dev_request_subtasks` | Kendi başına bir şey yazdırmaz: satır araçları ekranın `C`/`U`/`D` yetkisini, geliştirme araçları Wizard / Custom Components / SQL Query Manager / `App.DevRequests.Update` yetkisini ayrıca ister. Kiracıda geliştirme araçlarını **açmaz** — kiracı bağlamında bu araçlar yetkiden bağımsız kapalıdır (`McpToolNames.TenantTools`). `Invoke` olmadan işe yaramaz |
**Araç → gereken yetkiler** (Invoke hepsinde aranır, tabloda tekrar yazılmadı) **Araç → gereken yetkiler** (Invoke hepsinde aranır, tabloda tekrar yazılmadı)
@ -1481,6 +1485,7 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay
| `delete_component` | — | `.Write` | `App.DeveloperKit.CustomComponents.Delete` | | `delete_component` | — | `.Write` | `App.DeveloperKit.CustomComponents.Delete` |
| `start_dev_request` | — | `.Write` | `App.DevRequests.Update` | | `start_dev_request` | — | `.Write` | `App.DevRequests.Update` |
| `get_dev_request` | — | — | `App.DevRequests` | | `get_dev_request` | — | — | `App.DevRequests` |
| `save_dev_request_analysis` | — | `.Write` | `App.DevRequests.Update` |
| `append_dev_request_note` | — | `.Write` | `App.DevRequests.Update` | | `append_dev_request_note` | — | `.Write` | `App.DevRequests.Update` |
| `add_dev_request_subtasks` | — | `.Write` | `App.DevRequests.Update` | | `add_dev_request_subtasks` | — | `.Write` | `App.DevRequests.Update` |
@ -1541,7 +1546,10 @@ kimlik doğrulamayı istemci MCP OAuth akışıyla kendisi yürütür:
Üç ayrıntı bilinçlidir: Üç ayrıntı bilinçlidir:
- Yayıncı (`issuer`) `AuthServer:Authority`'den sabitlenir; ters vekil arkasında istekten - Yayıncı (`issuer`) `AuthServer:Authority`'den sabitlenir; ters vekil arkasında istekten
türetilirse `http://` olur ve istemci keşif belgesini reddeder. türetilirse `http://` olur ve istemci keşif belgesini reddeder. Korumalı kaynak belgesindeki
`authorization_servers` değeri bu `issuer` ile birebir aynıdır (sonda `/` dahil,
`McpOAuthOptions.AuthorizationServer`); RFC 8414 §3.3'ü uygulayan istemci (`mcp-remote`)
farkta `IssuerMismatchError` ile durur.
- MCP istemcisinin gönderdiği RFC 8707 `resource` parametresi yok sayılır - MCP istemcisinin gönderdiği RFC 8707 `resource` parametresi yok sayılır
(`McpOAuthServerConfiguration`): OpenIddict kayıtsız kaynağı reddeder ya da token kitlesini (`McpOAuthServerConfiguration`): OpenIddict kayıtsız kaynağı reddeder ya da token kitlesini
`/mcp` adresine daraltır, API ise `Platform` kitlesini doğrular. `/mcp` adresine daraltır, API ise `Platform` kitlesini doğrular.

View file

@ -147,6 +147,12 @@ public class McpDevRequestDto
/// <summary>Aciklamadaki gorseller, metinde gectikleri sirayla.</summary> /// <summary>Aciklamadaki gorseller, metinde gectikleri sirayla.</summary>
public List<McpDevRequestImageDto> Images { get; set; } = []; public List<McpDevRequestImageDto> Images { get; set; } = [];
/// <summary>
/// HTML. Talebin analizi ve MCP sonuc notlari; <see cref="Description"/> talebin yazildigi
/// haliyle kalir, uretim ikisine birlikte bakar.
/// </summary>
public string? Analysis { get; set; }
public string? Priority { get; set; } public string? Priority { get; set; }
public string? Tags { get; set; } public string? Tags { get; set; }

View file

@ -38,8 +38,14 @@ public interface IMcpDevelopAppService : IApplicationService
Task<McpDevRequestDto> GetDevRequestAsync(string request); Task<McpDevRequestDto> GetDevRequestAsync(string request);
/// <summary> /// <summary>
/// Aciklamanin sonuna HTML ekler. Aciklama gomulu gorsel tasidigi icin satir guncellemesiyle /// Talebin analizini yazar (var olani degistirir) ve talebi Analysis durumuna alir; yalnizca
/// yeniden yazilamaz; ekleme sunucuda yapilir. /// Backlog ya da Analysis durumundaki talep analiz edilir.
/// </summary>
Task<McpDevRequestDto> SaveDevRequestAnalysisAsync(string request, string html);
/// <summary>
/// Analizin sonuna HTML ekler. Aciklama talebin yazildigi haliyle kalir; uretim notlari
/// analize yazilir.
/// </summary> /// </summary>
Task AppendDevRequestNoteAsync(string request, string html); Task AppendDevRequestNoteAsync(string request, string html);

View file

@ -383,8 +383,37 @@ public class McpDevelopAppService(
} }
/// <summary> /// <summary>
/// Aciklama base64 gorsel tasir; model onu update_row ile yeniden yazarsa gorselleri geri /// Durum kurali modelin dikkatine birakilmaz: yalnizca Backlog ya da Analysis durumundaki talep
/// gonderemez ve siler. Ekleme bu yuzden sunucuda, var olan metne dokunmadan yapilir. /// analiz edilir. Analysis'teki talebin analizi begenilmediyse yeniden yazilir; Ready to Start'a
/// kullanici alir.
/// </summary>
public async Task<McpDevRequestDto> SaveDevRequestAnalysisAsync(string request, string html)
{
await EnsureToolAllowedAsync(McpToolNames.SaveDevRequestAnalysis);
await AuthorizationService.CheckAsync(DevRequestUpdatePermission);
if (string.IsNullOrWhiteSpace(html))
{
throw new UserFriendlyException(L[McpErrorCodes.DevRequestAnalysisRequired]);
}
var devRequest = await FindDevRequestAsync(request);
if (devRequest.Status is not (McpConsts.DevRequestStatuses.Backlog or McpConsts.DevRequestStatuses.Analysis))
{
throw new UserFriendlyException(L[McpErrorCodes.DevRequestNotAnalyzable, devRequest.Title ?? devRequest.Id.ToString(), devRequest.Status ?? string.Empty]);
}
devRequest.Analysis = html.Trim();
devRequest.Status = McpConsts.DevRequestStatuses.Analysis;
await devRequestRepository.UpdateAsync(devRequest, autoSave: true);
return ToDto(devRequest);
}
/// <summary>
/// Aciklama talebin yazildigi haliyle kalir ve base64 gorsel tasir; uretim notlari analize
/// eklenir. Ekleme sunucuda, var olan metne dokunmadan yapilir.
/// </summary> /// </summary>
public async Task AppendDevRequestNoteAsync(string request, string html) public async Task AppendDevRequestNoteAsync(string request, string html)
{ {
@ -397,7 +426,7 @@ public class McpDevelopAppService(
} }
var devRequest = await FindDevRequestAsync(request); var devRequest = await FindDevRequestAsync(request);
devRequest.Description = string.Concat(devRequest.Description, html.Trim()); devRequest.Analysis = string.Concat(devRequest.Analysis, html.Trim());
await devRequestRepository.UpdateAsync(devRequest, autoSave: true); await devRequestRepository.UpdateAsync(devRequest, autoSave: true);
} }
@ -461,6 +490,7 @@ public class McpDevelopAppService(
Status = devRequest.Status, Status = devRequest.Status,
Description = description, Description = description,
Images = images, Images = images,
Analysis = devRequest.Analysis,
Priority = devRequest.Priority, Priority = devRequest.Priority,
Tags = devRequest.Tags, Tags = devRequest.Tags,
DueDate = devRequest.DueDate, DueDate = devRequest.DueDate,

View file

@ -103,6 +103,8 @@ public static class McpConsts
/// </summary> /// </summary>
public static class DevRequestStatuses public static class DevRequestStatuses
{ {
public const string Backlog = "Backlog";
public const string Analysis = "Analysis";
public const string ReadyToStart = "Ready to Start"; public const string ReadyToStart = "Ready to Start";
public const string InProgress = "In Progress"; public const string InProgress = "In Progress";
} }

View file

@ -53,6 +53,8 @@ public static class McpErrorCodes
public const string DevRequestNotFound = Prefix + "DevRequestNotFound"; public const string DevRequestNotFound = Prefix + "DevRequestNotFound";
public const string DevRequestAmbiguous = Prefix + "DevRequestAmbiguous"; public const string DevRequestAmbiguous = Prefix + "DevRequestAmbiguous";
public const string DevRequestNotReady = Prefix + "DevRequestNotReady"; public const string DevRequestNotReady = Prefix + "DevRequestNotReady";
public const string DevRequestNotAnalyzable = Prefix + "DevRequestNotAnalyzable";
public const string DevRequestAnalysisRequired = Prefix + "DevRequestAnalysisRequired";
public const string DevRequestNoteRequired = Prefix + "DevRequestNoteRequired"; public const string DevRequestNoteRequired = Prefix + "DevRequestNoteRequired";
public const string DevRequestSubTasksRequired = Prefix + "DevRequestSubTasksRequired"; public const string DevRequestSubTasksRequired = Prefix + "DevRequestSubTasksRequired";
public const string DevRequestSubTasksInvalid = Prefix + "DevRequestSubTasksInvalid"; public const string DevRequestSubTasksInvalid = Prefix + "DevRequestSubTasksInvalid";

View file

@ -6,6 +6,8 @@ namespace Sozsoft.Mcp;
/// </summary> /// </summary>
public static class McpPromptNames public static class McpPromptNames
{ {
public const string AnalyzeDevRequest = "analyze_dev_request";
public const string AnalyzeBacklogRequests = "analyze_backlog_requests";
public const string ProcessDevRequest = "process_dev_request"; public const string ProcessDevRequest = "process_dev_request";
public const string ProcessReadyRequests = "process_ready_requests"; public const string ProcessReadyRequests = "process_ready_requests";
} }

View file

@ -31,6 +31,7 @@ public static class McpToolNames
public const string VerifyWizard = "verify_wizard"; public const string VerifyWizard = "verify_wizard";
public const string StartDevRequest = "start_dev_request"; public const string StartDevRequest = "start_dev_request";
public const string GetDevRequest = "get_dev_request"; public const string GetDevRequest = "get_dev_request";
public const string SaveDevRequestAnalysis = "save_dev_request_analysis";
public const string AppendDevRequestNote = "append_dev_request_note"; public const string AppendDevRequestNote = "append_dev_request_note";
public const string AddDevRequestSubTasks = "add_dev_request_subtasks"; public const string AddDevRequestSubTasks = "add_dev_request_subtasks";
public const string ListComponents = "list_components"; public const string ListComponents = "list_components";
@ -54,6 +55,7 @@ public static class McpToolNames
VerifyWizard, VerifyWizard,
StartDevRequest, StartDevRequest,
GetDevRequest, GetDevRequest,
SaveDevRequestAnalysis,
AppendDevRequestNote, AppendDevRequestNote,
AddDevRequestSubTasks, AddDevRequestSubTasks,
ListComponents, ListComponents,
@ -106,6 +108,7 @@ public static class McpToolNames
[DeployWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write, [DeployWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[DeleteWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write, [DeleteWizard] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[StartDevRequest] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write, [StartDevRequest] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[SaveDevRequestAnalysis] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[AppendDevRequestNote] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write, [AppendDevRequestNote] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[AddDevRequestSubTasks] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write, [AddDevRequestSubTasks] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[SaveComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write, [SaveComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,

View file

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.AspNetCore; using ModelContextProtocol.AspNetCore;
using Sozsoft.Mcp.Tools; using Sozsoft.Mcp.Tools;
@ -25,7 +26,12 @@ public class McpHttpApiModule : AbpModule
Configure<McpOAuthOptions>(options => Configure<McpOAuthOptions>(options =>
{ {
options.BaseUrl = (configuration["App:SelfUrl"] ?? string.Empty).TrimEnd('/'); options.BaseUrl = (configuration["App:SelfUrl"] ?? string.Empty).TrimEnd('/');
options.AuthorizationServer = (configuration["AuthServer:Authority"] ?? string.Empty).TrimEnd('/'); // OpenIddict keşif belgesindeki issuer'ı Uri.AbsoluteUri ile (sonda eğik çizgiyle) yazar;
// RFC 8414 §3.3 gereği istemci iki değeri birebir karşılaştırır.
var authority = configuration["AuthServer:Authority"];
options.AuthorizationServer = string.IsNullOrWhiteSpace(authority)
? string.Empty
: new Uri(authority).AbsoluteUri;
}); });
context.Services context.Services

View file

@ -9,7 +9,7 @@ public class McpOAuthOptions
/// <summary>API'nin disaridan erisilen kok adresi, sonunda egik cizgi olmadan.</summary> /// <summary>API'nin disaridan erisilen kok adresi, sonunda egik cizgi olmadan.</summary>
public string BaseUrl { get; set; } = string.Empty; public string BaseUrl { get; set; } = string.Empty;
/// <summary>Token'i veren yetkilendirme sunucusu (OpenIddict), sonunda egik cizgi olmadan.</summary> /// <summary>Token'i veren yetkilendirme sunucusu (OpenIddict); keşif belgesindeki issuer ile birebir aynı.</summary>
public string AuthorizationServer { get; set; } = string.Empty; public string AuthorizationServer { get; set; } = string.Empty;
/// <summary>Korumali kaynagin tanimlayicisi: istemcinin baglandigi /mcp adresi.</summary> /// <summary>Korumali kaynagin tanimlayicisi: istemcinin baglandigi /mcp adresi.</summary>

View file

@ -23,9 +23,11 @@ internal static class McpServerInfoDefaults
"section you need), inspect what exists with list_wizards/get_wizard and list_components/get_component, " + "section you need), inspect what exists with list_wizards/get_wizard and list_components/get_component, " +
"then produce them with deploy_wizard and save_component; the guide's file-writing steps map to these tools. " + "then produce them with deploy_wizard and save_component; the guide's file-writing steps map to these tools. " +
"Agree on screen type, data source, module and permissions with the user before deploying. " + "Agree on screen type, data source, module and permissions with the user before deploying. " +
"Analyzing development requests (Backlog -> Analysis) follows get_dev_guide section 0.12: read with get_dev_request, " +
"write the analysis with save_dev_request_analysis, produce nothing and never touch Description; the user moves an approved analysis to Ready to Start. " +
"Development requests (App.DevRequests) follow get_dev_guide section 0.11: claim each one with " + "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). " + "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. " + "A request (Description) and its approved Analysis are 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; " + "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."; "do not try to reach the same result through another tool.";
} }

View file

@ -1,16 +1,38 @@
using System.ComponentModel; using System.ComponentModel;
using ModelContextProtocol.Server; using ModelContextProtocol.Server;
namespace Sozsoft.Mcp.Tools; namespace Sozsoft.Mcp.Tools;
/// <summary> /// <summary>
/// Gelistirme dongusunun hazir prompt'lari. Proseduru tekrar etmez, rehberin 0.11 bolumune /// Gelistirme dongusunun hazir prompt'lari. Proseduru tekrar etmez, rehberin 0.11 (uretim) ve
/// yonlendirir; boylece adimlar tek yerde (lowcode.instructions.md) degisir. Durum kurali /// 0.12 (analiz) bolumlerine yonlendirir; boylece adimlar tek yerde (lowcode.instructions.md)
/// start_dev_request'te sunucu tarafinda uygulanir. /// degisir. Durum kurallari start_dev_request ve save_dev_request_analysis'te sunucu tarafinda
/// uygulanir.
/// </summary> /// </summary>
[McpServerPromptType] [McpServerPromptType]
public static class McpDevelopPrompts public static class McpDevelopPrompts
{ {
[McpServerPrompt(Name = McpPromptNames.AnalyzeDevRequest)]
[Description("Tek bir gelistirme talebini analiz eder ve Analysis durumuna alir. Talep Backlog ya da Analysis durumunda olmalidir.")]
public static string AnalyzeDevRequest(
[Description("Talebin Id'si (GUID) ya da tam basligi.")]
string request)
=> $"App.DevRequests kuyrugundaki '{request}' talebini analiz et; yalnizca bu talebi. " +
"Once get_dev_guide ile lowcode 0.12 bolumunu oku ve oradaki standarda uy. " +
"Talebi ve gorsellerini get_dev_request ile oku; analizi save_dev_request_analysis ile yaz. " +
"Hicbir sey uretme (tablo, wizard, bilesen yok) ve Description'a dokunma. " +
"Arac reddederse (talep Backlog ya da Analysis degil, bulunamadi ya da birden fazla eslesme var) nedenini bildir ve dur. " +
"Sonunda kisa rapor ver: menu yeri, uretilecek artefaktlar, (onay) etiketli kararlar ve Uretime hazir satiri; talebi Ready to Start'a kullanicinin alacagini belirt.";
[McpServerPrompt(Name = McpPromptNames.AnalyzeBacklogRequests)]
[Description("Backlog durumundaki tum gelistirme taleplerini SortOrder sirasiyla analiz eder ve Analysis durumuna alir.")]
public static string AnalyzeBacklogRequests()
=> "App.DevRequests kuyrugundaki Backlog taleplerini query_list ile SortOrder sirasiyla oku ve tek tek analiz et. " +
"Once get_dev_guide ile lowcode 0.12 bolumunu oku ve oradaki standarda uy. " +
"Her talebi get_dev_request ile oku, analizi save_dev_request_analysis ile yaz; reddedilen talebi atla ve rapora yaz. " +
"Hicbir sey uretme ve Description'a dokunma. " +
"Sonunda parti raporu ver: talep basina menu yeri, (onay) etiketli karar sayisi ve Uretime hazir durumu; talepleri Ready to Start'a kullanicinin alacagini tek satirda belirt.";
[McpServerPrompt(Name = McpPromptNames.ProcessDevRequest)] [McpServerPrompt(Name = McpPromptNames.ProcessDevRequest)]
[Description("Tek bir gelistirme talebini isler. Talep Ready to Start durumunda olmalidir.")] [Description("Tek bir gelistirme talebini isler. Talep Ready to Start durumunda olmalidir.")]
public static string ProcessDevRequest( public static string ProcessDevRequest(
@ -20,7 +42,7 @@ public static class McpDevelopPrompts
"Once get_dev_guide ile lowcode 0.11 bolumunu oku ve oradaki adimlari uygula. " + "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 " + "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. " + "birden fazla eslesme var) hicbir sey uretme, baska bir talebe gecme: nedenini bildir ve dur. " +
"Talep tarifin kendisidir: soru sorma, DB Migrate'i bekleme; talebi Testing'e alip bitir. " + "Talep (Description) ve onaylanmis analizi (Analysis) birlikte 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."; "Sonunda rapor ver: uretilen artefaktlar, talebin son durumu ve DB Migrate gerektigi.";
[McpServerPrompt(Name = McpPromptNames.ProcessReadyRequests)] [McpServerPrompt(Name = McpPromptNames.ProcessReadyRequests)]
@ -29,6 +51,6 @@ public static class McpDevelopPrompts
=> "App.DevRequests kuyrugundaki Ready to Start taleplerini query_list ile SortOrder sirasiyla oku ve tek tek isle. " + => "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. " + "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. " + "Her talebi start_dev_request ile ustlen; reddedilen talebi atla ve rapora yaz. " +
"Talep tarifin kendisidir: soru sorma, DB Migrate'i bekleme; her talebi Testing'e alip digerine gec. " + "Talep (Description) ve onaylanmis analizi (Analysis) birlikte 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."; "Sonunda parti raporu ver: talep basina uretilen artefaktlar ve son durum; DB Migrate gerektigini tek satirda belirt.";
} }

View file

@ -91,21 +91,30 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService)
=> await developAppService.VerifyWizardAsync(fileName); => await developAppService.VerifyWizardAsync(fileName);
[McpServerTool(Name = McpToolNames.StartDevRequest, Destructive = false, Idempotent = false)] [McpServerTool(Name = McpToolNames.StartDevRequest, Destructive = false, Idempotent = false)]
[Description("App.DevRequests kuyrugundaki bir gelistirme talebini ustlenir: talep Ready to Start durumundaysa In Progress'e alir ve talebin metnini (baslik, HTML aciklama, oncelik, etiketler, SubTasks) dondurur; baska bir durumdaysa reddeder. Aciklamaya yapistirilmis gorseller ayri resim icerigi olarak gelir, aciklamada yerlerinde [image-N] isareti durur; gorseldeki ekran duzeni ve satirlar talebin parcasidir (demo veri icin deploy_wizard SeedData). Bir talep uzerinde calismaya bu aracla baslanir, Status'u update_row ile In Progress yapma. Sonraki adimlar icin get_dev_guide lowcode 0.11.")] [Description("App.DevRequests kuyrugundaki bir gelistirme talebini ustlenir: talep Ready to Start durumundaysa In Progress'e alir ve talebin metnini (baslik, HTML aciklama, onaylanmis HTML analiz, oncelik, etiketler, SubTasks) dondurur; baska bir durumdaysa reddeder. Aciklamaya yapistirilmis gorseller ayri resim icerigi olarak gelir, aciklamada yerlerinde [image-N] isareti durur; gorseldeki ekran duzeni ve satirlar talebin parcasidir (demo veri icin deploy_wizard SeedData). Uretim aciklama ile analize birlikte dayanir; analizdeki menu, tablo ve kapsam kararlari onaylanmistir. Bir talep uzerinde calismaya bu aracla baslanir, Status'u update_row ile In Progress yapma. Sonraki adimlar icin get_dev_guide lowcode 0.11.")]
public async Task<IList<ContentBlock>> StartDevRequestAsync( public async Task<IList<ContentBlock>> StartDevRequestAsync(
[Description("Talebin Id'si (GUID) ya da tam basligi. Ayni basligi tasiyan birden fazla talep varsa Id ver.")] [Description("Talebin Id'si (GUID) ya da tam basligi. Ayni basligi tasiyan birden fazla talep varsa Id ver.")]
string request) string request)
=> ToContent(await developAppService.StartDevRequestAsync(request)); => ToContent(await developAppService.StartDevRequestAsync(request));
[McpServerTool(Name = McpToolNames.GetDevRequest, ReadOnly = true)] [McpServerTool(Name = McpToolNames.GetDevRequest, ReadOnly = true)]
[Description("Bir gelistirme talebini durumuna dokunmadan okur; start_dev_request ile ayni cevabi (aciklama + ayri resim icerikleri) dondurur. Yarim kalmis (In Progress/Testing) bir talebe donerken ya da gorselleri yeniden gormek icin kullan; talebi ustlenmek icin degil.")] [Description("Bir gelistirme talebini durumuna dokunmadan okur; start_dev_request ile ayni cevabi (aciklama + ayri resim icerikleri) dondurur. Analiz oncesi talebi okumak, yarim kalmis (In Progress/Testing) bir talebe donmek ya da gorselleri yeniden gormek icin kullan; talebi ustlenmek icin degil.")]
public async Task<IList<ContentBlock>> GetDevRequestAsync( public async Task<IList<ContentBlock>> GetDevRequestAsync(
[Description("Talebin Id'si (GUID) ya da tam basligi.")] [Description("Talebin Id'si (GUID) ya da tam basligi.")]
string request) string request)
=> ToContent(await developAppService.GetDevRequestAsync(request)); => ToContent(await developAppService.GetDevRequestAsync(request));
[McpServerTool(Name = McpToolNames.SaveDevRequestAnalysis, Destructive = true, Idempotent = true)]
[Description("Backlog ya da Analysis durumundaki bir talebin analizini (HTML) Analysis alanina yazar ve talebi Analysis durumuna alir; var olan analiz degistirilir, Description'a dokunulmaz. Diger durumlarda reddeder. Once get_dev_request ile talebi ve gorsellerini oku; analizin bolumleri ve standardi get_dev_guide lowcode 0.12'dedir. Analizi begenen kullanici talebi Ready to Start'a kendisi alir; MCP almaz.")]
public async Task<IList<ContentBlock>> SaveDevRequestAnalysisAsync(
[Description("Talebin Id'si (GUID) ya da tam basligi.")]
string request,
[Description("Analizin tamami, HTML.")]
string html)
=> ToContent(await developAppService.SaveDevRequestAnalysisAsync(request, html));
[McpServerTool(Name = McpToolNames.AppendDevRequestNote, Destructive = false, Idempotent = false)] [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 (<h4>MCP</h4> + uretilenler ve DB Migrate bekleyen wizard'lar) bu aracla ekle.")] [Description("Talebin Analysis alaninin sonuna HTML ekler; var olan analize ve Description'a dokunmaz. Description talebin yazildigi haliyle kalir; uretim sonuc notunu (<h4>MCP</h4> + uretilenler, varsayimlar ve DB Migrate bekleyen wizard'lar) bu aracla ekle. Description'i ya da Analysis'i update_row ile yazma.")]
public async Task<string> AppendDevRequestNoteAsync( public async Task<string> AppendDevRequestNoteAsync(
[Description("Talebin Id'si (GUID) ya da tam basligi.")] [Description("Talebin Id'si (GUID) ya da tam basligi.")]
string request, string request,

View file

@ -9,6 +9,7 @@ public class TodoOptionDto
public string TitleExpr { get; set; } public string TitleExpr { get; set; }
public string StatusExpr { get; set; } public string StatusExpr { get; set; }
public string DescriptionExpr { get; set; } public string DescriptionExpr { get; set; }
public string AnalysisExpr { get; set; }
public string DueDateExpr { get; set; } public string DueDateExpr { get; set; }
public string TagExpr { get; set; } public string TagExpr { get; set; }
public string AssigneeExpr { get; set; } public string AssigneeExpr { get; set; }

View file

@ -252,7 +252,10 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
Items = [] Items = []
}; };
var items = await dynamicDataRepository.QueryAsync(widget.SqlQuery, connectionString); // Widget sorgusu ekranın otomatik tenant filtresinden geçmez; kırılımı DefaultFilter ile
// aynı token'larla (ör. "TenantId" = '@TENANTID', host'ta IS NULL) sorgu kendisi yazar.
var sql = defaultValueHelper.GetDefaultValue(widget.SqlQuery);
var items = await dynamicDataRepository.QueryAsync(sql, connectionString);
if (items != null) if (items != null)
{ {
foreach (var item in items) foreach (var item in items)

View file

@ -962,54 +962,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
new() { FieldName = "UserType", FieldDbType = DbType.Int32, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value }, new() { FieldName = "UserType", FieldDbType = DbType.Int32, Value = "0", CustomValueType = FieldCustomValueTypeEnum.Value },
new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value } new() { FieldName = "IsActive", FieldDbType = DbType.Boolean, Value = "true", CustomValueType = FieldCustomValueTypeEnum.Value }
}), }),
WidgetsJson = JsonSerializer.Serialize(new List<WidgetEditDto> WidgetsJson = DefaultWidgetsJson("AbpUsers", listFormName, true,
{ new("App.Widget.Active", "green", "FaUserCheck", "\"IsActive\" = 'true'"),
new() new("App.Widget.Passive", "gray", "FaUserSlash", "\"IsActive\" = 'false'"),
{ new("App.Widget.Unverified", "purple", "FaUserClock", "\"IsVerified\" = 'false'"))
ColGap = 3,
ColSpan = 4,
SqlQuery = @"
SELECT
N'Aktif' AS ""Title"",
COUNT(""Id"") AS ""Value"",
'blue' AS ""Color"",
N'Aktif Kullanıcılar' AS ""SubTitle"",
'FaUserCheck' AS ""Icon""
FROM ""AbpUsers""
WHERE ""IsActive"" = 'true'
UNION ALL
SELECT
N'Pasif' AS ""Title"",
COUNT(""Id"") AS ""Value"",
'green' AS ""Color"",
N'Pasif Kullanıcılar' AS ""SubTitle"",
'FaUserSlash' AS ""Icon""
FROM ""AbpUsers""
WHERE ""IsActive"" = 'false'
UNION ALL
SELECT
N'Doğrulama' AS ""Title"",
COUNT(""Id"") AS ""Value"",
'purple' AS ""Color"",
N'Doğrulama bekleyenler' AS ""SubTitle"",
'FaUserClock' AS ""Icon""
FROM ""AbpUsers""
WHERE ""IsVerified"" = 'false';
",
Title = "Title",
Value = "Value",
ValueClassName = "bg-5 text-sm",
Color = "Color",
Icon = "Icon",
SubTitle = "SubTitle",
ClassName = "mb-3",
IsActive = true
}
})
}); });
#region Users Fields #region Users Fields
@ -1397,6 +1353,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = "AbpAuditLogs", SelectCommand = "AbpAuditLogs",
WidgetsJson = DefaultWidgetsJson("AbpAuditLogs", listFormName, true,
new("App.Widget.Total", "blue", "FaHistory"),
new("App.Widget.Today", "green", "FaCalendarDay", "CAST(\"ExecutionTime\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE)"),
new("App.Widget.Errors", "red", "FaExclamationTriangle", "(\"HttpStatusCode\" >= 400 OR \"Exceptions\" IS NOT NULL)")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SortMode = GridOptions.SortModeSingle, SortMode = GridOptions.SortModeSingle,
@ -2511,6 +2471,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = "AbpSessions", SelectCommand = "AbpSessions",
WidgetsJson = DefaultWidgetsJson("AbpSessions", listFormName, true,
new("App.Widget.Total", "blue", "FaDesktop"),
new("App.Widget.SignedInToday", "green", "FaSignInAlt", "CAST(\"SignedIn\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE)"),
new("App.Widget.ActiveToday", "purple", "FaUserClock", "CAST(\"LastAccessed\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE)")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SortMode = GridOptions.SortModeSingle, SortMode = GridOptions.SortModeSingle,
@ -2982,6 +2946,11 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Announcement)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Announcement)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Announcement)), listFormName, true,
new("App.Widget.Active", "green", "FaBullhorn", "(\"ExpiryDate\" IS NULL OR \"ExpiryDate\" >= CURRENT_TIMESTAMP)"),
new("App.Widget.Pinned", "purple", "FaThumbtack", "\"IsPinned\" = 'true'"),
new("App.Widget.Expired", "gray", "FaCalendarTimes", "\"ExpiryDate\" < CURRENT_TIMESTAMP"),
new("App.Widget.TotalViews", "blue", "FaEye", Value: "COALESCE(SUM(\"ViewCount\"), 0)")),
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = false, SeedSyncInsert = false,
SeedSyncUpdate = false, SeedSyncUpdate = false,
@ -3537,6 +3506,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Survey)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Survey)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Survey)), listFormName, true,
new("App.Widget.Active", "green", "FaCheckCircle", "\"Status\" = 'active'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "\"Status\" = 'passive'"),
new("App.Widget.TotalResponses", "purple", "FaPollH", Value: "COALESCE(SUM(\"Responses\"), 0)")),
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = false, SeedSyncInsert = false,
SeedSyncUpdate = false, SeedSyncUpdate = false,
@ -4379,6 +4352,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialPost)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialPost)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialPost)), listFormName, true,
new("App.Widget.Total", "blue", "FaComments"),
new("App.Widget.Today", "green", "FaCalendarDay", "CAST(\"CreationTime\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE)"),
new("App.Widget.TotalLikes", "pink", "FaHeart", Value: "COALESCE(SUM(\"LikeCount\"), 0)")),
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = false, SeedSyncInsert = false,
SeedSyncUpdate = false, SeedSyncUpdate = false,
@ -5426,6 +5403,11 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Event)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Event)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Event)), listFormName, true,
new("App.Widget.Upcoming", "blue", "FaCalendarAlt", "\"Date\" >= CURRENT_TIMESTAMP"),
new("App.StaticLookup.Published", "green", "FaCheckCircle", "\"Status\" = 'published'"),
new("App.StaticLookup.Draft", "gray", "FaPencilAlt", "\"Status\" = 'draft'"),
new("App.StaticLookup.Cancelled", "red", "FaBan", "\"Status\" = 'cancelled'")),
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = false, SeedSyncInsert = false,
SeedSyncUpdate = false, SeedSyncUpdate = false,
@ -6714,9 +6696,9 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
ValueExpr = "key", ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key="Backlog", Name="App.StaticLookup.Backlog" }, new () { Key="Backlog", Name="App.StaticLookup.Backlog" },
new () { Key="Analysis", Name="App.StaticLookup.Analysis" },
new () { Key="Ready to Start", Name="App.StaticLookup.ReadyToStart" }, new () { Key="Ready to Start", Name="App.StaticLookup.ReadyToStart" },
new () { Key="In Progress", Name="App.StaticLookup.InProgress" }, new () { Key="In Progress", Name="App.StaticLookup.InProgress" },
new () { Key="Code Review", Name="App.StaticLookup.CodeReview" },
new () { Key="Testing", Name="App.StaticLookup.Testing" }, new () { Key="Testing", Name="App.StaticLookup.Testing" },
new () { Key="Staging", Name="App.StaticLookup.Staging" } new () { Key="Staging", Name="App.StaticLookup.Staging" }
}), }),
@ -6766,7 +6748,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
CaptionName = "App.Listform.ListformField.Description", CaptionName = "App.Listform.ListformField.Description",
Width = 0, Width = 0,
ListOrderNo = 6, ListOrderNo = 6,
Visible = true, Visible = false,
IsActive = true, IsActive = true,
AllowSearch = true, AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson, ValidationRuleJson = DefaultValidationRuleRequiredJson,
@ -6775,6 +6757,26 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
PivotSettingsJson = DefaultPivotSettingsJson, PivotSettingsJson = DefaultPivotSettingsJson,
EditorOptions = EditorOptions.Html() EditorOptions = EditorOptions.Html()
}, },
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Analysis",
EditGroupOrderNo = 1,
EditOrderNo = 7,
EditorType2 = EditorTypes.dxHtmlEditor,
ColSpan = 2,
CaptionName = "App.Listform.ListformField.Analysis",
Width = 0,
ListOrderNo = 13,
Visible = false,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
EditorOptions = EditorOptions.Html()
},
new() { new() {
ListFormCode = listForm.ListFormCode, ListFormCode = listForm.ListFormCode,
@ -7209,6 +7211,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.NotificationRule)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.NotificationRule)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.NotificationRule)), listFormName, true,
new("App.Widget.Total", "blue", "FaSlidersH"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsActive\" = 'true'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "\"IsActive\" = 'false'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
@ -7523,6 +7529,11 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Notification)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Notification)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Notification)), listFormName, true,
new("App.Widget.Sent", "green", "FaPaperPlane", "\"IsSent\" = 'true'"),
new("App.Widget.Pending", "yellow", "FaHourglassHalf", "\"IsSent\" = 'false'"),
new("App.Platform.Read", "blue", "FaEnvelopeOpen", "\"IsRead\" = 'true'"),
new("App.Widget.Unread", "orange", "FaEnvelope", "\"IsSent\" = 'true' AND \"IsRead\" = 'false'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,

View file

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using Sozsoft.Platform.Entities; using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Enums; using Sozsoft.Platform.Enums;
@ -133,6 +135,7 @@ public static class ListFormSeeder_DefaultJsons
string TitleExpr = "Title", string TitleExpr = "Title",
string StatusExpr = "Status", string StatusExpr = "Status",
string DescriptionExpr = "Description", string DescriptionExpr = "Description",
string AnalysisExpr = "Analysis",
string DueDateExpr = "DueDate", string DueDateExpr = "DueDate",
string TagExpr = "Tags", string TagExpr = "Tags",
string AssigneeExpr = "Assignees", string AssigneeExpr = "Assignees",
@ -141,13 +144,14 @@ public static class ListFormSeeder_DefaultJsons
string OrderExpr = "SortOrder", string OrderExpr = "SortOrder",
string TimesheetExpr = "Timesheets", string TimesheetExpr = "Timesheets",
string SubTaskExpr = "SubTasks", string SubTaskExpr = "SubTasks",
string StatusOrder = "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging", string StatusOrder = "Backlog, Analysis, Ready to Start, In Progress, Testing, Staging",
bool AllowDragging = true bool AllowDragging = true
) => JsonSerializer.Serialize(new TodoOptionDto ) => JsonSerializer.Serialize(new TodoOptionDto
{ {
TitleExpr = TitleExpr, TitleExpr = TitleExpr,
StatusExpr = StatusExpr, StatusExpr = StatusExpr,
DescriptionExpr = DescriptionExpr, DescriptionExpr = DescriptionExpr,
AnalysisExpr = AnalysisExpr,
DueDateExpr = DueDateExpr, DueDateExpr = DueDateExpr,
TagExpr = TagExpr, TagExpr = TagExpr,
AssigneeExpr = AssigneeExpr, AssigneeExpr = AssigneeExpr,
@ -281,6 +285,53 @@ public static class ListFormSeeder_DefaultJsons
}); });
} }
/// <summary>
/// Ekranın üstündeki KPI şeridi: her kart tabloyu kendi koşuluyla sayar ve kartlar tek sorguda
/// <c>UNION ALL</c> ile döner. Alt başlık ekranın adıdır; kartın başlığı dil anahtarıdır.
/// Widget sorgusu ekranın otomatik tenant filtresinden geçmez; kiracı tablosunda
/// <paramref name="tenant"/> verilmezse kartlar bütün kiracıları sayar.
/// </summary>
public static string DefaultWidgetsJson(string tableName, string subTitle, bool tenant, params WidgetCard[] cards)
{
var sql = string.Join("\nUNION ALL\n", cards.Select(card =>
{
var conditions = new[]
{
// Host'ta DefaultValueHelper bunu "IS NULL"a çevirir; liste ekranıyla aynı kırılım.
tenant ? $"\"TenantId\" = '{DefaultValues.TenantId}'" : null,
card.Where,
}
.Where(c => !string.IsNullOrEmpty(c))
.ToArray();
var where = conditions.Length > 0 ? $" WHERE {string.Join(" AND ", conditions)}" : string.Empty;
// Sayı ve tutar kartları aynı UNION'da buluşabilir; tip çakışmasın diye değer metne çevrilir.
return $"SELECT '{card.Title}' AS \"Title\", CAST({card.Value} AS VARCHAR(32)) AS \"Value\", '{card.Color}' AS \"Color\", " +
$"'{subTitle}' AS \"SubTitle\", '{card.Icon}' AS \"Icon\" FROM \"{tableName}\"{where}";
}));
return DefaultWidgetsJson(sql, cards.Length);
}
/// <summary>Kart sayısı sorgudan belli olmayan (gruplamalı) şeritler için; şerit 12 kolonu eşit böler.</summary>
public static string DefaultWidgetsJson(string sqlQuery, int cardCount) => JsonSerializer.Serialize(new List<WidgetEditDto>
{
new()
{
ColGap = 3,
ColSpan = Math.Max(2, 12 / cardCount),
SqlQuery = sqlQuery,
Title = "Title",
Value = "Value",
ValueClassName = "text-3xl",
Color = "Color",
Icon = "Icon",
SubTitle = "SubTitle",
ClassName = "mb-3",
IsActive = true,
}
});
/// <summary> /// <summary>
/// Bir yolun arama motoru sozlesmesi. Ayri bir SEO tablosu yoktur: alanlar yolu tanimlayan /// Bir yolun arama motoru sozlesmesi. Ayri bir SEO tablosu yoktur: alanlar yolu tanimlayan
/// kaydin (Route, Custom Component) ekranina eklenir, bu yuzden kume tek yerde durur. /// kaydin (Route, Custom Component) ekranina eklenir, bu yuzden kume tek yerde durur.
@ -337,3 +388,6 @@ public static class ListFormSeeder_DefaultJsons
]; ];
} }
} }
/// <summary>KPI şeridindeki tek kart: başlık dil anahtarı, <c>Value</c> SQL ifadesi, <c>Where</c> ek koşul.</summary>
public record WidgetCard(string Title, string Color, string Icon, string Where = null, string Value = "COUNT(*)");

View file

@ -79,6 +79,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = "AbpTenants", SelectCommand = "AbpTenants",
WidgetsJson = DefaultWidgetsJson("AbpTenants", listFormName, false,
new("App.Widget.Total", "blue", "FaBuilding"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsActive\" = 'true'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "\"IsActive\" = 'false'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SortMode = GridOptions.SortModeSingle, SortMode = GridOptions.SortModeSingle,
@ -625,6 +629,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Branch)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Branch)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Branch)), listFormName, false,
new("App.Widget.Total", "blue", "FaCodeBranch"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsActive\" = 'true'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "(\"IsActive\" = 'false' OR \"IsActive\" IS NULL)")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
@ -1898,7 +1906,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = false, SeedSyncInsert = false,
SeedSyncUpdate = false, SeedSyncUpdate = false,
SeedSyncDelete = false, SeedSyncDelete = false,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle, SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson(), FilterRowJson = DefaultFilterRowJson(),
@ -2185,7 +2193,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = false, SeedSyncInsert = false,
SeedSyncUpdate = false, SeedSyncUpdate = false,
SeedSyncDelete = false, SeedSyncDelete = false,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle, SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson(), FilterRowJson = DefaultFilterRowJson(),
@ -2332,7 +2340,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = false, SeedSyncInsert = false,
SeedSyncUpdate = false, SeedSyncUpdate = false,
SeedSyncDelete = false, SeedSyncDelete = false,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
SortMode = GridOptions.SortModeSingle, SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson(), FilterRowJson = DefaultFilterRowJson(),
@ -4016,6 +4024,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Language)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Language)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Language)), listFormName, false,
new("App.Widget.Total", "blue", "FaGlobe"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsEnabled\" = 'true'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "\"IsEnabled\" = 'false'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
@ -4213,6 +4225,17 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText)),
WidgetsJson = DefaultWidgetsJson($@"
SELECT 'App.Widget.Total' AS ""Title"", CAST(COUNT(DISTINCT ""Key"") AS VARCHAR(32)) AS ""Value"", 'purple' AS ""Color"", '{listFormName}' AS ""SubTitle"", 'FaKey' AS ""Icon""
FROM ""{TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText))}""
UNION ALL
SELECT l.""DisplayName"", CAST(COUNT(t.""Id"") AS VARCHAR(32)), 'blue', '{listFormName}', 'FaLanguage'
FROM ""{TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText))}"" t
JOIN ""{TableNameResolver.GetFullTableName(nameof(TableNameEnum.Language))}"" l ON l.""CultureName"" = t.""CultureName""
GROUP BY l.""DisplayName""
", 3),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
@ -4553,6 +4576,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ListForm)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ListForm)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ListForm)), listFormName, false,
new("App.Widget.Total", "blue", "FaThList"),
new("App.Widget.List", "green", "FaTable", $"\"ListFormType\" = '{ListFormTypeEnum.List}'"),
new("App.Widget.Form", "purple", "FaWpforms", $"\"ListFormType\" = '{ListFormTypeEnum.Form}'"),
new("App.Widget.Chart", "orange", "FaChartBar", $"\"ListFormType\" = '{ListFormTypeEnum.Chart}'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SortMode = GridOptions.SortModeMultiple, SortMode = GridOptions.SortModeMultiple,
@ -5235,6 +5263,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker)), listFormName, false,
new("App.Widget.Total", "blue", "FaClock"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsActive\" = 'true'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "\"IsActive\" = 'false'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
@ -5531,6 +5563,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.View, SelectCommandType = SelectCommandTypeEnum.View,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker_JobFlow)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker_JobFlow)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker_JobFlow)), listFormName, false,
new("App.Platform.Success", "green", "FaCheckCircle", "\"Status\" = 'Succeeded'"),
new("App.Platform.Failed", "red", "FaTimesCircle", "\"Status\" = 'Failed'"),
new("App.Widget.Processing", "blue", "FaSpinner", "\"Status\" = 'Processing'"),
new("App.Widget.Enqueued", "yellow", "FaHourglassHalf", "\"Status\" IN ('Enqueued', 'Scheduled')")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
SortMode = GridOptions.SortModeSingle, SortMode = GridOptions.SortModeSingle,
@ -6055,6 +6092,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Menu)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Menu)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Menu)), listFormName, false,
new("App.Widget.Total", "blue", "FaBars"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsDisabled\" = 'false'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "\"IsDisabled\" = 'true'")),
KeyFieldName = "Code", KeyFieldName = "Code",
KeyFieldDbSourceType = DbType.String, KeyFieldDbSourceType = DbType.String,
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
@ -6425,6 +6466,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.CustomEndpoint)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.CustomEndpoint)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.CustomEndpoint)), listFormName, false,
new("GET", "blue", "FaDownload", "\"Method\" = 'GET'"),
new("POST", "green", "FaPlusCircle", "\"Method\" = 'POST'"),
new("PUT", "orange", "FaEdit", "\"Method\" = 'PUT'"),
new("DELETE", "red", "FaTrash", "\"Method\" = 'DELETE'")),
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = true, SeedSyncInsert = true,
SeedSyncUpdate = true, SeedSyncUpdate = true,
@ -7326,7 +7372,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ListOrderNo = 6, ListOrderNo = 6,
Visible = true, Visible = true,
IsActive = true, IsActive = true,
AllowSearch = true, AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson, ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name), PermissionJson = DefaultFieldPermissionJson(listForm.Name),
@ -7411,6 +7457,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = mcpServerTable, SelectCommand = mcpServerTable,
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.McpServer)), listFormName, false,
new("App.Widget.Total", "blue", "FaServer"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsActive\" = 'true'"),
new("App.Widget.Passive", "gray", "FaTimesCircle", "\"IsActive\" = 'false'")),
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName), SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
SeedSyncInsert = true, SeedSyncInsert = true,
SeedSyncUpdate = true, SeedSyncUpdate = true,
@ -8249,6 +8299,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Order)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Order)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.Order)), listFormName, false,
new("App.Widget.Total", "blue", "FaShoppingCart"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsActive\" = 'true'"),
new("App.Widget.TotalRevenue", "purple", "FaMoneyBillWave", Value: "CAST(COALESCE(SUM(\"Total\"), 0) AS DECIMAL(18, 2))")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
@ -9436,6 +9490,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumCategory)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumCategory)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumCategory)), listFormName, true,
new("App.Widget.Total", "blue", "FaFolder"),
new("App.Widget.Active", "green", "FaCheckCircle", "\"IsActive\" = 'true'"),
new("App.Listform.ListformField.IsLocked", "gray", "FaLock", "\"IsLocked\" = 'true'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
@ -9657,6 +9715,11 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumTopic)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumTopic)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumTopic)), listFormName, true,
new("App.Widget.Pinned", "purple", "FaThumbtack", "\"IsPinned\" = 'true'"),
new("App.Listform.ListformField.IsSolved", "green", "FaCheckCircle", "\"IsSolved\" = 'true'"),
new("App.Listform.ListformField.IsLocked", "gray", "FaLock", "\"IsLocked\" = 'true'"),
new("App.Widget.TotalViews", "blue", "FaEye", Value: "COALESCE(SUM(\"ViewCount\"), 0)")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
@ -10327,6 +10390,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumPost)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumPost)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ForumPost)), listFormName, true,
new("App.Widget.Total", "blue", "FaComment"),
new("App.Widget.AcceptedAnswers", "green", "FaCheckCircle", "\"IsAcceptedAnswer\" = 'true'"),
new("App.Widget.Today", "purple", "FaCalendarDay", "CAST(\"CreationTime\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE)")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
@ -11057,6 +11124,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BlogPost)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BlogPost)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.BlogPost)), listFormName, false,
new("App.Widget.Published", "green", "FaCheckCircle", "\"IsPublished\" = 'true'"),
new("App.Widget.Draft", "gray", "FaPencilAlt", "\"IsPublished\" = 'false'"),
new("App.Widget.TotalViews", "purple", "FaEye", Value: "COALESCE(SUM(\"ViewCount\"), 0)")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
@ -11716,6 +11787,13 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
Description = $"{listFormName}.Description", Description = $"{listFormName}.Description",
SelectCommandType = SelectCommandTypeEnum.Table, SelectCommandType = SelectCommandTypeEnum.Table,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.DevRequest)), SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.DevRequest)),
WidgetsJson = DefaultWidgetsJson(TableNameResolver.GetFullTableName(nameof(TableNameEnum.DevRequest)), listFormName, false,
new("App.StaticLookup.Backlog", "gray", "FaInbox", "\"Status\" = 'Backlog'"),
new("App.StaticLookup.Analysis", "pink", "FaSearch", "\"Status\" = 'Analysis'"),
new("App.StaticLookup.ReadyToStart", "blue", "FaFlag", "\"Status\" = 'Ready to Start'"),
new("App.StaticLookup.InProgress", "orange", "FaSpinner", "\"Status\" = 'In Progress'"),
new("App.StaticLookup.Testing", "yellow", "FaVial", "\"Status\" = 'Testing'"),
new("App.StaticLookup.Staging", "green", "FaRocket", "\"Status\" = 'Staging'")),
KeyFieldName = "Id", KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid, KeyFieldDbSourceType = DbType.Guid,
DefaultFilter = DefaultFilterJson, DefaultFilter = DefaultFilterJson,
@ -11857,9 +11935,9 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ValueExpr = "key", ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key="Backlog", Name="App.StaticLookup.Backlog" }, new () { Key="Backlog", Name="App.StaticLookup.Backlog" },
new () { Key="Analysis", Name="App.StaticLookup.Analysis" },
new () { Key="Ready to Start", Name="App.StaticLookup.ReadyToStart" }, new () { Key="Ready to Start", Name="App.StaticLookup.ReadyToStart" },
new () { Key="In Progress", Name="App.StaticLookup.InProgress" }, new () { Key="In Progress", Name="App.StaticLookup.InProgress" },
new () { Key="Code Review", Name="App.StaticLookup.CodeReview" },
new () { Key="Testing", Name="App.StaticLookup.Testing" }, new () { Key="Testing", Name="App.StaticLookup.Testing" },
new () { Key="Staging", Name="App.StaticLookup.Staging" } new () { Key="Staging", Name="App.StaticLookup.Staging" }
}), }),
@ -11909,7 +11987,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CaptionName = "App.Listform.ListformField.Description", CaptionName = "App.Listform.ListformField.Description",
Width = 0, Width = 0,
ListOrderNo = 6, ListOrderNo = 6,
Visible = true, Visible = false,
IsActive = true, IsActive = true,
AllowSearch = true, AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson, ValidationRuleJson = DefaultValidationRuleRequiredJson,
@ -11918,6 +11996,26 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
PivotSettingsJson = DefaultPivotSettingsJson, PivotSettingsJson = DefaultPivotSettingsJson,
EditorOptions = EditorOptions.Html() EditorOptions = EditorOptions.Html()
}, },
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Analysis",
EditGroupOrderNo = 1,
EditOrderNo = 7,
EditorType2 = EditorTypes.dxHtmlEditor,
ColSpan = 2,
CaptionName = "App.Listform.ListformField.Analysis",
Width = 0,
ListOrderNo = 15,
Visible = false,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
EditorOptions = EditorOptions.Html()
},
new() { new() {
ListFormCode = listForm.ListFormCode, ListFormCode = listForm.ListFormCode,
@ -12046,7 +12144,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion #endregion
} }
#endregion #endregion
#region IntranetWidget #region IntranetWidget
listFormName = AppCodes.DeveloperKits.IntranetWidgets; listFormName = AppCodes.DeveloperKits.IntranetWidgets;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName)) if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))

View file

@ -77,13 +77,17 @@ public static class EditorOptions
=> New().Width(width).Height(height).Flag("multiple", multiple); => New().Width(width).Height(height).Flag("multiple", multiple);
/// <summary>Zengin metin (HTML) editoru; tam arac cubugu ile.</summary> /// <summary>Zengin metin (HTML) editoru; tam arac cubugu ile.</summary>
public static EditorOptionsBuilder Html(int height = 200) /// <remarks>
/// <c>height</c> verilirse editor sabitlenir ve icerik kaydirilir; <c>min-height</c> ise
/// en az yuksekligi korurken yeni satirlarla buyumesine izin verir.
/// </remarks>
public static EditorOptionsBuilder Html(int minHeight = 200)
=> New() => New()
.Raw("mediaResizing", "{\"enabled\":true}") .Raw("mediaResizing", "{\"enabled\":true}")
.Raw("imageUpload", "{\"tabs\":[\"file\",\"url\"],\"fileUploadMode\":\"base64\"}") .Raw("imageUpload", "{\"tabs\":[\"file\",\"url\"],\"fileUploadMode\":\"base64\"}")
.Raw("toolbar", "{\"multiline\":true,\"items\":[" + string.Join(",", HtmlToolbarItems()) + "]}") .Raw("toolbar", "{\"multiline\":true,\"items\":[" + string.Join(",", HtmlToolbarItems()) + "]}")
.Flag("encodeHtml", true) .Flag("encodeHtml", true)
.Height(height); .Raw("elementAttr", "{\"style\":\"min-height:" + EditorJson.Number(minHeight) + "px\"}");
private static IEnumerable<string> HtmlToolbarItems() private static IEnumerable<string> HtmlToolbarItems()
{ {

View file

@ -12,6 +12,7 @@ public class DevRequest : FullAuditedEntity<Guid>
public string? Title { get; set; } public string? Title { get; set; }
public string? Status { get; set; } public string? Status { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public string? Analysis { get; set; }
public DateTime? DueDate { get; set; } public DateTime? DueDate { get; set; }
public string? Tags { get; set; } public string? Tags { get; set; }
public string? Assignees { get; set; } public string? Assignees { get; set; }

View file

@ -1599,6 +1599,7 @@ public class PlatformDbContext :
b.Property(e => e.Title).HasMaxLength(512).IsRequired(); b.Property(e => e.Title).HasMaxLength(512).IsRequired();
b.Property(e => e.Status).HasMaxLength(64).IsRequired(); b.Property(e => e.Status).HasMaxLength(64).IsRequired();
b.Property(e => e.Description).HasColumnType("nvarchar(max)"); b.Property(e => e.Description).HasColumnType("nvarchar(max)");
b.Property(e => e.Analysis).HasColumnType("nvarchar(max)");
b.Property(e => e.Tags).HasMaxLength(256); b.Property(e => e.Tags).HasMaxLength(256);
b.Property(e => e.Assignees).HasMaxLength(512); b.Property(e => e.Assignees).HasMaxLength(512);
b.Property(e => e.Priority).HasMaxLength(64); b.Property(e => e.Priority).HasMaxLength(64);

View file

@ -2375,6 +2375,9 @@ namespace Sozsoft.Platform.Migrations
b.Property<Guid>("Id") b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
b.Property<string>("Analysis")
.HasColumnType("nvarchar(max)");
b.Property<string>("Assignees") b.Property<string>("Assignees")
.HasMaxLength(512) .HasMaxLength(512)
.HasColumnType("nvarchar(512)"); .HasColumnType("nvarchar(512)");

View file

@ -1322,6 +1322,7 @@ namespace Sozsoft.Platform.Migrations
Title = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false), Title = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: false),
Status = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false), Status = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true), Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
Analysis = table.Column<string>(type: "nvarchar(max)", nullable: true),
DueDate = table.Column<DateTime>(type: "datetime2", nullable: true), DueDate = table.Column<DateTime>(type: "datetime2", nullable: true),
Tags = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true), Tags = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
Assignees = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true), Assignees = table.Column<string>(type: "nvarchar(512)", maxLength: 512, nullable: true),

View file

@ -2372,6 +2372,9 @@ namespace Sozsoft.Platform.Migrations
b.Property<Guid>("Id") b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier"); .HasColumnType("uniqueidentifier");
b.Property<string>("Analysis")
.HasColumnType("nvarchar(max)");
b.Property<string>("Assignees") b.Property<string>("Assignees")
.HasMaxLength(512) .HasMaxLength(512)
.HasColumnType("nvarchar(512)"); .HasColumnType("nvarchar(512)");

View file

@ -121,6 +121,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
1. **Yeni tablo** → tenant + tam audit kolonları varsayılan gelir (`Id`, `TenantId`, 1. **Yeni tablo** → tenant + tam audit kolonları varsayılan gelir (`Id`, `TenantId`,
`CreationTime`, `CreatorId`, `LastModificationTime`, `LastModifierId`, `IsDeleted`, `CreationTime`, `CreatorId`, `LastModificationTime`, `LastModifierId`, `IsDeleted`,
`DeletionTime`, `DeleterId`). Kullanıcı açıkça "tenant yok / audit yok" demedikçe çıkarma. `DeletionTime`, `DeleterId`). Kullanıcı açıkça "tenant yok / audit yok" demedikçe çıkarma.
Yeni **tanım** tablosu (tanım olup olmadığına talebin içeriğine göre analizde karar verilir;
sabit ad listesi yoktur) buna ek olarak `IsActive bit NOT NULL DEFAULT 1` ile gelir; liste + form alanı, varsayılan
`true`, Aktif/Pasif widget'ı. Ayrıntı: `lowcode.instructions.md` §8.1.
2. **Yeni ekran** → Wizard yolu. `Definitions` altına otomatik yerleştirme yok; modüle ait yeni 2. **Yeni ekran** → Wizard yolu. `Definitions` altına otomatik yerleştirme yok; modüle ait yeni
bir kök menü oluştur, `Order = max(Order) + 1`. bir kök menü oluştur, `Order = max(Order) + 1`.
3. **Yeni ekran/menü** → yetki sözleşmesi olmadan önerilmez. Menü ve route aynı ekran 3. **Yeni ekran/menü** → yetki sözleşmesi olmadan önerilmez. Menü ve route aynı ekran
@ -160,6 +163,11 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
`Pivot` + `Chart` açık, `DefaultLayout: "grid"` ile gelir; "liste/grid" demek diğerlerini `Pivot` + `Chart` açık, `DefaultLayout: "grid"` ile gelir; "liste/grid" demek diğerlerini
kapatmak değildir. Bir görünüm yalnızca istek onu açıkça dışarıda bıraktığında `false` yazılır. kapatmak değildir. Bir görünüm yalnızca istek onu açıkça dışarıda bıraktığında `false` yazılır.
Tree/Gantt/Scheduler/Todo yalnızca istenince açılır. Ayrıntı: `lowcode.instructions.md` §9.8. Tree/Gantt/Scheduler/Todo yalnızca istenince açılır. Ayrıntı: `lowcode.instructions.md` §9.8.
13. **Liste ekranı widget'ları** → Wizard ya da MCP `deploy_wizard` ile üretilen her List ekranı
sorulmadan **en az 3 kartlık** `Widgets` ile gelir; kartlar tablonun durum / boolean bayrak /
tarih / tutar kolonlarından seçilir, başlıklar dil anahtarıdır (`App.Widget.*` önce). Kiracı
tablosunda her kart `"TenantId" = '@TENANTID'` taşır; `IsDeleted` koşulu yazılmaz. Form/Chart,
menüsüz alt form ve basit tanım listeleri istisnadır. Ayrıntı: `lowcode.instructions.md` §4.6.1.
### Cross-cutting: touching one thing means touching these too ### Cross-cutting: touching one thing means touching these too
@ -250,9 +258,15 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
- **Talepteki görsel** → `App.DevRequests` açıklamasına yapıştırılan görsel base64 `data:` URI olarak - **Talepteki görsel** → `App.DevRequests` açıklamasına yapıştırılan görsel base64 `data:` URI olarak
kalır, hiçbir yere yüklenmez. `start_dev_request` / `get_dev_request` onu metinden ayıklar kalır, hiçbir yere yüklenmez. `start_dev_request` / `get_dev_request` onu metinden ayıklar
(`McpDevRequestImages`) ve MCP resim içeriği (`ImageContentBlock`) olarak döndürür; metinde (`McpDevRequestImages`) ve MCP resim içeriği (`ImageContentBlock`) olarak döndürür; metinde
`[image-N]` kalır. Açıklama bu yüzden `update_row` ile yeniden yazılmaz, not `[image-N]` kalır. Açıklama bu yüzden `update_row` ile yeniden yazılmaz. `SubTasks` de
`append_dev_request_note` ile eklenir. `SubTasks` de `add_dev_request_subtasks` ile yazılır: `add_dev_request_subtasks` ile yazılır: atanan kişi bağlanan kullanıcı, bitiş tarihi bugün —
atanan kişi bağlanan kullanıcı, bitiş tarihi bugün — sunucu doldurur, modele bırakılmaz. sunucu doldurur, modele bırakılmaz.
- **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 (hiçbir şey üretmez),
`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` - **MCP şema araçları** → tablo/sütun bilgisi canlı katalogdan değil `Sas_H_DbTableColumn`
sözlüğünden okunur (`IMcpSchemaDictionary`). Sözlüğün alan adları sözlüğünden okunur (`IMcpSchemaDictionary`). Sözlüğün alan adları
`dbo.Sas_H_ListFormFieldsDictionaryJson` prosedürünün ürettiği JSON ile birebir aynıdır; `dbo.Sas_H_ListFormFieldsDictionaryJson` prosedürünün ürettiği JSON ile birebir aynıdır;

View file

@ -1,7 +1,7 @@
{ {
"ListFormCode": "App.Languages.LanguageText", "ListFormCode": "App.Languages.LanguageText",
"KeyFieldName": "Id", "KeyFieldName": "Id",
"GeneratedAt": "2026-09-24T19:17:56.868760Z", "GeneratedAt": "2026-09-24T19:17:56.86876Z",
"Order": 2, "Order": 2,
"Rows": [ "Rows": [
{ {
@ -30986,6 +30986,20 @@
"Key": "App.Listform.ListformField.Description", "Key": "App.Listform.ListformField.Description",
"Value": "Açıklama" "Value": "Açıklama"
}, },
{
"Id": "76be8cec-c18d-5718-8063-e0c7f15615bc",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Listform.ListformField.Analysis",
"Value": "Analysis"
},
{
"Id": "095fb0a4-f3b8-5c6e-9bdb-d94ec1f6f496",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Listform.ListformField.Analysis",
"Value": "Analiz"
},
{ {
"Id": "9e6aa3ef-bf85-5719-a8cc-f3045f52a76d", "Id": "9e6aa3ef-bf85-5719-a8cc-f3045f52a76d",
"CultureName": "en", "CultureName": "en",
@ -42802,6 +42816,20 @@
"Key": "App.ListFormFieldEdit.TodoDescriptionExpr", "Key": "App.ListFormFieldEdit.TodoDescriptionExpr",
"Value": "Açıklama alanı" "Value": "Açıklama alanı"
}, },
{
"Id": "7227ccdb-ab79-5d0c-a68f-3819603da4f1",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.ListFormFieldEdit.TodoAnalysisExpr",
"Value": "Analysis Field"
},
{
"Id": "3bad05d0-dab7-580d-9867-a3d580e73b91",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.ListFormFieldEdit.TodoAnalysisExpr",
"Value": "Analiz alanı"
},
{ {
"Id": "bb493ec4-ef38-5298-8910-e855501c9d65", "Id": "bb493ec4-ef38-5298-8910-e855501c9d65",
"CultureName": "en", "CultureName": "en",
@ -42919,7 +42947,7 @@
"CultureName": "en", "CultureName": "en",
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "App.ListFormFieldEdit.TodoStatusOrderPlaceholder", "Key": "App.ListFormFieldEdit.TodoStatusOrderPlaceholder",
"Value": "Backlog, Ready to Start, In Progress, Code Review, Testing, Staging" "Value": "Backlog, Analysis, Ready to Start, In Progress, Testing, Staging"
}, },
{ {
"Id": "d17288e1-c462-5856-8d13-0da438583e5e", "Id": "d17288e1-c462-5856-8d13-0da438583e5e",
@ -43166,6 +43194,20 @@
"Key": "App.ListFormTodoBoard.DescriptionPlaceholder", "Key": "App.ListFormTodoBoard.DescriptionPlaceholder",
"Value": "Todo açıklamasını yazın…" "Value": "Todo açıklamasını yazın…"
}, },
{
"Id": "3a16fde9-ed85-5c4e-bcfb-ea9b7a8009a8",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.ListFormTodoBoard.AnalysisPlaceholder",
"Value": "Write the analysis of the request..."
},
{
"Id": "f91ecec5-6334-5237-8f7b-89ce9683cca8",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.ListFormTodoBoard.AnalysisPlaceholder",
"Value": "Talebin analizini yazın..."
},
{ {
"Id": "c5b1a520-3a12-55f7-828c-9a5ef4d33051", "Id": "c5b1a520-3a12-55f7-828c-9a5ef4d33051",
"CultureName": "en", "CultureName": "en",
@ -45420,6 +45462,20 @@
"Key": "App.StaticLookup.Backlog", "Key": "App.StaticLookup.Backlog",
"Value": "Bekleyen İşler" "Value": "Bekleyen İşler"
}, },
{
"Id": "c0cc1060-fd5d-5e4e-a052-8985b125732e",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Analysis",
"Value": "Analysis"
},
{
"Id": "16b66d78-6d17-5650-9831-fafbb76b408e",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Analysis",
"Value": "Analiz"
},
{ {
"Id": "2a645686-5b78-5a50-86fc-e6e4a44f72b1", "Id": "2a645686-5b78-5a50-86fc-e6e4a44f72b1",
"CultureName": "en", "CultureName": "en",
@ -45532,20 +45588,6 @@
"Key": "App.Platform.Chart", "Key": "App.Platform.Chart",
"Value": "Grafik" "Value": "Grafik"
}, },
{
"Id": "043893de-610e-55b5-a2a7-e4a5146672b9",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.StaticLookup.CodeReview",
"Value": "Code Review"
},
{
"Id": "3e6e364b-5b92-5489-8fee-83442eed3867",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.StaticLookup.CodeReview",
"Value": "Kod İncelemesi"
},
{ {
"Id": "dfa59c96-96b5-50f7-b462-229600230832", "Id": "dfa59c96-96b5-50f7-b462-229600230832",
"CultureName": "en", "CultureName": "en",
@ -67190,6 +67232,20 @@
"Key": "App.Mcp.Error.DevRequestNotReady", "Key": "App.Mcp.Error.DevRequestNotReady",
"Value": "\"{0}\" geliştirme talebi \"{1}\" durumunda; yalnızca \"Ready to Start\" durumundaki talepler başlatılabilir." "Value": "\"{0}\" geliştirme talebi \"{1}\" durumunda; yalnızca \"Ready to Start\" durumundaki talepler başlatılabilir."
}, },
{
"Id": "2b75ab25-37b8-56d3-b38e-ca5ba7296596",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Mcp.Error.DevRequestNotAnalyzable",
"Value": "Development request \"{0}\" is in \"{1}\"; only requests in \"Backlog\" or \"Analysis\" can be analyzed."
},
{
"Id": "4f36d621-7053-5d99-b952-37a64b4f63dc",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Mcp.Error.DevRequestNotAnalyzable",
"Value": "\"{0}\" geliştirme talebi \"{1}\" durumunda; yalnızca \"Backlog\" ya da \"Analysis\" durumundaki talepler analiz edilebilir."
},
{ {
"Id": "6cac6a6b-9329-5552-86d9-08e602afcb0f", "Id": "6cac6a6b-9329-5552-86d9-08e602afcb0f",
"CultureName": "en", "CultureName": "en",
@ -67204,6 +67260,20 @@
"Key": "App.Mcp.Error.DevRequestNoteRequired", "Key": "App.Mcp.Error.DevRequestNoteRequired",
"Value": "Eklenecek not boş olamaz." "Value": "Eklenecek not boş olamaz."
}, },
{
"Id": "d3c1c3fe-5dda-5113-8f47-515d5c91caab",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Mcp.Error.DevRequestAnalysisRequired",
"Value": "The analysis cannot be empty."
},
{
"Id": "ada842cf-97ef-544f-8993-504e450895ac",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Mcp.Error.DevRequestAnalysisRequired",
"Value": "Analiz boş olamaz."
},
{ {
"Id": "aa72e998-f4a1-5873-95ef-1fcfe0b601f9", "Id": "aa72e998-f4a1-5873-95ef-1fcfe0b601f9",
"CultureName": "en", "CultureName": "en",
@ -69261,6 +69331,370 @@
"ResourceName": "Platform", "ResourceName": "Platform",
"Key": "App.Mcp.Error.VerifyWizardPending", "Key": "App.Mcp.Error.VerifyWizardPending",
"Value": "\"{0}\" wizard'ı henüz kurulmadı: seed dosyaları yazıldı ama DB Migrate çalışmadı. Kullanıcıdan Wizard Manager'da DB Migrate çalıştırmasını isteyin, sonra tekrar doğrulayın." "Value": "\"{0}\" wizard'ı henüz kurulmadı: seed dosyaları yazıldı ama DB Migrate çalışmadı. Kullanıcıdan Wizard Manager'da DB Migrate çalıştırmasını isteyin, sonra tekrar doğrulayın."
},
{
"Id": "cfb9483e-f6b1-5041-af8f-a7b36b1c3d1b",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Total",
"Value": "Total"
},
{
"Id": "dcee1716-6f7b-5480-80f2-20c63f18cee3",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Total",
"Value": "Toplam"
},
{
"Id": "5b7ac5a9-0d19-56c8-b510-84dff08812b2",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Active",
"Value": "Active"
},
{
"Id": "5828ae1a-9592-56c3-aa72-192f873bcdde",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Active",
"Value": "Aktif"
},
{
"Id": "0d934934-b906-5656-91d4-845ed25a226f",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Passive",
"Value": "Passive"
},
{
"Id": "4794dbc8-dff2-52ea-8f0a-c7e45492454d",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Passive",
"Value": "Pasif"
},
{
"Id": "1c187a6c-50b1-5817-bffe-d43504316d09",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Published",
"Value": "Published"
},
{
"Id": "249cd74e-5d8d-59b4-ab1b-0e1e6027d059",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Published",
"Value": "Yayında"
},
{
"Id": "2e85ac8f-0013-5615-9303-1356666bf5b4",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Draft",
"Value": "Draft"
},
{
"Id": "cac1c52e-3a9a-5619-b630-9efc36bf570b",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Draft",
"Value": "Taslak"
},
{
"Id": "5a95141c-7795-59db-93c0-79de85bf2ec4",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.TotalViews",
"Value": "Total Views"
},
{
"Id": "06d4dd8f-b635-51a5-952c-634f07f63872",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.TotalViews",
"Value": "Toplam Görüntülenme"
},
{
"Id": "3733d251-e6ec-5185-a81d-9d5f243c7481",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.TotalRevenue",
"Value": "Total Revenue"
},
{
"Id": "4e9523f9-e134-51e5-b9ab-9c1d942de904",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.TotalRevenue",
"Value": "Toplam Tutar"
},
{
"Id": "dc0fc005-26b7-5ab3-a9e0-025b0c244f5e",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Processing",
"Value": "Processing"
},
{
"Id": "b6a42a49-550b-5507-924c-be549aafa17c",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Processing",
"Value": "İşleniyor"
},
{
"Id": "f59e997a-22a9-5272-91ef-74f169460b1f",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Enqueued",
"Value": "Queued"
},
{
"Id": "c1cab658-b676-5960-83ad-d80faead0ef2",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Enqueued",
"Value": "Kuyrukta"
},
{
"Id": "cb70d1eb-f81c-5ecc-960c-07259fa162b5",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.List",
"Value": "List"
},
{
"Id": "7c7cc937-d0b4-5a22-aa93-efba03a22f2c",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.List",
"Value": "Liste"
},
{
"Id": "0bd15ad4-59df-5eab-b18b-7108d5fbf31c",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Form",
"Value": "Form"
},
{
"Id": "90a05ae9-611a-50e1-913f-8d5c2d011017",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Form",
"Value": "Form"
},
{
"Id": "2a36f006-fb79-5371-bcc9-85f9665ffe6d",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Chart",
"Value": "Chart"
},
{
"Id": "9e91c54f-5e6f-53c0-838c-08023c5d2999",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Chart",
"Value": "Grafik"
},
{
"Id": "f14c54a8-6204-58d3-8e34-99dd6772ebf7",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Today",
"Value": "Today"
},
{
"Id": "4f4dc6ac-76da-533a-a9d0-0bff9a599429",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Today",
"Value": "Bugün"
},
{
"Id": "a5f51fca-5708-55fd-b4c1-86a4a09f6f49",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Errors",
"Value": "Errors"
},
{
"Id": "097213dd-41a9-59a0-8580-455344be487a",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Errors",
"Value": "Hatalar"
},
{
"Id": "32d44619-87d5-52a1-ad52-18ca0ae05f42",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.SignedInToday",
"Value": "Signed In Today"
},
{
"Id": "9ce0f85e-40e6-5537-a21c-52e519a59b18",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.SignedInToday",
"Value": "Bugün Giriş Yapan"
},
{
"Id": "392cffdf-de41-51ed-b54a-25b3740ece59",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.ActiveToday",
"Value": "Active Today"
},
{
"Id": "a64fc55c-a100-5b82-9ea0-b9f4821b2e93",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.ActiveToday",
"Value": "Bugün Aktif"
},
{
"Id": "db419b9f-31ef-5ba0-8a92-4b4e82d126fc",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Pinned",
"Value": "Pinned"
},
{
"Id": "40a10314-e283-5357-a1e5-e26b3d06faf3",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Pinned",
"Value": "Sabitlenmiş"
},
{
"Id": "efe1415e-db82-5812-8705-4672799a58ab",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Expired",
"Value": "Expired"
},
{
"Id": "75ec191d-a7cf-571d-b2c5-cb2f8c046c21",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Expired",
"Value": "Süresi Dolmuş"
},
{
"Id": "b43721ea-2ab7-5522-a7c4-96fa1b406e05",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.TotalResponses",
"Value": "Total Responses"
},
{
"Id": "19389bec-2911-5e15-934f-504f1542955d",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.TotalResponses",
"Value": "Toplam Yanıt"
},
{
"Id": "4328b107-b212-51d1-9d4a-a4803fa1cc0d",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.TotalLikes",
"Value": "Total Likes"
},
{
"Id": "da98e932-8f63-5b08-85cd-3814963833f0",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.TotalLikes",
"Value": "Toplam Beğeni"
},
{
"Id": "98fbfb84-ac4d-5dc1-8acd-3f3d2b4c3b01",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Upcoming",
"Value": "Upcoming"
},
{
"Id": "96ee24d2-a8cf-53c7-aa68-bc208a1b7a6c",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Upcoming",
"Value": "Yaklaşan"
},
{
"Id": "ca076402-bf1c-5fee-a38e-44e0c8904dab",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Sent",
"Value": "Sent"
},
{
"Id": "37d86092-7c5b-5bfd-ae10-7470c63f5347",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Sent",
"Value": "Gönderildi"
},
{
"Id": "a50e4ec1-03ce-5c61-88c6-2854ba91d762",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Pending",
"Value": "Pending"
},
{
"Id": "e7ddfd18-5a70-5ba6-b72a-228330f0372a",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Pending",
"Value": "Bekliyor"
},
{
"Id": "92f99a0d-c8aa-5a0f-b006-180b1072640e",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Unread",
"Value": "Unread"
},
{
"Id": "64d6688e-9d99-5794-8ade-643ddd8a38b9",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Unread",
"Value": "Okunmadı"
},
{
"Id": "ca8789e7-ffdc-502e-80db-62d72e745be0",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.AcceptedAnswers",
"Value": "Accepted Answers"
},
{
"Id": "92880cf9-e12e-5f94-a905-74e45e9af69e",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.AcceptedAnswers",
"Value": "Kabul Edilen Cevaplar"
},
{
"Id": "55d48500-09ce-5b61-bcdf-448f27cbdd44",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Widget.Unverified",
"Value": "Unverified"
},
{
"Id": "3b9d9693-bcb7-5c95-8bd5-c57bf6b4b5d4",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Widget.Unverified",
"Value": "Doğrulanmamış"
} }
] ]
} }

View file

@ -459,6 +459,7 @@ export interface TodoOptionDto {
titleExpr?: string titleExpr?: string
statusExpr?: string statusExpr?: string
descriptionExpr?: string descriptionExpr?: string
analysisExpr?: string
dueDateExpr?: string dueDateExpr?: string
tagExpr?: string tagExpr?: string
assigneeExpr?: string assigneeExpr?: string

View file

@ -28,6 +28,7 @@ const mappings: Array<{
{ name: 'titleExpr', labelKey: 'TodoTitleExpr', required: true }, { name: 'titleExpr', labelKey: 'TodoTitleExpr', required: true },
{ name: 'statusExpr', labelKey: 'TodoStatusExpr', required: true }, { name: 'statusExpr', labelKey: 'TodoStatusExpr', required: true },
{ name: 'descriptionExpr', labelKey: 'TodoDescriptionExpr' }, { name: 'descriptionExpr', labelKey: 'TodoDescriptionExpr' },
{ name: 'analysisExpr', labelKey: 'TodoAnalysisExpr' },
{ name: 'dueDateExpr', labelKey: 'TodoDueDateExpr' }, { name: 'dueDateExpr', labelKey: 'TodoDueDateExpr' },
{ name: 'tagExpr', labelKey: 'TodoTagExpr' }, { name: 'tagExpr', labelKey: 'TodoTagExpr' },
{ name: 'assigneeExpr', labelKey: 'TodoAssigneeExpr' }, { name: 'assigneeExpr', labelKey: 'TodoAssigneeExpr' },

View file

@ -137,13 +137,14 @@ const initialValues: ListFormWizardDto = {
titleExpr: '', titleExpr: '',
statusExpr: '', statusExpr: '',
descriptionExpr: '', descriptionExpr: '',
analysisExpr: '',
dueDateExpr: '', dueDateExpr: '',
tagExpr: '', tagExpr: '',
assigneeExpr: '', assigneeExpr: '',
priorityExpr: '', priorityExpr: '',
completedExpr: '', completedExpr: '',
orderExpr: '', orderExpr: '',
statusOrder: 'Backlog, Ready to Start, In Progress, Code Review, Testing, Staging', statusOrder: 'Backlog, Analysis, Ready to Start, In Progress, Testing, Staging',
allowDragging: true, allowDragging: true,
}, },
workflowDto: { workflowDto: {

View file

@ -55,6 +55,10 @@ const mappings: Array<{
name: 'descriptionExpr', name: 'descriptionExpr',
translationKey: '::App.ListFormFieldEdit.TodoDescriptionExpr', translationKey: '::App.ListFormFieldEdit.TodoDescriptionExpr',
}, },
{
name: 'analysisExpr',
translationKey: '::App.ListFormFieldEdit.TodoAnalysisExpr',
},
{ {
name: 'dueDateExpr', name: 'dueDateExpr',
translationKey: '::App.ListFormFieldEdit.TodoDueDateExpr', translationKey: '::App.ListFormFieldEdit.TodoDueDateExpr',

View file

@ -78,15 +78,15 @@ interface UserOption {
type LookupDisplayValues = Record<string, Map<string, string>> type LookupDisplayValues = Record<string, Map<string, string>>
type DetailTab = 'description' | 'timesheet' | 'subtask' type DetailTab = 'description' | 'analysis' | 'timesheet' | 'subtask'
type JsonRow = Record<string, any> type JsonRow = Record<string, any>
const TODO_STATUS_LANGUAGE_KEYS: Record<string, string> = { const TODO_STATUS_LANGUAGE_KEYS: Record<string, string> = {
backlog: 'App.StaticLookup.Backlog', backlog: 'App.StaticLookup.Backlog',
analysis: 'App.StaticLookup.Analysis',
'ready to start': 'App.StaticLookup.ReadyToStart', 'ready to start': 'App.StaticLookup.ReadyToStart',
'in progress': 'App.StaticLookup.InProgress', 'in progress': 'App.StaticLookup.InProgress',
'code review': 'App.StaticLookup.CodeReview',
testing: 'App.StaticLookup.Testing', testing: 'App.StaticLookup.Testing',
staging: 'App.StaticLookup.Staging', staging: 'App.StaticLookup.Staging',
} }
@ -972,6 +972,7 @@ const TodoBoardContent = ({
options.titleExpr, options.titleExpr,
options.statusExpr, options.statusExpr,
options.descriptionExpr, options.descriptionExpr,
options.analysisExpr,
options.dueDateExpr, options.dueDateExpr,
options.tagExpr, options.tagExpr,
options.assigneeExpr, options.assigneeExpr,
@ -1038,6 +1039,12 @@ const TodoBoardContent = ({
label: translate('::App.Listform.ListformField.Description'), label: translate('::App.Listform.ListformField.Description'),
} }
: undefined, : undefined,
options.analysisExpr
? {
key: 'analysis' as DetailTab,
label: translate('::App.Listform.ListformField.Analysis'),
}
: undefined,
options.timesheetExpr options.timesheetExpr
? { ? {
key: 'timesheet' as DetailTab, key: 'timesheet' as DetailTab,
@ -1136,6 +1143,21 @@ const TodoBoardContent = ({
} }
/> />
)} )}
{activeTab === 'analysis' && options.analysisExpr && (
<HtmlEditor
{...htmlEditorDefaults}
height="100%"
placeholder={translate('::App.ListFormTodoBoard.AnalysisPlaceholder')}
readOnly={!canUpdate}
value={String(draft[options.analysisExpr] ?? '')}
onValueChanged={(event) =>
setDraft((current) => ({
...current,
[options.analysisExpr!]: event.value,
}))
}
/>
)}
{activeTab === 'timesheet' && options.timesheetExpr && ( {activeTab === 'timesheet' && options.timesheetExpr && (
<div className="flex min-h-0 flex-1 flex-col gap-2"> <div className="flex min-h-0 flex-1 flex-col gap-2">
{hasInvalidJson(fieldValue(draft, options.timesheetExpr)) && ( {hasInvalidJson(fieldValue(draft, options.timesheetExpr)) && (