From 6081131f95a9b9c1a2bcb85c190eb3cce890fa8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?=
<76204082+iamsedatozturk@users.noreply.github.com>
Date: Wed, 30 Sep 2026 17:57:25 +0300
Subject: [PATCH] =?UTF-8?q?Baz=C4=B1=20hatalar=20giderildi?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/instructions/lowcode.instructions.md | 12 +++-
README.md | 9 ++-
.../McpDevelopAppService.cs | 8 +++
.../Services/McpTableCreator.cs | 61 +++++++++++++++++--
.../Tools/McpDevelopTools.cs | 2 +-
.../ListForms/ListFormWizardAppService.cs | 46 +++++++++++++-
claude.md | 3 +-
.../ce4269ad-8d08-487a-9e36-c002dd482a3c.json | 26 ++++++++
ui/src/views/list/TodoBoard.tsx | 2 +-
9 files changed, 157 insertions(+), 12 deletions(-)
create mode 100644 configs/seeds/host/data/App.DevRequests/ce4269ad-8d08-487a-9e36-c002dd482a3c.json
diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md
index 3c70d9eb..8ca213a7 100644
--- a/.github/instructions/lowcode.instructions.md
+++ b/.github/instructions/lowcode.instructions.md
@@ -707,6 +707,11 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma.
altında tek satırda gösterir — `Yetki: {İzin grubu} › {Ara yetki} › {ListFormCode}` — ve her ara
seviyenin yetkisi için Var / Yeni yazar (var olan `Abp.Identity.Permissions.json`'dan okunur).
Yetkinin yerini menüden ayrı seçen bir wizard alanı yoktur; yetki dalı menü yoluyla birlikte değişir.
+ **Menüsüz alt formun yetkisi listenin yetkisinin altındadır**: alt formun okuma yetkisinin
+ `ParentName` değeri onu `SubForms` içinde taşıyan listenin yetki kodudur (grubun kökü değil);
+ analiz satırı `Yetki: {İzin grubu} › {Ara yetki} › {Liste} › {AltForm}` olur. Deploy bunu kendisi
+ kurar — alt form önce deploy edilir, liste deploy edildiğinde alt formun yetkisi listenin
+ altına, listenin alt yetkilerinin (Create/Update/…) ardına taşınır; ayrıca bir alan yazılmaz.
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.
@@ -1115,6 +1120,10 @@ kaynağı odur. Grup yapısı yalnızca düzenleme formunun yerleşimini belirle
]
```
+Grup başlığı yalnızca düzenleme formunda **birden fazla grup** varsa yazılır; tek gruplu formda
+başlık popup başlığını tekrarlar ve boş bırakılır. MCP `deploy_wizard` bunu kendisi uygular:
+düzenleme formuna giren tek grup varsa `Caption` boşaltılır (`McpDevelopAppService.DeployWizardAsync`).
+
Seed dosyasında `Groups` **kırpılmış** hâlde durur: grup başlığı/kolon sayısı ve alanların
editör bilgileri `ListForm.EditingFormDto` ve `Fields` içinde olduğu için tekrarlanmaz; dosyada
her öğe yalnızca `FieldName`, `CaptionName` ve `En`/`Tr` taşır — çünkü
@@ -2159,7 +2168,8 @@ ardından `ListFormSeedDataApplier` menü/yetki/dil satırlarını kurar (§4.0)
1. Konfigürasyon doğrulama
2. Menü kaydı (`CreateMenu: false` ise atlanır)
3. Dil metinleri
-4. İzin grubu + yetkiler (okuma yetkisi menü dalındaki ara yetkinin altına, §0.12 Menü yeri)
+4. İzin grubu + yetkiler (okuma yetkisi menü dalındaki ara yetkinin altına; menüsüz alt formda
+ listenin yetkisinin altına, §0.12 Menü yeri)
5. List yolunda: veri kaynağı → ListForm → alan grupları · Custom yolunda: bileşen bağlama
6. Deploy + tamamlandı
diff --git a/README.md b/README.md
index 761c9015..f427acc5 100644
--- a/README.md
+++ b/README.md
@@ -951,8 +951,13 @@ zincirinden kurar, böylece çocuk her zaman ebeveyninin altında kalır. Değer
yazar: üst menü zincirindeki (`MenuParentPath` + `MenuParentCode`) her seviye, izin grubunun
kendisi hariç, aynı kodlu bir ara yetkidir (`İnsan Kaynakları › Tanımlamalar › Ekran` →
`App.Hr.Definitions › App.Hr.Doctor`). Var olan ara yetki kullanılır, eksik olan menü seviyesinin
-görünen adıyla wizard'a ait üretilir; başka bir gruptaki aynı adlı yetki atlanır. Menüsüz wizard'da
-(`CreateMenu: false`) okuma yetkisi grubun köküne yazılır.
+görünen adıyla wizard'a ait üretilir; başka bir gruptaki aynı adlı yetki atlanır. Menüsüz wizard'ın
+(`CreateMenu: false`, alt form) okuma yetkisi onu `SubForms` içinde taşıyan listenin yetkisine
+bağlanır: `ParentName` = listenin yetki kodu (`App.Hr.HealthCard › App.Hr.HealthCardFile`), sırası
+listenin alt yetkilerinin (Create/Update/…) ardındandır. Alt form listeden önce deploy edildiği için
+yetkisi önce grubun köküne yazılır; liste deploy edildiğinde alt formun satırı listenin altına
+taşınır (satır alt formun wizard'ına ait kalır, başka bir listeye bağlanmış alt form taşınmaz).
+Hiçbir listenin alt formu olmayan menüsüz wizard'ın yetkisi grubun kökünde kalır.
Wizard deploy'u yeni grubu ve yeni yetkileri (seed satırı olarak) veritabanı ile seed
dosyasındaki kardeşlerin en büyük sırasının bir fazlasıyla, alt yetkileri `WizardSeedRowKeys.ChildPermissions` dizilimiyle
(List: Create/Update/Delete/Export/Import/Note, Custom: Create/Update/Delete) 1'den yazar
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
index a0945800..29a7ede5 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs
@@ -175,6 +175,14 @@ public class McpDevelopAppService(
? 0
: (await wizardAppService.GetFile(wizard.EditFileName)).Wizard?.MenuOrder ?? 0;
+ // Tek gruplu duzenleme formunda grup basligi popup basligini tekrarlar; baslik yalnizca
+ // birden fazla grubu birbirinden ayirmak icin yazilir.
+ var editGroups = wizard.Groups
+ .Where(group => group.Items.Any(item => item.IncludeInEditingForm && item.FieldName != wizard.KeyFieldName))
+ .ToList();
+ if (editGroups.Count == 1)
+ editGroups[0].Caption = string.Empty;
+
// Satirlar deploy'dan once dogrulanir; gecersiz girdi yarim bir deploy birakmaz.
var seedRows = ParseSeedRows(input.SeedRows, wizard);
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs
index 30839e55..39013a2b 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs
@@ -47,6 +47,17 @@ public class McpTableCreator(
/// Varsayilan deger olarak kabul edilenler: sayi, metin sabiti ve tasarimcinin kendi
/// sablonlarinda kullandigi fonksiyonlar. Baska bir ifade script'e hic girmez.
///
+ ///
+ /// Istemcinin SQL bicimiyle yazdigi tip: nvarchar(50), nvarchar (MAX),
+ /// decimal(18, 4). Boyut tipten ayrilir; tip adi yine beyaz listeden gecer.
+ ///
+ private static readonly Regex SizedTypeRegex = new(
+ @"^(?[A-Za-z0-9]+)\s*\(\s*(?[^)]*?)\s*\)$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ /// Tasarimci decimal'i her zaman bu kesinlikle yazar (ToColumnLine).
+ private const string DecimalSize = "18,4";
+
private static readonly Regex SafeDefaultRegex = new(
@"^(?:-?\d{1,18}(?:\.\d{1,18})?|N?'(?:[^']|'')*'|NEWID\(\)|GETDATE\(\)|GETUTCDATE\(\)|SYSUTCDATETIME\(\))$",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
@@ -150,15 +161,12 @@ public class McpTableCreator(
var name = (column.Name ?? string.Empty).Trim();
EnsureSafeIdentifier(name);
- var type = SupportedTypes.FirstOrDefault(candidate =>
- string.Equals(candidate, column.Type?.Trim(), StringComparison.OrdinalIgnoreCase))
- ?? throw new UserFriendlyException(
- localizer[McpErrorCodes.UnsupportedColumnType, column.Type ?? string.Empty, string.Join(", ", SupportedTypes)]);
+ var (type, sizedLength) = ResolveType(column.Type);
int? maxLength = null;
if (type == "nvarchar")
{
- maxLength = column.MaxLength ?? DefaultTextLength;
+ maxLength = column.MaxLength ?? sizedLength ?? DefaultTextLength;
if (maxLength is < 1 or > MaxTextLength)
{
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidColumnLength, name, MaxTextLength]);
@@ -174,6 +182,49 @@ public class McpTableCreator(
return new McpTableColumn(name, type, maxLength, column.IsNullable, defaultValue);
}
+ ///
+ /// Tipi beyaz listedeki adina indirger. nvarchar(N) uzunlugu dondurur,
+ /// nvarchar(MAX) kendi tipidir; decimal yalnizca tasarimcinin kesinligiyle kabul edilir —
+ /// baska bir kesinligi 18,4'e cevirmek sessizce veri kaybi riski tasirdi.
+ ///
+ private (string Type, int? MaxLength) ResolveType(string? rawType)
+ {
+ var trimmed = rawType?.Trim() ?? string.Empty;
+ var name = trimmed;
+ string? size = null;
+
+ var sized = SizedTypeRegex.Match(trimmed);
+ if (sized.Success)
+ {
+ name = sized.Groups["name"].Value;
+ size = Regex.Replace(sized.Groups["size"].Value, @"\s+", string.Empty);
+ }
+
+ int? maxLength = null;
+ if (size is not null)
+ {
+ if (name.Equals("nvarchar", StringComparison.OrdinalIgnoreCase) && size.Equals("max", StringComparison.OrdinalIgnoreCase))
+ {
+ name = "nvarchar(MAX)";
+ }
+ else if (name.Equals("nvarchar", StringComparison.OrdinalIgnoreCase) && int.TryParse(size, out var length))
+ {
+ maxLength = length;
+ }
+ else if (!(name.Equals("decimal", StringComparison.OrdinalIgnoreCase) && size == DecimalSize))
+ {
+ name = trimmed;
+ }
+ }
+
+ var type = SupportedTypes.FirstOrDefault(candidate =>
+ string.Equals(candidate, name, StringComparison.OrdinalIgnoreCase))
+ ?? throw new UserFriendlyException(
+ localizer[McpErrorCodes.UnsupportedColumnType, trimmed, string.Join(", ", SupportedTypes)]);
+
+ return (type, maxLength);
+ }
+
private static string BuildScript(string tableName, List columns)
{
var fullTableName = $"[{SchemaName}].[{tableName}]";
diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs
index c6ed174c..841ac50f 100644
--- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs
+++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Tools/McpDevelopTools.cs
@@ -35,7 +35,7 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService)
public async Task CreateTableAsync(
[Description("Tablo adi; harf/rakam/alt cizgi. Adlandirma icin get_dev_guide'daki tablo onek kuralina uy (ornegin Adm_T_...).")]
string tableName,
- [Description("Is kolonlari. type: nvarchar, nvarchar(MAX), int, bigint, decimal (18,4), float, bit, datetime, datetime2, date, uniqueidentifier, money. maxLength yalnizca nvarchar icin (varsayilan 100). defaultValue: sayi, 'metin' ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().")]
+ [Description("Is kolonlari. type: nvarchar, nvarchar(MAX), int, bigint, decimal (her zaman 18,4 yazilir; boyut verme), float, bit, datetime, datetime2, date, uniqueidentifier, money. maxLength yalnizca nvarchar icin (varsayilan 100). defaultValue: sayi, 'metin' ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().")]
List columns,
[Description("Tam audit kolonlari eklensin mi. Kullanici acikca istemedikce true birak.")]
bool fullAudited = true,
diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs
index 9a8af58c..9a5c9853 100644
--- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs
+++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs
@@ -409,7 +409,8 @@ public class ListFormWizardAppService(
///
/// Okuma yetkisi ve alt yetkileri. Alt yetkilerin sirasi kardesler arasindadir ve listedeki
- /// dizilimi izler (Create = 1, ...); okuma yetkisi grubun kok yetkilerinin sonuna eklenir.
+ /// dizilimi izler (Create = 1, ...), menusuz alt formlarin okuma yetkileri bunlarin ardindan
+ /// gelir; okuma yetkisi menu dalinin, menusuz alt formda ise listenin yetkisinin altina eklenir.
/// Yetkiler menuyle ayni modul filtresine tabidir; modul bos kalirsa rol ekraninda modul
/// secimiyle filtrelenen listede gorunmez.
///
@@ -496,6 +497,24 @@ public class ListFormWizardAppService(
}
}
+ // Menusuz alt formun menude dali yoktur; yetkisi onu alt form olarak tasiyan listenin
+ // yetkisinin altindadir. Liste henuz deploy edilmediyse kokte kalir, liste deploy
+ // edildiginde asagida tasinir.
+ var wizards = (await ReadSeedFilesAsync()).Select(f => f.Seed?.Wizard).OfType().ToList();
+ bool InGroup(string name) =>
+ rows.Find(WizardSeedRowStore.PermissionFile, "Name", name) is { } row
+ ? Same(WizardSeedRowStore.Text(row, "GroupName"), groupName)
+ : dbPermissions.Any(p => Same(p.Name, name));
+
+ if (!input.CreateMenu)
+ {
+ var listFormCode = input.ResolveListFormCode();
+ parentName = wizards
+ .Where(w => !Same(w.ResolveMenuCode(), code) && (w.SubForms ?? []).Any(s => Same(s.Code, listFormCode)))
+ .Select(w => w.ResolveMenuCode())
+ .FirstOrDefault(InGroup);
+ }
+
if (IsOwnable(code))
Write(code, parentName, code, NextOrder(code, parentName));
@@ -508,6 +527,31 @@ public class ListFormWizardAppService(
if (IsOwnable(name))
Write(name, code, langKey, childOrder);
}
+
+ // Alt formlar listeden once deploy edilir (bagimlilik sirasi), yetkileri o an kokte yazilir.
+ // Satir alt formun wizard'ina ait kalir; liste yalnizca yerini degistirir. Baska bir listenin
+ // altina alinmis alt form orada birakilir.
+ foreach (var subForm in input.SubForms ?? [])
+ {
+ var child = wizards.FirstOrDefault(w => !w.CreateMenu && Same(w.ResolveListFormCode(), subForm.Code));
+ var childName = child?.ResolveMenuCode();
+ var row = rows.Find(WizardSeedRowStore.PermissionFile, "Name", childName);
+
+ if (child == null || row == null || Same(childName, code) || !InGroup(childName!))
+ continue;
+
+ var currentParent = WizardSeedRowStore.Text(row, "ParentName");
+ if (!string.IsNullOrWhiteSpace(currentParent) && !Same(currentParent, code))
+ continue;
+
+ childOrder++;
+ rows.Upsert(WizardSeedRowStore.PermissionFile, new Dictionary
+ {
+ ["Name"] = childName!,
+ ["ParentName"] = code,
+ [PlatformConsts.Permissions.Order] = childOrder,
+ }, "Name");
+ }
}
///
diff --git a/claude.md b/claude.md
index cca1f05e..1fd11241 100644
--- a/claude.md
+++ b/claude.md
@@ -128,7 +128,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
değer base64 saklanır, sınırlı boy ve `dxImageViewer` kullanılmaz (deploy ikisini de reddeder).
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`. Yetki ağacı menü ağacını izler: ekranın okuma
- yetkisi menüdeki dalın ara yetkisinin altına yazılır (`lowcode.instructions.md` §0.12 Menü yeri).
+ yetkisi menüdeki dalın ara yetkisinin altına yazılır; menüsüz alt formun yetkisi ise listenin
+ yetkisine bağlanır (`ParentName` = listenin yetki kodu) (`lowcode.instructions.md` §0.12 Menü yeri).
3. **Yeni ekran/menü** → yetki sözleşmesi olmadan önerilmez. Menü ve route aynı ekran
sözleşmesini göstermeli.
4. **Kullanıcıya görünen her metin** → dil anahtarı üzerinden, EN + TR birlikte. Koda gömülü
diff --git a/configs/seeds/host/data/App.DevRequests/ce4269ad-8d08-487a-9e36-c002dd482a3c.json b/configs/seeds/host/data/App.DevRequests/ce4269ad-8d08-487a-9e36-c002dd482a3c.json
new file mode 100644
index 00000000..7799bd21
--- /dev/null
+++ b/configs/seeds/host/data/App.DevRequests/ce4269ad-8d08-487a-9e36-c002dd482a3c.json
@@ -0,0 +1,26 @@
+{
+ "ListFormCode": "App.DevRequests",
+ "KeyFieldName": "Id",
+ "GeneratedAt": "2026-09-30T13:46:58.3804382Z",
+ "Order": 0,
+ "Rows": [
+ {
+ "Id": "ce4269ad-8d08-487a-9e36-c002dd482a3c",
+ "TenantId": null,
+ "UserName": "system@sozsoft.com",
+ "RequestDate": null,
+ "Status": "Backlog",
+ "Title": "Human Resources -> Sağlık",
+ "Description": "
Bu eklenecekler Human Resources -> Sağlık alt menüsünün içerisine eklenecektir.
İstirahat Listesi : Çalışanların istirahat listesi ekranıdır. İstirahat Tipi: Hastalık, İş Kazası, Analık, Meslek Hastalığı gibi seçilebilmelidir.
Muayene Listesi : Çalışanların Muayene Listesidir. Her kullanıcının periyodik veya plansız şekilde yapılan muayene sonuçlarının listesidir.
Sağlık Kartları: Her çalışanın detaylı şekilde sağlık bilgilerinin yer aldığı ekrandır. Bu ekranın içerisinde 9 farklı tab içerisinde ayrı bilgiler içermektedir. Aşağıda bu tabların resimlerini gönderiyorum. Bu bilgiler Kullanıcı Detail ekranında görünmeli. http://localhost:3000/admin/users/detail/61243cdf-045d-ee58-76df-3a2403667ea8 bu url de olduğu komponentte güncellenebilmeli.
Hasta Kayıt Defteri: Kullanıcıların hasta olduklarında doldurdukları listedir. Tip sütununda Hastalık, İş Kazası Dönüş, İstirahat Dönüş, Sonuç kısmında Tedavi (Taburcu), Sevk ve İstirahat seçenekleri olmalı. Sevk olaunca Sevk Yeri açılıyor.