diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index 1f20cf4f..8b45218e 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -701,6 +701,11 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma. üç 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). + **Yetki ağacı menü ağacıyla aynıdır**: deploy, izin grubu dışındaki her menü seviyesini aynı + kodlu ara yetki yapar ve ekranın okuma yetkisini onun altına yazar. Analiz bunu menü tablosunun + 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. 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. @@ -2135,7 +2140,7 @@ 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 +4. İzin grubu + yetkiler (okuma yetkisi menü dalındaki ara yetkinin 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 28b07ca4..dfc6fbfc 100644 --- a/README.md +++ b/README.md @@ -943,7 +943,13 @@ Sıra **kardeşler arasındadır**: her üst yetkinin altında 1'den başlar, gr kendi aralarında 1'den. `PlatformPermissionAppService` sıralama anahtarını kökten yaprağa üst yetki zincirinden kurar, böylece çocuk her zaman ebeveyninin altında kalır. Değerler seed dosyalarındaki `Order` alanında tutulur ve Permissions / Permission Groups ekranlarından düzenlenebilir. -Wizard deploy'u yeni grubu ve grubun yeni kök yetkisini (seed satırı olarak) veritabanı ile seed +**Yetki ağacı menü ağacını izler.** Wizard ekranın okuma yetkisini menüde durduğu dalın altına +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. +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 (`ListFormWizardAppService.WritePermissionGroupAsync` / `WritePermissionRowsAsync`). diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 586a660c..1cefe7f9 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -424,21 +424,61 @@ public class ListFormWizardAppService( inserted.PermissionNames.Add(name); } - if (IsOwnable(code)) - { - var existingRow = rows.Find(WizardSeedRowStore.PermissionFile, "Name", code); - var order = existingRow != null + static bool Same(string? a, string? b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase); + + // Kardesler arasindaki sira; parent bos ise grubun kok yetkileri. Zincirde az once yazilan + // seviye de sirada yer tutar. + int NextOrder(string name, string? parent) => + rows.Find(WizardSeedRowStore.PermissionFile, "Name", name) is { } existingRow ? WizardSeedRowStore.Int(existingRow, PlatformConsts.Permissions.Order) - : dbPermissions.Where(p => string.IsNullOrWhiteSpace(p.ParentName)).Select(p => p.GetProperty(PlatformConsts.Permissions.Order)) + : dbPermissions.Where(p => Same(p.ParentName ?? string.Empty, parent ?? string.Empty) && !Same(p.Name, name)) + .Select(p => p.GetProperty(PlatformConsts.Permissions.Order)) .Concat(rows.Rows(WizardSeedRowStore.PermissionFile) - .Where(r => string.Equals(WizardSeedRowStore.Text(r, "GroupName"), groupName, StringComparison.OrdinalIgnoreCase) && - string.IsNullOrWhiteSpace(WizardSeedRowStore.Text(r, "ParentName"))) + .Where(r => Same(WizardSeedRowStore.Text(r, "GroupName"), groupName) && + Same(WizardSeedRowStore.Text(r, "ParentName") ?? string.Empty, parent ?? string.Empty) && + !Same(WizardSeedRowStore.Text(r, "Name"), name)) .Select(r => WizardSeedRowStore.Int(r, PlatformConsts.Permissions.Order))) .DefaultIfEmpty(0).Max() + 1; - Write(code, null, code, order); + // Yetki agaci menu agacini izler: ust menu zincirindeki her seviye (grubun kendisi haric) + // ayni kodlu bir ara yetkidir; rol ekraninda ekran, menude durdugu dalin altinda gorunur. + string? parentName = null; + if (input.CreateMenu) + { + var dbMenus = await repoMenu.GetListAsync(); + + foreach (var level in input.ResolveMenuParentChain()) + { + if (Same(level.Code, groupName)) + continue; + + var fileLevel = rows.Find(WizardSeedRowStore.PermissionFile, "Name", level.Code); + var foreign = fileLevel != null + ? !Same(WizardSeedRowStore.Text(fileLevel, "GroupName"), groupName) + : !dbPermissions.Any(p => Same(p.Name, level.Code)) && await repoPerm.AnyAsync(a => a.Name == level.Code); + + // Yetki adi tekildir; baska grubun yetkisi bu grubun agacina baglanamaz. + if (foreign) + continue; + + if (IsOwnable(level.Code)) + { + // Menu seviyesi varsa gorunen adi ondan alinir; yeni seviyenin dil anahtari + // menu yazilirken kendi koduyla uretilir. + var displayName = dbMenus.FirstOrDefault(m => Same(m.Code, level.Code))?.DisplayName + ?? WizardSeedRowStore.Text(rows.Find(WizardSeedRowStore.MenuFile, nameof(Menu.Code), level.Code), nameof(Menu.DisplayName)) + ?? level.Code; + + Write(level.Code, parentName, displayName, NextOrder(level.Code, parentName)); + } + + parentName = level.Code; + } } + if (IsOwnable(code)) + Write(code, parentName, code, NextOrder(code, parentName)); + var childOrder = 0; foreach (var (suffix, langKey) in WizardSeedRowKeys.ChildPermissions(input)) { diff --git a/claude.md b/claude.md index d6cb7dab..ed19a231 100644 --- a/claude.md +++ b/claude.md @@ -127,7 +127,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → Görsel tutan her kolon (fotoğraf, logo, avatar kopyası) `nvarchar(MAX)` + editör `dxImageUpload`; 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`. + 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). 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ü