diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index 9e5e10e3..0c15ccac 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -1469,6 +1469,21 @@ kullanan, sonra kullanılan — ve her kontrol o ana kadar kalan kayıtlara kar | Veri kaynağı | ekran, Custom Endpoint, zamanlanmış iş | | Dil anahtarı | menü/yetki/yetki grubu adı, ekran başlığı/açıklaması, alan başlığı | +Silme yolunda wizard'ın **bağlandığı** kaplar da boşaldıysa gider, `InsertedRecords`'ta olmasalar +bile: kap başka bir wizard'la oluşmuş ve o wizard silinirken bu wizard yüzünden bırakılmış +olabilir. Üst menü zinciri yukarı doğru yürünür — alt menüsü kalmayan ve kendisi adres açmayan +(`Url` boş) menü silinir, adresi olan ya da altında menü kalan kayıtta zincir durur. Wizard'ın +`PermissionGroupName`'i içinde yetki kalmamışsa, silinen menülerin ve `MenuParentModuleId`'nin +modülü yukarıdaki tabloda kullananı yoksa silinir; adlarının dil anahtarları da aynı kontrolden +geçer. Bu kaplar rapora yalnızca silindiklerinde girer. Düzenleme (redeploy) yolunda kaplara +dokunulmaz. + +Silinen menü, modül, yetki ve yetki grubu satırları ekranların seed dosyalarından da çıkarılır +(`App.Menus.{Menu,Module}.json`, `Abp.Identity.Permission{s,Groups}.json`; +`IListFormSeedDataSynchronizer.RemoveRowsByFieldAsync`, iş anahtarıyla, `SeedSyncDelete`'e +bağlı). Wizard bu dosyalara yazmaz, ama kayıt menü/yetki ekranlarından düzenlendiğinde satır +senkronizasyonla girer; dosyada kalırsa migrate kaydı geri getirir. + Bağlı wizard'ı olan (alt formu ya da component bağımlılığı wizard olarak duran) bir wizard silinemez; önce bağlı olanlar silinir. Kural listedeki gruplamayla aynı kaynaktandır (`WizardFileInfoDto.Dependencies`), sunucu `DeleteFile`'da reddeder, istemci butonu kapatır. diff --git a/README.md b/README.md index 29c66522..78128fd8 100644 --- a/README.md +++ b/README.md @@ -911,6 +911,9 @@ 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 (ve migrate'te `WizardDataSeeder`) yeni grubu ve grubun yeni kök yetkisini kardeşlerin en +büyük sırasının bir fazlasıyla, alt yetkileri Create/Update/Delete/Export/Import/Note dizilimiyle +1'den yazar (`NextPermissionOrder`, `AbpPermissionsExtensions`). Aksiyon yetkisi olan alt gruplar (UI tarafındaki karşılıkları `constants/permission.constant.ts` içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppService'lerdedir): diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs index 9897a6ca..80afefc6 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs @@ -31,6 +31,15 @@ public interface IListFormSeedDataSynchronizer /// Silinen satirlari dosyadan cikarir; SeedSyncDelete kapaliysa hicbir sey yapmaz. Task RemoveRowsAsync(string listFormCode, object[] keys); + /// + /// alaninin degeri icinde olan satirlari + /// dosyadan cikarir; SeedSyncDelete kapaliysa ya da hedef diskte yoksa hicbir sey yapmaz. + /// Kaydi veritabani disinda silinmis satirlar icindir: eslesme is anahtari uzerinden yapilir, + /// cunku dosyanin anahtari ekraninkinden farkli olabilir (yetki dosyalari Name ile + /// yazilir, ekran Id kullanir) ve dosyanin kendi basligina dokunulmaz. + /// + Task RemoveRowsByFieldAsync(string listFormCode, string fieldName, IReadOnlyCollection values); + /// /// Dosyayi listenin tamamiyla yeniden yazar; toplu islemler (import) icin kullanilir. /// Toplu yukleme kayit ekledigi icin SeedSyncInsert bayragina baglidir. @@ -149,6 +158,44 @@ public class ListFormSeedDataSynchronizer( }); } + public async Task RemoveRowsByFieldAsync(string listFormCode, string fieldName, IReadOnlyCollection values) + { + var wanted = (values ?? []) + .Where(v => !string.IsNullOrWhiteSpace(v)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + if (string.IsNullOrWhiteSpace(fieldName) || wanted.Count == 0) + return; + + bool Matches(Dictionary row) => + row.Any(kv => string.Equals(kv.Key, fieldName, StringComparison.OrdinalIgnoreCase) && + wanted.Contains(ToComparableText(kv.Value))); + + await ExecuteAsync(listFormCode, listForm => listForm.SeedSyncDelete, async (_, target) => + { + List files = target.IsFolder + ? Directory.Exists(target.Path) ? [.. Directory.EnumerateFiles(target.Path, "*.json")] : [] + : File.Exists(target.Path) ? [target.Path] : []; + + foreach (var filePath in files) + { + var seedFile = JsonSerializer.Deserialize( + await File.ReadAllTextAsync(filePath), + ListFormSeedDataFileDto.JsonOptions); + + if (seedFile?.Rows == null || seedFile.Rows.RemoveAll(Matches) == 0) + continue; + + // Klasor modunda satir dosyasi tek satirliktir; bosalan dosya klasorde kalirsa + // applier onu satirsiz bir ekran dosyasi olarak okur. + if (target.IsFolder && seedFile.Rows.Count == 0) + File.Delete(filePath); + else + await WriteSeedFileAsync(filePath, seedFile); + } + }, createTarget: false); + } + public async Task SyncAllAsync(string listFormCode) { await ExecuteAsync(listFormCode, listForm => listForm.SeedSyncInsert, WriteAllRowsAsync); @@ -290,7 +337,8 @@ public class ListFormSeedDataSynchronizer( private async Task ExecuteAsync( string listFormCode, Func isEnabled, - Func action) + Func action, + bool createTarget = true) { if (string.IsNullOrWhiteSpace(listFormCode)) return; @@ -309,7 +357,8 @@ public class ListFormSeedDataSynchronizer( listForm.SeedFilePath.Replace('\\', '/').Trim('/').Split('/')); // Klasor ilk kayitta olusur; ekran, hedefi diskte hazir bulmak zorunda degildir. - Directory.CreateDirectory(isFolder ? fullPath : Path.GetDirectoryName(fullPath)!); + if (createTarget) + Directory.CreateDirectory(isFolder ? fullPath : Path.GetDirectoryName(fullPath)!); await action(listForm, new SeedTarget(fullPath, isFolder)); } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index bcc7710d..df8a7387 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -188,6 +188,7 @@ public class ListFormWizardAppService( { var permReadRecord = new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both); permReadRecord.SetModules(permissionModules); + permReadRecord.SetPermissionOrder(existingPerms.Where(p => string.IsNullOrWhiteSpace(p.ParentName)).NextPermissionOrder()); permRead = await repoPerm.InsertAsync(permReadRecord, autoSave: false); inserted.PermissionNames.Add(permRead.Name); @@ -217,16 +218,20 @@ public class ListFormWizardAppService( : [.. crudPermissions, .. listOnlyPermissions]; var permissionNames = new List { permRead.Name }; + // Alt yetkilerin sirasi kardesler arasindadir ve listedeki dizilimi izler (Create = 1, ...). + var childOrder = 0; foreach (var (suffix, langKey) in childPermissions) { var permissionName = $"{code}.{suffix}"; permissionNames.Add(permissionName); + childOrder++; if (existingPerms.Any(a => a.Name == permissionName)) continue; var childPermission = new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, permissionName, permRead.Name, langKey, true, MultiTenancySides.Both); childPermission.SetModules(permissionModules); + childPermission.SetPermissionOrder(childOrder); await repoPerm.InsertAsync(childPermission, autoSave: false); inserted.PermissionNames.Add(permissionName); @@ -2306,7 +2311,9 @@ public class ListFormWizardAppService( if (!await repoPermGroup.AnyAsync(a => a.Name == groupName)) { - await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false); + var group = new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName); + group.SetPermissionOrder((await repoPermGroup.GetListAsync()).NextPermissionOrder()); + await repoPermGroup.InsertAsync(group, autoSave: false); // Grup adi ust menu koduyla ayniysa anahtar menu adiyla ortaktir; metin varsa uzerine // yazmak menu adini bozar, ust menu daha once baska bir wizard'la olustuysa metin hic diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs index 925bbdfe..f2c197c9 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs @@ -33,6 +33,13 @@ namespace Sozsoft.Platform.ListForms; /// kaynagi) ve diger rollerin yetki atamalari korunur; silinip yeniden uretilseler menu sirasi ve /// rol atamalari kaybolurdu. /// +/// +/// Silme yolunda wizard'in baglandigi kaplar da (ust menu zinciri, modul, izin grubu) bosaldiysa +/// silinir: kap bir baska wizard'la olusmus ve o wizard silinirken bu wizard'in menusu yuzunden +/// birakilmis olabilir; sahibi kalmayan kap yeniden deploy'da artik olarak kalirdi. Bu kaplar +/// yalnizca silindiklerinde rapora girer. Silinen menu, modul, yetki ve izin grubu satirlari ekranlarin +/// seed dosyalarindan da cikarilir; aksi halde migrate onlari geri getirirdi. +/// /// public class WizardRecordCleaner( IRepository repoListForm, @@ -50,6 +57,7 @@ public class WizardRecordCleaner( IRepository repoBackgroundWorker, IRepository repoIntranetWidget, ITenantRepository tenantRepository, + IListFormSeedDataSynchronizer seedDataSynchronizer, IConfiguration configuration, ICurrentTenant currentTenant, ILogger logger) : ITransientDependency @@ -67,12 +75,38 @@ public class WizardRecordCleaner( if (wizard?.ComponentKind != WizardComponentKindEnum.Custom && !string.IsNullOrWhiteSpace(wizard?.ListFormCode)) await CleanListFormAsync(wizard.ListFormCode, mode, report); - await CleanMenusAsync(ins.MenuCodes, wizard?.MenuParentCode, mode, report); + var deletedMenus = await CleanMenusAsync(ins.MenuCodes, wizard?.MenuParentCode, mode, report); + var adoptedLanguageKeys = new List(); + + if (mode == WizardDeleteMode.Delete && wizard?.CreateMenu == true) + { + var parents = deletedMenus.Select(m => m.ParentCode).Append(wizard.MenuParentCode); + var emptyParents = await CleanEmptyParentMenusAsync(parents, deletedMenus, report); + deletedMenus.AddRange(emptyParents); + adoptedLanguageKeys.AddRange(emptyParents.Select(m => m.DisplayName)); + } + await CleanPermissionsAsync(ins.PermissionNames, mode, report); - await CleanPermissionGroupsAsync(ins.PermissionGroupNames, mode, report); - await CleanModulesAsync(ins.ModuleIds, mode, report); + + await CleanPermissionGroupsAsync(ins.PermissionGroupNames, mode, report, reportKept: true); + await CleanModulesAsync(ins.ModuleIds, mode, report, reportKept: true); + + if (mode == WizardDeleteMode.Delete) + { + var groups = await CleanPermissionGroupsAsync( + Adopted([wizard?.PermissionGroupName], ins.PermissionGroupNames), mode, report, reportKept: false); + adoptedLanguageKeys.AddRange(groups); + + await CleanModulesAsync( + Adopted([.. deletedMenus.Select(m => m.ModuleId), wizard?.MenuParentModuleId], ins.ModuleIds), + mode, report, reportKept: false); + } + await CleanDataSourcesAsync(ins.DataSourceCodes, mode, report); - await CleanLanguageKeysAsync(ins.LanguageKeys, report); + await CleanLanguageKeysAsync(ins.LanguageKeys, report, reportKept: true); + await CleanLanguageKeysAsync(Adopted(adoptedLanguageKeys, ins.LanguageKeys), report, reportKept: false); + + await RemoveSeedRowsAsync(report); foreach (var item in report) { @@ -188,8 +222,9 @@ public class WizardRecordCleaner( /// Altinda baska menu kalan menu silinmez. Wizard'in kendi menusu ust menuden once silinir; /// ust menunun bos kalip kalmadigi ancak o zaman bilinir. /// - private async Task CleanMenusAsync(List codes, string? menuParentCode, WizardDeleteMode mode, List report) + private async Task> CleanMenusAsync(List codes, string? menuParentCode, WizardDeleteMode mode, List report) { + var deleted = new List(); var pending = new List(); foreach (var code in codes.Distinct(StringComparer.OrdinalIgnoreCase)) { @@ -220,6 +255,7 @@ public class WizardRecordCleaner( await repoMenu.DeleteAsync(menu, autoSave: true); Add(report, WizardRecordTypeEnum.Menu, code, WizardDeleteStatusEnum.Deleted); + deleted.Add(menu); pending.Remove(code); progressed = true; } @@ -231,6 +267,39 @@ public class WizardRecordCleaner( AddKept(report, WizardRecordTypeEnum.Menu, code, [.. children.Select(c => Ref(WizardRecordTypeEnum.Menu, c.Code))]); } + + return deleted; + } + + /// + /// Ust menu zincirini yukari dogru temizler: alt menusu kalmayan ve kendisi bir adres acmayan + /// (yalnizca kap olan) menu silinir, ardindan onun ustune bakilir. Adresi olan ya da altinda + /// baska menu kalan kayitta zincir durur; bu kayitlar wizard'in olmadigi icin rapora yazilmaz. + /// + private async Task> CleanEmptyParentMenusAsync(IEnumerable parentCodes, List alreadyDeleted, List report) + { + var deleted = new List(); + var visited = new HashSet(alreadyDeleted.Select(m => m.Code), StringComparer.OrdinalIgnoreCase); + var pending = new Queue(parentCodes.Where(c => !string.IsNullOrWhiteSpace(c)).Select(c => c!.Trim())); + + while (pending.TryDequeue(out var code)) + { + if (!visited.Add(code)) + continue; + + var menu = await repoMenu.FirstOrDefaultAsync(a => a.Code == code); + if (menu == null || !string.IsNullOrWhiteSpace(menu.Url) || await repoMenu.AnyAsync(a => a.ParentCode == code)) + continue; + + await repoMenu.DeleteAsync(menu, autoSave: true); + Add(report, WizardRecordTypeEnum.Menu, code, WizardDeleteStatusEnum.Deleted); + deleted.Add(menu); + + if (!string.IsNullOrWhiteSpace(menu.ParentCode)) + pending.Enqueue(menu.ParentCode); + } + + return deleted; } // ── Yetki ────────────────────────────────────────────────────────────────────── @@ -317,8 +386,13 @@ public class WizardRecordCleaner( // ── Yetki grubu, modul, veri kaynagi ─────────────────────────────────────────── - private async Task CleanPermissionGroupsAsync(List names, WizardDeleteMode mode, List report) + /// + /// kapaliyken kayit wizard'in degil, yalnizca baglandigi kaptir: + /// bulunamamasi ya da kullanimda kalmasi rapora yazilmaz. Silinen grup adlarini dondurur. + /// + private async Task> CleanPermissionGroupsAsync(List names, WizardDeleteMode mode, List report, bool reportKept) { + var deleted = new List(); foreach (var name in names.Distinct(StringComparer.OrdinalIgnoreCase)) { if (mode == WizardDeleteMode.Redeploy) @@ -330,25 +404,35 @@ public class WizardRecordCleaner( var group = await repoPermGroup.FirstOrDefaultAsync(a => a.Name == name); if (group == null) { - Add(report, WizardRecordTypeEnum.PermissionGroup, name, WizardDeleteStatusEnum.NotFound); + if (reportKept) + Add(report, WizardRecordTypeEnum.PermissionGroup, name, WizardDeleteStatusEnum.NotFound); continue; } var permissions = await repoPerm.GetListAsync(a => a.GroupName == name); if (permissions.Count > 0) { - AddKept(report, WizardRecordTypeEnum.PermissionGroup, name, - [.. permissions.Select(p => Ref(WizardRecordTypeEnum.Permission, p.Name))]); + if (reportKept) + { + AddKept(report, WizardRecordTypeEnum.PermissionGroup, name, + [.. permissions.Select(p => Ref(WizardRecordTypeEnum.Permission, p.Name))]); + } continue; } await repoPermGroup.DeleteAsync(group, autoSave: true); Add(report, WizardRecordTypeEnum.PermissionGroup, name, WizardDeleteStatusEnum.Deleted); + deleted.Add(name); } + + return deleted; } - /// Modul; menuler, yetkilerin modul listesi ve kiracilarin modul listesi tarafindan kullanilir. - private async Task CleanModulesAsync(List ids, WizardDeleteMode mode, List report) + /// + /// Modul; menuler, yetkilerin modul listesi ve kiracilarin modul listesi tarafindan kullanilir. + /// izin gruplarindakiyle aynidir. + /// + private async Task CleanModulesAsync(List ids, WizardDeleteMode mode, List report, bool reportKept) { if (ids.Count == 0) return; @@ -367,7 +451,8 @@ public class WizardRecordCleaner( var module = await repoModule.FindAsync(id); if (module == null) { - Add(report, WizardRecordTypeEnum.Module, id, WizardDeleteStatusEnum.NotFound); + if (reportKept) + Add(report, WizardRecordTypeEnum.Module, id, WizardDeleteStatusEnum.NotFound); continue; } @@ -387,7 +472,8 @@ public class WizardRecordCleaner( if (usedBy.Count > 0) { - AddKept(report, WizardRecordTypeEnum.Module, id, usedBy); + if (reportKept) + AddKept(report, WizardRecordTypeEnum.Module, id, usedBy); continue; } @@ -439,7 +525,7 @@ public class WizardRecordCleaner( /// basligi onu gosteriyorsa birakilir. Alan basliklari (App.Listform.ListformField.*) /// ekranlar arasinda ortak oldugu icin bu kontrol olmadan baska ekranlarin basliklari kaybolurdu. /// - private async Task CleanLanguageKeysAsync(List keys, List report) + private async Task CleanLanguageKeysAsync(List keys, List report, bool reportKept) { var resourceName = PlatformConsts.AppName; @@ -448,7 +534,8 @@ public class WizardRecordCleaner( var languageKey = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == resourceName && a.Key == key); if (languageKey == null) { - Add(report, WizardRecordTypeEnum.LanguageKey, key, WizardDeleteStatusEnum.NotFound); + if (reportKept) + Add(report, WizardRecordTypeEnum.LanguageKey, key, WizardDeleteStatusEnum.NotFound); continue; } @@ -466,7 +553,8 @@ public class WizardRecordCleaner( if (usedBy.Count > 0) { - AddKept(report, WizardRecordTypeEnum.LanguageKey, key, usedBy); + if (reportKept) + AddKept(report, WizardRecordTypeEnum.LanguageKey, key, usedBy); continue; } @@ -479,6 +567,32 @@ public class WizardRecordCleaner( } } + // ── Seed dosyalari ───────────────────────────────────────────────────────────── + + /// + /// Silinen kayitlarin ekran seed dosyalarindaki satirlari. Wizard bu dosyalara yazmaz; satir, + /// kayit menu/modul/yetki ekranlarindan duzenlendiginde senkronizasyonla girer ve kayit silindikten + /// sonra dosyada kalirsa migrate onu geri getirir. + /// + private async Task RemoveSeedRowsAsync(List report) + { + List Deleted(WizardRecordTypeEnum type) => + [.. report.Where(i => i.RecordType == type && i.Status == WizardDeleteStatusEnum.Deleted).Select(i => i.Name)]; + + await seedDataSynchronizer.RemoveRowsByFieldAsync(PlatformConsts.AppCodes.Menus.Menu, nameof(Menu.Code), Deleted(WizardRecordTypeEnum.Menu)); + await seedDataSynchronizer.RemoveRowsByFieldAsync(PlatformConsts.AppCodes.Menus.Module, nameof(Module.Id), Deleted(WizardRecordTypeEnum.Module)); + await seedDataSynchronizer.RemoveRowsByFieldAsync(SeedConsts.AbpIdentity.Permissions.Default, nameof(PermissionDefinitionRecord.Name), Deleted(WizardRecordTypeEnum.Permission)); + await seedDataSynchronizer.RemoveRowsByFieldAsync(SeedConsts.AbpIdentity.PermissionGroups.Default, nameof(PermissionGroupDefinitionRecord.Name), Deleted(WizardRecordTypeEnum.PermissionGroup)); + } + + /// Wizard'in kendi urettikleri disinda kalan, bos olmayan adaylar. + private static List Adopted(IEnumerable candidates, List owned) => + [.. candidates + .Where(c => !string.IsNullOrWhiteSpace(c)) + .Select(c => c!.Trim()) + .Where(c => !owned.Contains(c, StringComparer.OrdinalIgnoreCase)) + .Distinct(StringComparer.OrdinalIgnoreCase)]; + // ── Rapor ────────────────────────────────────────────────────────────────────── /// JSON icinde tirnakli deger olarak arar; kod bir baska kodun parcasiysa eslesmez. diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs index d2355112..3907d7ff 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs @@ -197,8 +197,9 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency input.PermissionGroupName = groupName; if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName)) { - await _repoPermGroup.InsertAsync( - new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true); + var group = new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName); + group.SetPermissionOrder((await _repoPermGroup.GetListAsync()).NextPermissionOrder()); + await _repoPermGroup.InsertAsync(group, autoSave: true); } // Permissions - tek seferde mevcut permission'ları çek, sonra her birini kontrol et @@ -210,30 +211,32 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency var permissionModules = input.MenuParentModuleId?.Trim(); var permRead = existingPerms.FirstOrDefault(a => a.Name == code); - permRead ??= await InsertPermissionAsync(groupName, code, null, nameLangKey, permissionModules); + permRead ??= await InsertPermissionAsync(groupName, code, null, nameLangKey, permissionModules, + existingPerms.Where(p => string.IsNullOrWhiteSpace(p.ParentName)).NextPermissionOrder()); // CRUD üçlüsü her iki yolda da üretilir: Custom component de Form // üzerinden kayıt ekler, günceller ve siler (AppService.Create ile aynı davranış). + // Alt yetkilerin sirasi kardesler arasindadir ve bu dizilimi izler (Create = 1, ...). if (!existingPerms.Any(a => a.Name == permCreateName)) - await InsertPermissionAsync(groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, permissionModules); + await InsertPermissionAsync(groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, permissionModules, 1); if (!existingPerms.Any(a => a.Name == permUpdateName)) - await InsertPermissionAsync(groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, permissionModules); + await InsertPermissionAsync(groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, permissionModules, 2); if (!existingPerms.Any(a => a.Name == permDeleteName)) - await InsertPermissionAsync(groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, permissionModules); + await InsertPermissionAsync(groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, permissionModules, 3); // Export/Import/Note yalnızca ListForm ekosisteminde karşılığı olan işlemlerdir. if (!isCustomComponent) { if (!existingPerms.Any(a => a.Name == permExportName)) - await InsertPermissionAsync(groupName, permExportName, permRead.Name, WizardConsts.LangKeyExport, permissionModules); + await InsertPermissionAsync(groupName, permExportName, permRead.Name, WizardConsts.LangKeyExport, permissionModules, 4); if (!existingPerms.Any(a => a.Name == permImportName)) - await InsertPermissionAsync(groupName, permImportName, permRead.Name, WizardConsts.LangKeyImport, permissionModules); + await InsertPermissionAsync(groupName, permImportName, permRead.Name, WizardConsts.LangKeyImport, permissionModules, 5); if (!existingPerms.Any(a => a.Name == permNoteName)) - await InsertPermissionAsync(groupName, permNoteName, permRead.Name, WizardConsts.LangKeyNote, permissionModules); + await InsertPermissionAsync(groupName, permNoteName, permRead.Name, WizardConsts.LangKeyNote, permissionModules, 6); } // // Permission Grants - Admin role için, sadece eksik olanları ekle @@ -439,11 +442,13 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency string name, string parentName, string displayName, - string modules) + string modules, + int order) { var permission = new PermissionDefinitionRecord( Guid.NewGuid(), groupName, name, parentName, displayName, true, MultiTenancySides.Both); permission.SetModules(modules); + permission.SetPermissionOrder(order); return await _repoPerm.InsertAsync(permission, autoSave: true); } diff --git a/api/src/Sozsoft.Platform.Domain/Extensions/AbpPermissionsExtensions.cs b/api/src/Sozsoft.Platform.Domain/Extensions/AbpPermissionsExtensions.cs index db05ca39..6964ca01 100644 --- a/api/src/Sozsoft.Platform.Domain/Extensions/AbpPermissionsExtensions.cs +++ b/api/src/Sozsoft.Platform.Domain/Extensions/AbpPermissionsExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Volo.Abp.Data; using Volo.Abp.PermissionManagement; @@ -48,5 +49,20 @@ public static class AbpPermissionsExtensions .Distinct() .ToArray(); } + + /// + /// Yetki ekranindaki sira () kardesler arasindadir; + /// yeni kayit en buyuk kardes sirasinin bir fazlasini alir, kardes yoksa 1. Bos birakilan sira + /// 0 olur ve kayit listenin en ustune cikar. + /// + public static int NextPermissionOrder(this IEnumerable siblings) + { + return siblings.Select(s => s.GetProperty(PlatformConsts.Permissions.Order)).DefaultIfEmpty(0).Max() + 1; + } + + public static void SetPermissionOrder(this IHasExtraProperties record, int order) + { + record.SetProperty(PlatformConsts.Permissions.Order, order); + } }