2026-08-17 05:56:52 +00:00
|
|
|
|
using System;
|
2026-05-02 18:43:32 +00:00
|
|
|
|
using System.Collections.Generic;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
using System.Data;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2026-08-17 05:56:52 +00:00
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Sozsoft.Languages.Entities;
|
|
|
|
|
|
using Sozsoft.Languages.Languages;
|
|
|
|
|
|
using Sozsoft.Platform.Entities;
|
|
|
|
|
|
using Sozsoft.Platform.Enums;
|
2026-08-31 14:53:40 +00:00
|
|
|
|
using Sozsoft.Platform.Extensions;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
using Sozsoft.Platform.ListForms;
|
|
|
|
|
|
using Volo.Abp.Data;
|
|
|
|
|
|
using Volo.Abp.DependencyInjection;
|
|
|
|
|
|
using Volo.Abp.Domain.Repositories;
|
|
|
|
|
|
using Volo.Abp.MultiTenancy;
|
|
|
|
|
|
using Volo.Abp.PermissionManagement;
|
|
|
|
|
|
using static Sozsoft.Platform.PlatformConsts;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Sozsoft.Platform.Data.Seeds;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-08-17 09:23:47 +00:00
|
|
|
|
/// Wizard ile oluşturulan konfigürasyonları Seeds/{host|tenants/{tenantId}}/wizard/*.json
|
|
|
|
|
|
/// dosyalarından okuyarak veritabanına aktarır (kapsam, seed edilen veritabanını izler).
|
2026-05-02 17:08:06 +00:00
|
|
|
|
/// Wizard çalıştıktan sonra oluşturulan JSON dosyaları projeye commit edildikten sonra,
|
|
|
|
|
|
/// veritabanı silinip yeniden oluşturulduğunda bu seeder tüm wizard konfigürasyonlarını geri yükler.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IRepository<LanguageKey, Guid> _repoLangKey;
|
|
|
|
|
|
private readonly IRepository<LanguageText, Guid> _repoLangText;
|
|
|
|
|
|
private readonly IRepository<PermissionGroupDefinitionRecord, Guid> _repoPermGroup;
|
|
|
|
|
|
private readonly IRepository<PermissionDefinitionRecord, Guid> _repoPerm;
|
|
|
|
|
|
private readonly IRepository<Menu, Guid> _repoMenu;
|
2026-08-31 14:17:50 +00:00
|
|
|
|
private readonly IRepository<Module, string> _repoModule;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
private readonly IRepository<DataSource, Guid> _repoDataSource;
|
|
|
|
|
|
private readonly IRepository<ListForm, Guid> _repoListForm;
|
|
|
|
|
|
private readonly IRepository<ListFormField, Guid> _repoListFormField;
|
2026-06-07 19:42:02 +00:00
|
|
|
|
private readonly IRepository<ListFormWorkflow, string> _repoListFormWorkflow;
|
2026-08-17 05:56:52 +00:00
|
|
|
|
private readonly IConfiguration _configuration;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
private readonly ILogger<WizardDataSeeder> _logger;
|
|
|
|
|
|
|
|
|
|
|
|
private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage;
|
|
|
|
|
|
private readonly string _appName = PlatformConsts.AppName;
|
|
|
|
|
|
|
|
|
|
|
|
public WizardDataSeeder(
|
|
|
|
|
|
IRepository<LanguageKey, Guid> repoLangKey,
|
|
|
|
|
|
IRepository<LanguageText, Guid> repoLangText,
|
|
|
|
|
|
IRepository<PermissionGroupDefinitionRecord, Guid> repoPermGroup,
|
|
|
|
|
|
IRepository<PermissionDefinitionRecord, Guid> repoPerm,
|
|
|
|
|
|
IRepository<Menu, Guid> repoMenu,
|
2026-08-31 14:17:50 +00:00
|
|
|
|
IRepository<Module, string> repoModule,
|
2026-05-02 17:08:06 +00:00
|
|
|
|
IRepository<DataSource, Guid> repoDataSource,
|
|
|
|
|
|
IRepository<ListForm, Guid> repoListForm,
|
|
|
|
|
|
IRepository<ListFormField, Guid> repoListFormField,
|
2026-06-07 19:42:02 +00:00
|
|
|
|
IRepository<ListFormWorkflow, string> repoListFormWorkflow,
|
2026-08-17 05:56:52 +00:00
|
|
|
|
IConfiguration configuration,
|
2026-05-02 17:08:06 +00:00
|
|
|
|
ILogger<WizardDataSeeder> logger)
|
|
|
|
|
|
{
|
|
|
|
|
|
_repoLangKey = repoLangKey;
|
|
|
|
|
|
_repoLangText = repoLangText;
|
|
|
|
|
|
_repoPermGroup = repoPermGroup;
|
|
|
|
|
|
_repoPerm = repoPerm;
|
|
|
|
|
|
_repoMenu = repoMenu;
|
2026-08-31 14:17:50 +00:00
|
|
|
|
_repoModule = repoModule;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
_repoDataSource = repoDataSource;
|
|
|
|
|
|
_repoListForm = repoListForm;
|
|
|
|
|
|
_repoListFormField = repoListFormField;
|
2026-06-07 19:42:02 +00:00
|
|
|
|
_repoListFormWorkflow = repoListFormWorkflow;
|
2026-08-17 05:56:52 +00:00
|
|
|
|
_configuration = configuration;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
_logger = logger;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task SeedAsync(DataSeedContext context)
|
|
|
|
|
|
{
|
2026-08-17 09:23:47 +00:00
|
|
|
|
var scopeFolderName = SeedPathResolver.GetScopeFolderName(context.TenantId);
|
|
|
|
|
|
var wizardDataPath = SeedPathResolver.GetScopePath(_configuration, context.TenantId, SeedPathResolver.WizardFolder);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
if (!Directory.Exists(wizardDataPath))
|
|
|
|
|
|
{
|
2026-08-17 09:23:47 +00:00
|
|
|
|
_logger.LogInformation("Seeds/{Scope}/wizard directory not found, skipping.", scopeFolderName);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
var jsonFiles = Directory.GetFiles(wizardDataPath, "*.json");
|
2026-05-02 17:08:06 +00:00
|
|
|
|
if (jsonFiles.Length == 0)
|
|
|
|
|
|
{
|
2026-08-17 09:23:47 +00:00
|
|
|
|
_logger.LogInformation("No JSON files found in Seeds/{Scope}/wizard directory, skipping.", scopeFolderName);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
// Dosya adi tarih tasimadigi icin uygulama sirasi dosyanin kendi damgasindan gelir;
|
|
|
|
|
|
// birbirine bagli wizard'lar (once Customers, sonra Tasks) uretildikleri sirayla uygulanir.
|
|
|
|
|
|
var seedFiles = new List<(string FilePath, WizardSeedFileDto Seed)>();
|
2026-05-02 17:08:06 +00:00
|
|
|
|
foreach (var filePath in jsonFiles)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-09-02 08:32:53 +00:00
|
|
|
|
var seed = WizardSeedFileDto.FromSeedJson(await File.ReadAllTextAsync(filePath));
|
|
|
|
|
|
if (seed?.Wizard == null)
|
2026-05-02 17:08:06 +00:00
|
|
|
|
{
|
2026-05-02 19:31:43 +00:00
|
|
|
|
_logger.LogWarning("Invalid file skipped: {FilePath}", filePath);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
seedFiles.Add((filePath, seed));
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, $"Error - {filePath}: {ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("WizardDataSeeder started. {Count} files to be processed.", seedFiles.Count);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var (filePath, seedFile) in seedFiles
|
|
|
|
|
|
.OrderBy(x => x.Seed.Wizard.SeededAt)
|
|
|
|
|
|
.ThenBy(x => Path.GetFileName(x.FilePath), StringComparer.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
var fileName = Path.GetFileName(filePath);
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-05-02 17:08:06 +00:00
|
|
|
|
var wizardName = seedFile.Wizard.WizardName?.Trim();
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(wizardName))
|
|
|
|
|
|
{
|
2026-05-02 19:31:43 +00:00
|
|
|
|
_logger.LogWarning("WizardName is empty, skipped: {FilePath}", filePath);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
var isCustomComponent = seedFile.Wizard.ComponentKind == WizardComponentKindEnum.Custom;
|
|
|
|
|
|
if (!isCustomComponent && seedFile.ListForm == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("[{File}] ListForm section is missing, skipped.", fileName);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-05-02 18:43:32 +00:00
|
|
|
|
|
2026-08-31 14:17:50 +00:00
|
|
|
|
await BackfillMenuParentModuleIdAsync(seedFile.Wizard);
|
2026-07-17 20:23:43 +00:00
|
|
|
|
|
2026-08-18 08:00:53 +00:00
|
|
|
|
// Zaten seeded mi kontrol et. Custom yolunda ListForm üretilmediği için
|
|
|
|
|
|
// idempotency menü kaydı üzerinden değerlendirilir.
|
2026-08-24 12:00:31 +00:00
|
|
|
|
// Menusuz Custom wizard'da ne ListForm ne de menu kaydi olustugu icin
|
|
|
|
|
|
// idempotency okuma permission'i uzerinden degerlendirilir.
|
2026-09-02 08:32:53 +00:00
|
|
|
|
var alreadySeeded = isCustomComponent
|
2026-08-24 12:00:31 +00:00
|
|
|
|
? seedFile.Wizard.CreateMenu
|
2026-09-02 08:32:53 +00:00
|
|
|
|
? await _repoMenu.AnyAsync(a => a.Code == seedFile.Wizard.ResolveMenuCode())
|
|
|
|
|
|
: await _repoPerm.AnyAsync(a => a.Name == seedFile.Wizard.ResolveMenuCode())
|
2026-08-18 08:00:53 +00:00
|
|
|
|
: await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode);
|
|
|
|
|
|
|
|
|
|
|
|
if (alreadySeeded)
|
2026-05-02 17:08:06 +00:00
|
|
|
|
{
|
2026-09-02 14:31:39 +00:00
|
|
|
|
// Ekran kurulu olsa da dil metinleri eksik kalmis olabilir: seed dosyasina yeni
|
|
|
|
|
|
// bir alan eklendiginde ya da ekran seeder disinda olustugunda sutun basliklari
|
|
|
|
|
|
// ham anahtar olarak gorunur. Mevcut metinlerin uzerine yazilmadigi icin bu
|
|
|
|
|
|
// tamamlama ekrani degistirmez.
|
|
|
|
|
|
await SeedLanguageTextsAsync(seedFile.Wizard, isCustomComponent);
|
|
|
|
|
|
_logger.LogInformation("[{File}] '{WizardName}' already exists, only missing language texts were created.", fileName, wizardName);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-02 19:31:43 +00:00
|
|
|
|
_logger.LogInformation("[{File}] '{WizardName}' is being applied...", fileName, wizardName);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
await ApplyWizardSeedAsync(seedFile);
|
2026-05-02 19:31:43 +00:00
|
|
|
|
_logger.LogInformation("[{File}] '{WizardName}' has been successfully applied.", fileName, wizardName);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-05-26 09:29:46 +00:00
|
|
|
|
_logger.LogError(ex, $"Error - {filePath}: {ex.Message}");
|
2026-05-02 17:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task ApplyWizardSeedAsync(WizardSeedFileDto seedFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
var input = seedFile.Wizard;
|
2026-09-02 08:32:53 +00:00
|
|
|
|
input.Groups ??= [];
|
|
|
|
|
|
|
|
|
|
|
|
var code = input.ResolveMenuCode();
|
|
|
|
|
|
var listFormCode = input.ResolveListFormCode();
|
2026-06-06 15:22:14 +00:00
|
|
|
|
var nameLangKey = code;
|
|
|
|
|
|
var permCreateName = $"{code}.Create";
|
|
|
|
|
|
var permUpdateName = $"{code}.Update";
|
|
|
|
|
|
var permDeleteName = $"{code}.Delete";
|
|
|
|
|
|
var permExportName = $"{code}.Export";
|
|
|
|
|
|
var permImportName = $"{code}.Import";
|
|
|
|
|
|
var permNoteName = $"{code}.Note";
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-18 08:00:53 +00:00
|
|
|
|
// Yol ayrımı: Custom yolunda ListForm ekosistemi üretilmez (AppService.Create ile aynı davranış).
|
|
|
|
|
|
var isCustomComponent = input.ComponentKind == WizardComponentKindEnum.Custom;
|
|
|
|
|
|
var customRoutePath = NormalizeSeedCustomRoutePath(input);
|
|
|
|
|
|
|
2026-05-02 17:08:06 +00:00
|
|
|
|
// Dil - Language Keys
|
2026-09-02 14:31:39 +00:00
|
|
|
|
await SeedLanguageTextsAsync(input, isCustomComponent);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-09-02 14:31:39 +00:00
|
|
|
|
// Permission Group - gorunen adin dil anahtari SeedLanguageTextsAsync icinde uretilir.
|
|
|
|
|
|
var groupName = ResolvePermissionGroupName(input);
|
2026-08-24 12:00:31 +00:00
|
|
|
|
input.PermissionGroupName = groupName;
|
2026-05-02 17:08:06 +00:00
|
|
|
|
if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName))
|
|
|
|
|
|
{
|
|
|
|
|
|
await _repoPermGroup.InsertAsync(
|
|
|
|
|
|
new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-02 18:43:32 +00:00
|
|
|
|
// Permissions - tek seferde mevcut permission'ları çek, sonra her birini kontrol et
|
2026-05-02 17:08:06 +00:00
|
|
|
|
var permQueryable = await _repoPerm.GetQueryableAsync();
|
|
|
|
|
|
var existingPerms = permQueryable.Where(a => a.GroupName == groupName).ToList();
|
|
|
|
|
|
|
2026-08-31 14:53:40 +00:00
|
|
|
|
// Yetkiler menuyle ayni modul filtresine tabidir; modul bos kalirsa rol ekraninda
|
|
|
|
|
|
// modul secimiyle filtrelenen listede gorunmez.
|
|
|
|
|
|
var permissionModules = input.MenuParentModuleId?.Trim();
|
|
|
|
|
|
|
2026-05-02 18:43:32 +00:00
|
|
|
|
var permRead = existingPerms.FirstOrDefault(a => a.Name == code);
|
2026-08-31 14:53:40 +00:00
|
|
|
|
permRead ??= await InsertPermissionAsync(groupName, code, null, nameLangKey, permissionModules);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-21 12:21:58 +00:00
|
|
|
|
// 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ış).
|
|
|
|
|
|
if (!existingPerms.Any(a => a.Name == permCreateName))
|
2026-08-31 14:53:40 +00:00
|
|
|
|
await InsertPermissionAsync(groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, permissionModules);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-21 12:21:58 +00:00
|
|
|
|
if (!existingPerms.Any(a => a.Name == permUpdateName))
|
2026-08-31 14:53:40 +00:00
|
|
|
|
await InsertPermissionAsync(groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, permissionModules);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-21 12:21:58 +00:00
|
|
|
|
if (!existingPerms.Any(a => a.Name == permDeleteName))
|
2026-08-31 14:53:40 +00:00
|
|
|
|
await InsertPermissionAsync(groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, permissionModules);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-21 12:21:58 +00:00
|
|
|
|
// Export/Import/Note yalnızca ListForm ekosisteminde karşılığı olan işlemlerdir.
|
|
|
|
|
|
if (!isCustomComponent)
|
|
|
|
|
|
{
|
2026-08-18 08:00:53 +00:00
|
|
|
|
if (!existingPerms.Any(a => a.Name == permExportName))
|
2026-08-31 14:53:40 +00:00
|
|
|
|
await InsertPermissionAsync(groupName, permExportName, permRead.Name, WizardConsts.LangKeyExport, permissionModules);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-18 08:00:53 +00:00
|
|
|
|
if (!existingPerms.Any(a => a.Name == permImportName))
|
2026-08-31 14:53:40 +00:00
|
|
|
|
await InsertPermissionAsync(groupName, permImportName, permRead.Name, WizardConsts.LangKeyImport, permissionModules);
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-18 08:00:53 +00:00
|
|
|
|
if (!existingPerms.Any(a => a.Name == permNoteName))
|
2026-08-31 14:53:40 +00:00
|
|
|
|
await InsertPermissionAsync(groupName, permNoteName, permRead.Name, WizardConsts.LangKeyNote, permissionModules);
|
2026-08-18 08:00:53 +00:00
|
|
|
|
}
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-05-02 19:31:43 +00:00
|
|
|
|
// // Permission Grants - Admin role için, sadece eksik olanları ekle
|
|
|
|
|
|
// var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
|
|
|
|
|
// var existingGrantNames = new HashSet<string>(existingGrants.Select(g => g.Name));
|
|
|
|
|
|
|
|
|
|
|
|
// var grantsToInsert = new[]
|
|
|
|
|
|
// {
|
|
|
|
|
|
// permRead.Name, permCreate.Name, permUpdate.Name,
|
|
|
|
|
|
// permDelete.Name, permExport.Name, permImport.Name, permNote.Name
|
|
|
|
|
|
// }
|
|
|
|
|
|
// .Where(name => !existingGrantNames.Contains(name))
|
|
|
|
|
|
// .Select(name => new PermissionGrant(Guid.NewGuid(), name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName))
|
|
|
|
|
|
// .ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// if (grantsToInsert.Count > 0)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true);
|
|
|
|
|
|
// }
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-24 12:00:31 +00:00
|
|
|
|
// Menunun acacagi adres; menu uretilmese de seed dosyasindaki degeri korunur.
|
|
|
|
|
|
input.MenuUrl = isCustomComponent
|
2026-08-18 08:00:53 +00:00
|
|
|
|
? customRoutePath
|
|
|
|
|
|
: !string.IsNullOrWhiteSpace(input.MenuUrl)
|
|
|
|
|
|
? input.MenuUrl.Trim()
|
|
|
|
|
|
: WizardConsts.MenuUrl(code);
|
2026-08-24 12:00:31 +00:00
|
|
|
|
|
|
|
|
|
|
// Menusuz wizard: menu (ve ust menu) kaydi hic uretilmez.
|
|
|
|
|
|
if (input.CreateMenu)
|
2026-05-02 17:08:06 +00:00
|
|
|
|
{
|
2026-08-24 12:00:31 +00:00
|
|
|
|
// Menu Parent
|
2026-08-31 14:17:50 +00:00
|
|
|
|
await EnsureModuleAsync(input.MenuParentModuleId);
|
|
|
|
|
|
|
2026-08-24 12:00:31 +00:00
|
|
|
|
var menuQueryable = await _repoMenu.GetQueryableAsync();
|
|
|
|
|
|
var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode);
|
|
|
|
|
|
if (menuParent == null)
|
2026-05-02 17:08:06 +00:00
|
|
|
|
{
|
2026-08-24 12:00:31 +00:00
|
|
|
|
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
|
|
|
|
|
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
|
|
|
|
|
|
? input.MenuParentIcon
|
|
|
|
|
|
: !string.IsNullOrWhiteSpace(input.MenuIcon)
|
|
|
|
|
|
? input.MenuIcon
|
|
|
|
|
|
: WizardConsts.MenuIcon;
|
|
|
|
|
|
menuParent = await _repoMenu.InsertAsync(new Menu
|
|
|
|
|
|
{
|
|
|
|
|
|
Code = input.MenuParentCode,
|
|
|
|
|
|
DisplayName = input.MenuParentCode,
|
|
|
|
|
|
IsDisabled = false,
|
2026-08-31 14:17:50 +00:00
|
|
|
|
ModuleId = input.MenuParentModuleId,
|
2026-08-24 12:00:31 +00:00
|
|
|
|
Icon = menuParentIcon,
|
|
|
|
|
|
Order = maxRootOrder + 1,
|
|
|
|
|
|
}, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Menu
|
|
|
|
|
|
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0;
|
|
|
|
|
|
var menuOrder = input.MenuOrder > 0 ? input.MenuOrder : maxChildOrder + 1;
|
|
|
|
|
|
input.MenuOrder = menuOrder;
|
|
|
|
|
|
var menuUrl = input.MenuUrl;
|
|
|
|
|
|
var existingMenu = await _repoMenu.FirstOrDefaultAsync(a => a.Code == code);
|
|
|
|
|
|
if (existingMenu == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _repoMenu.InsertAsync(new Menu
|
|
|
|
|
|
{
|
|
|
|
|
|
Code = code,
|
|
|
|
|
|
DisplayName = nameLangKey,
|
|
|
|
|
|
IsDisabled = false,
|
|
|
|
|
|
ParentCode = menuParent.Code,
|
|
|
|
|
|
Icon = input.MenuIcon ?? WizardConsts.MenuIcon,
|
|
|
|
|
|
Target = null,
|
|
|
|
|
|
ElementId = null,
|
|
|
|
|
|
CssClass = null,
|
|
|
|
|
|
Url = menuUrl,
|
|
|
|
|
|
RequiredPermissionName = permRead.Name,
|
|
|
|
|
|
Order = menuOrder,
|
2026-08-31 14:17:50 +00:00
|
|
|
|
ModuleId = menuParent.ModuleId,
|
2026-08-24 12:00:31 +00:00
|
|
|
|
}, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (existingMenu.Order != menuOrder || existingMenu.Url != menuUrl)
|
|
|
|
|
|
{
|
|
|
|
|
|
existingMenu.Order = menuOrder;
|
|
|
|
|
|
existingMenu.Url = menuUrl;
|
|
|
|
|
|
await _repoMenu.UpdateAsync(existingMenu, autoSave: true);
|
|
|
|
|
|
}
|
2026-07-29 06:11:09 +00:00
|
|
|
|
}
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-08-18 08:00:53 +00:00
|
|
|
|
// Custom yolunda ListForm ekosistemi üretilmez.
|
|
|
|
|
|
if (isCustomComponent)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2026-05-02 17:08:06 +00:00
|
|
|
|
// DataSource
|
2026-05-02 18:43:32 +00:00
|
|
|
|
var existingDataSource = await _repoDataSource.FirstOrDefaultAsync(a => a.Code == input.DataSourceCode);
|
|
|
|
|
|
if (existingDataSource == null)
|
2026-05-02 17:08:06 +00:00
|
|
|
|
{
|
|
|
|
|
|
await _repoDataSource.InsertAsync(new DataSource
|
|
|
|
|
|
{
|
|
|
|
|
|
Code = input.DataSourceCode,
|
|
|
|
|
|
DataSourceType = input.DataSourceConnectionString != null &&
|
|
|
|
|
|
input.DataSourceConnectionString.IndexOf("Server", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|
|
|
|
? DataSourceTypeEnum.Mssql
|
|
|
|
|
|
: DataSourceTypeEnum.Postgresql,
|
|
|
|
|
|
ConnectionString = input.DataSourceConnectionString
|
|
|
|
|
|
}, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
// ListForm ve alanlari - varsa sil, seed dosyasindaki sozlesmeden yeniden kur
|
2026-05-02 18:43:32 +00:00
|
|
|
|
var existingListForm = await _repoListForm.FirstOrDefaultAsync(a => a.ListFormCode == input.ListFormCode);
|
|
|
|
|
|
if (existingListForm != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _repoListForm.DeleteAsync(existingListForm, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var existingListFormFields = await _repoListFormField.GetListAsync(a => a.ListFormCode == input.ListFormCode);
|
|
|
|
|
|
if (existingListFormFields.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 19:42:02 +00:00
|
|
|
|
var existingWorkflowCriteria = await _repoListFormWorkflow.GetListAsync(a => a.ListFormCode == input.ListFormCode);
|
|
|
|
|
|
if (existingWorkflowCriteria.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _repoListFormWorkflow.DeleteManyAsync(existingWorkflowCriteria, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
// Ekran, seed dosyasinin tasidigi ListForm sozlesmesinden kurulur; boylece wizard
|
|
|
|
|
|
// sonrasinda tasarimci uzerinde yapilan degisiklikler de geri gelir.
|
|
|
|
|
|
var listForm = new ListForm();
|
|
|
|
|
|
seedFile.ListForm.ApplyTo(listForm);
|
|
|
|
|
|
await _repoListForm.InsertAsync(listForm, autoSave: true);
|
2026-06-09 13:58:58 +00:00
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
foreach (var fieldDto in seedFile.Fields)
|
2026-05-02 17:08:06 +00:00
|
|
|
|
{
|
2026-09-02 08:32:53 +00:00
|
|
|
|
var field = new ListFormField();
|
|
|
|
|
|
fieldDto.ApplyTo(field);
|
|
|
|
|
|
await _repoListFormField.InsertAsync(field, autoSave: true);
|
|
|
|
|
|
}
|
2026-05-02 17:08:06 +00:00
|
|
|
|
|
2026-09-02 08:32:53 +00:00
|
|
|
|
foreach (var criteria in seedFile.ListForm.WorkflowDto.Criteria)
|
2026-06-07 19:42:02 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(criteria.Id))
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("Workflow criteria skipped because Id is empty. ListFormCode: {ListFormCode}, Title: {Title}", input.ListFormCode, criteria.Title);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await _repoListFormWorkflow.InsertAsync(new ListFormWorkflow(criteria.Id)
|
|
|
|
|
|
{
|
|
|
|
|
|
ListFormCode = string.IsNullOrWhiteSpace(criteria.ListFormCode) ? input.ListFormCode : criteria.ListFormCode,
|
|
|
|
|
|
Kind = criteria.Kind,
|
|
|
|
|
|
Title = criteria.Title,
|
|
|
|
|
|
CompareColumn = criteria.CompareColumn,
|
|
|
|
|
|
CompareOperator = criteria.CompareOperator,
|
|
|
|
|
|
CompareValue = criteria.CompareValue,
|
|
|
|
|
|
Approver = criteria.Approver,
|
|
|
|
|
|
NextOnStart = criteria.NextOnStart,
|
|
|
|
|
|
NextOnTrue = criteria.NextOnTrue,
|
|
|
|
|
|
NextOnFalse = criteria.NextOnFalse,
|
|
|
|
|
|
NextOnApprove = criteria.NextOnApprove,
|
|
|
|
|
|
NextOnReject = criteria.NextOnReject,
|
|
|
|
|
|
PositionX = criteria.PositionX,
|
|
|
|
|
|
PositionY = criteria.PositionY,
|
|
|
|
|
|
CompareOutcomesJson = JsonSerializer.Serialize(criteria.CompareOutcomes ?? []),
|
|
|
|
|
|
}, autoSave: true);
|
|
|
|
|
|
}
|
2026-05-02 17:08:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-18 08:00:53 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Custom yolunda menunun acacagi rotayi normalize eder. Rota MenuUrl uzerinden
|
|
|
|
|
|
/// tasinir; bos ise menu hicbir yere gitmeyecegi icin seed dosyasi hatali kabul edilir.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static string NormalizeSeedCustomRoutePath(ListFormWizardDto input)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (input.ComponentKind != WizardComponentKindEnum.Custom)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
var routePath = input.MenuUrl?.Trim();
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(routePath))
|
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"'{input.WizardName}' custom component wizard'inda Route Path bos olamaz.");
|
|
|
|
|
|
|
|
|
|
|
|
return routePath.StartsWith('/') ? routePath : "/" + routePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-31 14:17:50 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Seed dosyasindaki modul kaydi yoksa olusturur; menunun ModuleId yabanci anahtari
|
|
|
|
|
|
/// tanimsiz bir module bagli kalmasin diye menu yazilmadan once cagrilir.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task EnsureModuleAsync(string moduleId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var cleaned = moduleId?.Trim();
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(cleaned))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (await _repoModule.AnyAsync(a => a.Id == cleaned))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
await _repoModule.InsertAsync(new Module(cleaned) { Name = cleaned }, autoSave: true);
|
|
|
|
|
|
_logger.LogInformation("Module '{ModuleId}' seed sirasinda olusturuldu.", cleaned);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-31 14:53:40 +00:00
|
|
|
|
private async Task<PermissionDefinitionRecord> InsertPermissionAsync(
|
|
|
|
|
|
string groupName,
|
|
|
|
|
|
string name,
|
|
|
|
|
|
string parentName,
|
|
|
|
|
|
string displayName,
|
|
|
|
|
|
string modules)
|
|
|
|
|
|
{
|
|
|
|
|
|
var permission = new PermissionDefinitionRecord(
|
|
|
|
|
|
Guid.NewGuid(), groupName, name, parentName, displayName, true, MultiTenancySides.Both);
|
|
|
|
|
|
permission.SetModules(modules);
|
|
|
|
|
|
|
|
|
|
|
|
return await _repoPerm.InsertAsync(permission, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-31 14:17:50 +00:00
|
|
|
|
private async Task BackfillMenuParentModuleIdAsync(ListFormWizardDto input)
|
2026-07-17 20:23:43 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input.MenuParentCode) ||
|
2026-08-31 14:17:50 +00:00
|
|
|
|
string.IsNullOrWhiteSpace(input.MenuParentModuleId))
|
2026-07-17 20:23:43 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
2026-08-31 14:17:50 +00:00
|
|
|
|
await EnsureModuleAsync(input.MenuParentModuleId);
|
|
|
|
|
|
|
2026-07-17 20:23:43 +00:00
|
|
|
|
var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode);
|
2026-08-31 14:17:50 +00:00
|
|
|
|
if (menuParent == null || !string.IsNullOrWhiteSpace(menuParent.ModuleId))
|
2026-07-17 20:23:43 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
2026-08-31 14:17:50 +00:00
|
|
|
|
menuParent.ModuleId = input.MenuParentModuleId.Trim();
|
2026-07-17 20:23:43 +00:00
|
|
|
|
await _repoMenu.UpdateAsync(menuParent, autoSave: true);
|
|
|
|
|
|
_logger.LogInformation(
|
2026-08-31 14:17:50 +00:00
|
|
|
|
"Menu parent '{MenuParentCode}' ModuleId backfilled as '{ModuleId}'.",
|
2026-07-17 20:23:43 +00:00
|
|
|
|
input.MenuParentCode,
|
2026-08-31 14:17:50 +00:00
|
|
|
|
menuParent.ModuleId);
|
2026-07-17 20:23:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-08 11:53:23 +00:00
|
|
|
|
|
2026-09-02 14:31:39 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Wizard'in urettigi ekranin dil metinleri: menu adi, ust menu adi, izin grubu gorunen adi,
|
|
|
|
|
|
/// ekran basligi/aciklamasi ve alan basliklari. Ekran kurulumundan bagimsiz calisir; boylece
|
|
|
|
|
|
/// zaten kurulu bir ekranda eksik kalan anahtarlar da her seed calismasinda tamamlanir.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task SeedLanguageTextsAsync(ListFormWizardDto input, bool isCustomComponent)
|
|
|
|
|
|
{
|
|
|
|
|
|
await CreateLangKeyAsync(input.ResolveMenuCode(), input.MenuEn, input.MenuTr);
|
|
|
|
|
|
|
|
|
|
|
|
// Ust menu ve izin grubu paylasilan kayitlardir: ilk wizard onlari uretmis olsa bile dil
|
|
|
|
|
|
// metinleri eksik kalabilir, bu yuzden menu/grup kaydinin varligindan bagimsiz yazilir.
|
|
|
|
|
|
if (input.CreateMenu)
|
|
|
|
|
|
await CreateLangKeyAsync(input.MenuParentCode, input.MenuParentEn, input.MenuParentTr);
|
|
|
|
|
|
|
|
|
|
|
|
var groupName = ResolvePermissionGroupName(input);
|
|
|
|
|
|
|
|
|
|
|
|
// Grup adi ust menu koduyla ayniysa anahtar tektir; menu adi ile izin grubu adi ayni
|
|
|
|
|
|
// metni gosterir, ayrica gorunen ad girilmediyse menu metinlerine duser.
|
|
|
|
|
|
if (!string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
await CreateLangKeyAsync(groupName, input.PermissionGroupEn?.Trim(), input.PermissionGroupTr?.Trim());
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await CreateLangKeyAsync(groupName,
|
|
|
|
|
|
input.PermissionGroupEn?.Trim() ?? input.MenuParentEn,
|
|
|
|
|
|
input.PermissionGroupTr?.Trim() ?? input.MenuParentTr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Custom yolunda ListForm ekosistemi (baslik, aciklama, alan basliklari) uretilmez.
|
|
|
|
|
|
if (isCustomComponent)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var listFormCode = input.ResolveListFormCode();
|
|
|
|
|
|
await CreateLangKeyAsync($"{listFormCode}.Title", input.TitleEn, input.TitleTr);
|
|
|
|
|
|
await CreateLangKeyAsync($"{listFormCode}.Desc", input.DescEn, input.DescTr);
|
|
|
|
|
|
|
|
|
|
|
|
// Alan basliklarinin iki dilli metinleri yalnizca wizard bolumunde tasinir.
|
|
|
|
|
|
foreach (var item in (input.Groups ?? []).SelectMany(g => g.Items ?? []))
|
|
|
|
|
|
{
|
|
|
|
|
|
await CreateLangKeyAsync(item.CaptionName, item.EnglishCaption, item.TurkishCaption);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Yetkilerin toplanacagi izin grubu adi. Menuye eklenmeyen wizard'da grup sorulmaz; bos
|
|
|
|
|
|
/// geldiginde varsayilan uygulama grubuna duser.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static string ResolvePermissionGroupName(ListFormWizardDto input) =>
|
|
|
|
|
|
string.IsNullOrWhiteSpace(input.PermissionGroupName) ? AppName : input.PermissionGroupName.Trim();
|
|
|
|
|
|
|
2026-05-02 17:08:06 +00:00
|
|
|
|
private async Task CreateLangKeyAsync(string key, string textEn, string textTr)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(key)) return;
|
|
|
|
|
|
|
|
|
|
|
|
var langKey = await _repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == _appName && a.Key == key)
|
|
|
|
|
|
?? await _repoLangKey.InsertAsync(new LanguageKey { ResourceName = _appName, Key = key }, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
var existingTexts = await _repoLangText.GetListAsync(a => a.ResourceName == _appName && a.Key == langKey.Key);
|
|
|
|
|
|
|
|
|
|
|
|
var existingEn = existingTexts.FirstOrDefault(a => a.CultureName == _cultureNameDefault);
|
|
|
|
|
|
if (existingEn == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _repoLangText.InsertAsync(new LanguageText
|
|
|
|
|
|
{
|
|
|
|
|
|
ResourceName = _appName,
|
|
|
|
|
|
Key = langKey.Key,
|
|
|
|
|
|
CultureName = _cultureNameDefault,
|
|
|
|
|
|
Value = textEn ?? key
|
|
|
|
|
|
}, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var existingTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr);
|
|
|
|
|
|
if (existingTr == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _repoLangText.InsertAsync(new LanguageText
|
|
|
|
|
|
{
|
|
|
|
|
|
ResourceName = _appName,
|
|
|
|
|
|
Key = langKey.Key,
|
|
|
|
|
|
CultureName = LanguageCodes.Tr,
|
|
|
|
|
|
Value = textTr ?? key
|
|
|
|
|
|
}, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|