Compare commits

...

33 commits
1.1.03 ... main

Author SHA1 Message Date
Sedat Öztürk
233c9b7502 Kullanıcı Detayları ve Avatar 2026-06-07 23:45:39 +03:00
Sedat Öztürk
12f046f262 Workflow ve WizardSeeder düzenlemeleri yapıldı. 2026-06-07 22:42:02 +03:00
Sedat Öztürk
d0cccde53f Workflow için IsFilterUserName özelliği eklendi. 2026-06-07 19:26:19 +03:00
Sedat Öztürk
1d15c44a3d Seçilenleri ve Tümünü Sil butonları 2026-06-07 14:07:48 +03:00
Sedat Öztürk
bade0bab98 Workflow düzeltmeleri 2026-06-07 10:52:28 +03:00
Sedat Öztürk
c204eef755 Workflow Listlerinin Note Özelliği eklendi 2026-06-07 01:22:35 +03:00
Sedat Öztürk
27e65f05f0 Wizard kısmında sütunların Popupformda görünüp görünmeyeceğini belirlenmesi 2026-06-06 22:52:56 +03:00
Sedat Öztürk
64084679e8 Workflow problemleri 2026-06-06 21:31:03 +03:00
Sedat Öztürk
2f1b9d4e77 Wizard problemleri giderildi. 2026-06-06 18:22:14 +03:00
Sedat Öztürk
1c472a7d9a AuditLog
SubForms kısmında ListFormCode dahil edildi
2026-06-05 22:05:57 +03:00
Sedat ÖZTÜRK
119c3650f0 Netdata dashboard kurulumu ve sunucu performansı 2026-06-05 17:22:02 +03:00
Sedat ÖZTÜRK
ebab6ea114 Wizard üzerindeki problemler giderildi. 2026-06-05 12:30:40 +03:00
Sedat ÖZTÜRK
975bc8dd6c intranet kaydırılma işlemi 2026-06-05 08:44:01 +03:00
Sedat Öztürk
97a2a4b38d Wizard üzerinden Entity eklenebiliyor 2026-06-04 23:25:19 +03:00
Sedat ÖZTÜRK
20e7fae481 Public tasarımları 2026-06-04 17:13:55 +03:00
Sedat ÖZTÜRK
6a5881960f Backend takılma problemi giderildi. 2026-06-04 15:08:52 +03:00
Sedat ÖZTÜRK
37d3065ed7 Genel düzenlemeler 2026-06-04 13:11:31 +03:00
Sedat ÖZTÜRK
648a63d618 Setting tanımlama 2026-06-04 11:26:07 +03:00
Sedat Öztürk
197c4e0741 Scriban Problemi ve User Details 2026-06-04 00:38:21 +03:00
Sedat ÖZTÜRK
921c3c6d35 AbpSetting kısmı ayarların uygulanması 2026-06-03 17:43:41 +03:00
Sedat ÖZTÜRK
f56eccee55 MailKit ve MimeKit Deploy problemi 2026-06-03 13:45:30 +03:00
Sedat Öztürk
fd5364ff97 Extend Login Request 2026-06-03 00:24:33 +03:00
Sedat Öztürk
a9e1a15183 Identity NomalizeName 2026-06-02 23:24:06 +03:00
Sedat Öztürk
d161e0f4b9 Tenantlı uygulama için ForgotPassword, ResetPassword 2026-06-02 23:04:25 +03:00
Sedat ÖZTÜRK
f9c5910813 Tenantlı uygulama için Login düzenlemesi 2026-06-02 21:47:01 +03:00
Sedat ÖZTÜRK
67286232da Dark Mode Devexpress uygun hale getirildi. 2026-06-02 12:17:31 +03:00
Sedat ÖZTÜRK
9875ba3041 Route -> Component Type sütunu eklendi.
Dynamic component type
2026-06-02 10:14:33 +03:00
Sedat Öztürk
daf0d51960 FormDevexpress, Grid ve Tree setReadOnly 2026-06-01 23:51:59 +03:00
Sedat ÖZTÜRK
5e6d2f518b FormDevexpress Default Value değerleri düzenlendi 2026-06-01 17:19:49 +03:00
Sedat ÖZTÜRK
2df3e359c4 setReadOnly metodu eklendi. 2026-06-01 16:47:38 +03:00
Sedat ÖZTÜRK
57dbb0d308 Home sayfası için Seeder 2026-06-01 13:26:04 +03:00
Sedat Öztürk
5806ff5f9f Editor Options Builder 2026-05-31 21:16:41 +03:00
Sedat Öztürk
e1c808310d Helper Codes güncellemeleri ve EditorScript düzenlemesi 2026-05-31 12:36:51 +03:00
140 changed files with 7291 additions and 3251 deletions

View file

@ -17,9 +17,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<PackageReference Include="Scriban" Version="7.2.3" />
</ItemGroup>
</Project>

View file

@ -12,9 +12,9 @@
<PrivateAssets>All</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<PackageReference Include="Scriban" Version="7.2.3" />
</ItemGroup>

View file

@ -31,6 +31,8 @@ public class MailTrackingManager : DomainService
/// <returns></returns>
public async Task StartAsync()
{
// https://us-east-1.console.aws.amazon.com/iam/home?region=eu-central-1#/users
// https://eu-central-1.console.aws.amazon.com/ses/home?region=eu-central-1#/identities/system%40sozsoft.com?tabId=authentication
var accessKey = configuration.GetValue<string>(AmazonSesEmailSettingNames.AccessKey);
var accessKeyId = configuration.GetValue<string>(AmazonSesEmailSettingNames.AccessKeyId);
var region = configuration.GetValue<string>(AmazonSesEmailSettingNames.Region);

View file

@ -28,9 +28,9 @@
<PackageReference Include="Volo.Abp.Ddd.Application" Version="10.0.0" />
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="10.0.0" />
<PackageReference Include="Volo.Abp.TextTemplating.Razor" Version="10.0.0" />
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<PackageReference Include="Scriban" Version="7.2.3" />
</ItemGroup>
<ItemGroup>

View file

@ -12,9 +12,9 @@
<PrivateAssets>All</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<PackageReference Include="Scriban" Version="7.2.3" />
</ItemGroup>

View file

@ -65,6 +65,7 @@ public class AmazonSesEmailSender : EmailSenderBase, ISozsoftEmailSender, ITrans
await BackgroundJobManager.EnqueueAsync(
new ErpBackgroundEmailSendingJobArgs
{
TenantId = CurrentTenant.Id,
To = to,
Sender = sender,
Params = @params,

View file

@ -1,5 +1,6 @@
using Volo.Abp.BackgroundJobs;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Sender.Mail;
@ -7,22 +8,28 @@ public class ErpBackgroundEmailSendingJob :
AsyncBackgroundJob<ErpBackgroundEmailSendingJobArgs>, ITransientDependency
{
protected ISozsoftEmailSender EmailSender { get; }
protected ICurrentTenant CurrentTenant { get; }
public ErpBackgroundEmailSendingJob(ISozsoftEmailSender emailSender)
public ErpBackgroundEmailSendingJob(
ISozsoftEmailSender emailSender,
ICurrentTenant currentTenant)
{
EmailSender = emailSender;
CurrentTenant = currentTenant;
}
public override async Task ExecuteAsync(ErpBackgroundEmailSendingJobArgs args)
{
//await EmailSender.SendEmailAsync(args.Template, args.To, args.Params, args.Subject);
await EmailSender.SendEmailAsync(
args.To,
args.Sender,
args.Params,
args.TextContent,
args.Subject,
args.Attachments);
using (CurrentTenant.Change(args.TenantId))
{
//await EmailSender.SendEmailAsync(args.Template, args.To, args.Params, args.Subject);
await EmailSender.SendEmailAsync(
args.To,
args.Sender,
args.Params,
args.TextContent,
args.Subject,
args.Attachments);
}
}
}

View file

@ -1,8 +1,12 @@
namespace Sozsoft.Sender.Mail;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Sender.Mail;
[Serializable]
public class ErpBackgroundEmailSendingJobArgs
public class ErpBackgroundEmailSendingJobArgs : IMultiTenant
{
public Guid? TenantId { get; set; }
public string[] To { get; set; }
public KeyValuePair<string, string>? Sender { get; set; }
@ -14,6 +18,4 @@ public class ErpBackgroundEmailSendingJobArgs
public Dictionary<string, string>? Attachments { get; set; }
public string? TextContent { get; set; }
}

View file

@ -12,9 +12,9 @@
<PackageReference Include="Volo.Abp.MailKit" Version="10.0.0" />
<PackageReference Include="Volo.Abp.Sms" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<PackageReference Include="Scriban" Version="7.2.3" />
</ItemGroup>
</Project>

View file

@ -5,10 +5,9 @@ using System.Threading.Tasks;
using Sozsoft.Languages;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.SettingManagement;
using Volo.Abp.Settings;
using SettingDefinition = Sozsoft.Settings.Entities.SettingDefinition;
namespace Sozsoft.Settings;
@ -20,17 +19,20 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService
private readonly ISettingDefinitionManager settingDefinitionManager;
private readonly ISettingManager settingManager;
private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager;
private readonly ICurrentTenant currentTenant;
public SettingUiAppService(
ILanguageKeyIntegrationService languageKeyIntegrationService,
ISettingDefinitionManager settingDefinitionManager,
ISettingManager settingManager,
ErpSettingDefinitionManager ErpSettingDefinitionManager)
ErpSettingDefinitionManager ErpSettingDefinitionManager,
ICurrentTenant currentTenant)
{
this.languageKeyIntegrationService = languageKeyIntegrationService;
this.settingDefinitionManager = settingDefinitionManager;
this.settingManager = settingManager;
this.ErpSettingDefinitionManager = ErpSettingDefinitionManager;
this.currentTenant = currentTenant;
}
public virtual async Task<List<MainGroupedSettingDto>> GetListAsync()
@ -95,15 +97,18 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService
{
if (setting.Providers.IsNullOrEmpty())
{
await settingManager.SetForCurrentUserAsync(setting.Name, value);
if (currentTenant.Id.HasValue)
{
await settingManager.SetForCurrentTenantAsync(setting.Name, value);
}
else
{
await settingManager.SetGlobalAsync(setting.Name, value);
}
}
else
{
if (setting.Providers.Any(p => p == UserSettingValueProvider.ProviderName))
{
await settingManager.SetForCurrentUserAsync(setting.Name, value);
}
else if (setting.Providers.Any(p => p == TenantSettingValueProvider.ProviderName))
if (setting.Providers.Any(p => p == TenantSettingValueProvider.ProviderName) && currentTenant.Id.HasValue)
{
await settingManager.SetForCurrentTenantAsync(setting.Name, value);
}
@ -111,6 +116,10 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService
{
await settingManager.SetGlobalAsync(setting.Name, value);
}
else if (setting.Providers.Any(p => p == UserSettingValueProvider.ProviderName))
{
await settingManager.SetForCurrentUserAsync(setting.Name, value);
}
}
}

View file

@ -28,22 +28,38 @@ public class SettingsDefinitionProvider : SettingDefinitionProvider
foreach (var item in settingDefinitions.OrderBy(a => a.Order))
{
var iDescription = item.DescriptionKey.IsNullOrEmpty() ? null : L(item.DescriptionKey);
var providers = item.Providers.IsNullOrEmpty()
? []
: item.Providers.Split(MultiValueDelimiter, StringSplitOptions.RemoveEmptyEntries);
var def = new Volo.Abp.Settings.SettingDefinition(
item.Code, item.DefaultValue, L(item.NameKey), iDescription,
item.IsVisibleToClients, item.IsInherited, item.IsEncrypted)
.WithProperty(SettingsConsts.MainGroup, item.MainGroupKey)
var def = context.GetOrNull(item.Code);
if (def == null)
{
def = new Volo.Abp.Settings.SettingDefinition(
item.Code, item.DefaultValue, L(item.NameKey), iDescription,
item.IsVisibleToClients, item.IsInherited, item.IsEncrypted);
context.Add(def);
}
else
{
def.DefaultValue = item.DefaultValue;
def.DisplayName = L(item.NameKey);
def.Description = iDescription;
def.IsVisibleToClients = item.IsVisibleToClients;
def.IsInherited = item.IsInherited;
def.IsEncrypted = item.IsEncrypted;
def.Properties.Clear();
def.Providers.Clear();
}
def.Providers.AddRange(providers);
def.WithProperty(SettingsConsts.MainGroup, item.MainGroupKey)
.WithProperty(SettingsConsts.SubGroup, item.SubGroupKey)
.WithProperty(SettingsConsts.DataType, item.DataType)
.WithProperty(SettingsConsts.RequiredPermission, item.RequiredPermissionName)
.WithProperty(SettingsConsts.SelectOptions, item.SelectOptions?.ToDictionary(x => x.Key, x => L(x.Value)));
if (!item.Providers.IsNullOrEmpty())
{
def.Providers.AddRange(item.Providers.Split(MultiValueDelimiter));
}
context.Add(def);
}
}

View file

@ -1,5 +1,6 @@
using Volo.Abp.Modularity;
using Volo.Abp.Modularity;
using Volo.Abp.SettingManagement;
using Volo.Abp.Settings;
namespace Sozsoft.Settings;
@ -11,7 +12,10 @@ public class SettingsDomainModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSettingOptions>(options =>
{
options.DefinitionProviders.Remove<SettingsDefinitionProvider>();
options.DefinitionProviders.Add<SettingsDefinitionProvider>();
});
}
}

View file

@ -12,9 +12,9 @@
<PrivateAssets>All</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<PackageReference Include="Scriban" Version="7.2.3" />
</ItemGroup>

View file

@ -12,9 +12,9 @@
<PrivateAssets>All</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
<PackageReference Include="MailKit" Version="4.16.0" />
<PackageReference Include="MimeKit" Version="4.16.0" />
<PackageReference Include="Scriban" Version="7.2.3" />
</ItemGroup>

View file

@ -0,0 +1,9 @@
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.AuditLogs;
public class AuditLogListRequestDto : PagedAndSortedResultRequestDto
{
public string ListFormCode { get; set; }
public string EntityId { get; set; }
}

View file

@ -0,0 +1,11 @@
using System;
using Volo.Abp.Content;
namespace Sozsoft.Platform.Identity.Dto;
public class UserAvatarUpdateInput
{
public Guid UserId { get; set; }
public IRemoteStreamContent Avatar { get; set; }
}

View file

@ -5,6 +5,7 @@ namespace Sozsoft.Platform.ListForms;
public class WorkflowDto
{
public string ApprovalUserFieldName { get; set; }
public bool IsFilterUserName { get; set; }
public string ApprovalDateFieldName { get; set; }
public string ApprovalStatusFieldName { get; set; }
public string ApprovalDescriptionFieldName { get; set; }

View file

@ -39,6 +39,7 @@ public class ListFormWizardDto
public string PermissionGroupName { get; set; }
public string MenuParentCode { get; set; }
public string MenuParentIcon { get; set; }
public string MenuIcon { get; set; }
public string DataSourceCode { get; set; }
public string DataSourceConnectionString { get; set; }

View file

@ -12,6 +12,7 @@ public class WizardColumnItemInputDto
public string EditorScript { get; set; }
public int ColSpan { get; set; } = 1;
public bool IsRequired { get; set; }
public bool IncludeInEditingForm { get; set; } = true;
public DbType DbSourceType { get; set; } = DbType.String;
public string TurkishCaption { get; set; }
public string EnglishCaption { get; set; }

View file

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace Sozsoft.Platform.ListForms;
public class WorkflowRunResultDto
@ -8,5 +10,6 @@ public class WorkflowRunResultDto
public string CurrentNodeKind { get; set; }
public bool WaitingApproval { get; set; }
public bool Completed { get; set; }
public List<string> ToastMessages { get; set; } = [];
}

View file

@ -7,6 +7,7 @@ public class RouteDto : EntityDto<Guid>
{
public string Key { get; set; }
public string Path { get; set; }
public string ComponentType { get; set; }
public string ComponentPath { get; set; }
public string RouteType { get; set; }
public string[] Authority { get; set; }

View file

@ -1,30 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.ListForms;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.AuditLogging;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow;
using static Sozsoft.Platform.Data.Seeds.SeedConsts;
namespace Sozsoft.Platform.AuditLogs;
public interface IAuditLogAppService
: ICrudAppService<AuditLogDto, Guid>
: ICrudAppService<AuditLogDto, Guid, AuditLogListRequestDto>
{
}
[Authorize(AppCodes.IdentityManagement.AuditLogs)]
public class AuditLogAppService
: CrudAppService<AuditLog, AuditLogDto, Guid>
, IAuditLogAppService
public class AuditLogAppService : CrudAppService<
AuditLog,
AuditLogDto,
Guid,
AuditLogListRequestDto>, IAuditLogAppService
{
public AuditLogAppService(IAuditLogRepository auditLogRepository) : base(auditLogRepository)
private readonly IRepository<ListForm, Guid> _listFormRepository;
public AuditLogAppService(
IAuditLogRepository auditLogRepository,
IRepository<ListForm, Guid> listFormRepository
) : base(auditLogRepository)
{
_listFormRepository = listFormRepository;
}
public override async Task<AuditLogDto> GetAsync(Guid id)
@ -35,27 +48,30 @@ public class AuditLogAppService
}
[UnitOfWork]
public override async Task<PagedResultDto<AuditLogDto>> GetListAsync(PagedAndSortedResultRequestDto input)
public override async Task<PagedResultDto<AuditLogDto>> GetListAsync(AuditLogListRequestDto input)
{
var query = await CreateFilteredQueryAsync(input);
var query = await Repository.WithDetailsAsync();
if (!input.ListFormCode.IsNullOrWhiteSpace())
{
var filterRules = await GetListFormFilterRulesAsync(input.ListFormCode);
query = ApplyAuditLogActionParametersFilter(query, filterRules, input.EntityId);
}
else if (!input.EntityId.IsNullOrWhiteSpace())
{
query = query.Where(a => a.Actions.Any(action => action.Parameters.Contains(input.EntityId)));
}
var totalCount = await AsyncExecuter.CountAsync(query);
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
// EntityChanges ile birlikte getir (N+1 query önlenir)
var auditLogRepository = (IAuditLogRepository)Repository;
var auditLogsWithDetails = await auditLogRepository.GetListAsync(
sorting: input.Sorting,
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount,
includeDetails: true
);
var auditLogsWithDetails = await AsyncExecuter.ToListAsync(query);
// Mapping tek seferde yap
var entityDtos = ObjectMapper.Map<List<AuditLog>, List<AuditLogDto>>(auditLogsWithDetails);
// EntityChangeCount'u doldur (artık EntityChanges yüklü)
foreach (var dto in entityDtos)
{
@ -69,6 +85,102 @@ public class AuditLogAppService
);
}
private async Task<List<AuditLogListFormFilterRule>> GetListFormFilterRulesAsync(string listFormCode)
{
var rules = new List<AuditLogListFormFilterRule>
{
new(listFormCode, null)
};
var listForm = await _listFormRepository.FirstOrDefaultAsync(a => a.ListFormCode == listFormCode);
if (listForm?.SubFormsJson.IsNullOrWhiteSpace() != false)
{
return rules;
}
try
{
var subForms = JsonSerializer.Deserialize<List<SubFormDto>>(listForm.SubFormsJson) ?? [];
foreach (var subForm in subForms.Where(a => !a.Code.IsNullOrWhiteSpace()))
{
var childFieldNames = subForm.Relation?
.Select(a => a.ChildFieldName)
.Where(a => !a.IsNullOrWhiteSpace())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList() ?? [];
rules.AddRange(childFieldNames.Select(childFieldName => new AuditLogListFormFilterRule(subForm.Code, childFieldName)));
}
}
catch (JsonException)
{
// Invalid subform JSON should not block audit log listing for the main form.
}
return rules
.DistinctBy(a => $"{a.ListFormCode}|{a.ChildFieldName}".ToLowerInvariant())
.ToList();
}
private static IQueryable<AuditLog> ApplyAuditLogActionParametersFilter(
IQueryable<AuditLog> query,
List<AuditLogListFormFilterRule> rules,
string entityId)
{
var validRules = rules
.Where(rule => !rule.ListFormCode.IsNullOrWhiteSpace())
.ToList();
if (validRules.Count == 0)
{
return query;
}
var auditLog = Expression.Parameter(typeof(AuditLog), "auditLog");
var action = Expression.Parameter(typeof(AuditLogAction), "action");
var parameters = Expression.Property(action, nameof(AuditLogAction.Parameters));
Expression actionBody = Expression.Constant(false);
foreach (var rule in validRules)
{
Expression ruleBody = Contains(parameters, rule.ListFormCode);
if (!entityId.IsNullOrWhiteSpace())
{
ruleBody = Expression.AndAlso(ruleBody, Contains(parameters, entityId));
}
if (!rule.ChildFieldName.IsNullOrWhiteSpace())
{
ruleBody = Expression.AndAlso(ruleBody, Contains(parameters, rule.ChildFieldName));
}
actionBody = Expression.OrElse(actionBody, ruleBody);
}
var actions = Expression.Property(auditLog, nameof(AuditLog.Actions));
var actionPredicate = Expression.Lambda<Func<AuditLogAction, bool>>(actionBody, action);
var anyCall = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.Any),
[typeof(AuditLogAction)],
actions,
actionPredicate);
var auditLogPredicate = Expression.Lambda<Func<AuditLog, bool>>(anyCall, auditLog);
return query.Where(auditLogPredicate);
}
private static MethodCallExpression Contains(MemberExpression source, string value)
{
return Expression.Call(
source,
nameof(string.Contains),
Type.EmptyTypes,
Expression.Constant(value));
}
private sealed record AuditLogListFormFilterRule(string ListFormCode, string? ChildFieldName);
// Audit Log kayitlarini gormek istiyoruz fakat degistirmek istemiyoruz
[RemoteService(IsEnabled = false)]
public override Task<AuditLogDto> CreateAsync(AuditLogDto input)

View file

@ -76,8 +76,7 @@ public class PlatformAccountAppService : AccountAppService, IAccountAppService
var SenderName = await settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName);
var SenderEmailAddress = await settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromAddress);
var url = await appUrlProvider.GetUrlAsync("MVC", PlatformConsts.Urls.UserDetail);
var userDetailUrl = $"{url}/{user.Id}";
var userDetailUrl = await GetUserDetailUrlAsync(user.Id);
var content = $@"My name is: {user.GetFullName()}.
Email Address: {user.Email}
@ -111,17 +110,16 @@ User Detail: {userDetailUrl}";
}
[Captcha]
public async Task SendExtendLoginRequestAsync(SendExtendLoginRequestInputDto input)
public async Task<bool> SendExtendLoginRequestAsync(SendExtendLoginRequestInputDto input)
{
var user = await UserManager.FindByEmailAsync(input.EmailAddress);
if (user == null)
{
return;
return false;
}
var userDetailUrl = await appUrlProvider.GetUrlAsync(PlatformConsts.React, PlatformConsts.Urls.UserDetail);
var content = $@"My name is: {user.GetFullName()}.
User Detail: {string.Format(userDetailUrl, user.Id)}";
User Detail: {await GetUserDetailUrlAsync(user.Id)}";
var recipient = await settingProvider.GetOrNullAsync(PlatformConsts.AbpSettings.SiteManagement.General.TimedLoginEmails);
if (!recipient.IsNullOrWhiteSpace())
@ -132,17 +130,23 @@ User Detail: {string.Format(userDetailUrl, user.Id)}";
null,
content,
subject: PlatformConsts.AppName + " : Extend Login Request");
return true;
}
return;
return false;
}
[Captcha]
public async Task SendAccountConfirmationCodeAsync(SendAccountConfirmationCodeInputDto input)
public async Task<bool> SendAccountConfirmationCodeAsync(SendAccountConfirmationCodeInputDto input)
{
var user = await UserManager.FindByEmailAsync(input.EmailAddress);
if (user != null)
await SendConfirmationCodeAsync(user);
if (user == null)
{
return false;
}
return await SendConfirmationCodeAsync(user);
}
public async Task<bool> VerifyAccountConfirmationCodeAsync(VerifyAccountConfirmationCodeInputDto input)
@ -203,6 +207,15 @@ To validate your account, please complete your profile by clicking (or copy-past
return true;
}
private async Task<string> GetUserDetailUrlAsync(Guid userId)
{
var url = await appUrlProvider.GetUrlAsync(PlatformConsts.React, PlatformConsts.Urls.UserDetail);
return url.Contains("{0}", StringComparison.Ordinal)
? string.Format(url, userId)
: $"{url.TrimEnd('/')}/{userId}";
}
}

View file

@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Sozsoft.Platform.BlobStoring;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Extensions;
using Sozsoft.Platform.Identity.Dto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Abstractions;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
@ -32,6 +34,7 @@ public class PlatformIdentityAppService : ApplicationService
public IRepository<WorkHour, Guid> workHourRepository { get; }
public IRepository<Department, Guid> departmentRepository { get; }
public IRepository<JobPosition, Guid> jobPositionRepository { get; }
public BlobManager BlobCdnManager { get; }
public PlatformIdentityAppService(
IIdentityUserAppService identityUserAppService,
@ -45,6 +48,7 @@ public class PlatformIdentityAppService : ApplicationService
IRepository<WorkHour, Guid> workHourRepository,
IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository,
BlobManager blobCdnManager,
IGuidGenerator guidGenerator
)
{
@ -55,6 +59,7 @@ public class PlatformIdentityAppService : ApplicationService
this.workHourRepository = workHourRepository;
this.departmentRepository = departmentRepository;
this.jobPositionRepository = jobPositionRepository;
this.BlobCdnManager = blobCdnManager;
this.permissionRepository = permissionRepository;
this.branchRepository = branchRepository;
this.branchUsersRepository = branchUsersRepository;
@ -273,6 +278,22 @@ public class PlatformIdentityAppService : ApplicationService
await UserManager.UpdateAsync(user);
}
[Authorize(IdentityPermissions.Users.Update)]
public async Task UpdateAvatarAsync([FromForm] UserAvatarUpdateInput input)
{
var user = await UserManager.GetByIdAsync(input.UserId);
var fileName = $"{user.Id}.jpg";
if (input.Avatar is null || input.Avatar.ContentLength == 0)
{
await BlobCdnManager.DeleteAsync(BlobContainerNames.Avatar, fileName);
}
else
{
await BlobCdnManager.SaveAsync(BlobContainerNames.Avatar, fileName, input.Avatar.GetStream());
}
}
public async Task<List<PermissionDefinitionRecord>> GetPermissionList()
{
var list = await permissionRepository.GetListAsync();

View file

@ -18,6 +18,7 @@ using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
namespace Sozsoft.Platform.Intranet;
@ -27,7 +28,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private readonly ICurrentTenant _currentTenant;
private readonly BlobManager _blobContainer;
private readonly IConfiguration _configuration;
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IRepository<Event, Guid> _eventRepository;
private readonly IIdentityUserAppService _identityUserAppService;
private readonly IIdentityUserRepository _identityUserRepository;
@ -49,7 +50,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
ICurrentTenant currentTenant,
BlobManager blobContainer,
IConfiguration configuration,
ILookupNormalizer lookupNormalizer,
IRepository<Event, Guid> eventRepository,
IIdentityUserAppService identityUserAppService,
IIdentityUserRepository identityUserRepository,
@ -71,6 +73,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
_currentTenant = currentTenant;
_blobContainer = blobContainer;
_configuration = configuration;
_lookupNormalizer = lookupNormalizer;
_eventRepository = eventRepository;
_identityUserAppService = identityUserAppService;
_identityUserRepository = identityUserRepository;
@ -114,12 +117,12 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
}
private UserInfoViewModel MapUserInfoViewModel(
IdentityUser user,
Volo.Abp.Identity.IdentityUser user,
IReadOnlyDictionary<Guid, string> departmentDict,
IReadOnlyDictionary<Guid, JobPosition> jobPositionDict)
{
return ObjectMapper
.Map<IdentityUser, UserInfoViewModel>(user)
.Map<Volo.Abp.Identity.IdentityUser, UserInfoViewModel>(user)
.MapDepartmentAndJobPositionAssignments(departmentDict, jobPositionDict);
}
@ -127,9 +130,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
IReadOnlyDictionary<Guid, string> departmentDict,
IReadOnlyDictionary<Guid, JobPosition> jobPositionDict)
{
var normalizedAdmin = PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue;
var user = await _identityUserRepository.FindByNormalizedUserNameAsync(normalizedAdmin)
?? await _identityUserRepository.FindByNormalizedEmailAsync(normalizedAdmin);
var normalizedAdmin = _lookupNormalizer.NormalizeName(PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
var user = await _identityUserRepository.FindByNormalizedUserNameAsync(normalizedAdmin);
if (user == null && CurrentUser.Id.HasValue)
{

View file

@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Sozsoft.Platform.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Identity;
using Volo.Abp.TenantManagement;
using Volo.Abp.Data;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace Sozsoft.Platform.ListForms.DynamicApi;
@ -18,79 +20,125 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
private readonly ITenantManager tenantManager;
private readonly IIdentityUserAppService identityUserAppService;
private readonly IIdentityRoleAppService identityRoleAppService;
private readonly IdentityUserManager userManager;
private readonly IOptions<IdentityOptions> identityOptions;
public ListFormDynamicApiAppService(
ITenantRepository tenantRepository,
ITenantManager tenantManager,
IIdentityUserAppService identityUserAppService,
IIdentityRoleAppService identityRoleAppService)
IIdentityRoleAppService identityRoleAppService,
IdentityUserManager userManager,
IOptions<IdentityOptions> identityOptions)
{
this.tenantRepository = tenantRepository;
this.tenantManager = tenantManager;
this.identityUserAppService = identityUserAppService;
this.identityRoleAppService = identityRoleAppService;
this.userManager = userManager;
this.identityOptions = identityOptions;
}
private static Guid ParseGuid(string value)
{
return Guid.TryParse(value, out var id) ? id : Guid.Empty;
}
[Authorize(IdentityPermissions.Users.Create)]
public async Task PostUserInsertAsync(DynamicApiBaseInput<CreateUpdateUserInput> input)
{
var user = new IdentityUserCreateDto
{
UserName = input.Data.Email,
Name = input.Data.Name,
Surname = input.Data.Surname,
Email = input.Data.Email,
PhoneNumber = input.Data.PhoneNumber,
IsActive = input.Data.IsActive ?? true,
LockoutEnabled = true,
Password = input.Data.Password,
RoleNames = [], //input.Data.RoleNames,
};
user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour);
user.SetProperty(PlatformConsts.AbpIdentity.User.DepartmentId, input.Data.DepartmentId);
user.SetProperty(PlatformConsts.AbpIdentity.User.JobPositionId, input.Data.JobPositionId);
await identityOptions.SetAsync();
await identityUserAppService.CreateAsync(user);
var verifySetting = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpIdentity.Profile.General.RequireVerifiedAccount);
var verify = !string.Equals(verifySetting, "true", StringComparison.OrdinalIgnoreCase);
var user = new IdentityUser(
GuidGenerator.Create(),
input.Data.Email,
input.Data.Email,
CurrentTenant.Id)
{
Name = input.Data.Name,
Surname = input.Data.Surname
};
user.SetIsActive(input.Data.IsActive ?? true);
user.SetPhoneNumber(input.Data.PhoneNumber, user.PhoneNumberConfirmed);
user.SetWorkHour(input.Data.WorkHour);
user.SetDepartmentId(ParseGuid(input.Data.DepartmentId));
user.SetJobPositionId(ParseGuid(input.Data.JobPositionId));
user.SetIsVerified(verify);
(await userManager.CreateAsync(user, input.Data.Password)).CheckErrors();
await userManager.SetLockoutEnabledAsync(user, true);
}
[Authorize(IdentityPermissions.Users.Update)]
public async Task PostUserUpdateAsync(DynamicApiBaseInput<CreateUpdateUserInput> input)
{
await identityOptions.SetAsync();
if (input.Keys.IsNullOrEmpty())
{
throw new UserFriendlyException(L["RecordNotFound"]);
}
var id = Guid.Parse(input.Keys[0]!.ToString()!);
var entity = await identityUserAppService.GetAsync(id) ?? throw new EntityNotFoundException(L["RecordNotFound"]);
var user = new IdentityUserUpdateDto
var user = await userManager.GetByIdAsync(id);
if (user == null)
{
UserName = input.Data.Email ?? entity.Email,
Email = input.Data.Email ?? entity.Email,
Name = input.Data.Name ?? entity.Name,
Surname = input.Data.Surname ?? entity.Surname,
PhoneNumber = input.Data.PhoneNumber ?? entity.PhoneNumber,
IsActive = input.Data.IsActive ?? entity.IsActive,
LockoutEnabled = input.Data.LockoutEnabled ?? entity.LockoutEnabled,
//RoleNames = input.Data.RoleNames ?? identity.RoleNames,
};
if (!input.Data.Password.IsNullOrWhiteSpace())
{
user.Password = input.Data.Password;
}
if (input.Data.WorkHour != null)
{
user.SetProperty(PlatformConsts.AbpIdentity.User.WorkHour, input.Data.WorkHour);
}
if (input.Data.DepartmentId != null)
{
user.SetProperty(PlatformConsts.AbpIdentity.User.DepartmentId, input.Data.DepartmentId);
}
if (input.Data.JobPositionId != null)
{
user.SetProperty(PlatformConsts.AbpIdentity.User.JobPositionId, input.Data.JobPositionId);
throw new EntityNotFoundException(L["RecordNotFound"]);
}
await identityUserAppService.UpdateAsync(id, user);
if (!input.Data.Email.IsNullOrWhiteSpace() && input.Data.Email != user.Email)
{
(await userManager.SetUserNameAsync(user, input.Data.Email)).CheckErrors();
(await userManager.SetEmailAsync(user, input.Data.Email)).CheckErrors();
}
user.Name = input.Data.Name ?? user.Name;
user.Surname = input.Data.Surname ?? user.Surname;
if (input.Data.PhoneNumber != null)
{
user.SetPhoneNumber(input.Data.PhoneNumber, user.PhoneNumberConfirmed);
}
if (input.Data.IsActive.HasValue)
{
user.SetIsActive(input.Data.IsActive.Value);
}
if (input.Data.LockoutEnabled.HasValue)
{
(await userManager.SetLockoutEnabledAsync(user, input.Data.LockoutEnabled.Value)).CheckErrors();
}
if (!input.Data.Password.IsNullOrWhiteSpace())
{
if (await userManager.HasPasswordAsync(user))
{
(await userManager.RemovePasswordAsync(user)).CheckErrors();
}
(await userManager.AddPasswordAsync(user, input.Data.Password)).CheckErrors();
}
if (input.Data.WorkHour != null)
{
user.SetWorkHour(input.Data.WorkHour);
}
if (input.Data.DepartmentId != null)
{
user.SetDepartmentId(ParseGuid(input.Data.DepartmentId));
}
if (input.Data.JobPositionId != null)
{
user.SetJobPositionId(ParseGuid(input.Data.JobPositionId));
}
(await userManager.UpdateAsync(user)).CheckErrors();
}
//RoleAppService
@ -197,6 +245,5 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic
var id = Guid.Parse(input.Keys[0]!.ToString()!);
await tenantRepository.DeleteAsync(id);
}
}

View file

@ -12,8 +12,6 @@ using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement;
using Volo.Abp.Uow;
using static Sozsoft.Platform.PlatformConsts;
using System.Data;
using Microsoft.Extensions.Hosting;
using Sozsoft.Languages;
using Sozsoft.Platform.DynamicData;
@ -60,10 +58,21 @@ public class ListFormWizardAppService(
public async Task Create(ListFormWizardDto input)
{
var wizardName = input.WizardName.Trim();
var titleLangKey = WizardConsts.WizardKeyTitle(wizardName);
var nameLangKey = WizardConsts.WizardKey(wizardName);
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
var code = WizardConsts.WizardKey(wizardName);
var code = string.IsNullOrWhiteSpace(input.MenuCode)
? WizardConsts.WizardKey(wizardName)
: input.MenuCode.Trim();
var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode)
? code
: input.ListFormCode.Trim();
var titleLangKey = $"{listFormCode}.Title";
var nameLangKey = code;
var descLangKey = $"{listFormCode}.Desc";
var permCreateName = $"{code}.Create";
var permUpdateName = $"{code}.Update";
var permDeleteName = $"{code}.Delete";
var permExportName = $"{code}.Export";
var permImportName = $"{code}.Import";
var permNoteName = $"{code}.Note";
// Eklenen kayıtları takip et (silme işleminde kullanılır)
var inserted = new WizardInsertedRecordsDto();
@ -78,16 +87,15 @@ public class ListFormWizardAppService(
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
{
await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false);
await CreateLangKey(groupName, groupName, groupName, inserted);
if (string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase))
await EnsureLangKey(groupName, inserted);
else
await CreateLangKey(groupName, groupName, groupName, inserted);
inserted.PermissionGroupNames.Add(groupName);
}
// Permission'ları tek seferde kontrol et ve oluştur
var queryable = await repoPerm.GetQueryableAsync();
var existingPerms = await AsyncExecuter.ToListAsync(
queryable.Where(a => a.GroupName == groupName)
);
var existingPerms = await repoPerm.GetListAsync(a => a.GroupName == groupName);
var permRead = existingPerms.FirstOrDefault(a => a.Name == code);
if (permRead == null)
{
@ -95,45 +103,45 @@ public class ListFormWizardAppService(
inserted.PermissionNames.Add(permRead.Name);
}
var permCreate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermCreate(wizardName));
var permCreate = existingPerms.FirstOrDefault(a => a.Name == permCreateName);
if (permCreate == null)
{
permCreate = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermCreate(wizardName), permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: false);
permCreate = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: false);
inserted.PermissionNames.Add(permCreate.Name);
}
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermUpdate(wizardName));
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == permUpdateName);
if (permUpdate == null)
{
permUpdate = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermUpdate(wizardName), permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: false);
permUpdate = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: false);
inserted.PermissionNames.Add(permUpdate.Name);
}
var permDelete = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermDelete(wizardName));
var permDelete = existingPerms.FirstOrDefault(a => a.Name == permDeleteName);
if (permDelete == null)
{
permDelete = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermDelete(wizardName), permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: false);
permDelete = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: false);
inserted.PermissionNames.Add(permDelete.Name);
}
var permExport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermExport(wizardName));
var permExport = existingPerms.FirstOrDefault(a => a.Name == permExportName);
if (permExport == null)
{
permExport = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermExport(wizardName), permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: false);
permExport = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, permExportName, permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: false);
inserted.PermissionNames.Add(permExport.Name);
}
var permImport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermImport(wizardName));
var permImport = existingPerms.FirstOrDefault(a => a.Name == permImportName);
if (permImport == null)
{
permImport = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermImport(wizardName), permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: false);
permImport = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, permImportName, permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: false);
inserted.PermissionNames.Add(permImport.Name);
}
var permNote = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermNote(wizardName));
var permNote = existingPerms.FirstOrDefault(a => a.Name == permNoteName);
if (permNote == null)
{
permNote = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: false);
permNote = await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, permNoteName, permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: false);
inserted.PermissionNames.Add(permNote.Name);
}
@ -161,12 +169,18 @@ public class ListFormWizardAppService(
if (menuParent == null)
{
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
await CreateLangKey(WizardConsts.WizardKeyParent(wizardName), input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr, inserted);
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
? input.MenuParentIcon
: !string.IsNullOrWhiteSpace(input.MenuIcon)
? input.MenuIcon
: WizardConsts.MenuIcon;
await CreateLangKey(input.MenuParentCode, input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr, inserted);
menuParent = await repoMenu.InsertAsync(new Menu
{
Code = input.MenuParentCode,
DisplayName = WizardConsts.WizardKeyParent(wizardName),
DisplayName = input.MenuParentCode,
IsDisabled = false,
Icon = menuParentIcon,
Order = maxRootOrder + 1,
}, autoSave: false);
inserted.MenuCodes.Add(input.MenuParentCode);
@ -218,7 +232,7 @@ public class ListFormWizardAppService(
ColSpan = g.ColCount,
ItemType = "group",
Items = g.Items
.Where(i => i.DataField != input.KeyFieldName)
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
.Select((it, ii) => new EditingFormItemDto
{
Order = ii + 1,
@ -231,6 +245,7 @@ public class ListFormWizardAppService(
})
.ToArray()
})
.Where(g => g.Items.Length > 0)
.ToList();
//ListForm - varsa sil, yeniden ekle
@ -257,14 +272,15 @@ public class ListFormWizardAppService(
var isCreated = tableColumns.Contains("CreatorId");
input.Workflow ??= new WorkflowDto();
input.Workflow.Criteria = input.WorkflowCriteria;
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria);
var listForm = await repoListForm.InsertAsync(new ListForm
await repoListForm.InsertAsync(new ListForm
{
ListFormType = ListFormTypeEnum.List,
PageSize = 10,
ExportJson = WizardConsts.DefaultExportJson,
IsSubForm = false,
ShowNote = input.SubForms.Count > 0,
ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0,
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
CultureName = LanguageCodes.En,
ListFormCode = input.ListFormCode,
@ -280,12 +296,12 @@ public class ListFormWizardAppService(
KeyFieldName = input.KeyFieldName,
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null,
SortMode = GridOptions.SortModeSingle,
SortMode = PlatformConsts.GridOptions.SortModeSingle,
FilterRowJson = WizardConsts.DefaultFilterRowJson,
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.WorkflowCriteria.Count > 0 ? PlatformConsts.GridOptions.SelectionModeSingle : PlatformConsts.GridOptions.SelectionModeNone),
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
PermissionJson = WizardConsts.DefaultPermissionJson(code),
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
@ -395,13 +411,65 @@ public class ListFormWizardAppService(
{
return workflow != null && (
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
!string.IsNullOrWhiteSpace(workflow.ApprovalDateFieldName) ||
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
!string.IsNullOrWhiteSpace(workflow.ApprovalDescriptionFieldName) ||
criteria.Count > 0
);
}
private static void EnsureUniqueWorkflowCriteriaTitles(List<ListFormWorkflowCriteriaDto> criteria)
{
if (criteria == null || criteria.Count == 0)
{
return;
}
var baseTitles = criteria
.Select(x => string.IsNullOrWhiteSpace(x.Title) ? NormalizeWorkflowTitleFallback(x.Kind) : x.Title.Trim())
.ToList();
var duplicateTitles = baseTitles
.GroupBy(x => x, StringComparer.OrdinalIgnoreCase)
.Where(x => x.Count() > 1)
.Select(x => x.Key)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var titleCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var usedTitles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in criteria)
{
var baseTitle = string.IsNullOrWhiteSpace(item.Title) ? NormalizeWorkflowTitleFallback(item.Kind) : item.Title.Trim();
var title = baseTitle;
if (duplicateTitles.Contains(baseTitle))
{
titleCounts.TryGetValue(baseTitle, out var count);
count++;
titleCounts[baseTitle] = count;
title = $"{baseTitle}{count}";
}
if (usedTitles.Contains(title))
{
var index = 1;
var candidate = $"{title}{index}";
while (usedTitles.Contains(candidate))
{
index++;
candidate = $"{title}{index}";
}
title = candidate;
}
item.Title = title;
usedTitles.Add(title);
}
}
private static string NormalizeWorkflowTitleFallback(string kind)
{
return string.IsNullOrWhiteSpace(kind) ? "Step" : kind.Trim();
}
/// <summary>
/// Wizard konfigürasyonunu JSON dosyası olarak kaydeder.
/// Önce ContentRootPath'ten yukarı çıkarak Sozsoft.Platform.DbMigrator/Seeds/WizardData dizinini arar.
@ -677,4 +745,16 @@ public class ListFormWizardAppService(
return existing;
}
private async Task<LanguageKey> EnsureLangKey(string key, WizardInsertedRecordsDto inserted = null)
{
var res = PlatformConsts.AppName;
var existing = await repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == res && a.Key == key);
if (existing != null) return existing;
existing = await repoLangKey.InsertAsync(new LanguageKey { ResourceName = res, Key = key }, autoSave: true);
inserted?.LanguageKeys.Add(key);
return existing;
}
}

View file

@ -7,10 +7,12 @@ using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Sozsoft.Platform.Data.Seeds;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.ListForms.Select;
using Sozsoft.Platform.Localization;
using Sozsoft.Platform.Queries;
using Sozsoft.Sender.Mail;
using Volo.Abp;
@ -29,6 +31,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
private const string SystemApprovalDescription = "Sistem tarafından otomatik olarak onaylandı.";
private readonly IRepository<ListFormWorkflow, string> criteriaRepository;
private readonly IRepository<Note, Guid> noteRepository;
private readonly IListFormManager listFormManager;
private readonly IListFormAuthorizationManager authManager;
private readonly IListFormSelectAppService listFormSelectAppService;
@ -36,18 +39,22 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
private readonly IdentityUserManager identityUserManager;
private readonly ISozsoftEmailSender erpEmailSender;
private readonly ISettingProvider settingProvider;
private readonly IStringLocalizer<PlatformResource> localizer;
public ListFormWorkflowAppService(
IRepository<ListFormWorkflow, string> criteriaRepository,
IRepository<Note, Guid> noteRepository,
IListFormManager listFormManager,
IListFormAuthorizationManager authManager,
IListFormSelectAppService listFormSelectAppService,
IQueryManager queryManager,
IdentityUserManager identityUserManager,
ISozsoftEmailSender erpEmailSender,
ISettingProvider settingProvider)
ISettingProvider settingProvider,
IStringLocalizer<PlatformResource> localizer)
{
this.criteriaRepository = criteriaRepository;
this.noteRepository = noteRepository;
this.listFormManager = listFormManager;
this.authManager = authManager;
this.listFormSelectAppService = listFormSelectAppService;
@ -55,6 +62,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
this.identityUserManager = identityUserManager;
this.erpEmailSender = erpEmailSender;
this.settingProvider = settingProvider;
this.localizer = localizer;
}
[HttpGet("criteria")]
@ -188,7 +196,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
criteria.ListFormCode = code;
criteria.Kind = NormalizeRequired(input.Kind, "Compare");
criteria.Title = NormalizeRequired(input.Title, criteria.Kind);
criteria.Title = await NormalizeUniqueTitleAsync(code, criteria.Id, input.Kind, input.Title);
criteria.CompareColumn = NormalizeRequired(input.CompareColumn, "Price");
criteria.CompareOperator = NormalizeRequired(input.CompareOperator, ">");
criteria.CompareValue = input.CompareValue;
@ -300,7 +308,14 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start")
?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı.");
return await RunUntilWaitAsync(context, start);
context.WorkflowNoteRows.Add(("Started By: ", ResolveCurrentUserDisplayName()));
var result = await RunUntilWaitAsync(context, start);
await InsertWorkflowNoteAsync(
context,
$"Workflow Started: {start.Title}",
BuildWorkflowNoteContent(context.WorkflowNoteRows));
return result;
}
[HttpPost("decision")]
@ -365,7 +380,15 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
}
var next = FindNextCriteria(context.Criteria, input.Approved ? current.NextOnApprove : current.NextOnReject);
return await RunUntilWaitAsync(context, next);
context.WorkflowNoteRows.Add(("Decision By: ", ResolveCurrentUserDisplayName()));
AddWorkflowDecisionRows(context, current, input.Approved, input.Note ?? string.Empty);
var result = await RunUntilWaitAsync(context, next);
await InsertWorkflowNoteAsync(
context,
$"Workflow {(input.Approved ? "Approved" : "Rejected")}: {current.Title}",
BuildWorkflowNoteContent(context.WorkflowNoteRows));
return result;
}
private async Task<WorkflowRunResultDto> RunForEachKeyAsync(
@ -387,7 +410,10 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
CurrentNodeTitle = last?.CurrentNodeTitle,
CurrentNodeKind = last?.CurrentNodeKind,
WaitingApproval = results.Any(x => x.WaitingApproval),
Completed = results.All(x => x.Completed)
Completed = results.All(x => x.Completed),
ToastMessages = results
.SelectMany(result => result.ToastMessages ?? [])
.ToList()
};
}
@ -419,7 +445,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
.ToList();
var row = await GetRowAsync(code, listForm.KeyFieldName, keys[0]);
return new WorkflowRunContext(code, listForm.KeyFieldName, keys, workflow, criteria, row);
return new WorkflowRunContext(code, keys, workflow, criteria, row);
}
private async Task<IDictionary<string, object>> GetRowAsync(string listFormCode, string keyFieldName, object key)
@ -511,6 +537,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
await UpdateRowAsync(context, update);
MergeRowValues(context.Row, update);
AddWorkflowNodeRows(context, node);
AddWorkflowToastMessage(context, node);
if (node.Kind == "Inform")
{
@ -533,7 +561,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
recipientEmail,
sender,
new { },
BuildInformEmailBody(context, node),
BuildInformEmailBody(context, node, await BuildPreviousWorkflowNotesHtmlAsync(context)),
$"Workflow Bilgilendirme: {node.Title}",
null,
true);
@ -542,6 +570,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
{
throw new UserFriendlyException($"Bilgilendirme maili gonderilemedi: {result.ErrorMessage}");
}
}
private async Task<string> ResolveApproverEmailAsync(string approver)
@ -565,22 +594,257 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
return user.Email;
}
private static string BuildInformEmailBody(WorkflowRunContext context, ListFormWorkflow node)
private async Task<string> BuildPreviousWorkflowNotesHtmlAsync(WorkflowRunContext context)
{
var key = context.Keys?.FirstOrDefault()?.ToString();
if (key.IsNullOrWhiteSpace())
{
return string.Empty;
}
var notes = (await noteRepository.GetListAsync(note =>
note.EntityName == context.ListFormCode &&
note.EntityId == key &&
note.Type == "workflow"))
.OrderBy(note => note.CreationTime)
.ToList();
if (notes.Count == 0)
{
return string.Empty;
}
var noteItems = notes.Select(note =>
$"""
<div style="margin: 0 0 12px 0; padding: 10px 12px; border: 1px solid #e5e7eb; border-radius: 6px;">
<div style="font-weight: 600; margin-bottom: 6px;">{Encode(note.Subject)}</div>
<div>{note.Content}</div>
</div>
""");
return string.Join(string.Empty, noteItems);
}
private static string BuildInformEmailBody(
WorkflowRunContext context,
ListFormWorkflow node,
string previousWorkflowNotesHtml)
{
var keyText = string.Join(", ", context.Keys.Select(key => WebUtility.HtmlEncode(key?.ToString() ?? string.Empty)));
var listFormCode = WebUtility.HtmlEncode(context.ListFormCode ?? string.Empty);
var nodeTitle = WebUtility.HtmlEncode(node.Title ?? string.Empty);
var recipient = WebUtility.HtmlEncode(node.Approver ?? string.Empty);
var processRows = BuildWorkflowNoteContent(context.WorkflowNoteRows);
var previousNotesSection = previousWorkflowNotesHtml.IsNullOrWhiteSpace()
? string.Empty
: $"""
<h3 style="margin: 18px 0 8px 0;">Önceki Workflow Notları</h3>
{previousWorkflowNotesHtml}
""";
return $"""
<p>Workflow bilgilendirme adimina ulasildi.</p>
<table>
<tr><td><strong>Liste Formu</strong></td><td>{listFormCode}</td></tr>
<tr><td><strong>Adim</strong></td><td>{nodeTitle}</td></tr>
<tr><td><strong>Kayit</strong></td><td>{keyText}</td></tr>
</table>
<div style="font-family: Arial, sans-serif; color: #111827; line-height: 1.45;">
<p>Workflow sürecinde bilgilendirme adımına ulaşıldı.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 16px;">
<tr><td style="padding: 6px 8px;"><strong>Liste Formu</strong></td><td style="padding: 6px 8px;">{listFormCode}</td></tr>
<tr><td style="padding: 6px 8px;"><strong>Kayıt</strong></td><td style="padding: 6px 8px;">{keyText}</td></tr>
<tr><td style="padding: 6px 8px;"><strong>Bilgilendirme Adımı</strong></td><td style="padding: 6px 8px;">{nodeTitle}</td></tr>
<tr><td style="padding: 6px 8px;"><strong>Alıcı</strong></td><td style="padding: 6px 8px;">{recipient}</td></tr>
</table>
<h3 style="margin: 18px 0 8px 0;">Bu İşlemdeki Süreç Özeti</h3>
{processRows}
{previousNotesSection}
</div>
""";
}
private string ResolveCurrentUserDisplayName()
{
return CurrentUser.UserName
?? CurrentUser.Name
?? CurrentUser.Id?.ToString()
?? "System";
}
private static void AddWorkflowDecisionRows(
WorkflowRunContext context,
ListFormWorkflow current,
bool approved,
string description)
{
var action = approved ? "Approved" : "Rejected";
context.WorkflowNoteRows.Add(("Decision: ", $"{action}: {FormatNode(current)}"));
context.WorkflowNoteRows.Add(("Description: ", description ?? string.Empty));
}
private static void AddWorkflowNodeRows(
WorkflowRunContext context,
ListFormWorkflow node)
{
var action = node.Kind switch
{
"Start" => "Started",
"Compare" => "Evaluated",
"Approval" => "Waiting Approval",
"Inform" => "Informed",
"End" => "Completed",
_ => "Processed"
};
context.WorkflowNoteRows.Add(($"{action}: ", $"{FormatNode(node)}"));
if (!node.Approver.IsNullOrWhiteSpace())
{
context.WorkflowNoteRows.Add((node.Kind == "Inform" ? "Inform: " : "Approver: ", node.Approver));
}
}
private void AddWorkflowToastMessage(
WorkflowRunContext context,
ListFormWorkflow node)
{
var userName = ResolveCurrentUserDisplayName();
var next = FindNextToastNode(context, node);
var messageLines = node.Kind switch
{
"Start" => new[]
{
localizer["ListForms.ListForm.Workflow.WorkflowStarted"].Value,
localizer["ListForms.ListForm.Workflow.Step", node.Title].Value,
localizer["ListForms.ListForm.Workflow.PerformedBy", userName].Value
}.Concat(FormatNextToastNode(next)),
"Inform" => new[]
{
localizer["ListForms.ListForm.Workflow.InformReached"].Value,
localizer["ListForms.ListForm.Workflow.Step", node.Title].Value,
localizer["ListForms.ListForm.Workflow.InformUser", FormatToastUser(node.Approver)].Value
}.Concat(FormatNextToastNode(next)),
"End" => new[]
{
localizer["ListForms.ListForm.Workflow.WorkflowCompleted"].Value,
localizer["ListForms.ListForm.Workflow.Step", node.Title].Value,
localizer["ListForms.ListForm.Workflow.PerformedBy", userName].Value
},
_ => null
};
if (messageLines != null)
{
context.ToastMessages.Add(string.Join(Environment.NewLine, messageLines));
}
}
private ListFormWorkflow FindNextToastNode(
WorkflowRunContext context,
ListFormWorkflow node)
{
var current = FindNextCriteria(context.Criteria, ResolveNextNodeId(context, node));
var visited = new HashSet<string>();
while (current != null && visited.Add(current.Id))
{
if (current.Kind is "Approval" or "Inform" or "End")
{
return current;
}
current = FindNextCriteria(context.Criteria, ResolveNextNodeId(context, current));
}
return null;
}
private IEnumerable<string> FormatNextToastNode(ListFormWorkflow node)
{
if (node == null)
{
return [];
}
return node.Kind switch
{
"Approval" =>
[
localizer["ListForms.ListForm.Workflow.NextStep.Approval"].Value,
localizer["ListForms.ListForm.Workflow.NextStepName", node.Title].Value,
localizer["ListForms.ListForm.Workflow.ApproverUser", FormatToastUser(node.Approver)].Value
],
"Inform" =>
[
localizer["ListForms.ListForm.Workflow.NextStep.Inform"].Value,
localizer["ListForms.ListForm.Workflow.NextStepName", node.Title].Value,
localizer["ListForms.ListForm.Workflow.InformUser", FormatToastUser(node.Approver)].Value
],
"End" =>
[
localizer["ListForms.ListForm.Workflow.NextStep.End"].Value,
localizer["ListForms.ListForm.Workflow.NextStepName", node.Title].Value
],
_ => [localizer["ListForms.ListForm.Workflow.NextStep", node.Title].Value]
};
}
private string FormatToastUser(string userName)
{
return userName.IsNullOrWhiteSpace()
? localizer["ListForms.ListForm.Workflow.UndefinedUser"].Value
: userName;
}
private async Task InsertWorkflowNoteAsync(
WorkflowRunContext context,
string subject,
string content)
{
var key = context.Keys?.FirstOrDefault();
if (key == null)
{
return;
}
var note = new Note(GuidGenerator.Create())
{
TenantId = CurrentTenant.Id,
EntityName = context.ListFormCode,
EntityId = key.ToString(),
Type = "workflow",
Subject = subject,
Content = content,
FilesJson = "[]"
};
await noteRepository.InsertAsync(note, autoSave: true);
}
private static string BuildWorkflowNoteContent(List<(string Label, string Value)> rows)
{
var tableRows = rows
.Where(row => !row.Value.IsNullOrWhiteSpace())
.Select(row =>
$"<tr><td><strong>{Encode(row.Label)}</strong></td><td>{Encode(row.Value)}</td></tr>");
return $"<table class=\"workflow-note-log\">{string.Join(string.Empty, tableRows)}</table>";
}
private static string FormatNode(ListFormWorkflow node)
{
if (node == null)
{
return string.Empty;
}
var title = node.Title ?? string.Empty;
var kind = node.Kind ?? string.Empty;
return $"{title} ({kind} - {node.Id})";
}
private static string Encode(string value)
{
return WebUtility.HtmlEncode(value ?? string.Empty);
}
private async Task UpdateRowAsync(WorkflowRunContext context, Dictionary<string, object> data)
{
await queryManager.GenerateAndRunQueryAsync<int>(
@ -762,7 +1026,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
CurrentNodeTitle = node?.Title,
CurrentNodeKind = node?.Kind,
WaitingApproval = waitingApproval,
Completed = completed
Completed = completed,
ToastMessages = context.ToastMessages.ToList()
};
}
@ -906,6 +1171,36 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
return value.IsNullOrWhiteSpace() ? fallback : value.Trim();
}
private async Task<string> NormalizeUniqueTitleAsync(
string listFormCode,
string criteriaId,
string kind,
string title)
{
var baseTitle = NormalizeRequired(title, kind);
var existingTitles = (await criteriaRepository.GetListAsync(x =>
x.ListFormCode == listFormCode &&
x.Id != criteriaId))
.Select(x => x.Title?.Trim())
.Where(x => !x.IsNullOrWhiteSpace())
.ToHashSet(StringComparer.OrdinalIgnoreCase);
if (!existingTitles.Contains(baseTitle))
{
return baseTitle;
}
var index = 1;
var candidate = $"{baseTitle}{index}";
while (existingTitles.Contains(candidate))
{
index++;
candidate = $"{baseTitle}{index}";
}
return candidate;
}
private static string SerializeCompareOutcomes(List<CompareOutcomeDto> outcomes)
{
return JsonSerializer.Serialize(outcomes ?? []);
@ -947,12 +1242,14 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
private sealed record WorkflowRunContext(
string ListFormCode,
string KeyFieldName,
object[] Keys,
WorkflowDto Workflow,
List<ListFormWorkflow> Criteria,
IDictionary<string, object> Row)
{
public HashSet<string> UserUpdatedFields { get; } = [];
public List<(string Label, string Value)> WorkflowNoteRows { get; } = [];
public List<string> ToastMessages { get; } = [];
}
}

View file

@ -30,13 +30,15 @@ public class MenuAppService : CrudAppService<
private readonly IRepository<LanguageText, Guid> _repositoryText;
private readonly ITenantRepository _tenantRepository;
private readonly IPermissionDefinitionRecordRepository _permissionRepository;
private readonly LanguageTextAppService _languageTextAppService;
public MenuAppService(
IRepository<Menu, Guid> menuRepository,
IRepository<LanguageKey, Guid> languageKeyRepository,
IRepository<LanguageText, Guid> languageTextRepository,
ITenantRepository tenantRepository,
IPermissionDefinitionRecordRepository permissionRepository
IPermissionDefinitionRecordRepository permissionRepository,
LanguageTextAppService languageTextAppService
) : base(menuRepository)
{
_menuRepository = menuRepository;
@ -44,6 +46,7 @@ public class MenuAppService : CrudAppService<
_repositoryText = languageTextRepository;
_tenantRepository = tenantRepository;
_permissionRepository = permissionRepository;
_languageTextAppService = languageTextAppService;
CreatePolicyName = $"{AppCodes.Menus.Menu}.Create";
UpdatePolicyName = $"{AppCodes.Menus.Menu}.Update";
@ -275,7 +278,7 @@ public class MenuAppService : CrudAppService<
if (existingEnText != null)
{
existingEnText.Value = input.MenuTextEn;
await _repositoryText.UpdateAsync(existingEnText);
await _repositoryText.UpdateAsync(existingEnText, autoSave: true);
}
else
{
@ -285,7 +288,7 @@ public class MenuAppService : CrudAppService<
CultureName = "en",
Value = input.MenuTextEn,
ResourceName = PlatformConsts.AppName
});
}, autoSave: true);
}
// Türkçe text oluşturuluyor veya güncelleniyor.
@ -297,7 +300,7 @@ public class MenuAppService : CrudAppService<
if (existingTrText != null)
{
existingTrText.Value = input.MenuTextTr;
await _repositoryText.UpdateAsync(existingTrText);
await _repositoryText.UpdateAsync(existingTrText, autoSave: true);
}
else
{
@ -307,9 +310,12 @@ public class MenuAppService : CrudAppService<
CultureName = "tr",
Value = input.MenuTextTr,
ResourceName = PlatformConsts.AppName
});
}, autoSave: true);
}
// Clear Redis Cache
await _languageTextAppService.ClearRedisCacheAsync();
return await base.CreateAsync(input);
}
}

View file

@ -195,20 +195,13 @@ public class PublicAppService : PlatformAppService
public async Task<HomeDto> GetHomeAsync()
{
var entity = await _homeRepository.FirstOrDefaultAsync();
if (entity == null)
{
entity = await _homeRepository.InsertAsync(CreateDefaultHomeEntity(), autoSave: true);
}
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
return ObjectMapper.Map<Home, HomeDto>(entity);
}
public async Task SaveHomePageAsync(SaveHomePageInput input)
{
var entity = await _homeRepository.FirstOrDefaultAsync();
var isNewEntity = entity == null;
entity ??= CreateDefaultHomeEntity();
var entity = await _homeRepository.FirstOrDefaultAsync() ?? throw new EntityNotFoundException(typeof(Home));
entity.HeroBackgroundImageKey = input.HeroBackgroundImageKey;
entity.HeroPrimaryCtaKey = input.HeroPrimaryCtaKey;
@ -252,14 +245,7 @@ public class PublicAppService : PlatformAppService
StyleClass = solution.StyleClass,
}).ToList());
if (isNewEntity)
{
await _homeRepository.InsertAsync(entity, autoSave: false);
}
else
{
await _homeRepository.UpdateAsync(entity, autoSave: false);
}
await _homeRepository.UpdateAsync(entity, autoSave: false);
await UpsertLanguageTextAsync(input.CultureName, input.HeroBackgroundImageKey, input.HeroBackgroundImageValue);
await UpsertLanguageTextAsync(input.CultureName, input.HeroPrimaryCtaKey, input.HeroPrimaryCtaValue);
@ -608,83 +594,6 @@ public class PublicAppService : PlatformAppService
await _languageTextAppService.ClearRedisCacheAsync();
}
private static Home CreateDefaultHomeEntity()
{
var slides = new List<HomeSlideDto>
{
new()
{
TitleKey = "Public.hero.slide1.title",
SubtitleKey = "Public.hero.slide1.subtitle",
Services = new List<HomeSlideServiceDto>
{
new() { Icon = "FaCalendarAlt", TitleKey = "Public.hero.slide1.service1.title", DescriptionKey = "Public.hero.slide1.service1.desc" },
new() { Icon = "FaUsers", TitleKey = "Public.hero.slide1.service2.title", DescriptionKey = "Public.hero.slide1.service2.desc" },
new() { Icon = "FaShieldAlt", TitleKey = "Public.hero.slide1.service3.title", DescriptionKey = "Public.hero.slide1.service3.desc" },
},
},
new()
{
TitleKey = "Public.hero.slide2.title",
SubtitleKey = "Public.hero.slide2.subtitle",
Services = new List<HomeSlideServiceDto>
{
new() { Icon = "FaChartBar", TitleKey = "Public.hero.slide2.service1.title", DescriptionKey = "Public.hero.slide2.service1.desc" },
new() { Icon = "FaCreditCard", TitleKey = "Public.hero.slide2.service2.title", DescriptionKey = "Public.hero.slide2.service2.desc" },
new() { Icon = "FaDatabase", TitleKey = "Public.hero.slide2.service3.title", DescriptionKey = "Public.hero.slide2.service3.desc" },
},
},
new()
{
TitleKey = "Public.hero.slide3.title",
SubtitleKey = "Public.hero.slide3.subtitle",
Services = new List<HomeSlideServiceDto>
{
new() { Icon = "FaDesktop", TitleKey = "Public.hero.slide3.service1.title", DescriptionKey = "Public.hero.slide3.service1.desc" },
new() { Icon = "FaServer", TitleKey = "Public.hero.slide3.service2.title", DescriptionKey = "Public.hero.slide3.service2.desc" },
new() { Icon = "FaMobileAlt", TitleKey = "Public.hero.slide3.service3.title", DescriptionKey = "Public.hero.slide3.service3.desc" },
},
},
};
var features = new List<HomeFeatureDto>
{
new() { Icon = "FaUsers", TitleKey = "Public.features.reliable", DescriptionKey = "Public.features.reliable.desc" },
new() { Icon = "FaCalendarAlt", TitleKey = "App.Videoroom.Planning", DescriptionKey = "Public.features.rapid.desc" },
new() { Icon = "FaBookOpen", TitleKey = "Public.features.expert", DescriptionKey = "Public.features.expert.desc" },
new() { Icon = "FaCreditCard", TitleKey = "Public.features.muhasebe", DescriptionKey = "Public.features.muhasebe.desc" },
new() { Icon = "FaRegComment", TitleKey = "Public.features.iletisim", DescriptionKey = "Public.features.iletisim.desc" },
new() { Icon = "FaPhone", TitleKey = "Public.features.mobil", DescriptionKey = "Public.features.mobil.desc" },
new() { Icon = "FaChartBar", TitleKey = "Public.features.scalable", DescriptionKey = "Public.features.scalable.desc" },
new() { Icon = "FaShieldAlt", TitleKey = "Public.features.guvenlik", DescriptionKey = "Public.features.guvenlik.desc" },
};
var solutions = new List<HomeSolutionDto>
{
new() { Icon = "FaDesktop", ColorClass = "bg-blue-600", TitleKey = "Public.services.web.title", DescriptionKey = "Public.solutions.web.desc" },
new() { Icon = "FaMobileAlt", ColorClass = "bg-purple-600", TitleKey = "Public.services.mobile.title", DescriptionKey = "Public.solutions.mobile.desc" },
new() { Icon = "FaServer", ColorClass = "bg-green-600", TitleKey = "Public.solutions.custom.title", DescriptionKey = "Public.solutions.custom.desc" },
new() { Icon = "FaDatabase", ColorClass = "bg-red-600", TitleKey = "Public.solutions.database.title", DescriptionKey = "Public.solutions.database.desc" },
};
return new Home
{
HeroBackgroundImageKey = "Public.home.hero.backgroundImage",
HeroPrimaryCtaKey = "Public.hero.cta.consultation",
HeroSecondaryCtaKey = "Public.hero.cta.discover",
FeaturesTitleKey = "Public.features.title",
FeaturesSubtitleKey = "Public.features.subtitle",
SolutionsTitleKey = "Public.solutions.title",
SolutionsSubtitleKey = "Public.solutions.subtitle",
CtaTitleKey = "Public.common.getStarted",
CtaSubtitleKey = "Public.common.contact",
CtaButtonLabelKey = "Public.common.learnMore",
SlidesJson = JsonSerializer.Serialize(slides),
FeaturesJson = JsonSerializer.Serialize(features),
SolutionsJson = JsonSerializer.Serialize(solutions),
};
}
private async Task UpsertLanguageTextAsync(string cultureName, string key, string value)
{
if (key.IsNullOrWhiteSpace())

View file

@ -8,13 +8,101 @@
}
],
"Settings": [
{
"code": "App.SiteManagement.General.NewMemberNotificationEmails",
"nameKey": "App.SiteManagement.General.NewMemberNotificationEmails",
"descriptionKey": "App.SiteManagement.General.NewMemberNotificationEmails.Description",
"defaultValue": "system@sozsoft.com",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
"subGroupKey": "App.SiteManagement.General",
"requiredPermissionName": "App.SiteManagement.General",
"dataType": "Text",
"selectOptions": {},
"order": 1
},
{
"code": "App.SiteManagement.General.TimedLoginEmails",
"nameKey": "App.SiteManagement.General.TimedLoginEmails",
"descriptionKey": "App.SiteManagement.General.TimedLoginEmails.Description",
"defaultValue": "system@sozsoft.com",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
"subGroupKey": "App.SiteManagement.General",
"requiredPermissionName": "App.SiteManagement.General",
"dataType": "Text",
"selectOptions": {},
"order": 2
},
{
"code": "App.SiteManagement.Theme.Style",
"nameKey": "App.SiteManagement.Theme.Style",
"descriptionKey": "App.SiteManagement.Theme.Style.Description",
"defaultValue": "dx.material.blue.light.compact",
"isVisibleToClients": true,
"providers": "U|G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
"subGroupKey": "App.SiteManagement.Theme",
"requiredPermissionName": "App.SiteManagement.Theme",
"dataType": "List",
"selectOptions": {
"dx.light": "App.Setting.light",
"dx.light.compact": "App.Setting.light.compact",
"dx.dark": "App.Setting.dark",
"dx.dark.compact": "App.Setting.dark.compact",
"dx.contrast": "App.Setting.contrast",
"dx.contrast.compact": "App.Setting.contrast.compact",
"dx.carmine": "App.Setting.carmine",
"dx.darkmoon": "App.Setting.darkmoon",
"dx.softblue": "App.Setting.softblue",
"dx.darkviolet": "App.Setting.darkviolet",
"dx.greenmist": "App.Setting.greenmist",
"dx.material.blue.light": "App.Setting.material.blue.light",
"dx.material.blue.dark": "App.Setting.material.blue.dark",
"dx.material.lime.light": "App.Setting.material.lime.light",
"dx.material.lime.dark": "App.Setting.material.lime.dark",
"dx.material.orange.light": "App.Setting.material.orange.light",
"dx.material.orange.dark": "App.Setting.material.orange.dark",
"dx.material.purple.light": "App.Setting.material.purple.light",
"dx.material.purple.dark": "App.Setting.material.purple.dark",
"dx.material.teal.light": "App.Setting.material.teal.light",
"dx.material.teal.dark": "App.Setting.material.teal.dark",
"dx.material.blue.light.compact": "App.Setting.material.blue.light.compact",
"dx.material.blue.dark.compact": "App.Setting.material.blue.dark.compact",
"dx.material.lime.light.compact": "App.Setting.material.lime.light.compact",
"dx.material.lime.dark.compact": "App.Setting.material.lime.dark.compact",
"dx.material.orange.light.compact": "App.Setting.material.orange.light.compact",
"dx.material.orange.dark.compact": "App.Setting.material.orange.dark.compact",
"dx.material.purple.light.compact": "App.Setting.material.purple.light.compact",
"dx.material.purple.dark.compact": "App.Setting.material.purple.dark.compact",
"dx.material.teal.light.compact": "App.Setting.material.teal.light.compact",
"dx.material.teal.dark.compact": "App.Setting.material.teal.dark.compact",
"dx.fluent.blue.dark.compact": "App.Setting.fluent.blue.dark.compact",
"dx.fluent.blue.dark": "App.Setting.fluent.blue.dark",
"dx.fluent.blue.light.compact": "App.Setting.fluent.blue.light.compact",
"dx.fluent.blue.light": "App.Setting.fluent.blue.light",
"dx.fluent.saas.dark.compact": "App.Setting.fluent.saas.dark.compact",
"dx.fluent.saas.dark": "App.Setting.fluent.saas.dark",
"dx.fluent.saas.light.compact": "App.Setting.fluent.saas.light.compact",
"dx.fluent.saas.light": "App.Setting.fluent.saas.light"
},
"order": 3
},
{
"code": "Abp.Localization.DefaultLanguage",
"nameKey": "Abp.Localization.DefaultLanguage",
"descriptionKey": "Abp.Localization.DefaultLanguage.Description",
"defaultValue": "tr",
"defaultValue": "en",
"isVisibleToClients": false,
"providers": "T|G|D",
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
@ -40,7 +128,7 @@
"tr": "Türkçe",
"zh-Hans": "繁體中文"
},
"order": 1
"order": 4
},
{
"code": "Abp.Timing.TimeZone",
@ -48,7 +136,7 @@
"descriptionKey": "Abp.Timing.TimeZone.Description",
"defaultValue": "Turkey Standard Time",
"isVisibleToClients": false,
"providers": "T|G|D",
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
@ -196,94 +284,6 @@
"Yakutsk Standard Time": "Yakutsk Standard Time (+09:00)",
"Yukon Standard Time": "Yukon Standard Time (-07:00)"
},
"order": 2
},
{
"code": "App.SiteManagement.Theme.Style",
"nameKey": "App.SiteManagement.Theme.Style",
"descriptionKey": "App.SiteManagement.Theme.Style.Description",
"defaultValue": "dx.material.blue.light.compact",
"isVisibleToClients": true,
"providers": "U|G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
"subGroupKey": "App.SiteManagement.Theme",
"requiredPermissionName": "App.SiteManagement.Theme",
"dataType": "List",
"selectOptions": {
"dx.light": "App.Setting.light",
"dx.light.compact": "App.Setting.light.compact",
"dx.dark": "App.Setting.dark",
"dx.dark.compact": "App.Setting.dark.compact",
"dx.contrast": "App.Setting.contrast",
"dx.contrast.compact": "App.Setting.contrast.compact",
"dx.carmine": "App.Setting.carmine",
"dx.darkmoon": "App.Setting.darkmoon",
"dx.softblue": "App.Setting.softblue",
"dx.darkviolet": "App.Setting.darkviolet",
"dx.greenmist": "App.Setting.greenmist",
"dx.material.blue.light": "App.Setting.material.blue.light",
"dx.material.blue.dark": "App.Setting.material.blue.dark",
"dx.material.lime.light": "App.Setting.material.lime.light",
"dx.material.lime.dark": "App.Setting.material.lime.dark",
"dx.material.orange.light": "App.Setting.material.orange.light",
"dx.material.orange.dark": "App.Setting.material.orange.dark",
"dx.material.purple.light": "App.Setting.material.purple.light",
"dx.material.purple.dark": "App.Setting.material.purple.dark",
"dx.material.teal.light": "App.Setting.material.teal.light",
"dx.material.teal.dark": "App.Setting.material.teal.dark",
"dx.material.blue.light.compact": "App.Setting.material.blue.light.compact",
"dx.material.blue.dark.compact": "App.Setting.material.blue.dark.compact",
"dx.material.lime.light.compact": "App.Setting.material.lime.light.compact",
"dx.material.lime.dark.compact": "App.Setting.material.lime.dark.compact",
"dx.material.orange.light.compact": "App.Setting.material.orange.light.compact",
"dx.material.orange.dark.compact": "App.Setting.material.orange.dark.compact",
"dx.material.purple.light.compact": "App.Setting.material.purple.light.compact",
"dx.material.purple.dark.compact": "App.Setting.material.purple.dark.compact",
"dx.material.teal.light.compact": "App.Setting.material.teal.light.compact",
"dx.material.teal.dark.compact": "App.Setting.material.teal.dark.compact",
"dx.fluent.blue.dark.compact": "App.Setting.fluent.blue.dark.compact",
"dx.fluent.blue.dark": "App.Setting.fluent.blue.dark",
"dx.fluent.blue.light.compact": "App.Setting.fluent.blue.light.compact",
"dx.fluent.blue.light": "App.Setting.fluent.blue.light",
"dx.fluent.saas.dark.compact": "App.Setting.fluent.saas.dark.compact",
"dx.fluent.saas.dark": "App.Setting.fluent.saas.dark",
"dx.fluent.saas.light.compact": "App.Setting.fluent.saas.light.compact",
"dx.fluent.saas.light": "App.Setting.fluent.saas.light"
},
"order": 3
},
{
"code": "App.SiteManagement.General.NewMemberNotificationEmails",
"nameKey": "App.SiteManagement.General.NewMemberNotificationEmails",
"descriptionKey": "App.SiteManagement.General.NewMemberNotificationEmails.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
"subGroupKey": "App.SiteManagement.General",
"requiredPermissionName": "App.SiteManagement.General",
"dataType": "Text",
"selectOptions": {},
"order": 4
},
{
"code": "App.SiteManagement.General.TimedLoginEmails",
"nameKey": "App.SiteManagement.General.TimedLoginEmails",
"descriptionKey": "App.SiteManagement.General.TimedLoginEmails.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "App.SiteManagement",
"subGroupKey": "App.SiteManagement.General",
"requiredPermissionName": "App.SiteManagement.General",
"dataType": "Text",
"selectOptions": {},
"order": 5
},
{
@ -466,7 +466,7 @@
"code": "Abp.Mailing.DefaultFromAddress",
"nameKey": "Abp.Mailing.DefaultFromAddress",
"descriptionKey": "Abp.Mailing.DefaultFromAddress.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM",
"defaultValue": "system@sozsoft.com",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
@ -482,7 +482,7 @@
"code": "Abp.Mailing.Smtp.UserName",
"nameKey": "Abp.Mailing.Smtp.UserName",
"descriptionKey": "Abp.Mailing.Smtp.UserName.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM",
"defaultValue": "system@sozsoft.com",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
@ -574,6 +574,22 @@
"selectOptions": {},
"order": 27
},
{
"code": "Abp.Mailing.Smtp.UseDefaultCredentials",
"nameKey": "Abp.Mailing.Smtp.UseDefaultCredentials",
"descriptionKey": "Abp.Mailing.Smtp.UseDefaultCredentials.Description",
"defaultValue": "True",
"isVisibleToClients": true,
"providers": "T|G|D",
"isInherited": true,
"isEncrypted": false,
"mainGroupKey": "Abp.Mailing",
"subGroupKey": "Abp.Mailing.Smtp",
"requiredPermissionName": "Abp.Mailing.Smtp",
"dataType": "Bool",
"selectOptions": {},
"order": 28
},
{
"code": "Abp.Mailing.AWS.Profile",
"nameKey": "Abp.Mailing.AWS.Profile",
@ -588,7 +604,7 @@
"requiredPermissionName": "Abp.Mailing.AWS",
"dataType": "Text",
"selectOptions": {},
"order": 28
"order": 29
},
{
"code": "Abp.Mailing.AWS.Region",
@ -604,29 +620,13 @@
"requiredPermissionName": "Abp.Mailing.AWS",
"dataType": "Text",
"selectOptions": {},
"order": 29
"order": 30
},
{
"code": "Abp.Mailing.AWS.AccessKey",
"nameKey": "Abp.Mailing.AWS.AccessKey",
"descriptionKey": "Abp.Mailing.AWS.AccessKey.Description",
"defaultValue": "aXW8L21rP6dPO6Txj76Be2FCpWRBa25EMrSAVL76",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Mailing",
"subGroupKey": "Abp.Mailing.AWS",
"requiredPermissionName": "Abp.Mailing.AWS",
"dataType": "Text",
"selectOptions": {},
"order": 30
},
{
"code": "Abp.Mailing.AWS.AccessKeyId",
"nameKey": "Abp.Mailing.AWS.AccessKeyId",
"descriptionKey": "Abp.Mailing.AWS.AccessKeyId.Description",
"defaultValue": "AKIATULUYBLX4IY3S2P1",
"defaultValue": "SibFBAMiSApvz+NChYmlgZmx25JNbximemIDOFps",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
@ -638,11 +638,27 @@
"selectOptions": {},
"order": 31
},
{
"code": "Abp.Mailing.AWS.AccessKeyId",
"nameKey": "Abp.Mailing.AWS.AccessKeyId",
"descriptionKey": "Abp.Mailing.AWS.AccessKeyId.Description",
"defaultValue": "AKIA5OCSDJB5KOQY74NV",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Mailing",
"subGroupKey": "Abp.Mailing.AWS",
"requiredPermissionName": "Abp.Mailing.AWS",
"dataType": "Text",
"selectOptions": {},
"order": 32
},
{
"code": "Abp.Account.IsSelfRegistrationEnabled",
"nameKey": "Abp.Account.IsSelfRegistrationEnabled",
"descriptionKey": "Abp.Account.IsSelfRegistrationEnabled.Description",
"defaultValue": "True",
"defaultValue": "False",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
@ -674,8 +690,8 @@
"code": "Abp.Account.TwoFactor.Enabled",
"nameKey": "Abp.Account.TwoFactor.Enabled",
"descriptionKey": "Abp.Account.TwoFactor.Enabled.Description",
"defaultValue": "True",
"isVisibleToClients": false,
"defaultValue": "False",
"isVisibleToClients": true,
"providers": "T|G|D",
"isInherited": false,
"isEncrypted": false,
@ -722,7 +738,7 @@
"code": "Abp.Account.Captcha.SiteKey",
"nameKey": "Abp.Account.Captcha.SiteKey",
"descriptionKey": "Abp.Account.Captcha.SiteKey.Description",
"defaultValue": "0x4AAAAAAAGadwQME-GSYuJU",
"defaultValue": "0x4AAAAAABdEjmiXxcl0j7jp",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
@ -738,7 +754,7 @@
"code": "Abp.Account.Captcha.SecretKey",
"nameKey": "Abp.Account.Captcha.SecretKey",
"descriptionKey": "Abp.Account.Captcha.SecretKey.Description",
"defaultValue": "0x4AAAAAAAGad_f_WP47IcNBs9FTu5DhNX8",
"defaultValue": "0x4AAAAAABdEjhw1A8sJZUvQX8-CgqvB3mE",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
@ -754,8 +770,8 @@
"code": "Abp.Identity.Profile.General.RequireVerifiedAccount",
"nameKey": "Abp.Identity.Profile.General.RequireVerifiedAccount",
"descriptionKey": "Abp.Identity.Profile.General.RequireVerifiedAccount.Description",
"defaultValue": "True",
"isVisibleToClients": false,
"defaultValue": "False",
"isVisibleToClients": true,
"providers": "T|G|D",
"isInherited": false,
"isEncrypted": false,
@ -782,6 +798,104 @@
"selectOptions": {},
"order": 51
},
{
"code": "Abp.Identity.SignIn.RequireConfirmedEmail",
"nameKey": "Abp.Identity.SignIn.RequireConfirmedEmail",
"descriptionKey": "Abp.Identity.SignIn.RequireConfirmedEmail.Description",
"defaultValue": "True",
"isVisibleToClients": true,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.SignIn",
"requiredPermissionName": "Abp.Identity.SignIn",
"dataType": "Bool",
"selectOptions": {},
"order": 60
},
{
"code": "Abp.Identity.SignIn.RequireEmailVerificationToRegister",
"nameKey": "Abp.Identity.SignIn.RequireEmailVerificationToRegister",
"descriptionKey": "Abp.Identity.SignIn.RequireEmailVerificationToRegister.Description",
"defaultValue": "False",
"isVisibleToClients": true,
"providers": "G|D",
"isInherited": true,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.SignIn",
"requiredPermissionName": "Abp.Identity.SignIn",
"dataType": "Bool",
"selectOptions": {},
"order": 61
},
{
"code": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber",
"nameKey": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber",
"descriptionKey": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber.Description",
"defaultValue": "False",
"isVisibleToClients": true,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.SignIn",
"requiredPermissionName": "Abp.Identity.SignIn",
"dataType": "Bool",
"selectOptions": {},
"order": 62
},
{
"code": "Abp.Identity.SignIn.EnablePhoneNumberConfirmation",
"nameKey": "Abp.Identity.SignIn.EnablePhoneNumberConfirmation",
"descriptionKey": "Abp.Identity.SignIn.EnablePhoneNumberConfirmation.Description",
"defaultValue": "False",
"isVisibleToClients": true,
"providers": "G|D",
"isInherited": true,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.SignIn",
"requiredPermissionName": "Abp.Identity.SignIn",
"dataType": "Bool",
"selectOptions": {},
"order": 63
},
{
"code": "Abp.Identity.User.IsUserNameUpdateEnabled",
"nameKey": "Abp.Identity.User.IsUserNameUpdateEnabled",
"descriptionKey": "Abp.Identity.User.IsUserNameUpdateEnabled.Description",
"defaultValue": "True",
"isVisibleToClients": true,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.User",
"requiredPermissionName": "Abp.Identity.User",
"dataType": "Bool",
"selectOptions": {},
"order": 64
},
{
"code": "Abp.Identity.User.IsEmailUpdateEnabled",
"nameKey": "Abp.Identity.User.IsEmailUpdateEnabled",
"descriptionKey": "Abp.Identity.User.IsEmailUpdateEnabled.Description",
"defaultValue": "True",
"isVisibleToClients": true,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.User",
"requiredPermissionName": "Abp.Identity.User",
"dataType": "Bool",
"selectOptions": {},
"order": 65
},
{
"code": "Abp.Identity.Password.ForceUsersToPeriodicallyChangePassword",
"nameKey": "Abp.Identity.Password.ForceUsersToPeriodicallyChangePassword",
@ -796,13 +910,13 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Bool",
"selectOptions": {},
"order": 52
"order": 70
},
{
"code": "Abp.Identity.Password.PasswordChangePeriodDays",
"nameKey": "Abp.Identity.Password.PasswordChangePeriodDays",
"descriptionKey": "Abp.Identity.Password.PasswordChangePeriodDays.Description",
"defaultValue": "0",
"defaultValue": "180",
"isVisibleToClients": false,
"providers": "T|G|D",
"isInherited": false,
@ -812,7 +926,7 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Number",
"selectOptions": {},
"order": 53
"order": 71
},
{
"code": "Abp.Identity.Password.RequiredLength",
@ -828,7 +942,7 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Number",
"selectOptions": {},
"order": 54
"order": 72
},
{
"code": "Abp.Identity.Password.RequiredUniqueChars",
@ -844,7 +958,7 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Number",
"selectOptions": {},
"order": 55
"order": 73
},
{
"code": "Abp.Identity.Password.RequireNonAlphanumeric",
@ -860,7 +974,7 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Bool",
"selectOptions": {},
"order": 56
"order": 74
},
{
"code": "Abp.Identity.Password.RequireLowercase",
@ -876,7 +990,7 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Bool",
"selectOptions": {},
"order": 57
"order": 75
},
{
"code": "Abp.Identity.Password.RequireUppercase",
@ -892,7 +1006,7 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Bool",
"selectOptions": {},
"order": 58
"order": 76
},
{
"code": "Abp.Identity.Password.RequireDigit",
@ -908,7 +1022,7 @@
"requiredPermissionName": "Abp.Identity.Password",
"dataType": "Bool",
"selectOptions": {},
"order": 59
"order": 77
},
{
"code": "Abp.Identity.Lockout.AllowedForNewUsers",
@ -924,7 +1038,7 @@
"requiredPermissionName": "Abp.Identity.Lockout",
"dataType": "Bool",
"selectOptions": {},
"order": 60
"order": 78
},
{
"code": "Abp.Identity.Lockout.LockoutDuration",
@ -940,7 +1054,7 @@
"requiredPermissionName": "Abp.Identity.Lockout",
"dataType": "Number",
"selectOptions": {},
"order": 61
"order": 79
},
{
"code": "Abp.Identity.Lockout.MaxFailedAccessAttempts",
@ -956,71 +1070,23 @@
"requiredPermissionName": "Abp.Identity.Lockout",
"dataType": "Number",
"selectOptions": {},
"order": 62
"order": 80
},
{
"code": "Abp.Identity.SignIn.RequireConfirmedEmail",
"nameKey": "Abp.Identity.SignIn.RequireConfirmedEmail",
"descriptionKey": "Abp.Identity.SignIn.RequireConfirmedEmail.Description",
"defaultValue": "True",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
"code": "Abp.Identity.OrganizationUnit.MaxUserMembershipCount",
"nameKey": "Abp.Identity.OrganizationUnit.MaxUserMembershipCount",
"descriptionKey": "Abp.Identity.OrganizationUnit.MaxUserMembershipCount.Description",
"defaultValue": "2147483647",
"isVisibleToClients": true,
"providers": "T|G|D",
"isInherited": true,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.SignIn",
"requiredPermissionName": "Abp.Identity.SignIn",
"dataType": "Bool",
"subGroupKey": "Abp.Identity.OrganizationUnits",
"requiredPermissionName": "Abp.Identity.OrganizationUnits",
"dataType": "Number",
"selectOptions": {},
"order": 63
},
{
"code": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber",
"nameKey": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber",
"descriptionKey": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber.Description",
"defaultValue": "False",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.SignIn",
"requiredPermissionName": "Abp.Identity.SignIn",
"dataType": "Bool",
"selectOptions": {},
"order": 64
},
{
"code": "Abp.Identity.User.IsUserNameUpdateEnabled",
"nameKey": "Abp.Identity.User.IsUserNameUpdateEnabled",
"descriptionKey": "Abp.Identity.User.IsUserNameUpdateEnabled.Description",
"defaultValue": "True",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.User",
"requiredPermissionName": "Abp.Identity.User",
"dataType": "Bool",
"selectOptions": {},
"order": 65
},
{
"code": "Abp.Identity.User.IsEmailUpdateEnabled",
"nameKey": "Abp.Identity.User.IsEmailUpdateEnabled",
"descriptionKey": "Abp.Identity.User.IsEmailUpdateEnabled.Description",
"defaultValue": "True",
"isVisibleToClients": false,
"providers": "G|D",
"isInherited": false,
"isEncrypted": false,
"mainGroupKey": "Abp.Identity",
"subGroupKey": "Abp.Identity.User",
"requiredPermissionName": "Abp.Identity.User",
"dataType": "Bool",
"selectOptions": {},
"order": 66
"order": 90
}
],
"NotificationTypes": [],

View file

@ -948,6 +948,12 @@
"en": "No record found...",
"tr": "Kayıt Bulunamadı..."
},
{
"resourceName": "Platform",
"key": "App.Definitions.WorkHour",
"en": "Work Hours",
"tr": "Çalışma Saatleri"
},
{
"resourceName": "Platform",
"key": "App.Definitions.Sequence",
@ -1923,8 +1929,8 @@
{
"resourceName": "Platform",
"key": "App.Sender.Url.Description",
"en": "Url Description",
"tr": "Url Açıklaması"
"en": "Posta Güvercini URL is usually in the format: http://api.postaguvercini.com/v1/sms/send?username={username}&password={password}&to={to}&message={message}",
"tr": "Posta Güvercini URL'si genellikle şu formatta olur: http://api.postaguvercini.com/v1/sms/send?username={username}&password={password}&to={to}&message={message}"
},
{
"resourceName": "Platform",
@ -1935,8 +1941,8 @@
{
"resourceName": "Platform",
"key": "App.Sender.Sms.PostaGuvercini.Username.Description",
"en": "User Name Description",
"tr": "Kullanıcı Adııklaması"
"en": "The user name for the Posta Güvercini service.",
"tr": "Posta Güvercini hizmeti için kullanıcı adı."
},
{
"resourceName": "Platform",
@ -1947,8 +1953,8 @@
{
"resourceName": "Platform",
"key": "App.Sender.Sms.PostaGuvercini.Password.Description",
"en": "Password Description",
"tr": "Parola Açıklaması"
"en": "The password for the Posta Güvercini service.",
"tr": "Posta Güvercini hizmeti için parola."
},
{
"resourceName": "Platform",
@ -1971,8 +1977,8 @@
{
"resourceName": "Platform",
"key": "App.Sender.WhatsApp.PhoneNumberId.Description",
"en": "PhoneNumberId Description",
"tr": "PhoneNumberId Description"
"en": "The phone number ID for the WhatsApp service.",
"tr": "WhatsApp hizmeti için telefon numarası ID'si."
},
{
"resourceName": "Platform",
@ -1983,8 +1989,8 @@
{
"resourceName": "Platform",
"key": "App.Sender.WhatsApp.TemplateName.Description",
"en": "Template Name Description",
"tr": "Şablon Adııklaması"
"en": "The name of the template for the WhatsApp service.",
"tr": "WhatsApp hizmeti için şablon adı."
},
{
"resourceName": "Platform",
@ -1995,8 +2001,8 @@
{
"resourceName": "Platform",
"key": "App.Sender.WhatsApp.Token.Description",
"en": "Token Description",
"tr": "Token Açıklaması"
"en": "The token for the WhatsApp service.",
"tr": "WhatsApp hizmeti için token."
},
{
"resourceName": "Platform",
@ -2019,20 +2025,14 @@
{
"resourceName": "Platform",
"key": "App.Sender.Rocket.UserId.Description",
"en": "User Id Description",
"tr": "User Id Açıklaması"
},
{
"resourceName": "Platform",
"key": "App.Sender.Rocket.Token",
"en": "Token",
"tr": "Token"
"en": "The user ID for the Rocket Chat service.",
"tr": "Rocket Chat hizmeti için kullanıcı ID'si."
},
{
"resourceName": "Platform",
"key": "App.Sender.Rocket.Token.Description",
"en": "Token Description",
"tr": "Token Açıklaması"
"en": "The token for the Rocket Chat service.",
"tr": "Rocket Chat hizmeti için token."
},
{
"resourceName": "Platform",
@ -2055,8 +2055,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.DefaultFromDisplayName.Description",
"en": "From Display Name Description",
"tr": "Varsayılan İsim Açıklaması"
"en": "Default name to be shown when sending mails. Usually it is company name or application name.",
"tr": "Mail gönderilirken görünecek varsayılan isim. Genellikle şirket adı veya uygulama adı olur."
},
{
"resourceName": "Platform",
@ -2067,8 +2067,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.DefaultFromAddress.Description",
"en": "From Address Description",
"tr": "Varsayılan Email Adresi Açıklaması"
"en": "Default email address to be shown when sending mails. Usually it is company email or application email.",
"tr": "Mail gönderilirken görünecek varsayılan email adresi. Genellikle şirket emaili veya uygulama emaili olur."
},
{
"resourceName": "Platform",
@ -2085,8 +2085,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.Host.Description",
"en": "Host Description",
"tr": "Host Açıklaması"
"en": "The host address of the SMTP server.",
"tr": "SMTP sunucusunun ana bilgisayar adresi."
},
{
"resourceName": "Platform",
@ -2097,8 +2097,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.Port.Description",
"en": "Port Description",
"tr": "Port Açıklaması"
"en": "The port number of the SMTP server.",
"tr": "SMTP sunucusunun port numarası."
},
{
"resourceName": "Platform",
@ -2109,8 +2109,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.UserName.Description",
"en": "User Name Description",
"tr": "Kullanıcı Adııklaması"
"en": "The user name for the SMTP server.",
"tr": "SMTP sunucusu için kullanıcı adı."
},
{
"resourceName": "Platform",
@ -2121,8 +2121,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.Password.Description",
"en": "Password Description",
"tr": "Parola Açıklaması"
"en": "The password for the SMTP server.",
"tr": "SMTP sunucusu için parola."
},
{
"resourceName": "Platform",
@ -2133,8 +2133,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.Domain.Description",
"en": "Domain Description",
"tr": "Alan Adııklaması"
"en": "The domain for the SMTP server.",
"tr": "SMTP sunucusu için alan adı."
},
{
"resourceName": "Platform",
@ -2145,8 +2145,32 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.EnableSsl.Description",
"en": "Enable Ssl Description",
"tr": "Ssl Etkinleştir Açıklaması"
"en": "Whether to enable SSL for the SMTP server.",
"tr": "SMTP sunucusu için SSL etkinleştirilip etkinleştirilmeyeceğini belirtir."
},
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.UseDefaultCredentials",
"en": "Use Default Credentials",
"tr": "Varsayılan Kimlik Bilgilerini Kullan"
},
{
"resourceName": "Platform",
"key": "Abp.Mailing.Smtp.UseDefaultCredentials.Description",
"en": "Whether to use default credentials for the SMTP server.",
"tr": "SMTP sunucusu için varsayılan kimlik bilgilerini kullanıp kullanmayacağını belirtir."
},
{
"resourceName": "Platform",
"key": "Abp.Identity.OrganizationUnit.MaxUserMembershipCount",
"en": "Max User Membership Count",
"tr": "Maksimum Kullanıcı Üyelik Sayısı"
},
{
"resourceName": "Platform",
"key": "Abp.Identity.OrganizationUnit.MaxUserMembershipCount.Description",
"en": "Maximum number of users that can be members of each organization unit. 0 means unlimited.",
"tr": "Her bir organizasyon biriminin sahip olabileceği maksimum kullanıcı sayısı. 0 means unlimited."
},
{
"resourceName": "Platform",
@ -2163,8 +2187,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.AWS.Profile.Description",
"en": "Profile Description",
"tr": "Profil Açıklaması"
"en": "The profile for the AWS SES service.",
"tr": "AWS SES hizmeti için profil."
},
{
"resourceName": "Platform",
@ -2175,8 +2199,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.AWS.Region.Description",
"en": "Region Description",
"tr": "Bölge Açıklaması"
"en": "The region of the AWS SES service.",
"tr": "AWS SES hizmetinin bölgesi."
},
{
"resourceName": "Platform",
@ -2187,8 +2211,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.AWS.AccessKey.Description",
"en": "Access Key Description",
"tr": "Erişim Anahtarııklaması"
"en": "The access key for the AWS SES service.",
"tr": "AWS SES hizmeti için erişim anahtarı."
},
{
"resourceName": "Platform",
@ -2199,8 +2223,8 @@
{
"resourceName": "Platform",
"key": "Abp.Mailing.AWS.AccessKeyId.Description",
"en": "Access KeyId Description",
"tr": "Erişim Anahtarı Kimliği Açıklaması"
"en": "The access key ID for the AWS SES service.",
"tr": "AWS SES hizmeti için erişim anahtarı kimliği."
},
{
"resourceName": "Platform",
@ -2223,8 +2247,8 @@
{
"resourceName": "Platform",
"key": "App.SiteManagement.Theme.Style.Description",
"en": "Style Description",
"tr": "Stil Açıklaması"
"en": "The style for the site theme.",
"tr": "Site temasi için stil."
},
{
"resourceName": "Platform",
@ -2241,8 +2265,8 @@
{
"resourceName": "Platform",
"key": "App.SiteManagement.General.NewMemberNotificationEmails.Description",
"en": "New Member Notification Emails Description",
"tr": "Yeni Üye Bildirim Mailleri Açıklaması"
"en": "The emails sent to new members.",
"tr": "Yeni üelere gönderilen mailler."
},
{
"resourceName": "Platform",
@ -2253,8 +2277,8 @@
{
"resourceName": "Platform",
"key": "App.SiteManagement.General.TimedLoginEmails.Description",
"en": "Timed Login Emails Description",
"tr": "Süreli Giriş Mailleri Açıklaması"
"en": "The emails sent for timed login notifications.",
"tr": "Süreli giriş bildirimleri için gönderilen mailler."
},
{
"resourceName": "Platform",
@ -2271,8 +2295,8 @@
{
"resourceName": "Platform",
"key": "Abp.Localization.DefaultLanguage.Description",
"en": "Default Language Description",
"tr": "Varsayılan Dil Açıklaması"
"en": "Default language of the application. It is used as a fallback when a translation for the current language is not found.",
"tr": "Uygulamanın varsayılan dili. Geçerli dil için bir çeviri bulunamadığında yedek olarak kullanılır."
},
{
"resourceName": "Platform",
@ -2289,8 +2313,8 @@
{
"resourceName": "Platform",
"key": "Abp.Localization.Timezone.Description",
"en": "Timezone Description",
"tr": "Saat Dilimi Açıklaması"
"en": "Default timezone of the application. Users' timezones are adjusted according to this value.",
"tr": "Uygulamanın varsayılan saat dilimi. Kullanıcıların saat dilimleri bu değere göre ayarlanır."
},
{
"resourceName": "Platform",
@ -2313,8 +2337,8 @@
{
"resourceName": "Platform",
"key": "Abp.Account.EnableLocalLogin.Description",
"en": "Authenticate with a local account Description",
"tr": "Yerel bir hesapla kimlik doğrulama açıklaması"
"en": "Enables the local login option on the login page.",
"tr": "Login sayfasında yerel giriş seçeneğini etkinleştirir."
},
{
"resourceName": "Platform",
@ -2325,8 +2349,8 @@
{
"resourceName": "Platform",
"key": "Abp.Account.IsSelfRegistrationEnabled.Description",
"en": "Enable self registration Description",
"tr": "Kendi kendine kaydı etkinleştir Açıklaması"
"en": "Enables the self-registration option on the login page.",
"tr": "Login sayfasında kendi kendine kaydolma seçeneğini etkinleştirir."
},
{
"resourceName": "Platform",
@ -2433,8 +2457,8 @@
{
"resourceName": "Platform",
"key": "Abp.Account.TwoFactor.Enabled.Description",
"en": "Two Factor Enabled Description",
"tr": "İki Faktör Ektinleştirme Açıklaması"
"en": "When enabled, users will be required to enter a two-factor authentication code sent to their email address during login.",
"tr": "Etkinleştirildiğinde, kullanıcıların giriş sırasında e-posta adreslerine gönderilen iki faktörlü kimlik doğrulama kodunu girmeleri gerekecektir."
},
{
"resourceName": "Platform",
@ -2451,8 +2475,8 @@
{
"resourceName": "Platform",
"key": "Abp.Account.Captcha.MaxFailedAccessAttempts.Description",
"en": "Max failed show Captcha Description",
"tr": "Kaç başarısız girişte gösterilecek Açıklaması"
"en": "The number of failed login attempts after which the CAPTCHA will be displayed.",
"tr": "CAPTCHA'nın gösterileceği başarısız giriş denemesi sayısı."
},
{
"resourceName": "Platform",
@ -2463,8 +2487,8 @@
{
"resourceName": "Platform",
"key": "Abp.Account.Captcha.EndPoint.Description",
"en": "End Point URL Description",
"tr": "Doğrulama Servis URL Açıklaması"
"en": "The URL of the CAPTCHA verification endpoint.",
"tr": "CAPTCHA doğrulama uç noktasının URL'si."
},
{
"resourceName": "Platform",
@ -2475,8 +2499,8 @@
{
"resourceName": "Platform",
"key": "Abp.Account.Captcha.SiteKey.Description",
"en": "Site Key Description",
"tr": "Site Anahtarııklaması"
"en": "The site key for the CAPTCHA service.",
"tr": "CAPTCHA hizmeti için site anahtarı."
},
{
"resourceName": "Platform",
@ -2487,8 +2511,8 @@
{
"resourceName": "Platform",
"key": "Abp.Account.Captcha.SecretKey.Description",
"en": "Private Key Description",
"tr": "Özel Anahtarııklaması"
"en": "The private key for the CAPTCHA service.",
"tr": "CAPTCHA hizmeti için özel anahtarı."
},
{
"resourceName": "Platform",
@ -2814,6 +2838,30 @@
"en": "Whether a confirmed telephone number is required to sign in.",
"tr": "Oturum açmak için onaylanmış bir telefon numarasının gerekip gerekmediği."
},
{
"resourceName": "Platform",
"key": "Abp.Identity.SignIn.EnablePhoneNumberConfirmation",
"en": "Enable Phone Number Confirmation",
"tr": "Telefon Numarası Onayını Etkinleştir"
},
{
"resourceName": "Platform",
"key": "Abp.Identity.SignIn.EnablePhoneNumberConfirmation.Description",
"en": "Whether to enable phone number confirmation during sign in.",
"tr": "Oturum açarken telefon numarası onayının etkinleştirilip etkinleştirilmeyeceği."
},
{
"resourceName": "Platform",
"key": "Abp.Identity.SignIn.RequireEmailVerificationToRegister",
"en": "Require Email Verification to Register",
"tr": "Kayıt Olmak için E-posta Doğrulaması Gerektir"
},
{
"resourceName": "Platform",
"key": "Abp.Identity.SignIn.RequireEmailVerificationToRegister.Description",
"en": "Whether to require email verification to register.",
"tr": "Kayıt olmak için e-posta doğrulaması gerekip gerekmediği."
},
{
"resourceName": "Platform",
"key": "Abp.Identity.User",
@ -3309,14 +3357,14 @@
{
"resourceName": "Platform",
"key": "Abp.Identity.Profile.General.RequireVerifiedAccount",
"en": "Require Verified Account",
"tr": "Hesap Doğrulaması Zorunlu mu?"
"en": "Require Admin Verified Account",
"tr": "Yönetici Hesap Doğrulaması Yapacak mı?"
},
{
"resourceName": "Platform",
"key": "Abp.Identity.Profile.General.RequireVerifiedAccount.Description",
"en": "Require Verified Account Description",
"tr": "Hesap Doğrulama Zorunlu Açıklaması"
"en": "Whether to require admin verified account to display user profile",
"tr": "Kullanıcı profilini görüntülemek için yönetici tarafından doğrulanmış hesap gerekip gerekmediği"
},
{
"resourceName": "Platform",
@ -3327,8 +3375,8 @@
{
"resourceName": "Platform",
"key": "Abp.Identity.Profile.General.BlacklistedEmailProviders.Description",
"en": "Black listed Email Providers Description",
"tr": "Kara Listedeki e-Posta Sağlayıcılarııklaması"
"en": "Comma-separated email providers that are not allowed to be used for registration or updating email. For example: yahoo.com, hotmail.com",
"tr": "Kayıt veya e-posta güncelleme için kullanılmaya izin verilmeyen virgülle ayrılmış e-posta sağlayıcıları. Örneğin: yahoo.com, hotmail.com"
},
{
"resourceName": "Platform",
@ -3594,6 +3642,36 @@
"en": "The record was deleted",
"tr": "Kayıt silindi"
},
{
"resourceName": "Platform",
"key": "TumKayitlarSilindi",
"en": "All records were deleted.",
"tr": "Tüm kayıtlar silindi."
},
{
"resourceName": "Platform",
"key": "SeciliKayitBekliyor",
"en": "The selected record is not waiting for this approval step or approval user.",
"tr": "Seçili kayit bu onay adımında veya onay kullanıcısında beklemiyor."
},
{
"resourceName": "Platform",
"key": "WorkflowAlreadyStarted",
"en": "Workflow has already been started for the selected record",
"tr": "Seçili kayıt icin workflow zaten başlamış."
},
{
"resourceName": "Platform",
"key": "SeciliKayitlarSilmekIstiyormusunuz",
"en": "{0} records will be deleted. Are you sure you want to delete?",
"tr": "{0} kayit silinecek. Silmek istediginize emin misiniz?"
},
{
"resourceName": "Platform",
"key": "TumKayitlariSilmekIstiyormusunuz",
"en": "Are you sure to delete all {0} records?",
"tr": "Tüm {0} kayıtları silmek istediğinize emin misiniz?"
},
{
"resourceName": "Platform",
"key": "KayitEklendi",
@ -3687,8 +3765,8 @@
{
"resourceName": "Platform",
"key": "ListForms.ListForm.AddNewRecord",
"en": "Add New Record",
"tr": "Yeni Kayıt Ekle"
"en": "Add",
"tr": "Ekle"
},
{
"resourceName": "Platform",
@ -4106,9 +4184,9 @@
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.NoteModal.Type.Activity",
"en": "Activity",
"tr": "Aktivite"
"key": "ListForms.ListForm.NoteModal.Type.Workflow",
"en": "Workflow",
"tr": "Akış"
},
{
"resourceName": "Platform",
@ -7314,6 +7392,24 @@
"tr": "Demo Talep Formu",
"en": "Demo Request Form"
},
{
"resourceName": "Platform",
"key": "Public.demo.newDemo",
"tr": "Yeni Demo Talebi",
"en": "New Demo Request"
},
{
"resourceName": "Platform",
"key": "Public.demo.thankYou",
"tr": "Teşekkürler!",
"en": "Thank You!"
},
{
"resourceName": "Platform",
"key": "Public.demo.resultMessage",
"tr": "Demo talebiniz başarıyla gönderildi. 24 saat içinde size geri dönüş yapacağız.",
"en": "Your demo request has been submitted successfully. We will get back to you within 24 hours."
},
{
"resourceName": "Platform",
"key": "Public.demo.users",
@ -10240,7 +10336,7 @@
"resourceName": "Platform",
"key": "App.About",
"tr": "Hakkımızda",
"en": "About Us"
"en": "About"
},
{
"resourceName": "Platform",
@ -13856,6 +13952,12 @@
"en": "Component Path",
"tr": "Bileşen Yolu"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ComponentType",
"en": "Component Type",
"tr": "Bileşen Türü"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ConditionType",
@ -16664,6 +16766,24 @@
"en": "Approver",
"tr": "Onayla"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.Rejecter",
"en": "Rejecter",
"tr": "Reddet"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ApprovalComment",
"en": "Approval or Rejection Comment",
"tr": "Onay veya red açıklaması"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.WorkflowDecisionMessage",
"en": "Workflow decision will be made for {0} record(s).",
"tr": "{0} kayit icin workflow karari verilecek."
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.NextOnStart",
@ -17402,6 +17522,12 @@
"en": "Columns load after selecting a Select Command",
"tr": "Select Command seçince sütunlar yüklenir"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step3.IncludeInEditingForm",
"en": "Include in Editing Form",
"tr": "Düzenleme Formunda Dahil Et"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step3.GenerateFromTable",
@ -17642,6 +17768,12 @@
"en": "Key Field Type",
"tr": "Anahtar Alan Tipi"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step4.ColumnsAndFormLayout",
"en": "Columns & Form Layout",
"tr": "Sütunlar ve Form Yerleşimi"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step4.SelectedColumns",
@ -17666,6 +17798,24 @@
"en": "Field",
"tr": "Alan"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step4.EditingForm",
"en": "Popup Form",
"tr": "Popup Form"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step4.EditingFormColumns",
"en": "Popup Form Columns",
"tr": "Popup Form Sütunları"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step4.UngroupedColumns",
"en": "Ungrouped Columns",
"tr": "Gruplanmamış Sütunlar"
},
{
"resourceName": "Platform",
"key": "ListForms.Wizard.Step4.DeployAndSave",
@ -17966,6 +18116,12 @@
"en": "Add Multi-Tenant Column",
"tr": "MultiTenant Sütunları Ekle"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.AddWorkflowColumns",
"en": "Add Workflow Column",
"tr": "Workflow Sütunları Ekle"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.ClearAllColumns",
@ -18914,6 +19070,12 @@
"en": "Approval Status Field Name",
"tr": "Onay Durumu Alanı Adı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.Workflow.IsFilterUserName",
"en": "Filter User Name?",
"tr": "Kullanıcı Adı Filtresin mi?"
},
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName",
@ -19159,6 +19321,84 @@
"key": "FileManager.SortByModifiedDesc",
"en": "Modified (Newest)",
"tr": "Değiştirilme (En Yeni)"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.WorkflowStarted",
"en": "Operation: Workflow started",
"tr": "İşlem: Workflow başlatıldı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.InformReached",
"en": "Operation: Workflow inform step reached",
"tr": "İşlem: Workflow bilgilendirme adımına ulaştı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.WorkflowCompleted",
"en": "Operation: Workflow completed",
"tr": "İşlem: Workflow tamamlandı"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.Step",
"en": "Step: {0}",
"tr": "Adım: {0}"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.PerformedBy",
"en": "Performed by: {0}",
"tr": "İşlemi yapan: {0}"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.InformUser",
"en": "User to inform: {0}",
"tr": "Bilgilendirilecek kullanıcı: {0}"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.ApproverUser",
"en": "Approver user: {0}",
"tr": "Onaylayacak kullanıcı: {0}"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.NextStep.Approval",
"en": "Next step: Approval",
"tr": "Sonraki adım: Onay"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.NextStep.Inform",
"en": "Next step: Inform",
"tr": "Sonraki adım: Bilgilendirme"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.NextStep.End",
"en": "Next step: Workflow end",
"tr": "Sonraki adım: Workflow bitiş"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.NextStepName",
"en": "Next step name: {0}",
"tr": "Sonraki adım adı: {0}"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.NextStep",
"en": "Next step: {0}",
"tr": "Sonraki adım: {0}"
},
{
"resourceName": "Platform",
"key": "ListForms.ListForm.Workflow.UndefinedUser",
"en": "Undefined",
"tr": "Tanımsız"
}
]
}

View file

@ -797,7 +797,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
PermissionJson = DefaultPermissionJson(PlatformConsts.IdentityPermissions.Users.Create, listFormName, PlatformConsts.IdentityPermissions.Users.Update, PlatformConsts.IdentityPermissions.Users.Delete, PlatformConsts.IdentityPermissions.Users.Export, PlatformConsts.IdentityPermissions.Users.Import, PlatformConsts.IdentityPermissions.Users.Note),
DeleteCommand = $"UPDATE \"AbpUsers\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id",
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 600, true, true, true, true, false),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 710, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[
new EditingFormItemDto { Order=1, DataField="Email", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
@ -807,7 +807,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
new EditingFormItemDto { Order=5, DataField="WorkHour", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton },
new EditingFormItemDto { Order=6, DataField="DepartmentId", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton },
new EditingFormItemDto { Order=7, DataField="JobPositionId", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxSelectBox, EditorOptions = EditorOptionValues.ShowClearButton },
new EditingFormItemDto { Order=8, DataField="Password", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
new EditingFormItemDto { Order=8, DataField="Password", ColSpan=1, EditorType2=EditorTypes.dxTextBox },
new EditingFormItemDto { Order=9, DataField="IsActive", ColSpan=1, EditorType2=EditorTypes.dxCheckBox },
]}
}),
@ -980,6 +980,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
IsActive = true,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
EditorOptions = EditorOptionValues.PhoneEditorOptions,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(PlatformConsts.IdentityPermissions.Users.Create, PlatformConsts.IdentityPermissions.Users.Default, PlatformConsts.IdentityPermissions.Users.Update, true, true, false),
@ -1540,7 +1541,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
}),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "Status", FieldDbType = DbType.String, Value = "Aktif", CustomValueType = FieldCustomValueTypeEnum.Value },
new() { FieldName = "Status", FieldDbType = DbType.String, Value = "active", CustomValueType = FieldCustomValueTypeEnum.Value },
}),
CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] {
new() {
@ -1638,8 +1639,8 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
DisplayExpr = "name",
ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key="Aktif", Name="Aktif" },
new () { Key="Pasif", Name="Pasif" },
new () { Key= "active", Name= "Aktif" },
new () { Key= "passive", Name= "Pasif" },
}),
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
@ -3044,7 +3045,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key= "active", Name= "Aktif" },
new () { Key= "passive", Name= "Kapalı" },
new () { Key= "passive", Name= "Pasif" },
}),
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,

View file

@ -12,7 +12,7 @@ public static class ListFormSeeder_DefaultJsons
{
public static string DefaultDeleteCommand(string tableName)
{
return $"UPDATE \"{TableNameResolver.GetFullTableName(tableName)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
return $"UPDATE \"{TableNameResolver.GetFullTableName(tableName)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id";
}
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid, string newId = "@NEWID") => JsonSerializer.Serialize(new FieldsDefaultValue[]

View file

@ -104,7 +104,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
new EditingFormItemDto { Order=7, DataField = "PhoneNumber", ColSpan=1, IsRequired=false, EditorType2=EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions },
new EditingFormItemDto { Order=8, DataField = "FaxNumber", ColSpan=1, IsRequired=false, EditorType2=EditorTypes.dxTextBox, EditorOptions=EditorOptionValues.PhoneEditorOptions },
new EditingFormItemDto { Order=9, DataField = "IsActive", ColSpan=1, IsRequired=false, EditorType2=EditorTypes.dxCheckBox },
new EditingFormItemDto { Order=10, DataField = "MaxConcurrentUsers", ColSpan=1, IsRequired=false, EditorType2=EditorTypes.dxNumberBox },
]
},
new() { Order=2, ColCount=3, ColSpan=1, ItemType="group", Items =
@ -119,6 +118,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
new EditingFormItemDto { Order=8, DataField = "Email", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
new EditingFormItemDto { Order=9, DataField = "Website", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
new EditingFormItemDto { Order=10, DataField = "MenuGroup", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
new EditingFormItemDto { Order=11, DataField = "MaxConcurrentUsers", ColSpan=1, IsRequired=false, EditorType2=EditorTypes.dxNumberBox },
]
}
}),
@ -2302,7 +2302,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 300, true, true, true, false, false),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 550, true, true, true, false, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
new() {
Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
@ -4025,7 +4025,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
HeaderFilterJson = DefaultHeaderFilterJson,
SearchPanelJson = DefaultSearchPanelJson,
GroupPanelJson = DefaultGroupPanelJson,
SelectionJson = DefaultSelectionMultipleJson,
SelectionJson = DefaultSelectionSingleJson,
ColumnOptionJson = DefaultColumnOptionJson(false),
PermissionJson = DefaultPermissionJson(listFormName),
PagerOptionJson = DefaultPagerOptionJson,
@ -5469,7 +5469,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
DeleteCommand = $"UPDATE \"{FullNameTable(TableNameEnum.Route)}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id",
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
PagerOptionJson = DefaultPagerOptionJson,
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 400, true, true, true, true, false),
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 550, true, true, true, true, false),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
{
new() {
@ -5477,9 +5477,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
[
new EditingFormItemDto { Order = 1, DataField = "Key", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 2, DataField = "Path", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 3, DataField = "ComponentPath", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 4, DataField = "RouteType", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
new EditingFormItemDto { Order = 5, DataField = "Authority", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }
new EditingFormItemDto { Order = 3, DataField = "ComponentType", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox },
new EditingFormItemDto { Order = 4, DataField = "ComponentPath", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 5, DataField = "RouteType", ColSpan = 1, IsRequired = true, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
new EditingFormItemDto { Order = 6, DataField = "Authority", ColSpan = 1, EditorType2 = EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }
]
}
}),
@ -5541,6 +5542,33 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "ComponentType",
CaptionName = "App.Listform.ListformField.ComponentType",
Width = 0,
ListOrderNo = 5,
Visible = true,
IsActive = true,
AllowSearch = false,
LookupJson = JsonSerializer.Serialize(new LookupDto
{
DataSourceType = UiLookupDataSourceTypeEnum.StaticData,
DisplayExpr = "name",
ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key="normal",Name="Normal" },
new () { Key="dynamic",Name="Dynamic" },
}),
}),
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
@ -5548,7 +5576,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
FieldName = "ComponentPath",
CaptionName = "App.Listform.ListformField.ComponentPath",
Width = 0,
ListOrderNo = 4,
ListOrderNo = 5,
Visible = true,
IsActive = true,
@ -5565,7 +5593,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
FieldName = "RouteType",
CaptionName = "App.Listform.ListformField.RouteType",
Width = 0,
ListOrderNo = 5,
ListOrderNo = 6,
Visible = true,
IsActive = true,
@ -5593,7 +5621,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
FieldName = "Authority",
CaptionName = "App.Listform.ListformField.Authority",
Width = 0,
ListOrderNo = 6,
ListOrderNo = 7,
Visible = true,
IsActive = true,

View file

@ -14,6 +14,7 @@ public class RouteSeedDto
{
public string Key { get; set; }
public string Path { get; set; }
public string ComponentType { get; set; }
public string ComponentPath { get; set; }
public string RouteType { get; set; }
public string[] Authority { get; set; }
@ -79,6 +80,7 @@ public class MenuDataSeeder : IDataSeedContributor, ITransientDependency
await _routeRepository.InsertAsync(new Route(
item.Key,
item.Path,
item.ComponentType,
item.ComponentPath,
item.RouteType,
item.Authority ?? []

View file

@ -1,15 +1,17 @@
{
"Routes": [
{
"key": "dynamic.RoleListComponent",
"key": "roleListComponent",
"path": "/admin/RoleListComponent",
"componentPath": "dynamic:RoleListComponent",
"componentType": "dynamic",
"componentPath": "RoleListComponent",
"routeType": "protected",
"authority": []
},
{
"key": "home",
"path": "/home",
"componentType": "normal",
"componentPath": "@/views/public/Home",
"routeType": "public",
"authority": []
@ -17,6 +19,7 @@
{
"key": "about",
"path": "/about",
"componentType": "normal",
"componentPath": "@/views/public/About",
"routeType": "public",
"authority": []
@ -24,6 +27,7 @@
{
"key": "products",
"path": "/products",
"componentType": "normal",
"componentPath": "@/views/public/Products",
"routeType": "public",
"authority": []
@ -31,6 +35,7 @@
{
"key": "checkout",
"path": "/checkout",
"componentType": "normal",
"componentPath": "@/views/public/Checkout",
"routeType": "public",
"authority": []
@ -38,6 +43,7 @@
{
"key": "payment",
"path": "/payment",
"componentType": "normal",
"componentPath": "@/views/public/Payment",
"routeType": "public",
"authority": []
@ -45,6 +51,7 @@
{
"key": "success",
"path": "/success",
"componentType": "normal",
"componentPath": "@/views/public/Success",
"routeType": "public",
"authority": []
@ -52,6 +59,7 @@
{
"key": "services",
"path": "/services",
"componentType": "normal",
"componentPath": "@/views/public/Services",
"routeType": "public",
"authority": []
@ -59,6 +67,7 @@
{
"key": "blog",
"path": "/blog",
"componentType": "normal",
"componentPath": "@/views/public/Blog",
"routeType": "public",
"authority": []
@ -66,6 +75,7 @@
{
"key": "blogDetail",
"path": "/blog/:id",
"componentType": "normal",
"componentPath": "@/views/public/BlogDetail",
"routeType": "public",
"authority": []
@ -73,6 +83,7 @@
{
"key": "demo",
"path": "/demo",
"componentType": "normal",
"componentPath": "@/views/public/Demo",
"routeType": "public",
"authority": []
@ -80,6 +91,7 @@
{
"key": "contact",
"path": "/contact",
"componentType": "normal",
"componentPath": "@/views/public/Contact",
"routeType": "public",
"authority": []
@ -87,6 +99,7 @@
{
"key": "access-denied",
"path": "/access-denied",
"componentType": "normal",
"componentPath": "@/views/AccessDenied",
"routeType": "public",
"authority": []
@ -94,6 +107,7 @@
{
"key": "login",
"path": "/login",
"componentType": "normal",
"componentPath": "@/views/auth/Login",
"routeType": "authenticated",
"authority": []
@ -101,6 +115,7 @@
{
"key": "register",
"path": "/register",
"componentType": "normal",
"componentPath": "@/views/auth/Register",
"routeType": "authenticated",
"authority": []
@ -108,6 +123,7 @@
{
"key": "forgotPassword",
"path": "/forgot-password",
"componentType": "normal",
"componentPath": "@/views/auth/ForgotPassword",
"routeType": "authenticated",
"authority": []
@ -115,6 +131,7 @@
{
"key": "resetPassword",
"path": "/reset-password",
"componentType": "normal",
"componentPath": "@/views/auth/ResetPassword",
"routeType": "authenticated",
"authority": []
@ -122,6 +139,7 @@
{
"key": "sendConfirmationCode",
"path": "/confirm",
"componentType": "normal",
"componentPath": "@/views/auth/SendConfirmationCode",
"routeType": "authenticated",
"authority": []
@ -129,6 +147,7 @@
{
"key": "sendExtendLogin",
"path": "/extend-login",
"componentType": "normal",
"componentPath": "@/views/auth/ExtendLogin",
"routeType": "authenticated",
"authority": []
@ -136,6 +155,7 @@
{
"key": "verifyConfirmationCode",
"path": "/confirm/:userId/:token",
"componentType": "normal",
"componentPath": "@/views/auth/VerifyConfirmationCode",
"routeType": "authenticated",
"authority": []
@ -143,6 +163,7 @@
{
"key": "admin.dashboard",
"path": "/admin/dashboard",
"componentType": "normal",
"componentPath": "@/views/Dashboard",
"routeType": "protected",
"authority": []
@ -150,6 +171,7 @@
{
"key": "admin.menuManager",
"path": "/admin/menuManager",
"componentType": "normal",
"componentPath": "@/views/menu/MenuManager",
"routeType": "protected",
"authority": ["App.Menus.Manager"]
@ -157,6 +179,7 @@
{
"key": "admin.listFormManagement.wizard",
"path": "/admin/listform/wizard",
"componentType": "normal",
"componentPath": "@/views/admin/listForm/wizard/Wizard",
"routeType": "protected",
"authority": ["App.Listforms.Wizard"]
@ -164,6 +187,7 @@
{
"key": "admin.listFormManagement.wizardManager",
"path": "/admin/listform/wizardManager",
"componentType": "normal",
"componentPath": "@/views/admin/listForm/wizard/WizardFileManager",
"routeType": "protected",
"authority": ["App.Listforms.Wizard"]
@ -171,6 +195,7 @@
{
"key": "admin.listFormManagement.edit",
"path": "/admin/listform/edit/:listFormCode",
"componentType": "normal",
"componentPath": "@/views/admin/listForm/edit/FormEdit",
"routeType": "protected",
"authority": []
@ -178,6 +203,7 @@
{
"key": "admin.forumManagement",
"path": "/admin/forumManagement",
"componentType": "normal",
"componentPath": "@/views/forum/Management",
"routeType": "protected",
"authority": ["App.ForumManagement"]
@ -185,6 +211,7 @@
{
"key": "admin.ai",
"path": "/admin/ai",
"componentType": "normal",
"componentPath": "@/views/ai/Assistant",
"routeType": "protected",
"authority": ["App.Definitions.AiBot.Asistant"]
@ -192,6 +219,7 @@
{
"key": "admin.profile.general",
"path": "/admin/profile/general",
"componentType": "normal",
"componentPath": "@/views/admin/profile/Profile",
"routeType": "protected",
"authority": []
@ -199,6 +227,7 @@
{
"key": "admin.profile.password",
"path": "/admin/profile/password",
"componentType": "normal",
"componentPath": "@/views/admin/profile/Profile",
"routeType": "protected",
"authority": []
@ -206,6 +235,7 @@
{
"key": "admin.profile.notificationSettings",
"path": "/admin/profile/notification-settings",
"componentType": "normal",
"componentPath": "@/views/admin/profile/Profile",
"routeType": "protected",
"authority": []
@ -213,6 +243,7 @@
{
"key": "admin.activityLog",
"path": "/admin/activityLog",
"componentType": "normal",
"componentPath": "@/views/admin/activityLog/ActivityLog",
"routeType": "protected",
"authority": []
@ -220,6 +251,7 @@
{
"key": "admin.changeLog",
"path": "/admin/changeLog",
"componentType": "normal",
"componentPath": "@/views/version/ChangeLog",
"routeType": "protected",
"authority": []
@ -227,6 +259,7 @@
{
"key": "admin.settings",
"path": "/admin/settings",
"componentType": "normal",
"componentPath": "@/views/settings/Settings",
"routeType": "protected",
"authority": ["App.Setting"]
@ -234,6 +267,7 @@
{
"key": "admin.identity.user.detail",
"path": "/admin/users/detail/:userId",
"componentType": "normal",
"componentPath": "@/views/admin/user-management/Details",
"routeType": "protected",
"authority": ["AbpIdentity.Users.Update"]
@ -241,6 +275,7 @@
{
"key": "admin.identity.ous",
"path": "/admin/ous",
"componentType": "normal",
"componentPath": "@/views/admin/organization-unit/OrganizationUnits",
"routeType": "protected",
"authority": ["Abp.Identity.OrganizationUnits"]
@ -248,6 +283,7 @@
{
"key": "admin.hr.organization",
"path": "/admin/organization",
"componentType": "normal",
"componentPath": "@/views/admin/hr/OrgChart",
"routeType": "protected",
"authority": ["App.Definitions.Department"]
@ -255,6 +291,7 @@
{
"key": "admin.forum",
"path": "/admin/forum",
"componentType": "normal",
"componentPath": "@/views/forum/Forum",
"routeType": "protected",
"authority": ["App.ForumManagement.Publish"]
@ -262,6 +299,7 @@
{
"key": "admin.list",
"path": "/admin/list/:listFormCode",
"componentType": "normal",
"componentPath": "@/views/list/List",
"routeType": "protected",
"authority": []
@ -269,6 +307,7 @@
{
"key": "admin.formNew",
"path": "/admin/form/:listFormCode",
"componentType": "normal",
"componentPath": "@/views/form/FormNew",
"routeType": "protected",
"authority": []
@ -276,6 +315,7 @@
{
"key": "admin.formView",
"path": "/admin/form/:listFormCode/:id",
"componentType": "normal",
"componentPath": "@/views/form/FormView",
"routeType": "protected",
"authority": []
@ -283,6 +323,7 @@
{
"key": "admin.formEdit",
"path": "/admin/form/:listFormCode/:id/edit",
"componentType": "normal",
"componentPath": "@/views/form/FormEdit",
"routeType": "protected",
"authority": []
@ -290,6 +331,7 @@
{
"key": "admin.chart",
"path": "/admin/chart/:listFormCode",
"componentType": "normal",
"componentPath": "@/views/list/Chart",
"routeType": "protected",
"authority": []
@ -297,6 +339,7 @@
{
"key": "admin.sqlQueryManager",
"path": "/admin/sqlQueryManager",
"componentType": "normal",
"componentPath": "@/views/developerKit/SqlQueryManager",
"routeType": "protected",
"authority": ["App.SqlQueryManager"]
@ -304,6 +347,7 @@
{
"key": "admin.developerkit.endpoints",
"path": "/admin/developerkit/endpoints",
"componentType": "normal",
"componentPath": "@/views/developerKit/CrudEndpointManager",
"routeType": "protected",
"authority": ["App.DeveloperKit.CrudEndpoints"]
@ -311,6 +355,7 @@
{
"key": "admin.developerkit.dynamic-services",
"path": "/admin/developerkit/dynamic-services",
"componentType": "normal",
"componentPath": "@/views/developerKit/DynamicServiceManager",
"routeType": "protected",
"authority": ["App.DeveloperKit.DynamicServices"]
@ -318,6 +363,7 @@
{
"key": "admin.developerkit.dynamic-services.new",
"path": "/admin/developerkit/dynamic-services/new",
"componentType": "normal",
"componentPath": "@/views/developerKit/DynamicServiceEditor",
"routeType": "protected",
"authority": ["App.DeveloperKit.DynamicServices"]
@ -325,6 +371,7 @@
{
"key": "admin.developerkit.dynamic-services.edit",
"path": "/admin/developerkit/dynamic-services/edit/:id",
"componentType": "normal",
"componentPath": "@/views/developerKit/DynamicServiceEditor",
"routeType": "protected",
"authority": ["App.DeveloperKit.DynamicServices"]
@ -332,6 +379,7 @@
{
"key": "admin.developerkit.components",
"path": "/admin/developerkit/components",
"componentType": "normal",
"componentPath": "@/views/developerKit/ComponentManagerPage",
"routeType": "protected",
"authority": ["App.DeveloperKit.Components"]
@ -339,6 +387,7 @@
{
"key": "admin.developerkit.components.new",
"path": "/admin/developerkit/components/new",
"componentType": "normal",
"componentPath": "@/views/developerKit/ComponentEditorPage",
"routeType": "protected",
"authority": ["App.DeveloperKit.Components"]
@ -346,6 +395,7 @@
{
"key": "admin.developerkit.components.view",
"path": "/admin/developerkit/components/view/:id",
"componentType": "normal",
"componentPath": "@/views/developerKit/ComponentEditorPage",
"routeType": "protected",
"authority": ["App.DeveloperKit.Components"]
@ -353,6 +403,7 @@
{
"key": "admin.developerkit.components.edit",
"path": "/admin/developerkit/components/edit/:id",
"componentType": "normal",
"componentPath": "@/views/developerKit/ComponentCodeLayout",
"routeType": "protected",
"authority": ["App.DeveloperKit.Components"]
@ -360,6 +411,7 @@
{
"key": "admin.fileManagement",
"path": "/admin/files",
"componentType": "normal",
"componentPath": "@/views/admin/files/FileManager",
"routeType": "protected",
"authority": ["App.Files"]
@ -367,6 +419,7 @@
{
"key": "admin.devexpressReportView",
"path": "/admin/reports/:id/view",
"componentType": "normal",
"componentPath": "@/views/report/DevexpressReportViewer",
"routeType": "protected",
"authority": []
@ -374,6 +427,7 @@
{
"key": "admin.devexpressReportDesigner",
"path": "/admin/reports/:id/design",
"componentType": "normal",
"componentPath": "@/views/report/DevexpressReportDesigner",
"routeType": "protected",
"authority": []
@ -381,6 +435,7 @@
{
"key": "homeDesigner",
"path": "/admin/public/home/designer",
"componentType": "normal",
"componentPath": "@/views/public/Home",
"routeType": "protected",
"authority": ["App.Home"]
@ -388,6 +443,7 @@
{
"key": "aboutDesigner",
"path": "/admin/public/about/designer",
"componentType": "normal",
"componentPath": "@/views/public/About",
"routeType": "protected",
"authority": ["App.About"]
@ -395,6 +451,7 @@
{
"key": "servicesDesigner",
"path": "/admin/public/services/designer",
"componentType": "normal",
"componentPath": "@/views/public/Services",
"routeType": "protected",
"authority": ["App.Services"]
@ -402,6 +459,7 @@
{
"key": "contactDesigner",
"path": "/admin/public/contact/designer",
"componentType": "normal",
"componentPath": "@/views/public/Contact",
"routeType": "protected",
"authority": ["App.Contact"]
@ -409,6 +467,7 @@
{
"key": "admin.videoroom.dashboard",
"path": "/admin/videoroom/dashboard",
"componentType": "normal",
"componentPath": "@/views/admin/videoroom/Dashboard",
"routeType": "protected",
"authority": ["App.Videoroom.Dashboard"]
@ -416,6 +475,7 @@
{
"key": "admin.videoroom.list",
"path": "/admin/videoroom/list",
"componentType": "normal",
"componentPath": "@/views/admin/videoroom/RoomList",
"routeType": "protected",
"authority": ["App.Videoroom.List"]
@ -423,6 +483,7 @@
{
"key": "admin.videoroom.roomdetail",
"path": "/admin/videoroom/room/:id",
"componentType": "normal",
"componentPath": "@/views/admin/videoroom/RoomDetail",
"routeType": "protected",
"authority": ["App.Videoroom.RoomDetail"]

View file

@ -74,7 +74,7 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder
branchName = tenant.GetOrganizationName();
}
}
//Default Branch otomatik olarak oluşturuluyor.
var defaultBranch = await _branchRepository.FirstOrDefaultAsync(b => b.Code == branchCode);
if (defaultBranch == null)
@ -135,10 +135,10 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder
Surname = PlatformConsts.AbpIdentity.User.AdminSurNameDefaultValue,
};
adminUser.SetEmailConfirmed(true);
adminUser.SetIsVerified(true);
adminUser.SetEmailConfirmed(true);
adminUser.SetRocketUsername(PlatformConsts.AbpIdentity.User.AdminRocketUsernameDefaultValue);
adminUser.SetPhoneNumber(PlatformConsts.AbpIdentity.User.AdminPhoneNumberDefaultValue, true);
adminUser.SetPhoneNumber(PlatformConsts.AbpIdentity.User.AdminPhoneNumberDefaultValue, adminUser.PhoneNumberConfirmed);
adminUser.SetWorkHour(PlatformConsts.AbpIdentity.User.AdminWorkHourDefaultValue);
adminUser.SetNationality(PlatformConsts.AbpIdentity.User.AdminNationalityDefaultValue);
adminUser.SetBloodType(PlatformConsts.AbpIdentity.User.AdminBloodTypeDefaultValue);

View file

@ -0,0 +1,25 @@
IF OBJECT_ID(N'[dbo].[Sal_T_Approval]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Sal_T_Approval]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
[TenantId] uniqueidentifier NULL,
[ApprovalUserName] nvarchar(256) NULL,
[ApprovalStatus] nvarchar(50) NULL,
[ApprovalDate] datetime NULL,
[ApprovalDescription] nvarchar(200) NULL,
[Name] nvarchar(100) NULL,
CONSTRAINT [PK_Sal_T_Approval] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,385 @@
{
"Wizard": {
"WizardName": "Approval",
"ListFormCode": "App.Wizard.Approval",
"MenuCode": "App.Wizard.Approval",
"IsTenant": true,
"IsBranch": false,
"IsOrganizationUnit": false,
"AllowAdding": true,
"AllowUpdating": true,
"AllowDeleting": true,
"AllowDetail": false,
"ConfirmDelete": true,
"DefaultLayout": "grid",
"Grid": true,
"Pivot": true,
"Tree": true,
"Chart": true,
"Gantt": true,
"Scheduler": true,
"LanguageTextMenuEn": "Approval",
"LanguageTextMenuTr": "Approval",
"LanguageTextTitleEn": "Approval",
"LanguageTextTitleTr": "Onaylama",
"LanguageTextDescEn": "Approval",
"LanguageTextDescTr": "Onaylama",
"LanguageTextMenuParentEn": "Sales",
"LanguageTextMenuParentTr": "Sat\u0131\u015F",
"PermissionGroupName": "App.Wizard.Sales",
"MenuParentCode": "App.Wizard.Sales",
"MenuParentIcon": "FcAssistant",
"MenuIcon": "FcAndroidOs",
"DataSourceCode": "Default",
"DataSourceConnectionString": "",
"SelectCommandType": 1,
"SelectCommand": "Sal_T_Approval",
"KeyFieldName": "Id",
"KeyFieldDbSourceType": 9,
"TreeKeyExpr": "",
"TreeParentIdExpr": "",
"TreeAutoExpandAll": false,
"GanttKeyExpr": "",
"GanttParentIdExpr": "",
"GanttAutoExpandAll": false,
"GanttTitleExpr": "",
"GanttStartExpr": "",
"GanttEndExpr": "",
"GanttProgressExpr": "",
"SchedulerTextExpr": "",
"SchedulerStartDateExpr": "",
"SchedulerEndDateExpr": "",
"Groups": [
{
"Caption": "",
"ColCount": 1,
"Items": [
{
"DataField": "Id",
"CaptionName": "App.Listform.ListformField.Id",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": true,
"IncludeInEditingForm": true,
"DbSourceType": 9,
"TurkishCaption": "Id",
"EnglishCaption": "Id",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"DataField": "Name",
"CaptionName": "App.Listform.ListformField.Name",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": true,
"DbSourceType": 16,
"TurkishCaption": "Name",
"EnglishCaption": "Name",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"DataField": "ApprovalUserName",
"CaptionName": "App.Listform.ListformField.ApprovalUserName",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Approval User Name",
"EnglishCaption": "Approval User Name",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"DataField": "ApprovalStatus",
"CaptionName": "App.Listform.ListformField.ApprovalStatus",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Approval Status",
"EnglishCaption": "Approval Status",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"DataField": "ApprovalDate",
"CaptionName": "App.Listform.ListformField.ApprovalDate",
"EditorType": "dxDateBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 6,
"TurkishCaption": "Approval Date",
"EnglishCaption": "Approval Date",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
},
{
"DataField": "ApprovalDescription",
"CaptionName": "App.Listform.ListformField.ApprovalDescription",
"EditorType": "dxTextBox",
"EditorOptions": "",
"EditorScript": "",
"ColSpan": 1,
"IsRequired": false,
"IncludeInEditingForm": false,
"DbSourceType": 16,
"TurkishCaption": "Approval Description",
"EnglishCaption": "Approval Description",
"LookupDataSourceType": 1,
"ValueExpr": "Key",
"DisplayExpr": "Name",
"LookupQuery": ""
}
]
}
],
"SubForms": [],
"Widgets": [],
"Workflow": {
"ApprovalUserFieldName": "ApprovalUserName",
"IsFilterUserName": true,
"ApprovalDateFieldName": "ApprovalDate",
"ApprovalStatusFieldName": "ApprovalStatus",
"ApprovalDescriptionFieldName": "ApprovalDescription",
"Criteria": [
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Start",
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Ba\u015Flat1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "",
"NextOnStart": "N002",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 34,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N001"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "admin@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N003",
"NextOnReject": "N004",
"PositionX": 323,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N002"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay2",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "demo@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N004",
"NextOnReject": "N004",
"PositionX": 586,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N003"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Inform",
"Title": "Bilgilendirme1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "system@sozsoft.com",
"NextOnStart": "N005",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 458,
"PositionY": 411,
"CompareOutcomes": [],
"Id": "N004"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "End",
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Bitir1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 792,
"PositionY": 412,
"CompareOutcomes": [],
"Id": "N005"
}
]
},
"WorkflowCriteria": [
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Start",
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Ba\u015Flat1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "",
"NextOnStart": "N002",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 34,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N001"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "admin@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N003",
"NextOnReject": "N004",
"PositionX": 323,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N002"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Approval",
"Title": "Onay2",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "demo@sozsoft.com",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "N004",
"NextOnReject": "N004",
"PositionX": 586,
"PositionY": 104,
"CompareOutcomes": [],
"Id": "N003"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "Inform",
"Title": "Bilgilendirme1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "system@sozsoft.com",
"NextOnStart": "N005",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 458,
"PositionY": 411,
"CompareOutcomes": [],
"Id": "N004"
},
{
"ListFormCode": "App.Wizard.Approval",
"Kind": "End",
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Bitir1",
"CompareColumn": "Price",
"CompareOperator": "\u003E",
"CompareValue": 5000,
"Approver": "",
"NextOnStart": "",
"NextOnTrue": "",
"NextOnFalse": "",
"NextOnApprove": "",
"NextOnReject": "",
"PositionX": 792,
"PositionY": 412,
"CompareOutcomes": [],
"Id": "N005"
}
]
},
"IsDeletedField": true,
"IsCreatedField": true,
"InsertedRecords": {
"LanguageKeys": [
"App.Wizard.Approval",
"App.Wizard.Approval.Title",
"App.Wizard.Approval.Desc",
"App.Listform.ListformField.ApprovalUserName",
"App.Listform.ListformField.ApprovalStatus",
"App.Listform.ListformField.ApprovalDate",
"App.Listform.ListformField.ApprovalDescription"
],
"PermissionGroupNames": [
"App.Wizard.Sales"
],
"PermissionNames": [
"App.Wizard.Approval",
"App.Wizard.Approval.Create",
"App.Wizard.Approval.Update",
"App.Wizard.Approval.Delete",
"App.Wizard.Approval.Export",
"App.Wizard.Approval.Import",
"App.Wizard.Approval.Note"
],
"MenuCodes": [
"App.Wizard.Approval"
],
"DataSourceCodes": []
}
}

View file

@ -36,6 +36,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<DataSource, Guid> _repoDataSource;
private readonly IRepository<ListForm, Guid> _repoListForm;
private readonly IRepository<ListFormField, Guid> _repoListFormField;
private readonly IRepository<ListFormWorkflow, string> _repoListFormWorkflow;
private readonly ILogger<WizardDataSeeder> _logger;
private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage;
@ -51,6 +52,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<DataSource, Guid> repoDataSource,
IRepository<ListForm, Guid> repoListForm,
IRepository<ListFormField, Guid> repoListFormField,
IRepository<ListFormWorkflow, string> repoListFormWorkflow,
ILogger<WizardDataSeeder> logger)
{
_repoLangKey = repoLangKey;
@ -62,6 +64,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
_repoDataSource = repoDataSource;
_repoListForm = repoListForm;
_repoListFormField = repoListFormField;
_repoListFormWorkflow = repoListFormWorkflow;
_logger = logger;
}
@ -134,13 +137,29 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
input.Widgets ??= new List<WidgetEditDto>();
input.Workflow ??= new WorkflowDto();
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>();
if (input.WorkflowCriteria.Count == 0 && input.Workflow.Criteria?.Count > 0)
{
input.WorkflowCriteria = input.Workflow.Criteria;
}
input.Workflow.Criteria = input.WorkflowCriteria;
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria);
var wizardName = input.WizardName.Trim();
var titleLangKey = WizardConsts.WizardKeyTitle(wizardName);
var nameLangKey = WizardConsts.WizardKey(wizardName);
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
var code = WizardConsts.WizardKey(wizardName);
var code = string.IsNullOrWhiteSpace(input.MenuCode)
? WizardConsts.WizardKey(wizardName)
: input.MenuCode.Trim();
var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode)
? code
: input.ListFormCode.Trim();
var titleLangKey = $"{listFormCode}.Title";
var nameLangKey = code;
var descLangKey = $"{listFormCode}.Desc";
var permCreateName = $"{code}.Create";
var permUpdateName = $"{code}.Update";
var permDeleteName = $"{code}.Delete";
var permExportName = $"{code}.Export";
var permImportName = $"{code}.Import";
var permNoteName = $"{code}.Note";
// Dil - Language Keys
await CreateLangKeyAsync(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
@ -153,7 +172,10 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
{
await _repoPermGroup.InsertAsync(
new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true);
await CreateLangKeyAsync(groupName, groupName, groupName);
if (string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase))
await EnsureLangKeyAsync(groupName);
else
await CreateLangKeyAsync(groupName, groupName, groupName);
}
// Permissions - tek seferde mevcut permission'ları çek, sonra her birini kontrol et
@ -165,35 +187,35 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
permRead = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: true);
var permCreate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermCreate(wizardName));
var permCreate = existingPerms.FirstOrDefault(a => a.Name == permCreateName);
if (permCreate == null)
permCreate = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, WizardConsts.PermCreate(wizardName), permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: true);
Guid.NewGuid(), groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: true);
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermUpdate(wizardName));
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == permUpdateName);
if (permUpdate == null)
permUpdate = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, WizardConsts.PermUpdate(wizardName), permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: true);
Guid.NewGuid(), groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: true);
var permDelete = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermDelete(wizardName));
var permDelete = existingPerms.FirstOrDefault(a => a.Name == permDeleteName);
if (permDelete == null)
permDelete = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, WizardConsts.PermDelete(wizardName), permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: true);
Guid.NewGuid(), groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: true);
var permExport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermExport(wizardName));
var permExport = existingPerms.FirstOrDefault(a => a.Name == permExportName);
if (permExport == null)
permExport = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, WizardConsts.PermExport(wizardName), permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: true);
Guid.NewGuid(), groupName, permExportName, permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: true);
var permImport = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermImport(wizardName));
var permImport = existingPerms.FirstOrDefault(a => a.Name == permImportName);
if (permImport == null)
permImport = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, WizardConsts.PermImport(wizardName), permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: true);
Guid.NewGuid(), groupName, permImportName, permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: true);
var permNote = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermNote(wizardName));
var permNote = existingPerms.FirstOrDefault(a => a.Name == permNoteName);
if (permNote == null)
permNote = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: true);
Guid.NewGuid(), groupName, permNoteName, permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: true);
// // Permission Grants - Admin role için, sadece eksik olanları ekle
// var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
@ -219,12 +241,18 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
if (menuParent == null)
{
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
await CreateLangKeyAsync(WizardConsts.WizardKeyParent(wizardName), input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
? input.MenuParentIcon
: !string.IsNullOrWhiteSpace(input.MenuIcon)
? input.MenuIcon
: WizardConsts.MenuIcon;
await CreateLangKeyAsync(input.MenuParentCode, input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
menuParent = await _repoMenu.InsertAsync(new Menu
{
Code = input.MenuParentCode,
DisplayName = WizardConsts.WizardKeyParent(wizardName),
DisplayName = input.MenuParentCode,
IsDisabled = false,
Icon = menuParentIcon,
Order = maxRootOrder + 1,
}, autoSave: true);
}
@ -276,7 +304,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
ColSpan = g.ColCount,
ItemType = "group",
Items = g.Items
.Where(i => i.DataField != input.KeyFieldName)
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
.Select((it, ii) => new EditingFormItemDto
{
Order = ii + 1,
@ -289,6 +317,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
})
.ToArray()
})
.Where(g => g.Items.Length > 0)
.ToList();
// ListForm - varsa sil, yeniden ekle
@ -304,6 +333,12 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
await _repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true);
}
var existingWorkflowCriteria = await _repoListFormWorkflow.GetListAsync(a => a.ListFormCode == input.ListFormCode);
if (existingWorkflowCriteria.Count > 0)
{
await _repoListFormWorkflow.DeleteManyAsync(existingWorkflowCriteria, autoSave: true);
}
// ListForm
await _repoListForm.InsertAsync(new ListForm
{
@ -311,7 +346,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
PageSize = 10,
ExportJson = WizardConsts.DefaultExportJson,
IsSubForm = false,
ShowNote = input.SubForms.Count > 0,
ShowNote = input.SubForms.Count > 0 || input.WorkflowCriteria.Count > 0,
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
CultureName = LanguageCodes.En,
ListFormCode = input.ListFormCode,
@ -332,7 +367,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.WorkflowCriteria.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone),
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
PermissionJson = WizardConsts.DefaultPermissionJson(code),
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
@ -381,6 +416,34 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
await CreateLangKeyAsync(item.CaptionName, item.EnglishCaption, item.TurkishCaption);
}
}
foreach (var criteria in input.WorkflowCriteria)
{
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);
}
}
private static bool HasWorkflow(WorkflowDto workflow, List<ListFormWorkflowCriteriaDto> criteria)
@ -394,6 +457,60 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
);
}
private static void EnsureUniqueWorkflowCriteriaTitles(List<ListFormWorkflowCriteriaDto> criteria)
{
if (criteria == null || criteria.Count == 0)
{
return;
}
var baseTitles = criteria
.Select(x => string.IsNullOrWhiteSpace(x.Title) ? NormalizeWorkflowTitleFallback(x.Kind) : x.Title.Trim())
.ToList();
var duplicateTitles = baseTitles
.GroupBy(x => x, StringComparer.OrdinalIgnoreCase)
.Where(x => x.Count() > 1)
.Select(x => x.Key)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var titleCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var usedTitles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in criteria)
{
var baseTitle = string.IsNullOrWhiteSpace(item.Title) ? NormalizeWorkflowTitleFallback(item.Kind) : item.Title.Trim();
var title = baseTitle;
if (duplicateTitles.Contains(baseTitle))
{
titleCounts.TryGetValue(baseTitle, out var count);
count++;
titleCounts[baseTitle] = count;
title = $"{baseTitle}{count}";
}
if (usedTitles.Contains(title))
{
var index = 1;
var candidate = $"{title}{index}";
while (usedTitles.Contains(candidate))
{
index++;
candidate = $"{title}{index}";
}
title = candidate;
}
item.Title = title;
usedTitles.Add(title);
}
}
private static string NormalizeWorkflowTitleFallback(string kind)
{
return string.IsNullOrWhiteSpace(kind) ? "Step" : kind.Trim();
}
private async Task CreateLangKeyAsync(string key, string textEn, string textTr)
{
if (string.IsNullOrWhiteSpace(key)) return;
@ -427,6 +544,16 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
}, autoSave: true);
}
}
private async Task EnsureLangKeyAsync(string key)
{
if (string.IsNullOrWhiteSpace(key)) return;
if (!await _repoLangKey.AnyAsync(a => a.ResourceName == _appName && a.Key == key))
{
await _repoLangKey.InsertAsync(new LanguageKey { ResourceName = _appName, Key = key }, autoSave: true);
}
}
}

View file

@ -111,7 +111,7 @@
<PackageReference Include="Hangfire.SqlServer" Version="1.8.21" />
<PackageReference Include="Volo.Abp.Autofac" Version="10.0.0" />
<PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="10.0.0" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.8" PrivateAssets="all" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.8" />
<ProjectReference Include="..\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />
<ProjectReference Include="..\Sozsoft.Platform.EntityFrameworkCore\Sozsoft.Platform.EntityFrameworkCore.csproj" />
</ItemGroup>

View file

@ -93,6 +93,13 @@ public static class PlatformConsts
{
public const string GroupName = $"{Prefix.Abp}.Identity";
public static class SignIn
{
public const string Default = GroupName + ".SignIn";
public const string RequireConfirmedEmail = Default + ".RequireConfirmedEmail";
public const string RequireConfirmedPhoneNumber = Default + ".RequireConfirmedPhoneNumber";
}
public static class Profile
{
public const string Default = GroupName + ".Profile";
@ -162,7 +169,7 @@ public static class PlatformConsts
public const string AdminRoleName = "admin";
public const string AdminNameDefaultValue = "Sedat";
public const string AdminSurNameDefaultValue = "ÖZTÜRK";
public const string AdminEmailDefaultValue = "SYSTEM@SOZSOFT.COM";
public const string AdminEmailDefaultValue = "system@sozsoft.com";
public const string AdminPasswordDefaultValue = "1q2w3E*";
public const string AdminPhoneNumberDefaultValue = "05449476346";
public const string AdminRocketUsernameDefaultValue = "sedat.ozturk";
@ -766,10 +773,10 @@ public static class PlatformConsts
{
public static class ParameterTypes
{
public const string Static = "S";
public const string Query = "Q";
public const string Path = "P";
public const string Body = "B";
public const string Static = "Static";
public const string Query = "Query";
public const string Path = "Path";
public const string Body = "Body";
}
}

View file

@ -95,9 +95,9 @@ public static class WizardConsts
public static readonly string DefaultSearchPanelJson = JsonSerializer.Serialize(new { Visible = true });
public static readonly string DefaultGroupPanelJson = JsonSerializer.Serialize(new { Visible = true });
public static readonly string DefaultSelectionSingleJson = JsonSerializer.Serialize(new
public static string DefaultSelectionSingleJson(string Mode = "none") => JsonSerializer.Serialize(new
{
Mode = GridOptions.SelectionModeNone,
Mode = Mode,
AllowSelectAll = false
});
@ -134,7 +134,7 @@ public static class WizardConsts
R = permissionName,
U = permissionName + ".Update",
E = true,
I = false,
I = true,
Deny = false
});
}
@ -163,7 +163,7 @@ public static class WizardConsts
public static string DefaultDeleteCommand(string tableName)
{
return $"UPDATE \"{tableName}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id";
return $"UPDATE \"{tableName}\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id";
}
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid)

View file

@ -26,7 +26,7 @@ public class PlatformBackgroundWorkerTemplateDefinitionProvider : TemplateDefini
foreach (var worker in workers.Where(a =>
a.IsActive &&
a.WorkerType == WorkerTypeEnum.MailQueueWorker &&
a.Options != null && a.Options != string.Empty).ToList())
a.Options != null).ToList().Where(a => !a.Options.IsNullOrWhiteSpace()))
{
var Options = JsonSerializer.Deserialize<MailQueueWorkerOptions>(worker.Options);

View file

@ -7,14 +7,16 @@ public class Route : FullAuditedEntity<Guid>
{
public string Key { get; set; }
public string Path { get; set; }
public string ComponentType { get; set; }
public string ComponentPath { get; set; }
public string RouteType { get; set; }
public string[] Authority { get; set; }
public Route(string key, string path, string componentPath, string routeType, string[] authority)
public Route(string key, string path, string componentType, string componentPath, string routeType, string[] authority)
{
Key = key;
Path = path;
ComponentType = componentType;
ComponentPath = componentPath;
RouteType = routeType;
Authority = authority;

View file

@ -6,6 +6,14 @@ namespace Sozsoft.Platform.Entities;
public class Note : FullAuditedEntity<Guid>, IMultiTenant
{
public Note()
{
}
public Note(Guid id) : base(id)
{
}
public Guid? TenantId { get; set; }
public string EntityName { get; set; }
public string EntityId { get; set; }

View file

@ -1,4 +1,5 @@
using System;
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Flurl.Http;
using Sozsoft.Platform.Localization;
@ -23,6 +24,11 @@ public class TurnstileCaptchaManager : PlatformDomainService, ICaptchaManager
public async Task<bool> VerifyCaptchaAsync(string CaptchaResponse, bool throwOnFailure = false)
{
if (CaptchaResponse.IsNullOrWhiteSpace())
{
return false;
}
var endPoint = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpAccount.Captcha.EndPoint);
var privateKey = await SettingProvider.GetOrNullAsync(PlatformConsts.AbpAccount.Captcha.SecretKey);
if (endPoint.IsNullOrWhiteSpace() || privateKey.IsNullOrWhiteSpace())
@ -46,18 +52,24 @@ public class TurnstileCaptchaManager : PlatformDomainService, ICaptchaManager
if (response != null && response.StatusCode == 200)
{
var result = await response.GetJsonAsync<dynamic>();
if (!(bool)result.success)
var result = await response.GetJsonAsync<TurnstileCaptchaVerifyResponse>();
if (result?.Success != true)
{
if (throwOnFailure)
{
throw new UserFriendlyException(Localizer[PlatformConsts.AbpIdentity.User.CaptchaWrongCode]);
}
}
return (bool)result.success;
return result?.Success == true;
}
return false;
}
}
private class TurnstileCaptchaVerifyResponse
{
[JsonPropertyName("success")]
public bool Success { get; set; }
}
}

View file

@ -89,7 +89,9 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
else if (defaultField.Value == PlatformConsts.DefaultValues.Year)
value = Clock.Now.Year;
else if (defaultField.Value == PlatformConsts.DefaultValues.Id)
value = keys?.FirstOrDefault();
value = op == OperationEnum.Delete
? keys
: keys?.FirstOrDefault();
else if (defaultField.Value == PlatformConsts.DefaultValues.NewId)
value = Guid.NewGuid();
else if (defaultField.Value == PlatformConsts.DefaultValues.Selected_Ids)

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Sozsoft.Platform.DynamicData;
using Sozsoft.Platform.Entities;
@ -169,7 +170,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
// oncelik Command alanindadir, dolu ise silme islemi buradaki sorguya yonlendirilir
if (!string.IsNullOrEmpty(command))
{
sql = command;
sql = NormalizeCollectionParameterCommand(command, parameters, dataSourceType);
}
else
{
@ -189,7 +190,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
{
var where = dataSourceType switch
{
DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN (@{listForm.KeyFieldName})",
DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN @{listForm.KeyFieldName}",
DataSourceTypeEnum.Postgresql => $"\"{listForm.KeyFieldName}\" = ANY(@{listForm.KeyFieldName})",
_ => string.Empty,
};
@ -209,7 +210,14 @@ public class QueryManager : PlatformDomainService, IQueryManager
string where = string.Empty;
if (parameters.Any())
{
where = string.Join(" AND ", parameters.Select(a => $"\"{a.Key}\" IN (@{a.Key})").ToList());
where = string.Join(
" AND ",
parameters.Select(a => dataSourceType switch
{
DataSourceTypeEnum.Mssql => $"\"{a.Key}\" IN @{a.Key}",
DataSourceTypeEnum.Postgresql => $"\"{a.Key}\" = ANY(@{a.Key})",
_ => "1 = 0",
}).ToList());
}
else
{
@ -220,7 +228,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
where = dataSourceType switch
{
DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN (@{listForm.KeyFieldName})",
DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN @{listForm.KeyFieldName}",
DataSourceTypeEnum.Postgresql => $"\"{listForm.KeyFieldName}\" = ANY(@{listForm.KeyFieldName})",
_ => "1 = 0",
};
@ -231,5 +239,78 @@ public class QueryManager : PlatformDomainService, IQueryManager
return sql;
}
private static string NormalizeCollectionParameterCommand(
string command,
Dictionary<string, object> parameters,
DataSourceTypeEnum dataSourceType)
{
if (string.IsNullOrWhiteSpace(command) || parameters == null || parameters.Count == 0)
{
return command;
}
var sql = command;
foreach (var parameter in parameters)
{
if (!IsCollectionParameter(parameter.Value))
{
continue;
}
var escapedParameterName = Regex.Escape(parameter.Key);
sql = dataSourceType switch
{
DataSourceTypeEnum.Mssql => NormalizeMssqlCollectionParameter(sql, escapedParameterName, parameter.Key),
DataSourceTypeEnum.Postgresql => NormalizePostgresqlCollectionParameter(sql, escapedParameterName, parameter.Key),
_ => sql
};
}
return sql;
}
private static bool IsCollectionParameter(object value)
{
return value is System.Collections.IEnumerable
&& value is not string
&& value is not byte[];
}
private static string NormalizeMssqlCollectionParameter(
string sql,
string escapedParameterName,
string parameterName)
{
sql = Regex.Replace(
sql,
$@"IN\s*\(\s*@{escapedParameterName}\s*\)",
$"IN @{parameterName}",
RegexOptions.IgnoreCase);
return Regex.Replace(
sql,
$@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b",
match => $"{match.Groups["column"].Value} IN @{parameterName}",
RegexOptions.IgnoreCase);
}
private static string NormalizePostgresqlCollectionParameter(
string sql,
string escapedParameterName,
string parameterName)
{
sql = Regex.Replace(
sql,
$@"IN\s*\(\s*@{escapedParameterName}\s*\)",
$"= ANY(@{parameterName})",
RegexOptions.IgnoreCase);
return Regex.Replace(
sql,
$@"(?<column>(""[^""]+""|\[[^\]]+\]|`[^`]+`|\w+))\s*=\s*@{escapedParameterName}\b",
match => $"{match.Groups["column"].Value} = ANY(@{parameterName})",
RegexOptions.IgnoreCase);
}
}

View file

@ -465,6 +465,28 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
}
}
if (listform.WorkflowJson.IsNullOrWhiteSpace() == false)
{
var workflow = JsonSerializer.Deserialize<Workflow>(listform.WorkflowJson);
if (workflow != null && workflow.IsFilterUserName)
{
if (whereParts.Any())
{
whereParts.Add("AND");
}
// Hem CurrentUserName alanı boş olan kayıtları
// hem de ApprovalUserFieldName alanı CurrentUserName'e eşit olan kayıtları getirmek istiyoruz,
// Boş olanları getirmemizin sebebi workflow start edebilmektir.
// İlk kayıt eklenince onaylayacak kişi atanmaz, böylece o kayıt onaysız olarak kalmaz ve workflow başlatılabilir olur.
whereParts.Add(
$"(\"{workflow.ApprovalUserFieldName}\" = '{CurrentUser.UserName}' " +
$"OR \"{workflow.ApprovalUserFieldName}\" IS NULL " +
$"OR \"{workflow.ApprovalUserFieldName}\" = '')"
);
}
}
if (!whereParts.Any())
{
whereParts.Add("1 = 1");

View file

@ -0,0 +1,23 @@
using System.Collections.Generic;
using Volo.Abp.Domain.Values;
namespace Sozsoft.Platform.Queries;
public class Workflow : ValueObject
{
public string ApprovalUserFieldName { get; set; }
public bool IsFilterUserName { get; set; }
public string ApprovalDateFieldName { get; set; }
public string ApprovalStatusFieldName { get; set; }
public string ApprovalDescriptionFieldName { get; set; }
protected override IEnumerable<object> GetAtomicValues()
{
yield return ApprovalUserFieldName;
yield return IsFilterUserName;
yield return ApprovalDateFieldName;
yield return ApprovalStatusFieldName;
yield return ApprovalDescriptionFieldName;
}
}

View file

@ -37,7 +37,7 @@
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="10.0.0" />
<PackageReference Include="Volo.Abp.BlobStoring" Version="10.0.0" />
<PackageReference Include="Volo.Abp.BlobStoring.FileSystem" Version="10.0.0" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.8" PrivateAssets="all" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.8" />
</ItemGroup>
</Project>

View file

@ -2,8 +2,12 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Dapper;
using Sozsoft.Platform;
using Sozsoft.Platform.DynamicData;
using Microsoft.Data.SqlClient;
using Volo.Abp.DependencyInjection;
@ -178,7 +182,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
public virtual async Task<List<T>> QueryAsync<T>(string sql, string cs, Dictionary<string, object> parameters = null)
{
var param = new DynamicParameters(parameters);
var param = CreateDynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
@ -188,7 +192,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
public virtual async Task<IEnumerable<dynamic>> QueryAsync(string sql, string cs, Dictionary<string, object> parameters = null)
{
var param = new DynamicParameters(parameters);
var param = CreateDynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
@ -197,7 +201,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
public virtual async Task<T> QuerySingleAsync<T>(string sql, string cs, Dictionary<string, object> parameters = null)
{
var param = new DynamicParameters(parameters);
var param = CreateDynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
@ -206,7 +210,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
public virtual async Task<T> ExecuteScalarAsync<T>(string sql, string cs, Dictionary<string, object> parameters = null)
{
var param = new DynamicParameters(parameters);
var param = CreateDynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
@ -237,13 +241,257 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
public virtual async Task<int> ExecuteAsync(string sql, string cs, Dictionary<string, object> parameters = null)
{
var param = new DynamicParameters(parameters);
var param = CreateDynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.ExecuteAsync(sql, param, transaction);
}
private static DynamicParameters CreateDynamicParameters(Dictionary<string, object> parameters)
{
var dynamicParameters = new DynamicParameters();
if (parameters == null)
{
return dynamicParameters;
}
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, NormalizeParameterValue(parameter.Value));
}
return dynamicParameters;
}
private static object NormalizeParameterValue(object value)
{
if (value == null || value == DBNull.Value)
{
return value;
}
if (value is JsonElement jsonElement)
{
return NormalizeJsonElement(jsonElement);
}
if (value is Array array && value is not byte[])
{
return NormalizeArrayParameter(array);
}
return value;
}
private static object NormalizeArrayParameter(Array values)
{
var normalizedValues = values
.Cast<object>()
.Select(NormalizeParameterValue)
.Where(value => value != null && value != DBNull.Value)
.ToArray();
if (normalizedValues.Length == 0)
{
return Array.Empty<string>();
}
if (TryBuildGuidArray(normalizedValues, out var guidValues))
{
return guidValues;
}
if (TryBuildIntArray(normalizedValues, out var intValues))
{
return intValues;
}
if (TryBuildLongArray(normalizedValues, out var longValues))
{
return longValues;
}
if (TryBuildDecimalArray(normalizedValues, out var decimalValues))
{
return decimalValues;
}
if (TryBuildBoolArray(normalizedValues, out var boolValues))
{
return boolValues;
}
if (TryBuildDateTimeOffsetArray(normalizedValues, out var dateTimeOffsetValues))
{
return dateTimeOffsetValues;
}
var stringValues = normalizedValues.Select(value => value.ToString()).ToArray();
if (stringValues.Length == 1 && stringValues[0]?.Contains(PlatformConsts.MultiValueDelimiter) == true)
{
return stringValues[0].Split(PlatformConsts.MultiValueDelimiter, StringSplitOptions.RemoveEmptyEntries);
}
return stringValues;
}
private static object NormalizeJsonElement(JsonElement value)
{
return value.ValueKind switch
{
JsonValueKind.String => value.GetString(),
JsonValueKind.Number when value.TryGetInt32(out var intValue) => intValue,
JsonValueKind.Number when value.TryGetInt64(out var longValue) => longValue,
JsonValueKind.Number when value.TryGetDecimal(out var decimalValue) => decimalValue,
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
JsonValueKind.Undefined => null,
_ => value.ToString()
};
}
private static bool TryBuildGuidArray(object[] values, out Guid[] result)
{
result = new Guid[values.Length];
for (var i = 0; i < values.Length; i++)
{
if (values[i] is Guid guidValue)
{
result[i] = guidValue;
continue;
}
if (!Guid.TryParse(values[i]?.ToString(), out result[i]))
{
result = null;
return false;
}
}
return true;
}
private static bool TryBuildIntArray(object[] values, out int[] result)
{
result = new int[values.Length];
for (var i = 0; i < values.Length; i++)
{
if (values[i] is int intValue)
{
result[i] = intValue;
continue;
}
if (!int.TryParse(values[i]?.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result[i]))
{
result = null;
return false;
}
}
return true;
}
private static bool TryBuildLongArray(object[] values, out long[] result)
{
result = new long[values.Length];
for (var i = 0; i < values.Length; i++)
{
if (values[i] is long longValue)
{
result[i] = longValue;
continue;
}
if (!long.TryParse(values[i]?.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result[i]))
{
result = null;
return false;
}
}
return true;
}
private static bool TryBuildDecimalArray(object[] values, out decimal[] result)
{
result = new decimal[values.Length];
for (var i = 0; i < values.Length; i++)
{
if (values[i] is decimal decimalValue)
{
result[i] = decimalValue;
continue;
}
if (!decimal.TryParse(values[i]?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out result[i]))
{
result = null;
return false;
}
}
return true;
}
private static bool TryBuildBoolArray(object[] values, out bool[] result)
{
result = new bool[values.Length];
for (var i = 0; i < values.Length; i++)
{
if (values[i] is bool boolValue)
{
result[i] = boolValue;
continue;
}
if (!bool.TryParse(values[i]?.ToString(), out result[i]))
{
result = null;
return false;
}
}
return true;
}
private static bool TryBuildDateTimeOffsetArray(object[] values, out DateTimeOffset[] result)
{
result = new DateTimeOffset[values.Length];
for (var i = 0; i < values.Length; i++)
{
if (values[i] is DateTimeOffset dateTimeOffsetValue)
{
result[i] = dateTimeOffsetValue;
continue;
}
if (values[i] is DateTime dateTimeValue)
{
result[i] = dateTimeValue;
continue;
}
if (!DateTimeOffset.TryParse(values[i]?.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result[i]))
{
result = null;
return false;
}
}
return true;
}
// ------------------ Dispose ------------------
public void Dispose()

View file

@ -254,6 +254,7 @@ public class PlatformDbContext :
b.Property(x => x.Key).IsRequired().HasMaxLength(128);
b.Property(x => x.Path).IsRequired().HasMaxLength(256);
b.Property(x => x.ComponentType).HasMaxLength(32);
b.Property(x => x.ComponentPath).IsRequired().HasMaxLength(256);
b.Property(x => x.RouteType).HasMaxLength(64);
@ -506,6 +507,7 @@ public class PlatformDbContext :
b.Property(x => x.PositionX).IsRequired();
b.Property(x => x.PositionY).IsRequired();
b.Property(x => x.CompareOutcomesJson).HasColumnType("text");
b.HasIndex(x => new { x.ListFormCode, x.Title }).IsUnique();
});
builder.Entity<Note>(b =>

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260526185809_Initial")]
[Migration("20260606212623_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -3486,6 +3486,9 @@ namespace Sozsoft.Platform.Migrations
b.HasKey("Id");
b.HasIndex("ListFormCode", "Title")
.IsUnique();
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
});
@ -4153,6 +4156,10 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ComponentType")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");

View file

@ -1604,6 +1604,7 @@ namespace Sozsoft.Platform.Migrations
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Key = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Path = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
ComponentType = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: true),
ComponentPath = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
RouteType = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
Authority = table.Column<string>(type: "nvarchar(max)", nullable: true),
@ -3904,6 +3905,12 @@ namespace Sozsoft.Platform.Migrations
table: "Sas_H_ListFormImportLog",
column: "ImportId");
migrationBuilder.CreateIndex(
name: "IX_Sas_H_ListFormWorkflow_ListFormCode_Title",
table: "Sas_H_ListFormWorkflow",
columns: new[] { "ListFormCode", "Title" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Sas_H_Menu_Code",
table: "Sas_H_Menu",

View file

@ -3483,6 +3483,9 @@ namespace Sozsoft.Platform.Migrations
b.HasKey("Id");
b.HasIndex("ListFormCode", "Title")
.IsUnique();
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
});
@ -4150,6 +4153,10 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ComponentType")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");

View file

@ -202,7 +202,7 @@
"Summary": "blog.posts.ai.excerpt",
"CoverImage": "https://images.pexels.com/photos/8386434/pexels-photo-8386434.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CategoryName": "blog.categories.technology",
"Author": "SYSTEM@SOZSOFT.COM"
"Author": "system@sozsoft.com"
},
{
"Title": "blog.posts.web.title",
@ -213,7 +213,7 @@
"Summary": "blog.posts.web.excerpt",
"CoverImage": "https://images.pexels.com/photos/11035471/pexels-photo-11035471.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CategoryName": "blog.categories.webdev",
"Author": "SYSTEM@SOZSOFT.COM"
"Author": "system@sozsoft.com"
},
{
"Title": "blog.posts.security.title",
@ -224,7 +224,7 @@
"Summary": "blog.posts.security.excerpt",
"CoverImage": "https://images.pexels.com/photos/5380642/pexels-photo-5380642.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CategoryName": "blog.categories.security",
"Author": "SYSTEM@SOZSOFT.COM"
"Author": "system@sozsoft.com"
},
{
"Title": "blog.posts.mobile.title",
@ -235,7 +235,7 @@
"ReadTime": "4 dk",
"CoverImage": "https://images.pexels.com/photos/13017583/pexels-photo-13017583.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CategoryName": "blog.categories.mobile",
"Author": "SYSTEM@SOZSOFT.COM"
"Author": "system@sozsoft.com"
},
{
"Title": "blog.posts.database.title",
@ -246,7 +246,7 @@
"ReadTime": "8 dk",
"CoverImage": "https://images.pexels.com/photos/325229/pexels-photo-325229.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CategoryName": "blog.categories.database",
"Author": "SYSTEM@SOZSOFT.COM"
"Author": "system@sozsoft.com"
},
{
"Title": "blog.posts.digital.title",
@ -257,7 +257,7 @@
"ReadTime": "6 dk",
"CoverImage": "https://images.pexels.com/photos/7681091/pexels-photo-7681091.jpeg?auto=compress&cs=tinysrgb&w=1920",
"CategoryName": "blog.categories.digital",
"Author": "SYSTEM@SOZSOFT.COM"
"Author": "system@sozsoft.com"
}
],
"GlobalSearch": [
@ -324,16 +324,16 @@
"Url": "/dil/",
"Method": "GET",
"DataSourceCode": "Default",
"Sql": "SELECT * FROM Plat_H_Language WHERE IsEnabled = @IsEnabled AND CultureName = @CultureName",
"Sql": "SELECT * FROM Sas_H_Language WHERE IsEnabled = @IsEnabled AND CultureName = @CultureName",
"ParametersJson": [
{
"Type": "P",
"Type": "Path",
"Name": "CultureName",
"DefaultValue": "ar",
"Path": "/dil/:CultureName/"
},
{
"Type": "S",
"Type": "Static",
"Name": "IsEnabled",
"DefaultValue": "true"
}
@ -341,7 +341,7 @@
"PermissionsJson": [
{
"ResourceType": "User",
"ResourceId": "SYSTEM@SOZSOFT.COM"
"ResourceId": "system@sozsoft.com"
}
]
}
@ -429,6 +429,153 @@
]
}
],
"Homes": [
{
"heroBackgroundImageKey": "Public.home.hero.backgroundImage",
"heroPrimaryCtaKey": "Public.hero.cta.consultation",
"heroSecondaryCtaKey": "Public.hero.cta.discover",
"featuresTitleKey": "Public.features.title",
"featuresSubtitleKey": "Public.features.subtitle",
"solutionsTitleKey": "Public.solutions.title",
"solutionsSubtitleKey": "Public.solutions.subtitle",
"ctaTitleKey": "Public.common.getStarted",
"ctaSubtitleKey": "Public.common.contact",
"ctaButtonLabelKey": "Public.common.learnMore",
"slides": [
{
"titleKey": "Public.hero.slide1.title",
"subtitleKey": "Public.hero.slide1.subtitle",
"services": [
{
"icon": "FaCalendarAlt",
"titleKey": "Public.hero.slide1.service1.title",
"descriptionKey": "Public.hero.slide1.service1.desc"
},
{
"icon": "FaUsers",
"titleKey": "Public.hero.slide1.service2.title",
"descriptionKey": "Public.hero.slide1.service2.desc"
},
{
"icon": "FaShieldAlt",
"titleKey": "Public.hero.slide1.service3.title",
"descriptionKey": "Public.hero.slide1.service3.desc"
}
]
},
{
"titleKey": "Public.hero.slide2.title",
"subtitleKey": "Public.hero.slide2.subtitle",
"services": [
{
"icon": "FaChartBar",
"titleKey": "Public.hero.slide2.service1.title",
"descriptionKey": "Public.hero.slide2.service1.desc"
},
{
"icon": "FaCreditCard",
"titleKey": "Public.hero.slide2.service2.title",
"descriptionKey": "Public.hero.slide2.service2.desc"
},
{
"icon": "FaDatabase",
"titleKey": "Public.hero.slide2.service3.title",
"descriptionKey": "Public.hero.slide2.service3.desc"
}
]
},
{
"titleKey": "Public.hero.slide3.title",
"subtitleKey": "Public.hero.slide3.subtitle",
"services": [
{
"icon": "FaDesktop",
"titleKey": "Public.hero.slide3.service1.title",
"descriptionKey": "Public.hero.slide3.service1.desc"
},
{
"icon": "FaServer",
"titleKey": "Public.hero.slide3.service2.title",
"descriptionKey": "Public.hero.slide3.service2.desc"
},
{
"icon": "FaMobileAlt",
"titleKey": "Public.hero.slide3.service3.title",
"descriptionKey": "Public.hero.slide3.service3.desc"
}
]
}
],
"features": [
{
"icon": "FaUsers",
"titleKey": "Public.features.reliable",
"descriptionKey": "Public.features.reliable.desc"
},
{
"icon": "FaCalendarAlt",
"titleKey": "App.Videoroom.Planning",
"descriptionKey": "Public.features.rapid.desc"
},
{
"icon": "FaBookOpen",
"titleKey": "Public.features.expert",
"descriptionKey": "Public.features.expert.desc"
},
{
"icon": "FaCreditCard",
"titleKey": "Public.features.muhasebe",
"descriptionKey": "Public.features.muhasebe.desc"
},
{
"icon": "FaRegComment",
"titleKey": "Public.features.iletisim",
"descriptionKey": "Public.features.iletisim.desc"
},
{
"icon": "FaPhone",
"titleKey": "Public.features.mobil",
"descriptionKey": "Public.features.mobil.desc"
},
{
"icon": "FaChartBar",
"titleKey": "Public.features.scalable",
"descriptionKey": "Public.features.scalable.desc"
},
{
"icon": "FaShieldAlt",
"titleKey": "Public.features.guvenlik",
"descriptionKey": "Public.features.guvenlik.desc"
}
],
"solutions": [
{
"icon": "FaDesktop",
"colorClass": "bg-blue-600",
"titleKey": "Public.services.web.title",
"descriptionKey": "Public.solutions.web.desc"
},
{
"icon": "FaMobileAlt",
"colorClass": "bg-purple-600",
"titleKey": "Public.services.mobile.title",
"descriptionKey": "Public.solutions.mobile.desc"
},
{
"icon": "FaServer",
"colorClass": "bg-green-600",
"titleKey": "Public.solutions.custom.title",
"descriptionKey": "Public.solutions.custom.desc"
},
{
"icon": "FaDatabase",
"colorClass": "bg-red-600",
"titleKey": "Public.solutions.database.title",
"descriptionKey": "Public.solutions.database.desc"
}
]
}
],
"Services": [
{
"icon": "FaCode",
@ -1309,6 +1456,36 @@
"DepartmentName": "Muhasebe",
"Name": "Muhasebe Şefi",
"ParentName": "Muhasebe Müdürü"
},
{
"Id": "b7c8d9e0-f1a2-4b3c-8d9e-0f1a2b3c4d2e",
"DepartmentName": "Bilgi İşlem",
"Name": "Bilgi İşlem Müdürü",
"ParentName": "Genel Müdür"
},
{
"Id": "b7c8d9e0-f1a2-4b3c-1d9e-0f1a2b3c4d2e",
"DepartmentName": "Finans",
"Name": "Finans Müdürü",
"ParentName": "Genel Müdür"
},
{
"Id": "b7c8d9e0-f1b2-4b3c-1d9e-0f1a2b3c4d2e",
"DepartmentName": "Satış",
"Name": "İhracat Müdürü",
"ParentName": "Genel Müdür"
},
{
"Id": "b2c8d9e0-f1b2-4b3c-1d9e-0f1a2b3c4d2e",
"DepartmentName": "Satış",
"Name": "İç Piyasa Müdürü",
"ParentName": "Genel Müdür"
},
{
"Id": "b2c8d9e0-f1b2-2b3c-1d9e-0f1a2b3c4d2e",
"DepartmentName": "Üretim",
"Name": "Üretim Müdürü",
"ParentName": "Genel Müdür"
}
],
"Announcements": [
@ -1317,7 +1494,7 @@
"content": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor! Tüm çalışanlarımızıılış törenimize davet ediyoruz.",
"excerpt": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor!",
"category": "general",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"publishDate": "12-10-2024",
"isPinned": true,
"viewCount": 0,
@ -1328,7 +1505,7 @@
"content": "Yıl sonu performans değerlendirmelerimiz 20 Ekim - 5 Kasım tarihleri arasında gerçekleştirilecektir. Lütfen formları zamanında doldurunuz.",
"excerpt": "Yıl sonu performans değerlendirmeleri başlıyor.",
"category": "event",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"publishDate": "08-10-2024",
"expiryDate": "05-11-2024",
"isPinned": true,
@ -1339,7 +1516,7 @@
"content": "Bu Cumartesi saat 02: 00 - 06: 00 arası sistemlerimizde bakım çalışması yapılacaktır. Bu süre içinde sistemlere erişim sağlanamayacaktır.",
"excerpt": "Cumartesi gecesi planlı bakım çalışması",
"category": "urgent",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"publishDate": "08-10-2024",
"isPinned": false,
"viewCount": 0
@ -1349,7 +1526,7 @@
"content": "Yazılım Geliştirme ekibimiz için React İleri Seviye eğitimi 25-26 Ekim tarihlerinde düzenlenecektir. Katılım için IK birimine başvurunuz.",
"excerpt": "React İleri Seviye eğitimi kayıtları başladı",
"category": "event",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"publishDate": "09-10-2024",
"isPinned": false,
"viewCount": 0
@ -1359,7 +1536,7 @@
"content": "Bilgi güvenliği politikamız güncellenmiştir. Tüm çalışanlarımızın yeni politikayı okuması ve onaylaması gerekmektedir.",
"excerpt": "Güvenlik politikası güncellendi - Onay gerekli",
"category": "urgent",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"publishDate": "04-10-2024",
"isPinned": true,
"viewCount": 0
@ -1533,42 +1710,42 @@
"SocialPosts": [
{
"content": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"likeCount": 0,
"isLiked": false,
"isOwnPost": true
},
{
"content": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"likeCount": 0,
"isLiked": false,
"isOwnPost": true
},
{
"content": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"likeCount": 0,
"isLiked": false,
"isOwnPost": true
},
{
"content": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"likeCount": 0,
"isLiked": false,
"isOwnPost": true
},
{
"content": "Ekip üyelerimize yeni eğitim programımızı duyurmak istiyorum! 🎓 React, TypeScript ve Modern Web Geliştirme konularında kapsamlı bir program hazırladık.",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"likeCount": 0,
"isLiked": false,
"isOwnPost": true
},
{
"content": "Bugün müşteri ile harika bir toplantı yaptık! Yeni projenin detaylarını konuştuk. 🎯",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"likeCount": 0,
"isLiked": false,
"isOwnPost": true
@ -1654,53 +1831,53 @@
"SocialComments": [
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "Harika görünüyor! Başarılar 👏"
},
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "TypeScript gerçekten fark yaratıyor!"
},
{
"postContent": "Bu hafta sprint planlamasını yaptık. Ekibimizle birlikte yeni özellikleri değerlendirdik. Heyecan verici bir hafta olacak!",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "Mesajlaşma özelliğine kesinlikle ihtiyacımız var!"
},
{
"postContent": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "Tasarımlar çok şık! Renk paleti özellikle güzel 😍"
},
{
"postContent": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "Dark mode opsiyonu da olacak mı?"
},
{
"postContent": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "Harika iş! Detayları paylaşabilir misin?"
},
{
"postContent": "Ekip üyelerimize yeni eğitim programımızı duyurmak istiyorum! 🎓 React, TypeScript ve Modern Web Geliştirme konularında kapsamlı bir program hazırladık.",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "Ne zaman başlıyor?"
},
{
"postContent": "Ekip üyelerimize yeni eğitim programımızı duyurmak istiyorum! 🎓 React, TypeScript ve Modern Web Geliştirme konularında kapsamlı bir program hazırladık.",
"userName": "SYSTEM@SOZSOFT.COM",
"userName": "system@sozsoft.com",
"content": "Gelecek hafta başlıyoruz! Kayıt linki mail ile paylaşılacak."
}
],
"SocialLikes": [
{
"postContent": "Yeni proje üzerinde çalışıyoruz! React ve TypeScript ile harika bir deneyim oluşturuyoruz. Ekip çalışması harika gidiyor! 🚀",
"userName": "SYSTEM@SOZSOFT.COM"
"userName": "system@sozsoft.com"
},
{
"postContent": "Yeni tasarım sistemimizin ilk prototipini hazırladık! Kullanıcı deneyimini iyileştirmek için çok çalıştık. Geri bildirimlerinizi bekliyorum! 🎨",
"userName": "SYSTEM@SOZSOFT.COM"
"userName": "system@sozsoft.com"
}
],
"EventTypes": [
@ -1881,7 +2058,7 @@
"Description": "Tüm departmanların katılımıyla düzenlenen geleneksel yaz futbol turnuvası.",
"Place": "Şirket Kampüsü Spor Alanı",
"Status": "published",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"ParticipantsCount": 64,
"IsPublished": true,
"Likes": 0,
@ -1896,7 +2073,7 @@
"Description": "Çalışanlarımıza özel, rehber eşliğinde 2 günlük kültürel gezi.",
"Place": "Kapadokya, Nevşehir",
"Status": "published",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"ParticipantsCount": 25,
"IsPublished": true,
"Likes": 0,
@ -1911,7 +2088,7 @@
"Description": "Caz müziğinin en güzel örneklerinin canlı performanslarla sunulacağı özel akşam.",
"Place": "Şirket Konferans Salonu",
"Status": "published",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"ParticipantsCount": 40,
"IsPublished": true,
"Likes": 0,
@ -1922,55 +2099,55 @@
"EventComments": [
{
"EventName": "Yaz Futbol Turnuvası 2025",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "Muhteşem bir gündü! Yılın en güzel etkinliği 🎉",
"Likes": 12
},
{
"EventName": "Yaz Futbol Turnuvası 2025",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "Voleybol turnuvası harikaydı, gelecek yıl yine yapalım!",
"Likes": 8
},
{
"EventName": "Kültür Gezisi: Kapadokya",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "Ekibimiz 2. oldu! Çok gurur duydum herkesle 💪",
"Likes": 15
},
{
"EventName": "Kültür Gezisi: Kapadokya",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "Gece boyunca kod yazmak ve pizza yemek priceless! 🍕",
"Likes": 10
},
{
"EventName": "Müzik Dinletisi: Jazz Akşamı",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "İT departmanı şampiyon oldu! Gelecek sene kupayı koruyacağız 🏆",
"Likes": 18
},
{
"EventName": "Müzik Dinletisi: Jazz Akşamı",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "Yılın en şık gecesi! Organizasyon mükemmeldi 👏",
"Likes": 25
},
{
"EventName": "Müzik Dinletisi: Jazz Akşamı",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "Tombala hediyelerim harika, çok teşekkürler! 🎁",
"Likes": 14
},
{
"EventName": "Müzik Dinletisi: Jazz Akşamı",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "Müzik grubunuz süperdi, dans pistinden ayrılamadık! 🎵",
"Likes": 19
},
{
"EventName": "Müzik Dinletisi: Jazz Akşamı",
"UserName": "SYSTEM@SOZSOFT.COM",
"UserName": "system@sozsoft.com",
"Content": "İlk defa ebru yaptım, çok huzurlu bir deneyimdi 🎨",
"Likes": 11
}
@ -2077,4 +2254,4 @@
"ResetPeriod": "Daily"
}
]
}
}

View file

@ -14,9 +14,9 @@ using Volo.Abp.Identity;
using Volo.Abp.Timing;
using System.Collections.Generic;
using Volo.Abp.MultiTenancy;
using Sozsoft.Platform.Extensions;
using System.Linq;
using Sozsoft.Platform.Enums;
using Microsoft.AspNetCore.Identity;
namespace Sozsoft.Platform.Data.Seeds;
@ -40,6 +40,7 @@ public class TenantSeederDto
public List<JobPositionSeedDto> JobPositions { get; set; }
//Public
public List<HomeSeedDto> Homes { get; set; }
public List<AboutSeedDto> Abouts { get; set; }
public List<ServiceSeedDto> Services { get; set; }
public List<PaymentMethodSeedDto> PaymentMethods { get; set; }
@ -392,6 +393,56 @@ public class AboutSeedDto
public List<SectionDto> Sections { get; set; }
}
public class HomeSeedDto
{
public string HeroBackgroundImageKey { get; set; }
public string HeroPrimaryCtaKey { get; set; }
public string HeroSecondaryCtaKey { get; set; }
public string FeaturesTitleKey { get; set; }
public string FeaturesSubtitleKey { get; set; }
public string SolutionsTitleKey { get; set; }
public string SolutionsSubtitleKey { get; set; }
public string CtaTitleKey { get; set; }
public string CtaSubtitleKey { get; set; }
public string CtaButtonLabelKey { get; set; }
public List<HomeSlideSeedDto> Slides { get; set; }
public List<HomeFeatureSeedDto> Features { get; set; }
public List<HomeSolutionSeedDto> Solutions { get; set; }
}
public class HomeSlideSeedDto
{
public string TitleKey { get; set; }
public string SubtitleKey { get; set; }
public string StyleClass { get; set; }
public List<HomeSlideServiceSeedDto> Services { get; set; }
}
public class HomeSlideServiceSeedDto
{
public string Icon { get; set; }
public string TitleKey { get; set; }
public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
}
public class HomeFeatureSeedDto
{
public string Icon { get; set; }
public string TitleKey { get; set; }
public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
}
public class HomeSolutionSeedDto
{
public string Icon { get; set; }
public string ColorClass { get; set; }
public string TitleKey { get; set; }
public string DescriptionKey { get; set; }
public string StyleClass { get; set; }
}
public class WorkHourSeedDto
{
public string Name { get; set; }
@ -434,12 +485,14 @@ public class ProductSeedDto
public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
{
private readonly IClock _clock;
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IRepository<Branch, Guid> _branchRepository;
private readonly IRepository<GlobalSearch, int> _globalSearch;
private readonly IRepository<CustomEndpoint, Guid> _customEndpointRepository;
private readonly IRepository<CustomComponent, Guid> _customComponentRepository;
private readonly IRepository<ReportCategory, Guid> _reportCategoriesRepository;
private readonly IRepository<ReportTemplate, Guid> _reportTemplatesRepository;
private readonly IRepository<Home, Guid> _homeRepository;
private readonly IRepository<About, Guid> _aboutRepository;
private readonly IRepository<PaymentMethod, Guid> _paymentMethodRepository;
private readonly IRepository<InstallmentOption, Guid> _installmentOptionRepository;
@ -482,6 +535,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
public TenantDataSeeder(
IClock clock,
ILookupNormalizer lookupNormalizer,
IIdentityUserRepository repositoryUser,
IRepository<Branch, Guid> branchRepository,
IRepository<GlobalSearch, int> globalSearch,
@ -495,6 +549,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<CustomComponent, Guid> customComponentRepository,
IRepository<ReportCategory, Guid> reportCategoriesRepository,
IRepository<ReportTemplate, Guid> reportTemplatesRepository,
IRepository<Home, Guid> homeRepository,
IRepository<About, Guid> aboutRepository,
IRepository<Service, Guid> servicesRepository,
IRepository<Product, Guid> productRepository,
@ -530,6 +585,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
)
{
_clock = clock;
_lookupNormalizer = lookupNormalizer;
_repositoryUser = repositoryUser;
_branchRepository = branchRepository;
_globalSearch = globalSearch;
@ -548,6 +604,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
_customComponentRepository = customComponentRepository;
_reportCategoriesRepository = reportCategoriesRepository;
_reportTemplatesRepository = reportTemplatesRepository;
_homeRepository = homeRepository;
_servicesRepository = servicesRepository;
_aboutRepository = aboutRepository;
_contactRepository = contactRepository;
@ -848,6 +905,30 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
}
}
foreach (var item in items.Homes)
{
var exists = await _homeRepository.FirstOrDefaultAsync();
if (exists == null)
{
await _homeRepository.InsertAsync(new Home
{
HeroBackgroundImageKey = item.HeroBackgroundImageKey,
HeroPrimaryCtaKey = item.HeroPrimaryCtaKey,
HeroSecondaryCtaKey = item.HeroSecondaryCtaKey,
FeaturesTitleKey = item.FeaturesTitleKey,
FeaturesSubtitleKey = item.FeaturesSubtitleKey,
SolutionsTitleKey = item.SolutionsTitleKey,
SolutionsSubtitleKey = item.SolutionsSubtitleKey,
CtaTitleKey = item.CtaTitleKey,
CtaSubtitleKey = item.CtaSubtitleKey,
CtaButtonLabelKey = item.CtaButtonLabelKey,
SlidesJson = JsonSerializer.Serialize(item.Slides),
FeaturesJson = JsonSerializer.Serialize(item.Features),
SolutionsJson = JsonSerializer.Serialize(item.Solutions)
});
}
}
foreach (var item in items.Contacts)
{
var exists = await _contactRepository.FirstOrDefaultAsync();
@ -993,7 +1074,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
var user = await _repositoryUser.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(item.UserName));
await _announcementRepository.InsertAsync(new Announcement
{
Title = item.Title,
@ -1067,7 +1148,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
var user = await _repositoryUser.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(item.UserName));
await _socialPostRepository.InsertAsync(new SocialPost(Guid.NewGuid())
{
@ -1157,7 +1238,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
var user = await _repositoryUser.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(item.UserName));
await _socialCommentRepository.InsertAsync(new SocialComment(Guid.NewGuid())
{
UserId = user != null ? user.Id : null,
@ -1177,7 +1258,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
if (exists)
continue;
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
var user = await _repositoryUser.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(item.UserName));
await _socialLikeRepository.InsertAsync(new SocialLike(Guid.NewGuid())
{
SocialPostId = post != null ? post.Id : Guid.Empty,
@ -1218,7 +1299,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
{
var category = await _eventCategoryRepository.FirstOrDefaultAsync(x => x.Name == item.CategoryName);
var type = await _eventTypeRepository.FirstOrDefaultAsync(x => x.Name == item.TypeName);
var user = await _repositoryUser.FindByNormalizedUserNameAsync(item.UserName);
var user = await _repositoryUser.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(item.UserName));
if (category != null && type != null)
{

View file

@ -133,10 +133,10 @@ public class TenantIdentityDataSeeder : IdentityDataSeeder
Surname = PlatformConsts.AbpIdentity.User.AdminSurNameDefaultValue,
};
adminUser.SetEmailConfirmed(true);
adminUser.SetIsVerified(true);
adminUser.SetEmailConfirmed(true);
adminUser.SetRocketUsername(PlatformConsts.AbpIdentity.User.AdminRocketUsernameDefaultValue);
adminUser.SetPhoneNumber(PlatformConsts.AbpIdentity.User.AdminPhoneNumberDefaultValue, true);
adminUser.SetPhoneNumber(PlatformConsts.AbpIdentity.User.AdminPhoneNumberDefaultValue, adminUser.PhoneNumberConfirmed);
adminUser.SetWorkHour(PlatformConsts.AbpIdentity.User.AdminWorkHourDefaultValue);
adminUser.SetNationality(PlatformConsts.AbpIdentity.User.AdminNationalityDefaultValue);
adminUser.SetBloodType(PlatformConsts.AbpIdentity.User.AdminBloodTypeDefaultValue);

View file

@ -42,7 +42,7 @@
<PackageReference Include="Volo.Abp.TenantManagement.EntityFrameworkCore" Version="10.0.0" />
<PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="10.0.0" />
<PackageReference Include="Volo.Abp.OpenIddict.EntityFrameworkCore" Version="10.0.0" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.8" PrivateAssets="all" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.8" />
</ItemGroup>
<ItemGroup>

View file

@ -125,10 +125,9 @@ public class SetupController : ControllerBase
{
try
{
while (!reader.EndOfStream)
while (await reader.ReadLineAsync(ct) is { } line)
{
var line = await reader.ReadLineAsync(ct);
if (line != null) await Send(level, line);
await Send(level, line);
}
}
catch (OperationCanceledException) { }

View file

@ -264,10 +264,9 @@ internal static class SetupAppRunner
{
try
{
while (!reader.EndOfStream)
while (await reader.ReadLineAsync(ct) is { } line)
{
var line = await reader.ReadLineAsync(ct);
if (line != null) await Send(level, line);
await Send(level, line);
}
}
catch (OperationCanceledException) { }

View file

@ -187,9 +187,9 @@ public class PlatformHttpApiHostModule : AbpModule
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"]?.Split(',') ?? Array.Empty<string>());
options.Applications[PlatformConsts.React].RootUrl = configuration["App:ClientUrl"];
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.EmailConfirmation] = "account/confirm";
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.PasswordReset] = "account/reset-password";
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.UserDetail] = "account/{0}";
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.EmailConfirmation] = "confirm";
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.PasswordReset] = "reset-password";
options.Applications[PlatformConsts.React].Urls[PlatformConsts.Urls.UserDetail] = "admin/users/detail";
//options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
//options.Applications["MVC"].Urls[PlatformConsts.Urls.EmailConfirmation] = "Account/Confirm";
@ -525,10 +525,26 @@ public class PlatformHttpApiHostModule : AbpModule
app.UseConfiguredEndpoints();
}
public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
public override Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
{
using var scope = context.ServiceProvider.CreateScope();
var initializer = scope.ServiceProvider.GetRequiredService<BackgroundWorkerInitializer>();
await initializer.RunAsync();
var serviceProvider = context.ServiceProvider;
_ = Task.Run(async () =>
{
using var scope = serviceProvider.CreateScope();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<PlatformHttpApiHostModule>>();
try
{
var initializer = scope.ServiceProvider.GetRequiredService<BackgroundWorkerInitializer>();
await initializer.RunAsync();
}
catch (Exception ex)
{
logger.LogError(ex, "Background worker initialization failed.");
}
});
return Task.CompletedTask;
}
}

View file

@ -6,6 +6,7 @@
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>Sozsoft.Platform</RootNamespace>
<PreserveCompilationReferences>true</PreserveCompilationReferences>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>

View file

@ -117,6 +117,38 @@ server {
}
# dashboard.sozsoft.com
server {
listen 443 ssl http2;
server_name dashboard.sozsoft.com;
ssl_certificate /etc/letsencrypt/live/dashboard.sozsoft.com/fullchain.pem;
ssl_trusted_certificate /etc/ssl/sozsoft.com/chain1.pem;
ssl_certificate_key /etc/letsencrypt/live/dashboard.sozsoft.com/privkey.pem;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
#sudo htpasswd -c /etc/nginx/.htpasswd sedat.ozturk
#yukarıdaki komut ile kullanıcı adı ve şifre oluşturabilirsiniz
proxy_headers_hash_max_size 2048;
proxy_headers_hash_bucket_size 128;
location / {
proxy_pass http://127.0.0.1:19999;
proxy_http_version 1.1;
include /etc/nginx/proxy_params;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
}
# sozsoft.com
server {
listen 443 ssl http2;

View file

@ -13,6 +13,9 @@ volumes:
rocket_mongodb_data:
driver: local
n8n_data:
netdataconfig:
netdatalib:
netdatacache:
services:
forgejo:
@ -108,3 +111,30 @@ services:
- /etc/ssl/sozsoft.com:/etc/ssl/sozsoft.com:ro # Sertifikaları mount ettik
- ./logs/coturn:/var/log # Logları dışarı al (opsiyonel)
command: ["turnserver", "-c", "/etc/coturn/turnserver.conf"]
dashboard:
image: netdata/netdata:stable
container_name: dashboard
hostname: kursserver
restart: unless-stopped
pid: host
network_mode: host
cap_add:
- SYS_PTRACE
- SYS_ADMIN
security_opt:
- apparmor:unconfined
volumes:
- netdataconfig:/etc/netdata
- netdatalib:/var/lib/netdata
- netdatacache:/var/cache/netdata
- /:/host/root:ro,rslave
- /etc/passwd:/host/etc/passwd:ro
- /etc/group:/host/etc/group:ro
- /etc/localtime:/etc/localtime:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /etc/os-release:/host/etc/os-release:ro
- /var/log:/host/var/log:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /run/dbus:/run/dbus:ro

View file

@ -20,6 +20,7 @@ SUBDOMAINS=(
"sozsoft.com"
"www.sozsoft.com"
"demo.sozsoft.com"
"dashboard.sozsoft.com"
)
echo "Subdomain'ler için SSL sertifikaları alınıyor..."

View file

@ -1,6 +1,41 @@
{
"commit": "0b5eb3d",
"commit": "dc293fc",
"releases": [
{
"version": "1.1.04",
"buildDate": "2026-06-04",
"commit": "20e7fae481ce69e9a678508ce03b5ed7831aea9f",
"changeLog": [
"- Settingde yapılan ayarlar Auth komponentlerine uygulandı.",
"- Public home ve diğer sayfaların tasarım değişikliği yapıldı.",
"- Route Type Dinamik ve Normal olarak ayrıldı.",
"- Form Devexpress DefaultValue özelliği eklendi.",
"- Devexpress DarkModa uygun şekilde güncellendi.",
"- Grid, Tree ve FormDevexpress setReadonly özelliği eklendi."
]
},
{
"version": "1.1.03",
"buildDate": "2026-05-30",
"commit": "96f7091d46c248ba3c42849fe5d870db0ab96982",
"changeLog": [
"- User Detail komponentinin içerisinde Avatar ekleme",
"- EditForm un içerisinde EditorOptions dinamik oluşturulması",
"- Editform un içerisinde EditorScript dinamik oluşturulması"
]
},
{
"version": "1.1.02",
"buildDate": "2026-05-27",
"commit": "84b9f6510787bd82f3797728fd675f755b4caa2d",
"changeLog": [
"- .NET 10 yükseltildi.",
"- Abp Framework 10 yükseltildi.",
"- Sql Query Manager problemleri giderildi.",
"- AuditLog problemleri giderildi.",
"- Tenan yapısını uygunluğu için düzenlemeler yapıldı."
]
},
{
"version": "1.1.01",
"buildDate": "2026-05-24",
@ -115,4 +150,4 @@
]
}
]
}
}

View file

@ -4,7 +4,7 @@ import Card from '@/components/ui/Card'
import Logo from '@/components/template/Logo'
import type { ReactNode, ReactElement } from 'react'
import type { CommonProps } from '@/proxy/common'
import { FaArrowLeft, FaCheck } from 'react-icons/fa';
import { FaArrowLeft, FaCheck } from 'react-icons/fa'
import { Avatar, Select } from '@/components/ui'
import { useStoreActions, useStoreState } from '@/store'
import appConfig from '@/proxy/configs/app.config'
@ -90,7 +90,7 @@ const Simple = ({ children, content, ...rest }: SimpleProps) => {
return (
<div className="h-full">
<Container className="flex flex-col flex-auto items-center justify-center min-w-0 h-full">
<Card className="min-w-[320px] md:min-w-[450px]" bodyClass="md:p-5">
<Card className="w-full min-w-[320px] max-w-[360px] md:min-w-[450px]" bodyClass="md:p-5">
<div className="flex justify-between items-center mb-2">
{!hasSubdomain() && (
<a

View file

@ -121,14 +121,14 @@ const PublicLayout = () => {
onClick={() => setThemeMode(THEME_ENUM.MODE_LIGHT)}
aria-pressed={!isDarkMode}
title="Light mode"
className={`inline-flex h-8 items-center gap-1.5 px-2.5 text-xs font-semibold transition-colors rounded-l-md ${
className={`inline-flex h-8 w-8 xl:w-auto items-center justify-center gap-1.5 px-0 xl:px-2.5 text-xs font-semibold transition-colors rounded-l-md ${
!isDarkMode
? 'bg-white text-gray-950'
: 'text-gray-300 hover:bg-white/10 hover:text-white'
}`}
>
<LuSun size={15} strokeWidth={1.8} />
{translate('::App.Light')}
<span className="hidden xl:inline">{translate('::App.Light')}</span>
</button>
<button
@ -136,12 +136,12 @@ const PublicLayout = () => {
onClick={() => setThemeMode(THEME_ENUM.MODE_DARK)}
aria-pressed={isDarkMode}
title="Dark mode"
className={`inline-flex h-8 items-center gap-1.5 px-2.5 text-xs font-semibold transition-colors rounded-r-md ${
className={`inline-flex h-8 w-8 xl:w-auto items-center justify-center gap-1.5 px-0 xl:px-2.5 text-xs font-semibold transition-colors rounded-r-md ${
isDarkMode ? 'bg-white text-gray-950' : 'text-gray-300 hover:bg-white/10 hover:text-white'
}`}
>
<LuMoon size={15} strokeWidth={1.8} />
{translate('::App.Dark')}
<span className="hidden xl:inline">{translate('::App.Dark')}</span>
</button>
</div>
)
@ -163,11 +163,15 @@ const PublicLayout = () => {
: 'bg-gray-950/80 backdrop-blur-sm py-4 border-b border-white/5'
}`}
>
<div className="container mx-auto px-6 flex items-center justify-between">
<Logo mode="dark" />
<div className="container mx-auto px-6 relative grid grid-cols-[auto_1fr_auto] items-center lg:grid-cols-[1fr_auto] xl:grid-cols-[auto_1fr_auto]">
<Logo
mode="dark"
className="relative z-10 max-w-[190px] overflow-hidden lg:hidden xl:block xl:max-w-none"
imgClass="h-10 w-auto object-contain xl:h-auto"
/>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-1">
{/* Desktop / tablet navigation */}
<nav className="hidden lg:flex lg:col-start-1 xl:col-start-2 items-center justify-self-start xl:justify-self-center gap-1">
{navLinks
.filter((l) => !isLoginLink(l.resourceKey))
.map((link) => {
@ -216,7 +220,7 @@ const PublicLayout = () => {
</nav>
{/* Right side: Language + Login */}
<div className="hidden md:flex items-center gap-3">
<div className="relative z-10 hidden lg:flex lg:col-start-2 xl:col-start-3 lg:justify-self-end items-center gap-2 xl:gap-3">
<LanguageSelector />
{themeToggle}
<div className="w-px h-5 bg-white/20" />
@ -237,9 +241,10 @@ const PublicLayout = () => {
{/* Mobile Menu Button */}
<button
className="md:hidden flex items-center justify-center w-9 h-9 text-white rounded-md hover:bg-white/10 transition-colors"
className="lg:hidden col-start-3 justify-self-end flex items-center justify-center w-9 h-9 text-white rounded-md hover:bg-white/10 transition-colors"
onClick={toggleMenu}
aria-label="Toggle menu"
aria-expanded={isOpen}
>
{isOpen ? <LuX size={20} strokeWidth={2} /> : <LuMenu size={20} strokeWidth={2} />}
</button>
@ -247,68 +252,88 @@ const PublicLayout = () => {
{/* Mobile Navigation */}
<div
className={`md:hidden overflow-hidden transition-all duration-300 ease-in-out ${
className={`lg:hidden overflow-hidden transition-all duration-300 ease-in-out ${
isOpen ? 'max-h-screen opacity-100' : 'max-h-0 opacity-0'
}`}
>
<div className="bg-gray-950/98 backdrop-blur-md border-t border-white/10">
<div className="border-t border-white/10 bg-gray-950/98 backdrop-blur-md">
<div className="container mx-auto px-6 py-3">
<nav className="flex flex-col gap-1">
{navLinks.map((link) => {
const active = isActiveLink(link.path)
const isLogin = isLoginLink(link.resourceKey)
if (isLogin) {
<div className="lg:hidden flex flex-col gap-1">
{navLinks.map((link) => {
const active = isActiveLink(link.path)
const isLogin = isLoginLink(link.resourceKey)
if (isLogin) {
return link.path ? (
<Link
key={link.path}
to={link.path}
onClick={toggleMenu}
className="mt-2 flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-lg transition-colors"
>
{link.name}
</Link>
) : null
}
return link.path ? (
<Link
key={link.path}
to={link.path}
onClick={toggleMenu}
className="mt-2 flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-lg transition-colors"
className={`flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium rounded-md transition-colors ${
active
? 'bg-white/10 text-white'
: 'text-gray-300 hover:bg-white/5 hover:text-white'
}`}
>
{link.icon && (
<link.icon
size={16}
strokeWidth={1.75}
className={active ? 'text-blue-400' : 'text-gray-400'}
/>
)}
{link.name}
</Link>
) : null
}
return link.path ? (
<Link
key={link.path}
to={link.path}
onClick={toggleMenu}
className={`flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium rounded-md transition-colors ${
active
? 'bg-white/10 text-white'
: 'text-gray-300 hover:bg-white/5 hover:text-white'
}`}
>
{link.icon && (
<link.icon
size={16}
strokeWidth={1.75}
className={active ? 'text-blue-400' : 'text-gray-400'}
/>
)}
{link.name}
</Link>
) : (
<button
key={link.name}
onClick={() => {
link.action?.()
toggleMenu()
}}
className="flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white rounded-md transition-colors text-left"
>
{link.icon && (
<link.icon size={16} strokeWidth={1.75} className="text-gray-400" />
)}
{link.name}
</button>
)
})}
<div className="pt-2 pb-1 border-t border-white/10 mt-1">
) : (
<button
key={link.name}
onClick={() => {
link.action?.()
toggleMenu()
}}
className="flex items-center gap-2.5 px-3 py-2.5 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white rounded-md transition-colors text-left"
>
{link.icon && (
<link.icon size={16} strokeWidth={1.75} className="text-gray-400" />
)}
{link.name}
</button>
)
})}
</div>
<div className="mt-1 border-t border-white/10 pt-2 pb-1">
<div className="flex items-center justify-between gap-3">
<LanguageSelector />
{themeToggle}
<div className="flex h-10 min-w-10 items-center justify-center rounded-lg bg-white/5 ring-1 ring-white/10">
<LanguageSelector />
</div>
<div className="flex h-10 items-center rounded-lg bg-white/5 px-1 ring-1 ring-white/10">
{themeToggle}
</div>
{navLinks
.filter((l) => isLoginLink(l.resourceKey))
.map((link) =>
link.path ? (
<Link
key={link.path}
to={link.path}
onClick={toggleMenu}
className="hidden h-10 lg:inline-flex items-center justify-center px-4 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-lg shadow-md shadow-blue-900/30 transition-colors"
>
{link.name}
</Link>
) : null,
)}
</div>
</div>
</nav>

View file

@ -168,7 +168,7 @@ export const ProductCard: React.FC<ProductCardProps> = ({
<button
onClick={handleAddToCart}
disabled={isDisabled}
className={`w-full font-medium py-3 px-4 rounded-lg transition-colors duration-200 transform ${
className={`w-full font-medium py-3 px-4 rounded-lg transition-colors duration-200 transform dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600 dark:hover:text-white ${
isDisabled
? 'bg-gray-400 text-gray-700 cursor-not-allowed dark:bg-gray-700 dark:text-gray-400'
: 'bg-blue-600 hover:bg-blue-700 text-white hover:scale-[1.02] active:scale-[0.98]'

View file

@ -12,7 +12,6 @@ const Captcha = forwardRef((props: CaptchaProps, ref: Ref<TurnstileInstance>) =>
const { className, onError, onExpire, onSuccess } = props
return (
<>
<Turnstile
ref={ref}
className={className ?? 'mb-4 mx-auto'}
@ -21,7 +20,6 @@ const Captcha = forwardRef((props: CaptchaProps, ref: Ref<TurnstileInstance>) =>
onExpire={onExpire}
onSuccess={onSuccess}
/>
</>
)
})
Captcha.displayName = 'Captcha'

View file

@ -61,7 +61,7 @@ function DbMigrateLogPanel({ onClose }: DbMigrateLogPanelProps) {
<div className="flex-1 overflow-y-auto p-3 font-mono text-xs leading-relaxed">
{logs.map((log, i) => (
<div key={i} className={`whitespace-pre-wrap break-words ${levelColor(log.level)}`}>
<span className="text-gray-500 mr-2 select-none">[{log.level}]</span>
<span className="text-gray-500 mr-2 select-none">{String(i + 1).padStart(4, '0')}</span>
{log.message}
</div>
))}

View file

@ -0,0 +1,148 @@
import Input from '@/components/ui/Input'
import { getTenantByNameDetail } from '@/services/tenant.service'
import { useStoreActions, useStoreState } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { defaultDomain, getSubdomain } from '@/utils/subdomain'
import type { CSSProperties } from 'react'
import { useCallback, useEffect, useRef } from 'react'
const hiddenTenantStyle: CSSProperties = {
opacity: 0,
position: 'absolute',
pointerEvents: 'none',
height: 0,
margin: 0,
padding: 0,
border: 'none',
}
const TenantSelector = () => {
const { translate } = useLocalization()
const isMultiTenant = useStoreState((state) => state.abpConfig.config?.multiTenancy.isEnabled)
const tenantName = useStoreState((state) => state.locale.currentTenantName)
const { setTenantName } = useStoreActions((actions) => actions.locale)
const { setTenant } = useStoreActions((actions) => actions.auth.tenant)
const { setWarning } = useStoreActions((actions) => actions.base.messages)
const requestIdRef = useRef(0)
const lastRequestedTenantNameRef = useRef<string>()
const subDomainName = getSubdomain()
const isSubdomainTenant = !!subDomainName && subDomainName !== defaultDomain
const tenantStyle = isSubdomainTenant ? hiddenTenantStyle : undefined
const setWarningTimeout = useCallback(
(message: string) => {
setTimeout(() => {
setWarning(message)
}, 100)
},
[setWarning],
)
const redirectToMainDomain = useCallback(
(name: string) => {
setTenantName(undefined)
const parts = window.location.hostname.split('.')
const mainDomain = parts.length >= 3 ? parts.slice(1).join('.') : window.location.hostname
setWarningTimeout(
`"${name}" kurumuna ait kayıt bulunamadı.\nAna sayfaya yönlendiriliyorsunuz...`,
)
setTimeout(() => {
window.location.href = `${window.location.protocol}//${mainDomain}`
}, 3000)
},
[setTenantName, setWarningTimeout],
)
const fetchDataByName = useCallback(
async (name: string, isSubdomain = false) => {
if (!isSubdomain && name === lastRequestedTenantNameRef.current) {
return
}
lastRequestedTenantNameRef.current = name
const requestId = requestIdRef.current + 1
requestIdRef.current = requestId
if (name) {
try {
const response = await getTenantByNameDetail(name)
if (requestId !== requestIdRef.current) {
return
}
if (response.data) {
setTenant({
tenantId: response.data.id,
tenantName: response.data.name,
menuGroup: response.data.menuGroup,
})
} else {
setTenant(undefined)
if (isSubdomain) redirectToMainDomain(name)
}
} catch {
if (requestId !== requestIdRef.current) {
return
}
setTenant(undefined)
if (isSubdomain) redirectToMainDomain(name)
}
} else {
setTenant(undefined)
}
},
[redirectToMainDomain, setTenant],
)
const handleTenantNameChange = (value: string) => {
setTenantName(value)
}
const handleTenantNameBlur = () => {
if (subDomainName) {
return
}
fetchDataByName(tenantName || '')
}
useEffect(() => {
if (!isMultiTenant) {
setTenant(undefined)
return
}
if (subDomainName) {
setTenantName(subDomainName)
fetchDataByName(subDomainName, true)
}
}, [fetchDataByName, isMultiTenant, setTenant, setTenantName, subDomainName])
if (!isMultiTenant) {
return null
}
return (
<>
<label className="form-label mb-2" style={tenantStyle}>
{translate('::Organization')}
</label>
<div className="mb-4">
<Input
placeholder={translate('::Organization')}
value={tenantName ?? ''}
onChange={(event) => handleTenantNameChange(event.target.value)}
onBlur={handleTenantNameBlur}
style={tenantStyle}
aria-hidden={isSubdomainTenant ? 'true' : 'false'}
autoFocus={!isSubdomainTenant}
/>
</div>
</>
)
}
export default TenantSelector

View file

@ -19,6 +19,7 @@ export { default as SegmentItemOption } from './SegmentItemOption'
export { default as StickyFooter } from './StickyFooter'
export { default as SvgIcon } from './SvgIcon'
export { default as TableRowSkeleton } from './loaders/TableRowSkeleton'
export { default as TenantSelector } from './TenantSelector'
export { default as TextBlockSkeleton } from './loaders/TextBlockSkeleton'
export { default as TextEllipsis } from './TextEllipsis'
export { default as UsersAvatarGroup } from './UsersAvatarGroup'

View file

@ -28,7 +28,7 @@ const Logo = (props: LogoProps) => {
return (
<div
className={classNames('logo', 'my-1', className)}
className={classNames('logo', 'my-2', className)}
style={{
...style,
...{ width: logoWidth },

View file

@ -1,7 +1,7 @@
import { useState, forwardRef } from 'react'
import classNames from 'classnames'
import useTimeout from '../hooks/useTimeout'
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa';
import { FaCheckCircle, FaInfoCircle, FaExclamation, FaTimesCircle } from 'react-icons/fa'
import { motion } from 'framer-motion'
import CloseButton from '../CloseButton'
import StatusIcon from '../StatusIcon'
@ -9,173 +9,153 @@ import type { TypeAttributes, CommonProps } from '../@types/common'
import type { ReactNode, MouseEvent } from 'react'
export interface AlertProps extends CommonProps {
closable?: boolean
customClose?: ReactNode | string
customIcon?: ReactNode | string
duration?: number
title?: ReactNode | string
onClose?: (e?: MouseEvent<HTMLDivElement>) => void
rounded?: boolean
showIcon?: boolean
triggerByToast?: boolean
type?: TypeAttributes.Status
closable?: boolean
customClose?: ReactNode | string
customIcon?: ReactNode | string
duration?: number
title?: ReactNode | string
onClose?: (e?: MouseEvent<HTMLDivElement>) => void
rounded?: boolean
showIcon?: boolean
triggerByToast?: boolean
type?: TypeAttributes.Status
}
const DEFAULT_TYPE = 'warning'
const TYPE_MAP = {
success: {
backgroundColor: 'bg-emerald-50 dark:bg-emerald-500',
titleColor: 'text-emerald-700 dark:text-emerald-50',
textColor: 'text-emerald-500 dark:text-emerald-50',
iconColor: 'text-emerald-400 dark:text-emerald-50',
icon: <FaCheckCircle />,
},
info: {
backgroundColor: 'bg-blue-50 dark:bg-blue-500',
titleColor: 'text-blue-700 dark:text-blue-100',
textColor: 'text-blue-500 dark:text-blue-100',
iconColor: 'text-blue-400 dark:text-blue-100',
icon: <FaInfoCircle />,
},
warning: {
backgroundColor: 'bg-yellow-50 dark:bg-yellow-500',
titleColor: 'text-yellow-700 dark:text-yellow-50',
textColor: 'text-yellow-500 dark:text-yellow-50',
iconColor: 'text-yellow-400 dark:text-yellow-50',
icon: <FaExclamation />,
},
danger: {
backgroundColor: 'bg-red-50 dark:bg-red-500',
titleColor: 'text-red-700 dark:text-red-100',
textColor: 'text-red-500 dark:text-red-100',
iconColor: 'text-red-400 dark:text-red-100',
icon: <FaTimesCircle />,
},
success: {
backgroundColor: 'bg-emerald-50 dark:bg-emerald-500',
titleColor: 'text-emerald-700 dark:text-emerald-50',
textColor: 'text-emerald-500 dark:text-emerald-50',
iconColor: 'text-emerald-400 dark:text-emerald-50',
icon: <FaCheckCircle />,
},
info: {
backgroundColor: 'bg-blue-50 dark:bg-blue-500',
titleColor: 'text-blue-700 dark:text-blue-100',
textColor: 'text-blue-500 dark:text-blue-100',
iconColor: 'text-blue-400 dark:text-blue-100',
icon: <FaInfoCircle />,
},
warning: {
backgroundColor: 'bg-yellow-50 dark:bg-yellow-500',
titleColor: 'text-yellow-700 dark:text-yellow-50',
textColor: 'text-yellow-500 dark:text-yellow-50',
iconColor: 'text-yellow-400 dark:text-yellow-50',
icon: <FaExclamation />,
},
danger: {
backgroundColor: 'bg-red-50 dark:bg-red-500',
titleColor: 'text-red-700 dark:text-red-100',
textColor: 'text-red-500 dark:text-red-100',
iconColor: 'text-red-400 dark:text-red-100',
icon: <FaTimesCircle />,
},
}
const TYPE_ARRAY: TypeAttributes.Status[] = [
'success',
'danger',
'info',
'warning',
]
const TYPE_ARRAY: TypeAttributes.Status[] = ['success', 'danger', 'info', 'warning']
const Alert = forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
const {
children,
className,
closable = false,
customClose,
customIcon,
duration = 3000,
title = null,
onClose,
rounded = true,
showIcon = false,
triggerByToast = false,
...rest
} = props
const {
children,
className,
closable = false,
customClose,
customIcon,
duration = 3000,
title = null,
onClose,
rounded = true,
showIcon = false,
triggerByToast = false,
...rest
} = props
const getType = () => {
const { type = DEFAULT_TYPE } = props
if (TYPE_ARRAY.includes(type)) {
return type
}
return DEFAULT_TYPE
const getType = () => {
const { type = DEFAULT_TYPE } = props
if (TYPE_ARRAY.includes(type)) {
return type
}
return DEFAULT_TYPE
}
const type = getType()
const typeMap = TYPE_MAP[type]
const type = getType()
const typeMap = TYPE_MAP[type]
const [display, setDisplay] = useState('show')
const [display, setDisplay] = useState('show')
const { clear } = useTimeout(
onClose as () => void,
duration,
(duration as number) > 0
)
const { clear } = useTimeout(onClose as () => void, duration, (duration as number) > 0)
const handleClose = (e: MouseEvent<HTMLDivElement>) => {
setDisplay('hiding')
onClose?.(e)
clear()
if (!triggerByToast) {
setTimeout(() => {
setDisplay('hide')
}, 400)
}
}
const renderClose = () => {
return (
<div
className="cursor-pointer"
role="presentation"
onClick={(e) => handleClose(e)}
>
{customClose || <CloseButton defaultStyle={false} />}
</div>
)
}
const alertDefaultClass = 'p-2 relative flex'
const alertClass = classNames(
'alert',
alertDefaultClass,
typeMap.backgroundColor,
typeMap.textColor,
!title ? 'font-semibold' : '',
closable ? 'justify-between' : '',
closable && !title ? 'items-center' : '',
rounded && 'rounded-lg',
className
)
if (display === 'hide') {
return null
const handleClose = (e: MouseEvent<HTMLDivElement>) => {
setDisplay('hiding')
onClose?.(e)
clear()
if (!triggerByToast) {
setTimeout(() => {
setDisplay('hide')
}, 400)
}
}
const renderClose = () => {
return (
<motion.div
ref={ref}
className={alertClass}
initial={{ opacity: 1 }}
animate={display === 'hiding' ? 'exit' : 'animate'}
transition={{ duration: 0.25, type: 'tween' }}
variants={{
animate: {
opacity: 1,
},
exit: {
opacity: 0,
},
}}
{...rest}
>
<div className={`flex ${title ? '' : 'items-center'}`}>
{showIcon && (
<StatusIcon
iconColor={typeMap.iconColor}
custom={customIcon}
type={type}
/>
)}
<div className={showIcon ? 'ltr:ml-2 rtl:mr-2' : ''}>
{title ? (
<div
className={`font-semibold mb-1 ${typeMap.titleColor}`}
>
{title}
</div>
) : null}
{children}
</div>
</div>
{closable ? renderClose() : null}
</motion.div>
<div className="cursor-pointer" role="presentation" onClick={(e) => handleClose(e)}>
{customClose || <CloseButton defaultStyle={false} />}
</div>
)
}
const alertDefaultClass = 'p-2 relative flex'
const alertClass = classNames(
'alert',
alertDefaultClass,
typeMap.backgroundColor,
typeMap.textColor,
!title ? 'font-semibold' : '',
closable ? 'justify-between' : '',
closable && !title ? 'items-center' : '',
rounded && 'rounded-lg',
className,
)
if (display === 'hide') {
return null
}
return (
<motion.div
ref={ref}
className={alertClass}
initial={{ opacity: 1 }}
animate={display === 'hiding' ? 'exit' : 'animate'}
transition={{ duration: 0.25, type: 'tween' }}
variants={{
animate: {
opacity: 1,
},
exit: {
opacity: 0,
},
}}
{...rest}
>
<div className={`flex min-w-0 ${title ? '' : 'items-center'}`}>
{showIcon && <StatusIcon iconColor={typeMap.iconColor} custom={customIcon} type={type} />}
<div
className={classNames(
'min-w-0 flex-1 whitespace-normal break-words leading-6',
showIcon ? 'ltr:ml-2 rtl:mr-2' : '',
)}
>
{title ? <div className={`font-semibold mb-1 ${typeMap.titleColor}`}>{title}</div> : null}
{children}
</div>
</div>
{closable ? renderClose() : null}
</motion.div>
)
})
Alert.displayName = 'Alert'

View file

@ -14,6 +14,7 @@ export interface ListFormWizardColumnItemDto {
editorScript: string
colSpan: number
isRequired: boolean
includeInEditingForm: boolean
dbSourceType: number
}
@ -54,6 +55,7 @@ export interface ListFormWizardDto {
languageTextMenuParentTr: string
permissionGroupName: string
menuParentCode: string
menuParentIcon?: string
menuIcon: string
dataSourceCode: string
dataSourceConnectionString: string
@ -113,6 +115,7 @@ export interface WizardSeedFileItemDto {
editorScript: string
colSpan: number
isRequired: boolean
includeInEditingForm?: boolean
dbSourceType: number
turkishCaption?: string
englishCaption?: string

View file

@ -908,6 +908,7 @@ export interface WidgetEditDto {
export interface WorkflowDto {
approvalUserFieldName: string
isFilterUserName: boolean
approvalDateFieldName: string
approvalStatusFieldName: string
approvalDescriptionFieldName: string

View file

@ -4,6 +4,7 @@ export interface RouteDto extends FullAuditedEntityDto<string> {
id: string;
key: string;
path: string;
componentType: string;
componentPath: string;
routeType: string;
authority: string[];

View file

@ -1,6 +1,5 @@
import { RouteDto } from '@/proxy/routes/models'
import { lazy } from 'react'
import { useComponents } from '@/contexts/ComponentContext'
// Tüm view bileşenlerini import et (vite özel)
// shared klasörü hariç, çünkü bu bileşenler genellikle başka yerlerde statik import ediliyor
@ -8,25 +7,12 @@ const modules = import.meta.glob(['../views/**/*.tsx', '!../views/shared/**/*.ts
const lazyComponentCache = new Map<string, React.LazyExoticComponent<React.ComponentType<any>>>()
// ComponentPath'in fiziksel mi yoksa dinamik mi olduğunu belirle
function isPhysicalComponent(componentPath: string): boolean {
// @ ile başlayan path'ler fiziksel dosya yolu
// Başka bir kural: dynamic: ile başlayan path'ler dinamik
return componentPath.startsWith('@/') || componentPath.startsWith('../')
}
function isDynamicComponent(componentPath: string): boolean {
// dynamic: ile başlayan path'ler dinamik komponent
return componentPath.startsWith('dynamic:')
}
// Fiziksel komponent yükleme (mevcut mantık)
function loadPhysicalComponent(componentPath: string) {
const cleanedPath = componentPath.replace(/^@\//, '')
const fullPath = `../${cleanedPath}.tsx`
if (lazyComponentCache.has(fullPath)) {
// console.log(`Physical component loaded from cache: ${fullPath}`)
return lazyComponentCache.get(fullPath)!
}
@ -43,53 +29,65 @@ function loadPhysicalComponent(componentPath: string) {
// Dinamik komponent yükleme (yeni mantık)
function loadDynamicComponent(
componentPath: string,
componentPath: string,
registeredComponents: Record<string, React.ComponentType<unknown>>,
renderComponent?: (name: string, props?: any) => React.ReactNode,
isComponentRegistered?: (name: string) => boolean
isComponentRegistered?: (name: string) => boolean,
) {
const componentName = componentPath.replace('dynamic:', '')
if (lazyComponentCache.has(componentPath)) {
// console.log(`Dynamic component loaded from cache: ${componentName}`)
return lazyComponentCache.get(componentPath)!
}
// Önce manuel registered komponentleri kontrol et
let DynamicComponent = registeredComponents[componentName]
let DynamicComponent = registeredComponents[componentPath]
// Eğer manuel registered'da yoksa, database compiled komponentleri kontrol et
if (!DynamicComponent && isComponentRegistered && renderComponent && isComponentRegistered(componentName)) {
DynamicComponent = (props: any) => renderComponent(componentName, props) as React.ReactElement
if (
!DynamicComponent &&
isComponentRegistered &&
renderComponent &&
isComponentRegistered(componentPath)
) {
DynamicComponent = (props: any) => renderComponent(componentPath, props) as React.ReactElement
}
if (!DynamicComponent) {
if (isComponentRegistered) {
console.log('Database component registry available - checking...')
}
throw new Error(`Dynamic component not found: ${componentName}`)
throw new Error(`Dynamic component not found: ${componentPath}`)
}
// console.log(`Dynamic component loaded: ${componentName}`)
// console.log(`Dynamic component loaded: ${componentPath}`)
// Dinamik komponent için lazy wrapper oluştur
const LazyComponent = lazy(() => Promise.resolve({ default: DynamicComponent as React.ComponentType<any> }))
const LazyComponent = lazy(() =>
Promise.resolve({ default: DynamicComponent as React.ComponentType<any> }),
)
lazyComponentCache.set(componentPath, LazyComponent)
return LazyComponent
}
export function loadComponent(
componentPath: string,
componentType: string,
componentPath: string,
registeredComponents?: Record<string, React.ComponentType<unknown>>,
renderComponent?: (name: string, props?: any) => React.ReactNode,
isComponentRegistered?: (name: string) => boolean
isComponentRegistered?: (name: string) => boolean,
) {
if (isPhysicalComponent(componentPath)) {
if (componentType === 'normal') {
return loadPhysicalComponent(componentPath)
} else if (isDynamicComponent(componentPath)) {
} else if (componentType === 'dynamic') {
if (!registeredComponents) {
throw new Error('Registered components required for dynamic component loading')
}
return loadDynamicComponent(componentPath, registeredComponents, renderComponent, isComponentRegistered)
return loadDynamicComponent(
componentPath,
registeredComponents,
renderComponent,
isComponentRegistered,
)
} else {
// Backward compatibility: varsayılan olarak fiziksel komponent kabul et
return loadPhysicalComponent(componentPath)
@ -103,13 +101,12 @@ export interface DynamicReactRoute {
getComponent: (
registeredComponents?: Record<string, React.ComponentType<unknown>>,
renderComponent?: (name: string, props?: any) => React.ReactNode,
isComponentRegistered?: (name: string) => boolean
isComponentRegistered?: (name: string) => boolean,
) => React.LazyExoticComponent<React.ComponentType<any>>
routeType: string
authority?: string[]
componentType: string
componentPath: string
isPhysical: boolean
isDynamic: boolean
}
// API'den gelen route objesini, React Router için uygun hale getirir
@ -117,12 +114,17 @@ export function mapDynamicRoutes(routes: RouteDto[]): DynamicReactRoute[] {
return routes.map((route) => ({
key: route.path,
path: route.path,
getComponent: (registeredComponents, renderComponent, isComponentRegistered) =>
loadComponent(route.componentPath, registeredComponents, renderComponent, isComponentRegistered),
getComponent: (registeredComponents, renderComponent, isComponentRegistered) =>
loadComponent(
route.componentType,
route.componentPath,
registeredComponents,
renderComponent,
isComponentRegistered,
),
routeType: route.routeType,
authority: route.authority,
componentType: route.componentType,
componentPath: route.componentPath,
isPhysical: isPhysicalComponent(route.componentPath),
isDynamic: isDynamicComponent(route.componentPath),
}))
}

View file

@ -15,11 +15,42 @@ const AccessDenied = React.lazy(() => import('@/views/AccessDenied'))
const NotFound = React.lazy(() => import('@/views/NotFound'))
const DatabaseSetup = React.lazy(() => import('@/views/setup/DatabaseSetup'))
const RootRedirect = () => {
const location = useLocation()
const searchParams = new URLSearchParams(location.search)
const isPasswordResetLink = searchParams.has('userId') && searchParams.has('resetToken')
return (
<Navigate
to={
isPasswordResetLink
? `${ROUTES_ENUM.authenticated.resetPassword}${location.search}`
: hasSubdomain()
? ROUTES_ENUM.authenticated.login
: ROUTES_ENUM.public.home
}
replace
/>
)
}
const LegacyPasswordResetRedirect = () => {
const location = useLocation()
return <Navigate to={`${ROUTES_ENUM.authenticated.resetPassword}${location.search}`} replace />
}
const LegacyEmailConfirmationRedirect = () => {
const location = useLocation()
return <Navigate to={location.pathname.replace(/^\/account/, '')} replace />
}
export const DynamicRouter: React.FC = () => {
const { routes, loading, error } = useDynamicRoutes()
const { registeredComponents, renderComponent, isComponentRegistered } = useComponents()
const location = useLocation()
const dynamicRoutes = React.useMemo(() => mapDynamicRoutes(routes), [routes])
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı
@ -33,7 +64,11 @@ export const DynamicRouter: React.FC = () => {
{dynamicRoutes
.filter((r) => r.routeType === 'protected')
.map((route) => {
const Component = route.getComponent(registeredComponents, renderComponent, isComponentRegistered)
const Component = route.getComponent(
registeredComponents,
renderComponent,
isComponentRegistered,
)
return (
<Route
key={route.key}
@ -85,7 +120,11 @@ export const DynamicRouter: React.FC = () => {
hasSubdomain() ? r.routeType === 'authenticated' : r.routeType !== 'protected',
)
.map((route) => {
const Component = route.getComponent(registeredComponents, renderComponent, isComponentRegistered)
const Component = route.getComponent(
registeredComponents,
renderComponent,
isComponentRegistered,
)
return (
<Route
key={route.key}
@ -100,15 +139,9 @@ export const DynamicRouter: React.FC = () => {
})}
{/* root redirect */}
<Route
path="/"
element={
<Navigate
to={hasSubdomain() ? ROUTES_ENUM.authenticated.login : ROUTES_ENUM.public.home}
replace
/>
}
/>
<Route path="/" element={<RootRedirect />} />
<Route path="/account/confirm/:userId/:token" element={<LegacyEmailConfirmationRedirect />} />
<Route path="/account/reset-password" element={<LegacyPasswordResetRedirect />} />
{/* public access denied (statik) */}
<Route

View file

@ -52,8 +52,8 @@ export const resetPassword = (userId: string, resetToken: string, password: stri
},
})
export const sendAccountConfirmationCode = (data: any) => {
apiService.fetchData({
export const sendAccountConfirmationCode = (data: any) =>
apiService.fetchData<boolean>({
method: 'POST',
url: 'api/app/platform-account/send-account-confirmation-code',
data: {
@ -61,7 +61,6 @@ export const sendAccountConfirmationCode = (data: any) => {
captchaResponse: data.captchaResponse,
},
})
}
export const verifyAccountConfirmationCode = (userId: string, token: string) =>
apiService.fetchData({
@ -74,7 +73,7 @@ export const verifyAccountConfirmationCode = (userId: string, token: string) =>
})
export const sendExtendLoginRequest = (data: any) =>
apiService.fetchData({
apiService.fetchData<boolean>({
method: 'POST',
url: 'api/app/platform-account/send-extend-login-request',
data: {

View file

@ -0,0 +1,24 @@
import { PagedResultDto } from '@/proxy'
import { AuditLogDto } from '@/proxy/auditLog/audit-log'
import apiService from '@/services/api.service'
export interface AuditLogListRequestDto {
skipCount?: number
maxResultCount?: number
sorting?: string
listFormCode?: string
entityId?: string
}
class AuditLogService {
async getList(params?: AuditLogListRequestDto): Promise<PagedResultDto<AuditLogDto>> {
const response = await apiService.fetchData<PagedResultDto<AuditLogDto>>({
url: '/api/app/audit-log',
method: 'GET',
params,
})
return response.data
}
}
export const auditLogService = new AuditLogService()

View file

@ -11,6 +11,11 @@ import { ListResultDto, PagedAndSortedResultRequestDto, PagedResultDto } from '.
import { AuditLogDto } from '../proxy/auditLog/audit-log'
import apiService from './api.service'
export interface UserAvatarUpdateInput {
userId: string
avatar?: File
}
export const getRoles = (skipCount = 0, maxResultCount = 10) =>
apiService.fetchData<ListResultDto<IdentityRoleDto>>({
method: 'GET',
@ -36,6 +41,21 @@ export const putUserDetail = (input: UserInfoViewModel) =>
data: input,
})
export const putUserAvatar = (input: UserAvatarUpdateInput) => {
const formData = new FormData()
formData.append('userId', input.userId)
if (input.avatar) {
formData.append('avatar', input.avatar)
}
return apiService.fetchData({
method: 'PUT',
url: `/api/app/platform-identity/avatar`,
data: formData,
})
}
export const putUserLookout = (input: UserInfoViewModel) =>
apiService.fetchData({
method: 'PUT',

View file

@ -43,6 +43,7 @@ export interface WorkflowRunResultDto {
currentNodeKind?: string | null
waitingApproval: boolean
completed: boolean
toastMessages?: string[]
}
export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {

View file

@ -0,0 +1,29 @@
export type EditorScriptRuntimeContext = {
formData: Record<string, any>
e: any
editor: any
runtimeSetEditorReadOnly?: (field: string, readOnly: boolean) => void
setFormData?: (newData: any) => void
}
export const executeEditorScript = (
script: string,
{
formData,
e,
editor,
runtimeSetEditorReadOnly,
setFormData,
}: EditorScriptRuntimeContext,
) => {
const executor = new Function(
'formData',
'e',
'editor',
'runtimeSetEditorReadOnly',
'setFormData',
script,
)
return executor(formData, e, editor, runtimeSetEditorReadOnly, setFormData)
}

View file

@ -1,5 +1,6 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant'
import {
sendAccountConfirmationCode,
verifyAccountConfirmationCode,
@ -12,15 +13,28 @@ function useAccount() {
const sendConfirmationCode = async (values: any) => {
try {
await sendAccountConfirmationCode(values)
const result = await sendAccountConfirmationCode(values)
if (result.data !== true) {
throw new Error('This email is already confirmed or no account was found.')
}
setError('')
setMessage('Verification code has been sent to your e-mail address.')
return {
status: 'success',
}
} catch (error: any) {
const err =
error?.response?.data?.error?.message ||
error?.response?.data?.message ||
error?.message ||
error.toString()
setMessage('')
setError(error?.response?.data?.message || error.toString())
setError(err)
return {
status: 'failed',
message: error?.response?.data?.message || error.toString(),
message: err,
}
}
}
@ -38,7 +52,7 @@ function useAccount() {
setError('')
setMessage('Your account is confirmed')
setTimeout(() => {
navigate('/account/login')
navigate(ROUTES_ENUM.authenticated.login)
}, 3000)
} else {
throw new Error('Invalid token')

View file

@ -328,6 +328,91 @@ export function emptyCriteria(kind = 'Compare', listFormCode = ''): WorkflowCrit
}
}
export function uniqueCriteriaTitle(
kind: string,
criteria: Array<Pick<WorkflowCriteriaDto, 'id' | 'kind' | 'title'>>,
currentId?: string | null,
preferredTitle?: string | null,
) {
const hasPreferredTitle = Boolean(preferredTitle?.trim())
const baseTitle = (preferredTitle || defaultTitle(kind)).trim()
const usedTitles = new Set(
criteria
.filter((item) => !currentId || item.id !== currentId)
.map((item) => (item.title || '').trim().toLocaleLowerCase('tr-TR'))
.filter(Boolean),
)
if (!hasPreferredTitle) {
const sameKindCount = criteria.filter(
(item) =>
(!currentId || item.id !== currentId) &&
item.kind === kind &&
isDefaultTitleVariant(item.title, baseTitle),
).length
let index = sameKindCount + 1
let candidate = `${baseTitle}${index}`
while (usedTitles.has(candidate.toLocaleLowerCase('tr-TR'))) {
index += 1
candidate = `${baseTitle}${index}`
}
return candidate
}
if (!usedTitles.has(baseTitle.toLocaleLowerCase('tr-TR'))) {
return baseTitle
}
let index = 1
let candidate = `${baseTitle}${index}`
while (usedTitles.has(candidate.toLocaleLowerCase('tr-TR'))) {
index += 1
candidate = `${baseTitle}${index}`
}
return candidate
}
export function uniqueCriteriaId(
criteria: Array<Pick<WorkflowCriteriaDto, 'id'>>,
reservedIds: string[] = [],
) {
const usedIds = new Set(
[...criteria.map((item) => item.id), ...reservedIds]
.map((id) => (id || '').trim().toLocaleLowerCase('tr-TR'))
.filter(Boolean),
)
const maxNumber = [...usedIds].reduce((max, id) => Math.max(max, parseCriteriaIdNumber(id)), 0)
let nextNumber = maxNumber + 1
let candidate = formatCriteriaId(nextNumber)
while (usedIds.has(candidate.toLocaleLowerCase('tr-TR'))) {
nextNumber += 1
candidate = formatCriteriaId(nextNumber)
}
return candidate
}
function parseCriteriaIdNumber(id: string) {
const match = id.match(/^(?:n)?(\d+)$/iu)
return match ? Number(match[1]) : 0
}
function formatCriteriaId(number: number) {
return `N${String(number).padStart(3, '0')}`.slice(-4)
}
function isDefaultTitleVariant(title: string | null | undefined, baseTitle: string) {
const normalized = (title || '').trim()
return normalized === baseTitle || new RegExp(`^${escapeRegExp(baseTitle)}\\d+$`, 'u').test(normalized)
}
function escapeRegExp(value: string) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm {
const sharedPerson = item.approver || ''
@ -347,7 +432,8 @@ export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm
}
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
const sharedPerson = item.approver || ''
const sharedPerson =
item.kind === 'Approval' || item.kind === 'Inform' ? item.approver || '' : ''
const compareOutcomes = (item.compareOutcomes || [])
.slice(0, 4)
.filter((outcome) => outcome.label?.trim())
@ -504,7 +590,7 @@ export function criteriaSummary(item: WorkflowCriteriaDto) {
if (item.kind === 'Approval' || item.kind === 'Inform') {
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
}
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
return item.title
}
export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) {

View file

@ -46,9 +46,11 @@ const Views = (props: ViewsProps) => {
<ErrorBoundary fallbackRender={fallbackRender}>
<Suspense fallback={<Loading loading={true} />}>
{!!warning?.length && (
<Alert showIcon className="mb-4" type="warning">
<Alert showIcon className="mb-4 text-sm text-left" type="warning">
{warning.map((w, i) => (
<div key={i}>{w}</div>
<div key={i} className="whitespace-normal break-words">
{w}
</div>
))}
</Alert>
)}

View file

@ -456,13 +456,15 @@ function OrgChartNode({
style={{ cursor: dragging ? 'grabbing' : 'grab' }}
>
{/* Header bar */}
<div data-header="" className={`${headerBg} rounded-t-xl px-3 py-2 flex items-center gap-2`}>
<div data-header="" className={`${headerBg} rounded-t-xl px-3 py-2 flex items-center gap-2 dark:bg-gray-900 dark:text-gray-100`}>
{mode === 'department' ? (
<FaBuilding className="w-3 h-3 text-white opacity-80 flex-shrink-0" />
) : (
<FaBriefcase className="w-3 h-3 text-white opacity-80 flex-shrink-0" />
)}
<span data-node-name="" className="text-white font-semibold text-xs truncate flex-1">{node.name}</span>
<span data-node-name="" className="text-white font-semibold text-xs truncate flex-1 dark:bg-gray-900 dark:text-gray-100">
{node.name}
</span>
{hasChildren && (
<button
data-stop-drag="true"
@ -514,7 +516,7 @@ function OrgChartNode({
{/* Child count badge */}
{hasChildren && (
<div className="absolute -bottom-2.5 left-1/2 -translate-x-1/2 bg-white border border-slate-200 rounded-full px-2 py-0.5 text-xs text-slate-500 shadow-sm whitespace-nowrap z-10">
<div className="absolute -bottom-2.5 left-1/2 -translate-x-1/2 bg-white border border-slate-200 rounded-full px-2 py-0.5 text-xs text-slate-500 shadow-sm whitespace-nowrap z-10 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100">
{node.children.length}
</div>
)}

View file

@ -5,15 +5,16 @@ import {
emptyCriteria,
normalizeCriteria,
toCriteriaForm,
uniqueCriteriaTitle,
type WorkflowCriteriaForm,
} from '@/utils/workflow/workflowHelpers'
import { workflowService, type WorkflowCriteriaDto } from '@/services/workflow.service'
import { WorkflowDesigner } from '../workflow/WorkflowDesigner'
import { SelectBoxOption } from '@/types/shared'
import { Field, FieldProps, Form, Formik } from 'formik'
import { Button, Card, FormContainer, FormItem, Input, Select } from '@/components/ui'
import { Button, Card, Checkbox, FormContainer, FormItem, Input, Select } from '@/components/ui'
import { ListFormEditTabs } from '@/proxy/admin/list-form/options'
import { object, string } from 'yup'
import { bool, object, string } from 'yup'
import { useStoreState } from '@/store/store'
import { FormEditProps } from './FormEdit'
import { useLocalization } from '@/utils/hooks/useLocalization'
@ -112,9 +113,17 @@ export function FormTabWorkflow(
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
runAction(async () => {
const normalized = normalizeCriteria(criteriaForm)
const preferredTitle = criteriaForm.title || normalized.title
await workflowService.saveCriteria({
...normalizeCriteria(criteriaForm),
...normalized,
listFormCode: props.listFormCode,
title: uniqueCriteriaTitle(
normalized.kind || '',
currentCriteria,
normalized.id,
preferredTitle,
),
})
setSelectedId('')
})
@ -123,9 +132,11 @@ export function FormTabWorkflow(
const addCriteria = (kind: string) => {
setDesignerTab('flow')
runAction(async () => {
const nextTitle = uniqueCriteriaTitle(kind, currentCriteria)
const saved = await workflowService.saveCriteria({
...normalizeCriteria(emptyCriteria(kind, props.listFormCode)),
listFormCode: props.listFormCode,
title: nextTitle,
positionX: 80 + (currentCriteria.length % 5) * 230,
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
})
@ -296,10 +307,13 @@ export function FormTabWorkflow(
}
const schema = object().shape({
approvalUserFieldName: string().required(),
approvalStatusFieldName: string().required(),
approvalDateFieldName: string(),
approvalDescriptionFieldName: string(),
workflowDto: object().shape({
approvalUserFieldName: string().required(),
isFilterUserName: bool(),
approvalStatusFieldName: string().required(),
approvalDateFieldName: string(),
approvalDescriptionFieldName: string(),
}),
})
const initialValues = useStoreState((s) => s.admin.lists.values)
@ -320,7 +334,7 @@ export function FormTabWorkflow(
<Form>
<FormContainer size="sm">
<Card className="my-2">
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
<div className="grid grid-cols-1 md:grid-cols-5 gap-2">
<FormItem
asterisk
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalUserFieldName')}
@ -370,7 +384,7 @@ export function FormTabWorkflow(
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalDateFieldName')}
invalid={
@ -421,9 +435,22 @@ export function FormTabWorkflow(
)}
</Field>
</FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.Workflow.IsFilterUserName')}
invalid={
!!(
errors.workflowDto?.isFilterUserName &&
touched.workflowDto?.isFilterUserName
)
}
errorMessage={errors.workflowDto?.isFilterUserName as string}
>
<Field name="workflowDto.isFilterUserName" component={Checkbox} />
</FormItem>
</div>
<Button block variant="solid" loading={isSubmitting}>
<Button block variant="solid" type="submit" loading={isSubmitting}>
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
</Button>
</Card>

View file

@ -24,16 +24,12 @@ const baseInputClass =
const boolOptions = [
{ key: 'showClearButton', label: 'showClearButton' },
{ key: 'readOnly', label: 'readOnly' },
{ key: 'disabled', label: 'disabled' },
{ key: 'searchEnabled', label: 'searchEnabled' },
{ key: 'showDataBeforeSearch', label: 'showDataBeforeSearch' },
{ key: 'acceptCustomValue', label: 'acceptCustomValue' },
{ key: 'showSpinButtons', label: 'showSpinButtons' },
{ key: 'useMaskBehavior', label: 'useMaskBehavior' },
{ key: 'useMaskedValue', label: 'useMaskedValue' },
{ key: 'spellcheck', label: 'spellcheck' },
{ key: 'openOnFieldClick', label: 'openOnFieldClick' },
{ key: 'showDropDownButton', label: 'showDropDownButton' },
]
const htmlToolbarItems = [
@ -302,10 +298,6 @@ function EditorOptionsBuilderDialog({
>
Editor Options Builder
</h5>
<FaSlidersH
className="text-gray-400"
title="Bu dialog editorOptions alanını metin yazmadan oluşturmak için kullanılır."
/>
</div>
{parseError && (
@ -314,8 +306,8 @@ function EditorOptionsBuilderDialog({
</div>
)}
<div className="grid min-h-0 grid-cols-1 gap-4 overflow-y-auto pr-1 lg:grid-cols-3">
<section className="lg:col-span-2 flex flex-col gap-4">
<div className="grid grid-cols-2 min-h-0 gap-4 overflow-y-auto pr-1 ">
<section className="flex flex-col gap-4">
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
<div
className="mb-3 text-sm font-semibold"
@ -327,7 +319,7 @@ function EditorOptionsBuilderDialog({
{boolOptions.map((option) => (
<label
key={option.key}
className="flex items-center gap-2 rounded border border-gray-100 dark:border-gray-700 px-2 py-2 text-sm"
className="flex items-center gap-2 rounded px-1 py-1 text-sm"
title={`${option.key}: true/false olarak editorOptions içine yazılır.`}
>
<input
@ -565,7 +557,7 @@ function EditorOptionsBuilderDialog({
>
5. NumberBox
</div>
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="grid grid-cols-1 md:grid-cols-5 gap-3">
{['min', 'max', 'step'].map((key) => (
<label key={key} className="text-xs text-gray-500">
{key}
@ -585,7 +577,7 @@ function EditorOptionsBuilderDialog({
</label>
))}
<label className="text-xs text-gray-500">
format.type
type
<select
className={baseInputClass}
value={toSelectValue(getByPath(options, 'format.type'))}
@ -600,7 +592,7 @@ function EditorOptionsBuilderDialog({
</select>
</label>
<label className="text-xs text-gray-500">
format.precision
precision
<input
className={baseInputClass}
value={toInputValue(getByPath(options, 'format.precision'))}
@ -706,12 +698,106 @@ function EditorOptionsBuilderDialog({
</div>
</div>
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
<div className="flex items-center justify-between gap-2 mb-3">
<div>
<div
className="text-sm font-semibold"
title="DevExtreme dokümanındaki herhangi bir editorOptions path'ini eklemek için kullan. Örnek path: toolbar.multiline, maskRules.X, inputAttr.aria-label."
>
7. Özel Ayarlar
</div>
</div>
<Button
type="button"
size="sm"
icon={<FaPlus />}
onClick={() =>
setCustomOptions((current) => [
...current,
{
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
path: '',
value: '',
type: 'string',
},
])
}
>
Ekle
</Button>
</div>
<div className="flex flex-col gap-2">
{customOptions.map((option) => (
<div key={option.id} className="grid grid-cols-12 gap-2">
<input
className={`${baseInputClass} col-span-4`}
value={option.path}
onChange={(event) =>
setCustomOptions((current) =>
current.map((item) =>
item.id === option.id ? { ...item, path: event.target.value } : item,
),
)
}
placeholder="path.to.option"
title="Nokta ile nested path yaz. Örnek: toolbar.multiline"
/>
<select
className={`${baseInputClass} col-span-2`}
value={option.type}
onChange={(event) =>
setCustomOptions((current) =>
current.map((item) =>
item.id === option.id
? { ...item, type: event.target.value as CustomOption['type'] }
: item,
),
)
}
>
<option value="string">string</option>
<option value="number">number</option>
<option value="boolean">boolean</option>
<option value="json">json</option>
</select>
<input
className={`${baseInputClass} col-span-5`}
value={option.value}
onChange={(event) =>
setCustomOptions((current) =>
current.map((item) =>
item.id === option.id ? { ...item, value: event.target.value } : item,
),
)
}
placeholder={option.type === 'boolean' ? 'true / false' : 'value'}
title="Tip JSON ise object/array yazabilirsin. Tip boolean ise true veya false yaz."
/>
<Button
type="button"
shape="circle"
variant="plain"
icon={<FaTrash />}
onClick={() =>
setCustomOptions((current) =>
current.filter((item) => item.id !== option.id),
)
}
/>
</div>
))}
</div>
</div>
</section>
<section className="rounded border border-gray-200 dark:border-gray-700 p-3 flex flex-col min-h-[420px]">
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
<div
className="mb-3 text-sm font-semibold"
title="Daha önce kullandığın hazır editorOptions örnekleri. Tıkladığın preset mevcut JSON ile birleştirilir, yani başka seçenekleri silmeden üzerine ekler."
>
7. Hazır Ayarlar
Hazır Ayarlar
</div>
<div className="flex flex-wrap gap-2">
<Button
@ -886,108 +972,14 @@ function EditorOptionsBuilderDialog({
</div>
</div>
<div className="rounded border border-gray-200 dark:border-gray-700 p-3">
<div className="flex items-center justify-between gap-2 mb-3">
<div>
<div
className="text-sm font-semibold"
title="DevExtreme dokümanındaki herhangi bir editorOptions path'ini eklemek için kullan. Örnek path: toolbar.multiline, maskRules.X, inputAttr.aria-label."
>
8. Özel Ayarlar
</div>
</div>
<Button
type="button"
size="sm"
icon={<FaPlus />}
onClick={() =>
setCustomOptions((current) => [
...current,
{
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
path: '',
value: '',
type: 'string',
},
])
}
>
Ekle
</Button>
</div>
<div className="flex flex-col gap-2">
{customOptions.map((option) => (
<div key={option.id} className="grid grid-cols-12 gap-2">
<input
className={`${baseInputClass} col-span-4`}
value={option.path}
onChange={(event) =>
setCustomOptions((current) =>
current.map((item) =>
item.id === option.id ? { ...item, path: event.target.value } : item,
),
)
}
placeholder="path.to.option"
title="Nokta ile nested path yaz. Örnek: toolbar.multiline"
/>
<select
className={`${baseInputClass} col-span-2`}
value={option.type}
onChange={(event) =>
setCustomOptions((current) =>
current.map((item) =>
item.id === option.id
? { ...item, type: event.target.value as CustomOption['type'] }
: item,
),
)
}
>
<option value="string">string</option>
<option value="number">number</option>
<option value="boolean">boolean</option>
<option value="json">json</option>
</select>
<input
className={`${baseInputClass} col-span-5`}
value={option.value}
onChange={(event) =>
setCustomOptions((current) =>
current.map((item) =>
item.id === option.id ? { ...item, value: event.target.value } : item,
),
)
}
placeholder={option.type === 'boolean' ? 'true / false' : 'value'}
title="Tip JSON ise object/array yazabilirsin. Tip boolean ise true veya false yaz."
/>
<Button
type="button"
shape="circle"
variant="plain"
icon={<FaTrash />}
onClick={() =>
setCustomOptions((current) =>
current.filter((item) => item.id !== option.id),
)
}
/>
</div>
))}
</div>
</div>
</section>
<section className="rounded border border-gray-200 dark:border-gray-700 p-3 flex flex-col min-h-[420px]">
<div
className="flex items-center gap-2 text-sm font-semibold mb-3"
className="flex items-center gap-2 text-sm font-semibold mt-3"
title="Kaydet/Uygula sonrası editorOptions alanına yazılacak net JSON budur."
>
<FaCode />
JSON Önizleme
</div>
<pre className="flex-1 overflow-auto rounded bg-gray-50 dark:bg-gray-900 p-3 text-xs whitespace-pre-wrap">
<pre className="flex overflow-auto rounded bg-gray-50 dark:bg-gray-900 p-3 text-xs whitespace-pre-wrap">
{preview || '{}'}
</pre>
</section>

View file

@ -3,6 +3,7 @@ import { ROUTES_ENUM } from '@/routes/route.constant'
import { SelectBoxOption } from '@/types/shared'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Form, Formik, FormikProps } from 'formik'
import type { KeyboardEvent } from 'react'
import { useEffect, useRef, useState } from 'react'
import { Helmet } from 'react-helmet'
import { useLocation, useNavigate } from 'react-router-dom'
@ -70,6 +71,7 @@ const initialValues: ListFormWizardDto = {
languageTextMenuParentTr: '',
permissionGroupName: '',
menuParentCode: '',
menuParentIcon: '',
menuIcon: '',
dataSourceCode: '',
dataSourceConnectionString: '',
@ -94,6 +96,7 @@ const initialValues: ListFormWizardDto = {
widgets: [],
workflow: {
approvalUserFieldName: '',
isFilterUserName: false,
approvalDateFieldName: '',
approvalStatusFieldName: '',
approvalDescriptionFieldName: '',
@ -221,6 +224,7 @@ const Wizard = () => {
const [widgets, setWidgets] = useState<WidgetEditDto[]>([])
const [workflow, setWorkflow] = useState<WorkflowDto>({
approvalUserFieldName: '',
isFilterUserName: false,
approvalDateFieldName: '',
approvalStatusFieldName: '',
approvalDescriptionFieldName: '',
@ -240,6 +244,18 @@ const Wizard = () => {
])
const isAuditColumn = (columnName: string) => AUDIT_COLUMNS.has(columnName.toLowerCase())
const isTenantColumn = (columnName: string) => columnName.toLowerCase() === 'tenantid'
const isAutoSelectedColumn = (columnName: string, isTenant = false) =>
!isAuditColumn(columnName) && !(isTenant && isTenantColumn(columnName))
const removeTenantColumn = (columns: Set<string>) =>
new Set([...columns].filter((columnName) => !isTenantColumn(columnName)))
const removeTenantGroupItems = (groups: WizardGroup[]) =>
groups.map((group) => ({
...group,
items: group.items.filter((item) => !isTenantColumn(item.dataField)),
}))
const loadColumns = async (dsCode: string, schema: string, name: string) => {
if (!dsCode || !name) {
@ -253,12 +269,13 @@ const Wizard = () => {
const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
const cols = res.data ?? []
setSelectCommandColumns(cols)
const selectableColumns = cols.filter((c) => !isAuditColumn(c.columnName))
const colNames = new Set(cols.map((c) => c.columnName.toLowerCase()))
const hasTenantColumn = colNames.has('tenantid')
const selectableColumns = cols.filter((c) => isAutoSelectedColumn(c.columnName, hasTenantColumn))
setSelectedColumns(new Set(selectableColumns.map((c) => c.columnName)))
setEditingGroups([])
// Auto-check isTenant / isBranch based on column presence
const colNames = new Set(cols.map((c) => c.columnName.toLowerCase()))
formikRef.current?.setFieldValue('isTenant', colNames.has('tenantid'))
formikRef.current?.setFieldValue('isTenant', hasTenantColumn)
formikRef.current?.setFieldValue('isBranch', colNames.has('branchid'))
// Auto-select first column as key field
if (cols.length > 0) {
@ -284,17 +301,23 @@ const Wizard = () => {
return next
})
const toggleAllColumns = (all: boolean) =>
const toggleAllColumns = (all: boolean, isTenant = formikRef.current?.values.isTenant ?? false) =>
setSelectedColumns(
all
? new Set(
selectCommandColumns
.filter((c) => !isAuditColumn(c.columnName))
.filter((c) => isAutoSelectedColumn(c.columnName, isTenant))
.map((c) => c.columnName),
)
: new Set(),
)
const handleTenantChange = (isTenant: boolean) => {
if (!isTenant) return
setSelectedColumns((prev) => removeTenantColumn(prev))
setEditingGroups((prev) => removeTenantGroupItems(prev))
}
const getDataSourceList = async () => {
setIsLoadingDataSource(true)
const response = await getDataSources()
@ -391,6 +414,7 @@ const Wizard = () => {
languageTextMenuParentTr: w.languageTextMenuParentTr ?? '',
permissionGroupName: w.permissionGroupName ?? '',
menuParentCode: w.menuParentCode ?? '',
menuParentIcon: w.menuParentIcon ?? '',
menuIcon: w.menuIcon ?? '',
dataSourceCode: w.dataSourceCode ?? '',
dataSourceConnectionString: w.dataSourceConnectionString ?? '',
@ -415,6 +439,7 @@ const Wizard = () => {
widgets: w.widgets ?? [],
workflow: w.workflow ?? {
approvalUserFieldName: '',
isFilterUserName: false,
approvalDateFieldName: '',
approvalStatusFieldName: '',
approvalDescriptionFieldName: '',
@ -453,6 +478,7 @@ const Wizard = () => {
editorScript: it.editorScript ?? '',
colSpan: it.colSpan ?? 1,
isRequired: it.isRequired ?? false,
includeInEditingForm: it.includeInEditingForm ?? true,
turkishCaption: it.turkishCaption ?? it.dataField,
englishCaption: it.englishCaption ?? it.dataField,
captionName: it.captionName ?? `App.Listform.ListformField.${it.dataField}`,
@ -477,6 +503,7 @@ const Wizard = () => {
setWorkflow(
w.workflow ?? {
approvalUserFieldName: '',
isFilterUserName: false,
approvalDateFieldName: '',
approvalStatusFieldName: '',
approvalDescriptionFieldName: '',
@ -516,24 +543,38 @@ const Wizard = () => {
.trim()
const handleWizardNameChange = (name: string) => {
const formik = formikRef.current
const spacedLabel = toSpacedLabel(name)
const previousSpacedLabel = toSpacedLabel(formik?.values.wizardName ?? '')
const derived = deriveListFormCode(name)
formikRef.current?.setFieldValue('wizardName', name)
formikRef.current?.setFieldValue('listFormCode', derived)
formikRef.current?.setFieldValue('menuCode', derived)
formikRef.current?.setFieldValue('languageTextMenuEn', spacedLabel)
formikRef.current?.setFieldValue('languageTextMenuTr', spacedLabel)
formikRef.current?.setFieldValue('languageTextTitleEn', spacedLabel)
formikRef.current?.setFieldValue('languageTextTitleTr', spacedLabel)
formikRef.current?.setFieldValue('languageTextDescEn', spacedLabel)
formikRef.current?.setFieldValue('languageTextDescTr', spacedLabel)
const setAutoText = (field: keyof Pick<
ListFormWizardDto,
| 'languageTextMenuEn'
| 'languageTextMenuTr'
| 'languageTextTitleEn'
| 'languageTextTitleTr'
| 'languageTextDescEn'
| 'languageTextDescTr'
>) => {
const current = formik?.values[field]
if (!current || current === previousSpacedLabel) {
formik?.setFieldValue(field, spacedLabel)
}
}
formik?.setFieldValue('wizardName', name)
formik?.setFieldValue('listFormCode', derived)
formik?.setFieldValue('menuCode', derived)
setAutoText('languageTextMenuEn')
setAutoText('languageTextMenuTr')
setAutoText('languageTextTitleEn')
setAutoText('languageTextTitleTr')
setAutoText('languageTextDescEn')
setAutoText('languageTextDescTr')
}
const handleMenuParentChange = (code: string) => {
formikRef.current?.setFieldValue('menuParentCode', code)
if (!code) return
const rootCode = findRootCode(rawMenuItems, code)
const applyPermissionGroupFromRoot = (rootCode: string) => {
const rootItem = rawMenuItems.find((i) => i.code === rootCode)
// 1. Use group field if set
@ -565,6 +606,32 @@ const Wizard = () => {
formikRef.current?.setFieldValue('permissionGroupName', rootCode)
}
const handleMenuParentChange = (code: string) => {
formikRef.current?.setFieldValue('menuParentCode', code)
const selectedMenu = rawMenuItems.find((item) => item.code === code)
formikRef.current?.setFieldValue('menuParentIcon', selectedMenu?.icon ?? '')
if (!code) return
applyPermissionGroupFromRoot(findRootCode(rawMenuItems, code))
}
const handleMenuCreated = async (menu: {
code: string
parentCode?: string
menuTextEn: string
menuTextTr: string
icon?: string
}) => {
formikRef.current?.setFieldValue('menuParentCode', menu.code)
formikRef.current?.setFieldValue('menuParentIcon', menu.icon ?? '')
formikRef.current?.setFieldValue('languageTextMenuParentEn', menu.menuTextEn)
formikRef.current?.setFieldValue('languageTextMenuParentTr', menu.menuTextTr)
const rootCode = menu.parentCode ? findRootCode(rawMenuItems, menu.parentCode) : menu.code
applyPermissionGroupFromRoot(rootCode)
await getMenuList()
}
const handleNext = async () => {
if (!formikRef.current) return
const errors = await formikRef.current.validateForm()
@ -586,6 +653,19 @@ const Wizard = () => {
const handleBack = () => setCurrentStep(0)
const { getConfig } = useStoreActions((a) => a.abpConfig)
const preventEnterSubmit = (event: KeyboardEvent<HTMLFormElement>) => {
if (event.key !== 'Enter') return
const target = event.target as HTMLElement
const tagName = target.tagName.toLowerCase()
const isTextArea = tagName === 'textarea'
const isExplicitSubmit = target.getAttribute('type') === 'submit'
if (!isTextArea && !isExplicitSubmit) {
event.preventDefault()
}
}
const handleNext2 = async () => {
if (!formikRef.current) return
const errors = await formikRef.current.validateForm()
@ -627,6 +707,7 @@ const Wizard = () => {
editorScript: item.editorScript ?? '',
colSpan: item.colSpan,
isRequired: item.isRequired,
includeInEditingForm: item.includeInEditingForm,
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12,
turkishCaption: item.turkishCaption,
englishCaption: item.englishCaption,
@ -676,7 +757,10 @@ const Wizard = () => {
/>
<div className="mb-6 mt-2">
<Steps current={currentStep}>
<Steps
current={currentStep}
className="flex flex-row flex-wrap !justify-start gap-y-2 lg:flex-nowrap lg:!justify-between"
>
<Steps.Item title={translate('::ListForms.Wizard.MenuInfo') || 'Menu Info'} />
<Steps.Item
title={translate('::ListForms.Wizard.ListFormSettings') || 'List Form Settings'}
@ -701,40 +785,12 @@ const Wizard = () => {
innerRef={formikRef}
initialValues={{ ...initialValues }}
validationSchema={listFormValidationSchema}
onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true)
try {
// 🔴 1. Kaydet (bekle)
await postListFormWizard({ ...values })
// 🔴 2. Config güncelle (bekle)
await getConfig(true)
// 🔴 3. Navigate
navigate(
ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode),
{ replace: true },
)
// 🔴 4. Toast (istersen navigate öncesi de olabilir)
toast.push(
<Notification type="success" duration={2000}>
{translate('::ListForms.FormBilgileriKaydedildi')}
</Notification>,
{ placement: 'top-end' },
)
} catch (error: any) {
toast.push(<Notification title={error.message} type="danger" />, {
placement: 'top-end',
})
} finally {
setSubmitting(false)
}
onSubmit={(_, { setSubmitting }) => {
setSubmitting(false)
}}
>
{({ touched, errors, isSubmitting, values }) => (
<Form>
<Form onKeyDown={preventEnterSubmit}>
<FormContainer size={currentStep >= 2 ? undefined : 'sm'}>
{/* ─── Step 1: Basic Info ─────────────────────────────── */}
{currentStep === 0 && (
@ -748,7 +804,11 @@ const Wizard = () => {
menuTree={menuTree}
isLoadingMenu={isLoadingMenu}
onMenuParentChange={handleMenuParentChange}
onClearMenuParent={() => formikRef.current?.setFieldValue('menuParentCode', '')}
onClearMenuParent={() => {
formikRef.current?.setFieldValue('menuParentCode', '')
formikRef.current?.setFieldValue('menuParentIcon', '')
}}
onMenuCreated={handleMenuCreated}
onReloadMenu={getMenuList}
permissionGroupList={permissionGroupList}
isLoadingPermissionGroup={isLoadingPermissionGroup}
@ -770,6 +830,7 @@ const Wizard = () => {
onDataSourceNewChange={setIsDataSourceNew}
dbObjects={dbObjects}
isLoadingDbObjects={isLoadingDbObjects}
onDbObjectsRefresh={loadDbObjects}
selectCommandColumns={selectCommandColumns}
isLoadingColumns={isLoadingColumns}
selectedColumns={selectedColumns}
@ -779,7 +840,8 @@ const Wizard = () => {
setSelectedColumns(new Set())
}}
onToggleColumn={toggleColumn}
onToggleAllColumns={toggleAllColumns}
onToggleAllColumns={(all) => toggleAllColumns(all, values.isTenant)}
onTenantChange={handleTenantChange}
translate={translate}
onBack={handleBack}
onNext={handleNext2}

View file

@ -414,6 +414,14 @@ export interface WizardStep1Props {
isLoadingMenu: boolean
onMenuParentChange: (code: string) => void
onClearMenuParent: () => void
onMenuCreated: (menu: {
code: string
parentCode?: string
menuTextEn: string
menuTextTr: string
icon?: string
shortName?: string
}) => void | Promise<void>
onReloadMenu: () => void
permissionGroupList: SelectBoxOption[]
isLoadingPermissionGroup: boolean
@ -432,6 +440,7 @@ const WizardStep1 = ({
isLoadingMenu,
onMenuParentChange,
onClearMenuParent,
onMenuCreated,
onReloadMenu,
permissionGroupList,
isLoadingPermissionGroup,
@ -544,7 +553,7 @@ const WizardStep1 = ({
initialParentCode={menuDialogParentCode}
initialOrder={menuDialogInitialOrder}
rawItems={rawMenuItems}
onSaved={onReloadMenu}
onSaved={onMenuCreated}
/>
</div>

View file

@ -2,11 +2,13 @@ import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
import { SelectCommandTypeEnum } from '@/proxy/form/models'
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
import { SelectBoxOption } from '@/types/shared'
import { Field, FieldProps, FormikErrors, FormikTouched } from 'formik'
import { Field, FieldProps, FormikErrors, FormikTouched, useFormikContext } from 'formik'
import { useState } from 'react'
import CreatableSelect from 'react-select/creatable'
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
import { FaArrowLeft, FaArrowRight, FaPlus } from 'react-icons/fa'
import { dbSourceTypeOptions, listFormDefaultLayoutOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from '../edit/options'
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
import SqlTableDesignerDialog from '@/views/developerKit/SqlTableDesignerDialog'
// ─── Props ────────────────────────────────────────────────────────────────────
@ -23,6 +25,7 @@ export interface WizardStep2Props {
// DB Objects
dbObjects: SqlObjectExplorerDto | null
isLoadingDbObjects: boolean
onDbObjectsRefresh: (dsCode: string) => void | Promise<void>
// Columns
selectCommandColumns: DatabaseColumnDto[]
isLoadingColumns: boolean
@ -31,6 +34,7 @@ export interface WizardStep2Props {
onClearColumns: () => void
onToggleColumn: (col: string) => void
onToggleAllColumns: (all: boolean) => void
onTenantChange: (isTenant: boolean) => void
// Navigation
translate: (key: string) => string
onBack: () => void
@ -50,6 +54,7 @@ const WizardStep2 = ({
onDataSourceNewChange,
dbObjects,
isLoadingDbObjects,
onDbObjectsRefresh,
selectCommandColumns,
isLoadingColumns,
selectedColumns,
@ -57,10 +62,23 @@ const WizardStep2 = ({
onClearColumns,
onToggleColumn,
onToggleAllColumns,
onTenantChange,
translate,
onBack,
onNext,
}: WizardStep2Props) => {
const [showTableDesignerDialog, setShowTableDesignerDialog] = useState(false)
const formik = useFormikContext<ListFormWizardDto>()
const handleTableDeployed = async (table: { schemaName: string; tableName: string }) => {
await onDbObjectsRefresh(values.dataSourceCode)
formik.setFieldValue('selectCommand', table.tableName)
formik.setFieldValue('selectCommandType', SelectCommandTypeEnum.Table)
formik.setFieldValue('keyFieldName', '')
formik.setFieldTouched('keyFieldName', false)
onLoadColumns(values.dataSourceCode, table.schemaName || 'dbo', table.tableName)
}
const step2Missing = [
!values.listFormCode && translate('::App.Listform.ListformField.ListFormCode'),
!values.dataSourceCode && translate('::ListForms.Wizard.Step4.DataSource'),
@ -217,7 +235,9 @@ const WizardStep2 = ({
]
: []
return (
<Select
<div className="flex items-center gap-2">
<div className="flex-1 min-w-0">
<Select
field={field}
form={form}
isClearable
@ -256,7 +276,18 @@ const WizardStep2 = ({
form.setFieldTouched('keyFieldName', false)
onClearColumns()
}}
/>
/>
</div>
<Button
type="button"
variant="solid"
icon={<FaPlus />}
disabled={!values.dataSourceCode}
onClick={() => setShowTableDesignerDialog(true)}
>
New Table
</Button>
</div>
)
}}
</Field>
@ -328,7 +359,18 @@ const WizardStep2 = ({
invalid={!!(errors.isTenant && touched.isTenant)}
errorMessage={errors.isTenant}
>
<Field type="checkbox" autoComplete="off" name="isTenant" component={Checkbox} />
<Field name="isTenant">
{({ field, form }: FieldProps<boolean>) => (
<Checkbox
name={field.name}
checked={Boolean(field.value)}
onChange={(checked) => {
form.setFieldValue(field.name, checked)
onTenantChange(checked)
}}
/>
)}
</Field>
</FormItem>
<FormItem
@ -700,6 +742,7 @@ const WizardStep2 = ({
<div className="flex items-center gap-2 ml-3">
<Button
variant="solid"
type="button"
onClick={() => onToggleAllColumns(true)}
className="text-xs px-2 py-0.5 rounded bg-indigo-500 text-white hover:bg-indigo-600"
>
@ -707,6 +750,7 @@ const WizardStep2 = ({
</Button>
<Button
variant="default"
type="button"
onClick={() => onToggleAllColumns(false)}
className="text-xs px-2 py-0.5 rounded border border-gray-300 dark:border-gray-600 text-gray-500 hover:text-red-500 hover:border-red-400"
>
@ -785,6 +829,14 @@ const WizardStep2 = ({
</div>
</div>
</div>
<SqlTableDesignerDialog
isOpen={showTableDesignerDialog}
onClose={() => setShowTableDesignerDialog(false)}
dataSource={values.dataSourceCode}
initialTableData={null}
onDeployed={handleTableDeployed}
/>
</div>
)
}

Some files were not shown because too many files have changed in this diff Show more