Wizard Delete eksiklikleri
This commit is contained in:
parent
9a14be4ee4
commit
9816fb91ce
7 changed files with 238 additions and 29 deletions
15
.github/instructions/lowcode.instructions.md
vendored
15
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -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ş |
|
| Veri kaynağı | ekran, Custom Endpoint, zamanlanmış iş |
|
||||||
| Dil anahtarı | menü/yetki/yetki grubu adı, ekran başlığı/açıklaması, alan başlığı |
|
| 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
|
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
|
silinemez; önce bağlı olanlar silinir. Kural listedeki gruplamayla aynı kaynaktandır
|
||||||
(`WizardFileInfoDto.Dependencies`), sunucu `DeleteFile`'da reddeder, istemci butonu kapatır.
|
(`WizardFileInfoDto.Dependencies`), sunucu `DeleteFile`'da reddeder, istemci butonu kapatır.
|
||||||
|
|
|
||||||
|
|
@ -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
|
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
|
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.
|
`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`
|
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):
|
içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppService'lerdedir):
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,15 @@ public interface IListFormSeedDataSynchronizer
|
||||||
/// <summary>Silinen satirlari dosyadan cikarir; <c>SeedSyncDelete</c> kapaliysa hicbir sey yapmaz.</summary>
|
/// <summary>Silinen satirlari dosyadan cikarir; <c>SeedSyncDelete</c> kapaliysa hicbir sey yapmaz.</summary>
|
||||||
Task RemoveRowsAsync(string listFormCode, object[] keys);
|
Task RemoveRowsAsync(string listFormCode, object[] keys);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <paramref name="fieldName"/> alaninin degeri <paramref name="values"/> icinde olan satirlari
|
||||||
|
/// dosyadan cikarir; <c>SeedSyncDelete</c> 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 <c>Name</c> ile
|
||||||
|
/// yazilir, ekran <c>Id</c> kullanir) ve dosyanin kendi basligina dokunulmaz.
|
||||||
|
/// </summary>
|
||||||
|
Task RemoveRowsByFieldAsync(string listFormCode, string fieldName, IReadOnlyCollection<string> values);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dosyayi listenin tamamiyla yeniden yazar; toplu islemler (import) icin kullanilir.
|
/// Dosyayi listenin tamamiyla yeniden yazar; toplu islemler (import) icin kullanilir.
|
||||||
/// Toplu yukleme kayit ekledigi icin <c>SeedSyncInsert</c> bayragina baglidir.
|
/// Toplu yukleme kayit ekledigi icin <c>SeedSyncInsert</c> bayragina baglidir.
|
||||||
|
|
@ -149,6 +158,44 @@ public class ListFormSeedDataSynchronizer(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RemoveRowsByFieldAsync(string listFormCode, string fieldName, IReadOnlyCollection<string> values)
|
||||||
|
{
|
||||||
|
var wanted = (values ?? [])
|
||||||
|
.Where(v => !string.IsNullOrWhiteSpace(v))
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(fieldName) || wanted.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool Matches(Dictionary<string, object> 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<string> 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<ListFormSeedDataFileDto>(
|
||||||
|
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)
|
public async Task SyncAllAsync(string listFormCode)
|
||||||
{
|
{
|
||||||
await ExecuteAsync(listFormCode, listForm => listForm.SeedSyncInsert, WriteAllRowsAsync);
|
await ExecuteAsync(listFormCode, listForm => listForm.SeedSyncInsert, WriteAllRowsAsync);
|
||||||
|
|
@ -290,7 +337,8 @@ public class ListFormSeedDataSynchronizer(
|
||||||
private async Task ExecuteAsync(
|
private async Task ExecuteAsync(
|
||||||
string listFormCode,
|
string listFormCode,
|
||||||
Func<ListForm, bool> isEnabled,
|
Func<ListForm, bool> isEnabled,
|
||||||
Func<ListForm, SeedTarget, Task> action)
|
Func<ListForm, SeedTarget, Task> action,
|
||||||
|
bool createTarget = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(listFormCode))
|
if (string.IsNullOrWhiteSpace(listFormCode))
|
||||||
return;
|
return;
|
||||||
|
|
@ -309,7 +357,8 @@ public class ListFormSeedDataSynchronizer(
|
||||||
listForm.SeedFilePath.Replace('\\', '/').Trim('/').Split('/'));
|
listForm.SeedFilePath.Replace('\\', '/').Trim('/').Split('/'));
|
||||||
|
|
||||||
// Klasor ilk kayitta olusur; ekran, hedefi diskte hazir bulmak zorunda degildir.
|
// 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));
|
await action(listForm, new SeedTarget(fullPath, isFolder));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ public class ListFormWizardAppService(
|
||||||
{
|
{
|
||||||
var permReadRecord = new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both);
|
var permReadRecord = new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both);
|
||||||
permReadRecord.SetModules(permissionModules);
|
permReadRecord.SetModules(permissionModules);
|
||||||
|
permReadRecord.SetPermissionOrder(existingPerms.Where(p => string.IsNullOrWhiteSpace(p.ParentName)).NextPermissionOrder());
|
||||||
|
|
||||||
permRead = await repoPerm.InsertAsync(permReadRecord, autoSave: false);
|
permRead = await repoPerm.InsertAsync(permReadRecord, autoSave: false);
|
||||||
inserted.PermissionNames.Add(permRead.Name);
|
inserted.PermissionNames.Add(permRead.Name);
|
||||||
|
|
@ -217,16 +218,20 @@ public class ListFormWizardAppService(
|
||||||
: [.. crudPermissions, .. listOnlyPermissions];
|
: [.. crudPermissions, .. listOnlyPermissions];
|
||||||
|
|
||||||
var permissionNames = new List<string> { permRead.Name };
|
var permissionNames = new List<string> { permRead.Name };
|
||||||
|
// Alt yetkilerin sirasi kardesler arasindadir ve listedeki dizilimi izler (Create = 1, ...).
|
||||||
|
var childOrder = 0;
|
||||||
foreach (var (suffix, langKey) in childPermissions)
|
foreach (var (suffix, langKey) in childPermissions)
|
||||||
{
|
{
|
||||||
var permissionName = $"{code}.{suffix}";
|
var permissionName = $"{code}.{suffix}";
|
||||||
permissionNames.Add(permissionName);
|
permissionNames.Add(permissionName);
|
||||||
|
childOrder++;
|
||||||
|
|
||||||
if (existingPerms.Any(a => a.Name == permissionName))
|
if (existingPerms.Any(a => a.Name == permissionName))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var childPermission = new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, permissionName, permRead.Name, langKey, true, MultiTenancySides.Both);
|
var childPermission = new PermissionDefinitionRecord(GuidGenerator.Create(), groupName, permissionName, permRead.Name, langKey, true, MultiTenancySides.Both);
|
||||||
childPermission.SetModules(permissionModules);
|
childPermission.SetModules(permissionModules);
|
||||||
|
childPermission.SetPermissionOrder(childOrder);
|
||||||
|
|
||||||
await repoPerm.InsertAsync(childPermission, autoSave: false);
|
await repoPerm.InsertAsync(childPermission, autoSave: false);
|
||||||
inserted.PermissionNames.Add(permissionName);
|
inserted.PermissionNames.Add(permissionName);
|
||||||
|
|
@ -2306,7 +2311,9 @@ public class ListFormWizardAppService(
|
||||||
|
|
||||||
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
|
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
|
// 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
|
// yazmak menu adini bozar, ust menu daha once baska bir wizard'la olustuysa metin hic
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ namespace Sozsoft.Platform.ListForms;
|
||||||
/// kaynagi) ve diger rollerin yetki atamalari korunur; silinip yeniden uretilseler menu sirasi ve
|
/// kaynagi) ve diger rollerin yetki atamalari korunur; silinip yeniden uretilseler menu sirasi ve
|
||||||
/// rol atamalari kaybolurdu.
|
/// rol atamalari kaybolurdu.
|
||||||
/// </para>
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Silme yolunda wizard'in <b>baglandigi</b> 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.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WizardRecordCleaner(
|
public class WizardRecordCleaner(
|
||||||
IRepository<ListForm, Guid> repoListForm,
|
IRepository<ListForm, Guid> repoListForm,
|
||||||
|
|
@ -50,6 +57,7 @@ public class WizardRecordCleaner(
|
||||||
IRepository<BackgroundWorker, Guid> repoBackgroundWorker,
|
IRepository<BackgroundWorker, Guid> repoBackgroundWorker,
|
||||||
IRepository<IntranetWidget, Guid> repoIntranetWidget,
|
IRepository<IntranetWidget, Guid> repoIntranetWidget,
|
||||||
ITenantRepository tenantRepository,
|
ITenantRepository tenantRepository,
|
||||||
|
IListFormSeedDataSynchronizer seedDataSynchronizer,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
ICurrentTenant currentTenant,
|
ICurrentTenant currentTenant,
|
||||||
ILogger<WizardRecordCleaner> logger) : ITransientDependency
|
ILogger<WizardRecordCleaner> logger) : ITransientDependency
|
||||||
|
|
@ -67,12 +75,38 @@ public class WizardRecordCleaner(
|
||||||
if (wizard?.ComponentKind != WizardComponentKindEnum.Custom && !string.IsNullOrWhiteSpace(wizard?.ListFormCode))
|
if (wizard?.ComponentKind != WizardComponentKindEnum.Custom && !string.IsNullOrWhiteSpace(wizard?.ListFormCode))
|
||||||
await CleanListFormAsync(wizard.ListFormCode, mode, report);
|
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<string>();
|
||||||
|
|
||||||
|
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 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 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)
|
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;
|
/// Altinda baska menu kalan menu silinmez. Wizard'in kendi menusu ust menuden once silinir;
|
||||||
/// ust menunun bos kalip kalmadigi ancak o zaman bilinir.
|
/// ust menunun bos kalip kalmadigi ancak o zaman bilinir.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task CleanMenusAsync(List<string> codes, string? menuParentCode, WizardDeleteMode mode, List<WizardDeleteLogItemDto> report)
|
private async Task<List<Menu>> CleanMenusAsync(List<string> codes, string? menuParentCode, WizardDeleteMode mode, List<WizardDeleteLogItemDto> report)
|
||||||
{
|
{
|
||||||
|
var deleted = new List<Menu>();
|
||||||
var pending = new List<string>();
|
var pending = new List<string>();
|
||||||
foreach (var code in codes.Distinct(StringComparer.OrdinalIgnoreCase))
|
foreach (var code in codes.Distinct(StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
@ -220,6 +255,7 @@ public class WizardRecordCleaner(
|
||||||
|
|
||||||
await repoMenu.DeleteAsync(menu, autoSave: true);
|
await repoMenu.DeleteAsync(menu, autoSave: true);
|
||||||
Add(report, WizardRecordTypeEnum.Menu, code, WizardDeleteStatusEnum.Deleted);
|
Add(report, WizardRecordTypeEnum.Menu, code, WizardDeleteStatusEnum.Deleted);
|
||||||
|
deleted.Add(menu);
|
||||||
pending.Remove(code);
|
pending.Remove(code);
|
||||||
progressed = true;
|
progressed = true;
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +267,39 @@ public class WizardRecordCleaner(
|
||||||
AddKept(report, WizardRecordTypeEnum.Menu, code,
|
AddKept(report, WizardRecordTypeEnum.Menu, code,
|
||||||
[.. children.Select(c => Ref(WizardRecordTypeEnum.Menu, c.Code))]);
|
[.. children.Select(c => Ref(WizardRecordTypeEnum.Menu, c.Code))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<List<Menu>> CleanEmptyParentMenusAsync(IEnumerable<string?> parentCodes, List<Menu> alreadyDeleted, List<WizardDeleteLogItemDto> report)
|
||||||
|
{
|
||||||
|
var deleted = new List<Menu>();
|
||||||
|
var visited = new HashSet<string>(alreadyDeleted.Select(m => m.Code), StringComparer.OrdinalIgnoreCase);
|
||||||
|
var pending = new Queue<string>(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 ──────────────────────────────────────────────────────────────────────
|
// ── Yetki ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -317,8 +386,13 @@ public class WizardRecordCleaner(
|
||||||
|
|
||||||
// ── Yetki grubu, modul, veri kaynagi ───────────────────────────────────────────
|
// ── Yetki grubu, modul, veri kaynagi ───────────────────────────────────────────
|
||||||
|
|
||||||
private async Task CleanPermissionGroupsAsync(List<string> names, WizardDeleteMode mode, List<WizardDeleteLogItemDto> report)
|
/// <summary>
|
||||||
|
/// <paramref name="reportKept"/> kapaliyken kayit wizard'in degil, yalnizca baglandigi kaptir:
|
||||||
|
/// bulunamamasi ya da kullanimda kalmasi rapora yazilmaz. Silinen grup adlarini dondurur.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<List<string>> CleanPermissionGroupsAsync(List<string> names, WizardDeleteMode mode, List<WizardDeleteLogItemDto> report, bool reportKept)
|
||||||
{
|
{
|
||||||
|
var deleted = new List<string>();
|
||||||
foreach (var name in names.Distinct(StringComparer.OrdinalIgnoreCase))
|
foreach (var name in names.Distinct(StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (mode == WizardDeleteMode.Redeploy)
|
if (mode == WizardDeleteMode.Redeploy)
|
||||||
|
|
@ -330,25 +404,35 @@ public class WizardRecordCleaner(
|
||||||
var group = await repoPermGroup.FirstOrDefaultAsync(a => a.Name == name);
|
var group = await repoPermGroup.FirstOrDefaultAsync(a => a.Name == name);
|
||||||
if (group == null)
|
if (group == null)
|
||||||
{
|
{
|
||||||
Add(report, WizardRecordTypeEnum.PermissionGroup, name, WizardDeleteStatusEnum.NotFound);
|
if (reportKept)
|
||||||
|
Add(report, WizardRecordTypeEnum.PermissionGroup, name, WizardDeleteStatusEnum.NotFound);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var permissions = await repoPerm.GetListAsync(a => a.GroupName == name);
|
var permissions = await repoPerm.GetListAsync(a => a.GroupName == name);
|
||||||
if (permissions.Count > 0)
|
if (permissions.Count > 0)
|
||||||
{
|
{
|
||||||
AddKept(report, WizardRecordTypeEnum.PermissionGroup, name,
|
if (reportKept)
|
||||||
[.. permissions.Select(p => Ref(WizardRecordTypeEnum.Permission, p.Name))]);
|
{
|
||||||
|
AddKept(report, WizardRecordTypeEnum.PermissionGroup, name,
|
||||||
|
[.. permissions.Select(p => Ref(WizardRecordTypeEnum.Permission, p.Name))]);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await repoPermGroup.DeleteAsync(group, autoSave: true);
|
await repoPermGroup.DeleteAsync(group, autoSave: true);
|
||||||
Add(report, WizardRecordTypeEnum.PermissionGroup, name, WizardDeleteStatusEnum.Deleted);
|
Add(report, WizardRecordTypeEnum.PermissionGroup, name, WizardDeleteStatusEnum.Deleted);
|
||||||
|
deleted.Add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Modul; menuler, yetkilerin modul listesi ve kiracilarin modul listesi tarafindan kullanilir.</summary>
|
/// <summary>
|
||||||
private async Task CleanModulesAsync(List<string> ids, WizardDeleteMode mode, List<WizardDeleteLogItemDto> report)
|
/// Modul; menuler, yetkilerin modul listesi ve kiracilarin modul listesi tarafindan kullanilir.
|
||||||
|
/// <paramref name="reportKept"/> izin gruplarindakiyle aynidir.
|
||||||
|
/// </summary>
|
||||||
|
private async Task CleanModulesAsync(List<string> ids, WizardDeleteMode mode, List<WizardDeleteLogItemDto> report, bool reportKept)
|
||||||
{
|
{
|
||||||
if (ids.Count == 0)
|
if (ids.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -367,7 +451,8 @@ public class WizardRecordCleaner(
|
||||||
var module = await repoModule.FindAsync(id);
|
var module = await repoModule.FindAsync(id);
|
||||||
if (module == null)
|
if (module == null)
|
||||||
{
|
{
|
||||||
Add(report, WizardRecordTypeEnum.Module, id, WizardDeleteStatusEnum.NotFound);
|
if (reportKept)
|
||||||
|
Add(report, WizardRecordTypeEnum.Module, id, WizardDeleteStatusEnum.NotFound);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,7 +472,8 @@ public class WizardRecordCleaner(
|
||||||
|
|
||||||
if (usedBy.Count > 0)
|
if (usedBy.Count > 0)
|
||||||
{
|
{
|
||||||
AddKept(report, WizardRecordTypeEnum.Module, id, usedBy);
|
if (reportKept)
|
||||||
|
AddKept(report, WizardRecordTypeEnum.Module, id, usedBy);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,7 +525,7 @@ public class WizardRecordCleaner(
|
||||||
/// basligi onu gosteriyorsa birakilir. Alan basliklari (<c>App.Listform.ListformField.*</c>)
|
/// basligi onu gosteriyorsa birakilir. Alan basliklari (<c>App.Listform.ListformField.*</c>)
|
||||||
/// ekranlar arasinda ortak oldugu icin bu kontrol olmadan baska ekranlarin basliklari kaybolurdu.
|
/// ekranlar arasinda ortak oldugu icin bu kontrol olmadan baska ekranlarin basliklari kaybolurdu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task CleanLanguageKeysAsync(List<string> keys, List<WizardDeleteLogItemDto> report)
|
private async Task CleanLanguageKeysAsync(List<string> keys, List<WizardDeleteLogItemDto> report, bool reportKept)
|
||||||
{
|
{
|
||||||
var resourceName = PlatformConsts.AppName;
|
var resourceName = PlatformConsts.AppName;
|
||||||
|
|
||||||
|
|
@ -448,7 +534,8 @@ public class WizardRecordCleaner(
|
||||||
var languageKey = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == resourceName && a.Key == key);
|
var languageKey = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == resourceName && a.Key == key);
|
||||||
if (languageKey == null)
|
if (languageKey == null)
|
||||||
{
|
{
|
||||||
Add(report, WizardRecordTypeEnum.LanguageKey, key, WizardDeleteStatusEnum.NotFound);
|
if (reportKept)
|
||||||
|
Add(report, WizardRecordTypeEnum.LanguageKey, key, WizardDeleteStatusEnum.NotFound);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,7 +553,8 @@ public class WizardRecordCleaner(
|
||||||
|
|
||||||
if (usedBy.Count > 0)
|
if (usedBy.Count > 0)
|
||||||
{
|
{
|
||||||
AddKept(report, WizardRecordTypeEnum.LanguageKey, key, usedBy);
|
if (reportKept)
|
||||||
|
AddKept(report, WizardRecordTypeEnum.LanguageKey, key, usedBy);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,6 +567,32 @@ public class WizardRecordCleaner(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Seed dosyalari ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private async Task RemoveSeedRowsAsync(List<WizardDeleteLogItemDto> report)
|
||||||
|
{
|
||||||
|
List<string> 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Wizard'in kendi urettikleri disinda kalan, bos olmayan adaylar.</summary>
|
||||||
|
private static List<string> Adopted(IEnumerable<string?> candidates, List<string> owned) =>
|
||||||
|
[.. candidates
|
||||||
|
.Where(c => !string.IsNullOrWhiteSpace(c))
|
||||||
|
.Select(c => c!.Trim())
|
||||||
|
.Where(c => !owned.Contains(c, StringComparer.OrdinalIgnoreCase))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)];
|
||||||
|
|
||||||
// ── Rapor ──────────────────────────────────────────────────────────────────────
|
// ── Rapor ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// <summary>JSON icinde tirnakli deger olarak arar; kod bir baska kodun parcasiysa eslesmez.</summary>
|
/// <summary>JSON icinde tirnakli deger olarak arar; kod bir baska kodun parcasiysa eslesmez.</summary>
|
||||||
|
|
|
||||||
|
|
@ -197,8 +197,9 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
input.PermissionGroupName = groupName;
|
input.PermissionGroupName = groupName;
|
||||||
if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName))
|
if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName))
|
||||||
{
|
{
|
||||||
await _repoPermGroup.InsertAsync(
|
var group = new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName);
|
||||||
new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true);
|
group.SetPermissionOrder((await _repoPermGroup.GetListAsync()).NextPermissionOrder());
|
||||||
|
await _repoPermGroup.InsertAsync(group, autoSave: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permissions - tek seferde mevcut permission'ları çek, sonra her birini kontrol et
|
// 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 permissionModules = input.MenuParentModuleId?.Trim();
|
||||||
|
|
||||||
var permRead = existingPerms.FirstOrDefault(a => a.Name == code);
|
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
|
// 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ış).
|
// ü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))
|
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))
|
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))
|
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.
|
// Export/Import/Note yalnızca ListForm ekosisteminde karşılığı olan işlemlerdir.
|
||||||
if (!isCustomComponent)
|
if (!isCustomComponent)
|
||||||
{
|
{
|
||||||
if (!existingPerms.Any(a => a.Name == permExportName))
|
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))
|
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))
|
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
|
// // Permission Grants - Admin role için, sadece eksik olanları ekle
|
||||||
|
|
@ -439,11 +442,13 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
string name,
|
string name,
|
||||||
string parentName,
|
string parentName,
|
||||||
string displayName,
|
string displayName,
|
||||||
string modules)
|
string modules,
|
||||||
|
int order)
|
||||||
{
|
{
|
||||||
var permission = new PermissionDefinitionRecord(
|
var permission = new PermissionDefinitionRecord(
|
||||||
Guid.NewGuid(), groupName, name, parentName, displayName, true, MultiTenancySides.Both);
|
Guid.NewGuid(), groupName, name, parentName, displayName, true, MultiTenancySides.Both);
|
||||||
permission.SetModules(modules);
|
permission.SetModules(modules);
|
||||||
|
permission.SetPermissionOrder(order);
|
||||||
|
|
||||||
return await _repoPerm.InsertAsync(permission, autoSave: true);
|
return await _repoPerm.InsertAsync(permission, autoSave: true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.PermissionManagement;
|
using Volo.Abp.PermissionManagement;
|
||||||
|
|
@ -48,5 +49,20 @@ public static class AbpPermissionsExtensions
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Yetki ekranindaki sira (<see cref="PlatformConsts.Permissions.Order"/>) 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.
|
||||||
|
/// </summary>
|
||||||
|
public static int NextPermissionOrder(this IEnumerable<IHasExtraProperties> siblings)
|
||||||
|
{
|
||||||
|
return siblings.Select(s => s.GetProperty<int>(PlatformConsts.Permissions.Order)).DefaultIfEmpty(0).Max() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetPermissionOrder(this IHasExtraProperties record, int order)
|
||||||
|
{
|
||||||
|
record.SetProperty(PlatformConsts.Permissions.Order, order);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue