From ae226d359b85f7b2c9e48d6bf3d404956455cedf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?=
<76204082+iamsedatozturk@users.noreply.github.com>
Date: Mon, 28 Sep 2026 15:50:38 +0300
Subject: [PATCH] =?UTF-8?q?wizard=203=20sekmeli=20sistem=20ve=20timesheet?=
=?UTF-8?q?=20=C3=A7al=C4=B1=C5=9Fmas=C4=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../lowcode-reference.instructions.md | 1 +
.github/instructions/lowcode.instructions.md | 63 +++--
README.md | 11 +-
.../IMcpDevelopAppService.cs | 1 +
.../McpDataAppService.cs | 6 +-
.../McpDevRequestActivityCacheItem.cs | 13 +
.../McpDevRequestTimesheetRecorder.cs | 115 +++++++++
.../McpDevelopAppService.cs | 35 ++-
.../McpTimesheetTexts.cs | 20 ++
.../Tools/McpDevelopTools.cs | 3 +-
.../ListForms/Wizard/ListFormWizardDto.cs | 39 +++
.../ListForms/Wizard/WizardLanguageTextDto.cs | 12 +
.../ListForms/Wizard/WizardMenuLevelDto.cs | 13 +
.../ListForms/Wizard/WizardSeedFileDto.cs | 10 +
.../ListForms/Wizard/WizardSeedRowKeys.cs | 11 +-
.../ListForms/ListFormWizardAppService.cs | 228 ++++++++++++++----
claude.md | 7 +-
.../host/data/App.Languages.LanguageText.json | 140 +++++++++++
ui/src/proxy/admin/wizard/models.ts | 21 ++
ui/src/views/admin/listForm/wizard/Wizard.tsx | 9 +
.../listForm/wizard/WizardStepDeploy.tsx | 7 +-
21 files changed, 683 insertions(+), 82 deletions(-)
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestActivityCacheItem.cs
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestTimesheetRecorder.cs
create mode 100644 api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpTimesheetTexts.cs
create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardLanguageTextDto.cs
create mode 100644 api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardMenuLevelDto.cs
diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md
index 3ad73d2b..81a9b81a 100644
--- a/.github/instructions/lowcode-reference.instructions.md
+++ b/.github/instructions/lowcode-reference.instructions.md
@@ -834,6 +834,7 @@ tipli okumaya bağlı davranış (GridBox kolonları, TagBox etiket sayısı) ç
| İhtiyaç | Nerede |
| --- | --- |
| Fotoğraf çekme | `dxImageUpload` · `Capture` (yukarıda) |
+| Görsel tutan kolon | Editör `dxImageUpload`, kolon `nvarchar(MAX)` (base64); `dxImageViewer` kolona bağlanmaz (`lowcode.instructions.md` §0.12 madde 5) |
| Alana barkod/QR okutma | `dxTextBox` `EditorOptions.buttons[]` içinde `onClick: "@SCAN_BARCODE"` (`lowcode` §6.4) |
| Sürekli okutup her kodu yeni satır yapma | Toolbar butonu `DialogName: "BarcodeScanDialog"` + `DialogParameters` `{"field": "…", …sabit alanlar}` — ekranın insert ucundan geçer (`lowcode` §4.9) |
| Cihaz konumu | `EditorScript` `location` tarifi → `geoTo('Enlem', 'Boylam')` (`lowcode` §5.3) |
diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md
index 2d438b2e..5dbd9573 100644
--- a/.github/instructions/lowcode.instructions.md
+++ b/.github/instructions/lowcode.instructions.md
@@ -365,7 +365,7 @@ dörtlüyle üretilir (§9.8), görseldeki görünüm `DefaultLayout` olur.
| Renk kutucuğu | `dxColorBox` | |
| Kalın/italik araç çubuklu alan | `dxHtmlEditor` | `toolbar.items` |
| Kaydırıcı | `dxSlider` / `dxRangeSlider` | `min`, `max` |
-| Görsel küçük resmi / yükleme alanı | `dxImageViewer` / `dxImageUpload` | `accept`, `maxFileSize` |
+| Fotoğraf / görsel alanı (seçilen, çekilen ya da başka kayıttan kopyalanan) | `dxImageUpload`, kolon `nvarchar(MAX)` (base64); salt okunur gösterilecekse `EditorOptions: {"readOnly": true}` — `dxImageViewer` değil | `accept`, `maxFileSize`, `capture` |
| Gri, tıklanamaz alan | ilgili editör + `EditorOptions: {"readOnly": true}` | |
| Kırmızı yıldızlı etiket | `IsRequired: true` | |
@@ -505,7 +505,7 @@ ister ve kiracı kullanıcısına hiçbiri açılmaz.
| `get_dev_guide` | Bu dosyanın (`lowcode`) ya da `reference`'ın başlık listesini / bir bölümünü döner | Bölüm numarayla istenir (`4.3`); aynı numarayı iki başlık taşımamalıdır |
| `create_table` | Yeni SQL Server tablosu kurar, `sql/object/{Tablo}.sql` dosyasını yazar | Ek olarak `App.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz |
| `list_wizards` · `get_wizard` | Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner | Var olan menü kökünü/izin grubunu bulmak için önce `list_wizards` |
-| `deploy_wizard` | Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) | Girdi §4.4'teki düz cevap ya da `get_wizard` çıktısıyla aynı dosya biçimi; enum'lar sayı. `replaceFileName` = `EditFileName` (§4.5). 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 |
+| `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); kartlardaki her dil anahtarı var olmalı ya da `Language.Texts`'te EN + TR verilmeli, yoksa deploy reddeder. `MenuOrder` yazılmaz — sunucu yeni ekranı kardeşlerinin sonuna koyar, güncellemede yerini korur. İstek demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1). Çıkıştaki `isPending: true` ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır; model migrate'i beklemez |
| `delete_wizard` | Wizard'ı ve yalnızca onun ürettiği kayıtları siler; `dropTables: true` ekranın tablosunu da düşürür | Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; `App.SqlQueryManager` ister (§4.5) |
| `start_dev_request` | `App.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `App.DevRequests.Update` ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11) |
| `get_dev_request` | Talebi durumuna dokunmadan okur (aynı metin + analiz + resim içerikleri) | Analizden önce ya da yarım kalan talebe dönmek için; `App.DevRequests` ister |
@@ -602,6 +602,14 @@ 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
bağlanan kullanıcı, bitiş tarihini bugün yapar ve var olan satırları korur. `SubTasks`
`update_row` ile yazılmaz.
+- `Timesheets` — **sunucu yazar, model hiçbir şey yapmaz.** `get_dev_request` / `start_dev_request`
+ talebi kullanıcının etkin talebi yapar; ardından yapılan her MCP yazma işlemi (`create_table`,
+ `deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, `insert_row` /
+ `update_row` / `delete_row`, `save_dev_request_analysis`) o talebe bir satır ekler
+ (`IMcpDevRequestTimesheetRecorder`). Açıklama `App.Mcp.Timesheet.*` dil anahtarından **Türkçe**
+ üretilir ("Hr_T_Doctor tablosu oluşturuldu"), süre önceki satırdan ya da talebin açıldığı andan bu
+ yana ölçülür (en az 1 dakika, saat olarak), çalışan bağlanan kullanıcı, tarih bugündür.
+ `Timesheets` `update_row` ile yazılmaz.
- `Analysis` — `append_dev_request_note` ile sonuna `
MCP
` başlığıyla kısa özet:
tablo, ekran, alan, demo veri, menü/yetki, analize eklenen varsayılanlar ve DB Migrate bekleyen
wizard'lar. Tekrar üretimde yeni bir `MCP
` bloğu eklenir, eskisi silinmez.
@@ -616,10 +624,16 @@ değildir) ve içindeki satırları taşır; satırlar demo veridir (§0.10.1).
**Kurallar**
- **Onaylanmış analiz tarifin kendisidir; üretim karar vermez.** Soru sorulmaz, seçim yapılmaz,
- analizin yazdığı hiçbir şey değiştirilmez ya da daraltılmaz. Bu dosyadaki "kullanıcıya sor"
+ analizin yazdığı hiçbir şey değiştirilmez ya da daraltılmaz — tek istisna platform kuralıyla çelişen kısımdır (aşağıda). Bu dosyadaki "kullanıcıya sor"
kuralları (onaycı, görselden okunamayan zorunlu alan, çerçeve dışı ihtiyaç) bu döngüde analizin
**(onay)** kararlarıyla çözülmüştür. Analizde karşılığı olmayan küçük bir ayrıntı (§0.6
varsayılanı olan türden) varsayılanla tamamlanır ve sonuç notuna yazılır.
+- **Platform kuralı analizden önce gelir.** Analiz, bu dosyadaki bağlayıcı bir kuralla çelişiyorsa
+ (görsel kolonu `nvarchar(MAX)` + `dxImageUpload`, tanım tablosunda `IsActive`, parametreli SQL,
+ kiracı filtresi…) üretim kuralı uygular, çelişkiyi sonuç notuna yazar; durmaz, soru sormaz.
+ Bu yüzden her `create_table` öncesi kolon listesi ekranın editörleriyle karşılaştırılır: editörü
+ `dxImageUpload` olan kolon `nvarchar(MAX)` açılır — tablo kurulduktan sonra kolon değiştirilemez.
+ Kural analizi talebin istediğinden uzaklaştırmaz; yalnızca platformun kurallarına uydurur.
- **Ü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.
@@ -643,7 +657,7 @@ bileşen, seed dosyası yazılmaz; yalnızca okuma araçları ve `save_dev_reque
| 1. Kuyruğu oku (yalnızca toplu) | `query_list` — `ListFormCode: App.DevRequests`, filtre `["Status","=","Backlog"]`, sıralama `SortOrder` | — |
| 2. Talebi oku | `get_dev_request` — metin + görseller | — |
| 3. Keşif | `list_wizards` (menü kökleri, modüller, izin grupları), `list_lists` / `describe_list` / `search_schema` (var olan ekran, tablo, lookup kaynağı), `list_components`, `get_dev_guide` (§0.6, §0.7, §0.8, §1) | — |
-| 4. Yaz | `save_dev_request_analysis` — analizin tamamı, HTML | Analysis |
+| 4. Yaz | `save_dev_request_analysis` — analizin tamamı, HTML (Timesheets satırını sunucu yazar, §0.11) | Analysis |
**Durum kuralı sunucudadır:** araç yalnızca `Backlog` ya da `Analysis` durumundaki talebi kabul eder;
`Analysis`'teki talebe yeniden çağrılırsa analiz değiştirilir. `Ready to Start` ve sonrası
@@ -676,27 +690,38 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma.
| Seviye | Kod | Ad (TR / EN) | İkon | Durum |
| --- | --- | --- | --- | --- |
| Kök | … | … / … | … | Var / Yeni |
- | Ara (varsa, her biri) | … | … / … | … | Var / Yeni |
+ | Ara (yalnızca talep istiyorsa, 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.
+ yazılmaz. Seviye sayısı talepten gelir, sabit değildir: `Kök › Ekran` iki, `Kök › Ara › Ekran`
+ üç seviyedir; analiz talebin istemediği bir ara seviye eklemez. Talep menü yolunu yazıyorsa
+ (başlıkta ya da açıklamada) o yol girdidir ve seviye seviye aynen alınır. Wizard karşılığı:
+ ekranın bir üstü `MenuParentCode`, daha üstündekiler `MenuParentPath` (iki seviyede boş, §4.4).
+ Aynı üst menü altındaki ekranlar **bağımlılık sırasıyla** yazılır: başka bir ekranın lookup
+ kaynağı olan (tanım) ekran, onu kullanan ekranın üstündedir. Menüdeki sıra deploy sırasından
+ gelir; madde 9'daki üretim planı da aynı sırayla yazılı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.
+ **Görsel kolonu:** görsel tutan her kolon (fotoğraf, resim, logo, avatar kopyası, belge
+ görüntüsü) `nvarchar(MAX)`'tir ve editörü `dxImageUpload`'dır — değer başka bir kayıttan
+ kopyalanıyor olsa da. Görsel base64 metin olarak saklanır; `nvarchar(512)` gibi sınırlı boy
+ yazılmaz, `dxImageViewer` kullanılmaz (§4.3 editör tablosu). Çok satırlı serbest metin
+ (`dxTextArea`, `dxHtmlEditor`) de `nvarchar(MAX)`'tir.
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.
+ `EditorScript` / `EditorOptions` ihtiyaçları, hesaplar ve doğrulamalar. Görsel alanı her zaman
+ `dxImageUpload`'dır (madde 5).
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`,
+9. **Üretim planı** — bağımlılık sırasıyla (tablo → lookup kaynağı ekran → onu kullanan ekran) 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
@@ -915,6 +940,7 @@ sürümünü taşır.
"SeededAt": "2026-09-01T09:03:00", "CustomComponentName": null,
"PermissionGroupName": "App.Prj", "ModuleId": "Prj", // boş bağlantı metni yazılmaz
"Menu": { "MenuCode": "…", "MenuOrder": 2, "MenuUrl": "…", "MenuIcon": "…",
+ "MenuParentPath": [ { "Code": "…", "Icon": "…", "En": "…", "Tr": "…" } ], // yalnızca derin yolda
"MenuParentCode": "…", "MenuParentIcon": "…" }, // "CreateMenu": false ise eklenir
"Language": { "MenuEn": "…", /* Menu/Title/Desc/MenuParent/PermissionGroup · EN + TR */ },
"Groups": [ { "Items": [ { "FieldName": "…", "CaptionName": "…",
@@ -999,9 +1025,10 @@ saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldur
| Alan | Anlam |
| --- | --- |
| `CreateMenu` | `false` ise menü ve üst menü kaydı **hiç üretilmez**; ListForm, yetki ve dil anahtarları yine üretilir. SubGrid/parça olarak kullanılacak ekranlar için. Varsayılan `true` olduğu için dosyaya **yalnızca `false` iken** yazılır. |
-| `MenuParentCode` / `MenuParentModuleId` / `MenuParentIcon` | Üst menü; yoksa oluşturulur. |
+| `MenuParentCode` / `MenuParentModuleId` / `MenuParentIcon` | Ekranın doğrudan üst menüsü; yoksa oluşturulur. |
+| `MenuParentPath` | `MenuParentCode`'un üstündeki seviyeler, kökten aşağı; her biri `{ Code, Icon, En, Tr }`. Derinlik sınırsızdır: deploy zinciri kökten yürür, var olan seviyeye bağlanır, eksik seviyeyi bir üstünün altına kardeşlerinin sonuna kurar (menü + dil satırı, `InsertedRecords`'a girer). Boşsa üst menü kök seviyededir; boş liste dosyaya yazılmaz. Örnekler: `İnsan Kaynakları › Yeterlilik Belgeleri` (iki seviye) → `MenuParentCode: "App.Hr"`, `MenuParentPath` yok; `İnsan Kaynakları › Tanımlamalar › Yeterlilik Belgeleri` (üç seviye) → `MenuParentPath: [{ Code: "App.Hr", … }]`, `MenuParentCode: "App.Hr.Definitions"`. |
| `MenuIcon` | React-icons adı (`FcBiohazard`, `FaBox`…). |
-| `MenuOrder` | Kardeşler arası sıra. |
+| `MenuOrder` | Kardeşler arası sıra; 0/boş ise kardeşlerin sonu. MCP deploy'unda yok sayılır (yeni ekran sona, güncelleme yerinde kalır). |
**Yetki ve dil**
@@ -1018,6 +1045,7 @@ saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldur
| `Language.TitleEn` / `Tr` | Ekran başlığı (yalnızca List yolunda üretilir). |
| `Language.DescEn` / `Tr` | Ekran açıklaması (yalnızca List yolunda). |
| `Language.MenuParentEn` / `Tr` | Üst menü etiketi. |
+| `Language.Texts` | Başka bir cevaptan üretilmeyen dil anahtarları: `[{ "Key", "En", "Tr" }]` (widget başlığı gibi). Anahtar yoksa deploy satırı yazar ve wizard'a bağlar (silmede gider). Deploy, widget sorgularındaki tek tırnaklı noktalı her tanımlayıcıyı (`'App.Widget.ThisMonth'`) dil anahtarı sayar; veritabanında, bekleyen dil satırlarında, bu wizard'ın ürettiklerinde ya da burada yoksa deploy'u adlarıyla reddeder. |
Üretilen yetkiler: `{MenuCode}` (okuma, kök) + List yolunda `.Create .Update .Delete .Export
.Import .Note`, Custom yolunda yalnızca `.Create .Update .Delete`. Hiçbiri bir role otomatik
@@ -1154,8 +1182,8 @@ anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir.
| `dxSlider` | Tek değerli kaydırıcı | Sayı | `min`, `max`, `tooltip.showMode` |
| `dxRangeSlider` | Aralık kaydırıcı | Sayı | `min`, `max`, `showRange` |
| `dxHtmlEditor` | Zengin metin | String (16) | `toolbar.items`, `toolbar.multiline`, `valueType` (`html`/`markdown`), `mediaResizing.enabled`, `imageUpload.tabs`, `imageUpload.uploadUrl`, `imageUpload.fileUploadMode`, `tableResizing.enabled`, `tableContextMenu.enabled`. Boş `editorOptions` da tam araç çubuğu, dosyadan/URL'den resim, resim boyutlandırma ve tablo araçlarıyla gelir (`ui/src/views/shared/htmlEditorOptions.ts`); yazılan değer varsayılanın üstüne tek seviye birleştirilir, yalnızca farkı yaz. |
-| `dxImageUpload` | Görsel yükleme (platform) | String (16) | `uploadUrl`\*, `accept`\*, `capture`\*, `multiple`\*, `maxFileSize`\*, `width`\*, `height`\* |
-| `dxImageViewer` | Görsel gösterimi (platform) | String (16) | `width`, `height` |
+| `dxImageUpload` | Görsel yükleme (platform); görsel tutan her kolonun editörü. `uploadUrl` yoksa dosya **base64** metin olarak kolona yazılır → kolon `nvarchar(MAX)`; sınırlı boylu kolonla deploy reddedilir. `uploadUrl` varsa kolona dönen adres yazılır | String (16) | `uploadUrl`\*, `accept`\*, `capture`\*, `multiple`\*, `maxFileSize`\*, `width`\*, `height`\* |
+| `dxImageViewer` | Görsel gösterimi (platform), yükleme yapmaz. Tablo kolonuna bağlı alanda kullanılmaz — görsel kolonu `dxImageUpload`'dır, deploy `dxImageViewer`'ı reddeder | String (16) | `width`, `height` |
`\*` ile işaretli anahtarlar backend'de **tipli DTO'ya** çözülür:
`GridBoxOptionsDto`, `TagBoxOptionsDto`, `ImageUploadOptionsDto`. Yanlış tipte yazılan bir değer
@@ -1535,7 +1563,7 @@ ile ekler (§4.0). Sıra değerleri veritabanı ile dosyadaki satırların birle
| Dil anahtarları | `{MenuCode}` (menü), `{ListFormCode}.Title`, `{ListFormCode}.Desc`, `{PermissionGroupName}` ve her alan için `CaptionName` |
| Yetkiler | `{MenuCode}` kökü + `.Create/.Update/.Delete` (+ List yolunda `.Export/.Import/.Note`) |
| Yetkilerin dil anahtarları | Ortak sabitler: `App.Platform.Create/Update/Delete/Export/Import/Note` |
-| Üst menü | `MenuParentCode` yoksa yaratılır, `Order = max(kök Order) + 1` |
+| Üst menü zinciri | `MenuParentPath` + `MenuParentCode` kökten yürünür; eksik seviye bir üstünün altına `Order = max(kardeş Order) + 1` ile yaratılır (kökte kök menülerin sonu) |
| Menü sırası | `MenuOrder` 0/boş ise `max(kardeş Order) + 1` |
| `DataSource` | `DataSourceCode` yoksa migrate yaratır; bağlantı metninde `Server` geçiyorsa `Mssql`, aksi hâlde `Postgresql` |
| `LayoutJson` | Görünüm bayrakları + `DefaultLayout` |
@@ -1743,7 +1771,8 @@ gelir; kullanıcı "widget/KPI" demese de, sorulmadan eklenir ve tarifte tek sat
- **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.
+ alanın `App.Listform.ListformField.*` başlığı. Karşılığı yoksa yeni anahtar wizard'ın
+ `Language.Texts` alanında EN + TR verilir (§4.4); tanımsız anahtarla deploy reddedilir.
`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`,
@@ -2048,7 +2077,7 @@ bölümünü doldurur.** Keşif turu (§0.6) soruları da bu adımlarla bire bir
| --- | --- | --- | --- |
| 0 | Komponent Tipi | her zaman | `ComponentKind` (`0` List · `1` Custom) |
| 1 | Custom Component | yalnız Custom yolu | `CustomComponentName`, `MenuUrl` (bileşenin `RoutePath`'i) |
-| 2 | Menü | her zaman | `WizardName`, `CreateMenu`, `MenuParentCode`, `MenuCode`, `Language.MenuEn/Tr`, `PermissionGroupName` (+ görünen adlar), `MenuIcon`, `MenuOrder` |
+| 2 | Menü | her zaman | `WizardName`, `CreateMenu`, `MenuParentCode` (ekranda eksik seviyeler menü diyaloğuyla kurulur; MCP `MenuParentPath` yazar), `MenuCode`, `Language.MenuEn/Tr`, `PermissionGroupName` (+ görünen adlar), `MenuIcon`, `MenuOrder` |
| 3 | Ayarlar | yalnız List yolu | `ListFormCode`, `DataSourceCode` (+`DataSourceConnectionString`), `SelectCommandType`, `SelectCommand`, `KeyFieldName`, `KeyFieldDbSourceType`, `IsTenant/IsBranch/IsOrganizationUnit`, `AllowAdding/Updating/Deleting`, `ConfirmDelete`, `AllowDetail`, `DefaultLayout` + 8 görünüm bayrağı (`SeedData` adımda sorulmaz, düzenlemede korunur), `Language.TitleEn/Tr`, `Language.DescEn/Tr` |
| 4 | Alanlar | yalnız List | `Groups[]` (aşağıda) |
| 5 | Alt Formlar | yalnız List | `SubForms[]` |
@@ -3860,7 +3889,7 @@ silme, DB Migrate, Export (zip), Import (diff + rollback). Yetkiler
`App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}`.
Listede her ekranın üst menü zinciri (`WizardFileInfoDto.MenuPath`, Menu tablosundan; kayıt yoksa
-wizard'ın `MenuParentCode` cevabı) görünür. Bağlı wizard'lar üst ekranın altında gruplanır
+wizard'ın `MenuParentPath` + `MenuParentCode` cevabı) görünür. Bağlı wizard'lar üst ekranın altında gruplanır
(`Dependencies`): List yolunda alt formlar, Custom yolunda component `Dependencies` zinciri —
export kapanışıyla aynı kural; wizard'ı olmayan ara component atlanır. Kök, hiçbir wizard'ın
bağımlılığı olmayan dosyadır; ortak bir alt form her üst ekranın altında tekrar görünür.
diff --git a/README.md b/README.md
index af874d55..ca081d54 100644
--- a/README.md
+++ b/README.md
@@ -1406,7 +1406,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| `get_dev_guide` | Üretim sözleşmesi: `lowcode` / `reference` belgelerinin başlık listesi ya da istenen bölümü (numarayla, örn. `4.2`). Çok büyük bir üst bölüm yalnızca giriş metni + alt başlıklarıyla döner. |
| `create_table` | Yeni tabloyu SQL Query Manager tablo tasarımcısıyla aynı script biçimiyle kurar (`McpTableCreator`): `Id` + `TenantId` + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. Script `{kapsam}/sql/object/{Tablo}.sql` dosyasına yazılır. Yalnızca oluşturur — tablo varsa reddeder; yalnızca SQL Server. `.Write` + `App.SqlQueryManager` ister. |
| `list_wizards` / `get_wizard` | Wizard File Manager dosyaları; `get_wizard` seed dosyasını diskteki biçimiyle döndürür. |
-| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur ve `SeedSync*` bayrakları açılır — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). |
+| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur ve `SeedSync*` bayrakları açılır — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. |
| `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). |
| `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `App.DevRequests` döngüsünün adımı değildir — döngü `Testing`'de biter, kullanıcı elle test edip `Staging`'e alır (`lowcode.instructions.md` §0.11). |
| `start_dev_request` | Geliştirme talebini Id ya da tam başlıkla üstlenir: yalnızca `Ready to Start` durumundakini `In Progress` durumuna alır, diğerlerini reddeder; talebin metnini döndürür. Açıklamaya yapıştırılmış base64 görseller (`data:image/png|jpeg|gif|webp`) metinden ayıklanır (`McpDevRequestImages`), yerlerinde `[image-N]` kalır ve her biri ayrı MCP resim içeriği (`ImageContentBlock`) olarak döner — model görseli resim olarak görür, görsel hiçbir yere yüklenmez. `.Write` + `App.DevRequests.Update` ister. |
@@ -1415,6 +1415,15 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| `add_dev_request_subtasks` | Talebin `SubTasks` listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (`userName`), bitiş tarihi bugün (`yyyy-MM-dd`) olarak sunucuda yazılır; var olan satırlar korunur. `.Write` + `App.DevRequests.Update` ister. |
| `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler (üretim sonuç notu); `Description` talebin yazıldığı hâliyle kalır. İkisi de `update_row` ile yazılmaz — açıklama base64 görsel taşır. `.Write` + `App.DevRequests.Update` ister. |
+**Timesheets.** Talebin `Timesheets` listesini sunucu doldurur (`IMcpDevRequestTimesheetRecorder`):
+`get_dev_request` / `start_dev_request` talebi kullanıcının etkin talebi yapar (dağıtık önbellek,
+`McpDevRequestActivityCacheItem`, 12 saat); sonraki her MCP yazma işlemi (`create_table`,
+`deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, satır araçları,
+`save_dev_request_analysis`) o talebe bir satır ekler. Açıklama `App.Mcp.Timesheet.*` anahtarından
+Türkçe üretilir, süre önceki satırdan ya da talebin açıldığı andan ölçülür (en az 1 dakika, saat
+olarak), çalışan bağlanan kullanıcı, tarih bugündür. Etkin talep yoksa satır yazılmaz; bozuk bir
+`Timesheets` değeri işlemi geri almaz, yalnızca satır atlanır ve log'a düşer.
+
**Prompt'lar** (`McpPromptNames`, `McpDevelopPrompts`). İstemcide komut olarak görünür; yalnızca
metindir, yetki taşımaz. `analyze_dev_request` tek talebi, `analyze_backlog_requests` bütün
`Backlog` kuyruğunu analiz edip `Analysis` durumuna alır (§0.12); `process_dev_request` tek talebi,
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs
index c052d90d..7da8fc6c 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/IMcpDevelopAppService.cs
@@ -55,6 +55,7 @@ public interface IMcpDevelopAppService : IApplicationService
///
Task AddDevRequestSubTasksAsync(string request, List titles);
+
Task> GetComponentsAsync(string? search);
Task GetComponentAsync(string name);
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs
index fc4a89be..dd28818f 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDataAppService.cs
@@ -21,13 +21,15 @@ namespace Sozsoft.Mcp;
public class McpDataAppService(
IMcpServerConfigProvider serverConfigProvider,
IListFormDataAppService listFormDataAppService,
- IMcpListFormAccessFilter accessFilter)
+ IMcpListFormAccessFilter accessFilter,
+ IMcpDevRequestTimesheetRecorder timesheetRecorder)
: McpAppService(serverConfigProvider), IMcpDataAppService
{
public async Task InsertRowAsync(McpRowRequestDto input)
{
var request = await BuildRequestAsync(input, McpToolNames.InsertRow, AuthorizationTypeEnum.Create, requireKeys: false, requireValues: true);
var key = await listFormDataAppService.PostInsertAsync(request);
+ await timesheetRecorder.RecordAsync(McpTimesheetTexts.InsertRow, input.ListFormCode);
return new McpWriteResultDto { Key = ((object?)key)?.ToString() };
}
@@ -36,6 +38,7 @@ public class McpDataAppService(
{
var request = await BuildRequestAsync(input, McpToolNames.UpdateRow, AuthorizationTypeEnum.Update, requireKeys: true, requireValues: true);
var affected = await listFormDataAppService.PostUpdateAsync(request);
+ await timesheetRecorder.RecordAsync(McpTimesheetTexts.UpdateRow, input.ListFormCode, string.Join(", ", input.Values.Keys));
return new McpWriteResultDto { AffectedRows = affected };
}
@@ -44,6 +47,7 @@ public class McpDataAppService(
{
var request = await BuildRequestAsync(input, McpToolNames.DeleteRow, AuthorizationTypeEnum.Delete, requireKeys: true, requireValues: false);
var affected = await listFormDataAppService.PostDeleteAsync(request);
+ await timesheetRecorder.RecordAsync(McpTimesheetTexts.DeleteRow, input.ListFormCode);
return new McpWriteResultDto { AffectedRows = affected };
}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestActivityCacheItem.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestActivityCacheItem.cs
new file mode 100644
index 00000000..9e870590
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestActivityCacheItem.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Sozsoft.Mcp;
+
+///
+/// Kullanicinin MCP ile uzerinde calistigi talep ve son kaydedilen etkinlik ani; bir sonraki
+/// timesheet satirinin suresi bu andan olculur.
+///
+public class McpDevRequestActivityCacheItem
+{
+ public Guid RequestId { get; set; }
+ public DateTime At { get; set; }
+}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestTimesheetRecorder.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestTimesheetRecorder.cs
new file mode 100644
index 00000000..a6ab1ba1
--- /dev/null
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevRequestTimesheetRecorder.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Logging;
+using Sozsoft.Platform.Entities;
+using Sozsoft.Platform.Localization;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.Guids;
+using Volo.Abp.Localization;
+using Volo.Abp.Timing;
+using Volo.Abp.Users;
+
+namespace Sozsoft.Mcp;
+
+///
+/// Gelistirme talebinin Timesheets listesini MCP islemlerinden doldurur. Talep
+/// get_dev_request / start_dev_request ile acildiginda kullanicinin etkin talebi
+/// olur; ardindan yapilan her yazma islemi o talebe bir satir ekler. Satir modelin dikkatine
+/// birakilmaz: aciklama dil anahtarindan Turkce uretilir, sure son etkinlikten bu yana olculur.
+///
+public interface IMcpDevRequestTimesheetRecorder
+{
+ /// Talebi kullanicinin etkin talebi yapar ve olcumu baslatir.
+ Task BeginAsync(Guid devRequestId);
+
+ ///
+ /// Etkin talebe bir satir yazar; etkin talep yoksa (islem bir talep dongusunun parcasi
+ /// degilse) hicbir sey yapmaz.
+ ///
+ Task RecordAsync(string descriptionKey, params object[] args);
+}
+
+public class McpDevRequestTimesheetRecorder(
+ IDistributedCache activityCache,
+ IRepository devRequestRepository,
+ IStringLocalizer localizer,
+ ICurrentUser currentUser,
+ IClock clock,
+ IGuidGenerator guidGenerator,
+ ILogger logger) : IMcpDevRequestTimesheetRecorder, ITransientDependency
+{
+ /// Timesheet satirlarinin dili; talep ekrani Turkce kullanilir.
+ private const string TimesheetCulture = "tr";
+
+ /// Talep ekranindaki tarih kutusunun okudugu bicim.
+ private const string DateFormat = "yyyy-MM-dd";
+
+ ///
+ /// Etkin talebin omru bir calisma oturumunu kapsar; suresi dolan kayit, gunler once acilmis
+ /// bir talebe baska bir isin yazilmasini ve sahte uzun sureyi engeller.
+ ///
+ private static readonly TimeSpan ActivityLifetime = TimeSpan.FromHours(12);
+
+ public Task BeginAsync(Guid devRequestId) => StampAsync(devRequestId, clock.Now);
+
+ public async Task RecordAsync(string descriptionKey, params object[] args)
+ {
+ var activity = await activityCache.GetAsync(ActivityKey());
+ if (activity is null)
+ return;
+
+ var devRequest = await devRequestRepository.FindAsync(activity.RequestId);
+ if (devRequest is null)
+ return;
+
+ JsonArray timesheets;
+ try
+ {
+ timesheets = string.IsNullOrWhiteSpace(devRequest.Timesheets)
+ ? []
+ : JsonNode.Parse(devRequest.Timesheets) as JsonArray ?? throw new JsonException();
+ }
+ catch (JsonException)
+ {
+ // Yapilan is (deploy, tablo) zaten tamamlandi; bozuk bir timesheet onu geri almamali.
+ logger.LogWarning("Timesheets of development request {RequestId} could not be read; entry skipped.", devRequest.Id);
+ return;
+ }
+
+ string description;
+ using (CultureHelper.Use(TimesheetCulture))
+ {
+ description = localizer[descriptionKey, args];
+ }
+
+ var now = clock.Now;
+ var minutes = Math.Max(1, (int)Math.Round((now - activity.At).TotalMinutes));
+
+ timesheets.Add(new JsonObject
+ {
+ ["id"] = guidGenerator.Create().ToString(),
+ ["date"] = now.ToString(DateFormat, CultureInfo.InvariantCulture),
+ ["userName"] = currentUser.UserName ?? string.Empty,
+ ["description"] = description,
+ // Ekranin sure kutusu saati ondalik sayi olarak okur.
+ ["duration"] = Math.Round(minutes / 60d, 4)
+ });
+
+ devRequest.Timesheets = timesheets.ToJsonString();
+ await devRequestRepository.UpdateAsync(devRequest, autoSave: true);
+ await StampAsync(devRequest.Id, now);
+ }
+
+ private string ActivityKey() => currentUser.Id?.ToString() ?? string.Empty;
+
+ private Task StampAsync(Guid devRequestId, DateTime at) =>
+ activityCache.SetAsync(ActivityKey(), new McpDevRequestActivityCacheItem { RequestId = devRequestId, At = at },
+ new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ActivityLifetime });
+}
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
index a5a2e48e..338a8999 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
@@ -42,6 +42,7 @@ public class McpDevelopAppService(
IRepository