Compare commits
14 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
233c9b7502 | ||
|
|
12f046f262 | ||
|
|
d0cccde53f | ||
|
|
1d15c44a3d | ||
|
|
bade0bab98 | ||
|
|
c204eef755 | ||
|
|
27e65f05f0 | ||
|
|
64084679e8 | ||
|
|
2f1b9d4e77 | ||
|
|
1c472a7d9a | ||
|
|
119c3650f0 | ||
|
|
ebab6ea114 | ||
|
|
975bc8dd6c | ||
|
|
97a2a4b38d |
63 changed files with 2989 additions and 426 deletions
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ namespace Sozsoft.Platform.ListForms;
|
||||||
public class WorkflowDto
|
public class WorkflowDto
|
||||||
{
|
{
|
||||||
public string ApprovalUserFieldName { get; set; }
|
public string ApprovalUserFieldName { get; set; }
|
||||||
|
public bool IsFilterUserName { get; set; }
|
||||||
public string ApprovalDateFieldName { get; set; }
|
public string ApprovalDateFieldName { get; set; }
|
||||||
public string ApprovalStatusFieldName { get; set; }
|
public string ApprovalStatusFieldName { get; set; }
|
||||||
public string ApprovalDescriptionFieldName { get; set; }
|
public string ApprovalDescriptionFieldName { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ public class ListFormWizardDto
|
||||||
|
|
||||||
public string PermissionGroupName { get; set; }
|
public string PermissionGroupName { get; set; }
|
||||||
public string MenuParentCode { get; set; }
|
public string MenuParentCode { get; set; }
|
||||||
|
public string MenuParentIcon { get; set; }
|
||||||
public string MenuIcon { get; set; }
|
public string MenuIcon { get; set; }
|
||||||
public string DataSourceCode { get; set; }
|
public string DataSourceCode { get; set; }
|
||||||
public string DataSourceConnectionString { get; set; }
|
public string DataSourceConnectionString { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public class WizardColumnItemInputDto
|
||||||
public string EditorScript { get; set; }
|
public string EditorScript { get; set; }
|
||||||
public int ColSpan { get; set; } = 1;
|
public int ColSpan { get; set; } = 1;
|
||||||
public bool IsRequired { get; set; }
|
public bool IsRequired { get; set; }
|
||||||
|
public bool IncludeInEditingForm { get; set; } = true;
|
||||||
public DbType DbSourceType { get; set; } = DbType.String;
|
public DbType DbSourceType { get; set; } = DbType.String;
|
||||||
public string TurkishCaption { get; set; }
|
public string TurkishCaption { get; set; }
|
||||||
public string EnglishCaption { get; set; }
|
public string EnglishCaption { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class WorkflowRunResultDto
|
public class WorkflowRunResultDto
|
||||||
|
|
@ -8,5 +10,6 @@ public class WorkflowRunResultDto
|
||||||
public string CurrentNodeKind { get; set; }
|
public string CurrentNodeKind { get; set; }
|
||||||
public bool WaitingApproval { get; set; }
|
public bool WaitingApproval { get; set; }
|
||||||
public bool Completed { get; set; }
|
public bool Completed { get; set; }
|
||||||
|
public List<string> ToastMessages { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,43 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.ListForms;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.AuditLogging;
|
using Volo.Abp.AuditLogging;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Uow;
|
using Volo.Abp.Uow;
|
||||||
using static Sozsoft.Platform.Data.Seeds.SeedConsts;
|
using static Sozsoft.Platform.Data.Seeds.SeedConsts;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.AuditLogs;
|
namespace Sozsoft.Platform.AuditLogs;
|
||||||
|
|
||||||
public interface IAuditLogAppService
|
public interface IAuditLogAppService
|
||||||
: ICrudAppService<AuditLogDto, Guid>
|
: ICrudAppService<AuditLogDto, Guid, AuditLogListRequestDto>
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(AppCodes.IdentityManagement.AuditLogs)]
|
[Authorize(AppCodes.IdentityManagement.AuditLogs)]
|
||||||
public class AuditLogAppService
|
public class AuditLogAppService : CrudAppService<
|
||||||
: CrudAppService<AuditLog, AuditLogDto, Guid>
|
AuditLog,
|
||||||
, IAuditLogAppService
|
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)
|
public override async Task<AuditLogDto> GetAsync(Guid id)
|
||||||
|
|
@ -35,27 +48,30 @@ public class AuditLogAppService
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnitOfWork]
|
[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);
|
var totalCount = await AsyncExecuter.CountAsync(query);
|
||||||
|
|
||||||
query = ApplySorting(query, input);
|
query = ApplySorting(query, input);
|
||||||
query = ApplyPaging(query, input);
|
query = ApplyPaging(query, input);
|
||||||
|
|
||||||
// EntityChanges ile birlikte getir (N+1 query önlenir)
|
var auditLogsWithDetails = await AsyncExecuter.ToListAsync(query);
|
||||||
var auditLogRepository = (IAuditLogRepository)Repository;
|
|
||||||
var auditLogsWithDetails = await auditLogRepository.GetListAsync(
|
|
||||||
sorting: input.Sorting,
|
|
||||||
maxResultCount: input.MaxResultCount,
|
|
||||||
skipCount: input.SkipCount,
|
|
||||||
includeDetails: true
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mapping tek seferde yap
|
// Mapping tek seferde yap
|
||||||
var entityDtos = ObjectMapper.Map<List<AuditLog>, List<AuditLogDto>>(auditLogsWithDetails);
|
var entityDtos = ObjectMapper.Map<List<AuditLog>, List<AuditLogDto>>(auditLogsWithDetails);
|
||||||
|
|
||||||
// EntityChangeCount'u doldur (artık EntityChanges yüklü)
|
// EntityChangeCount'u doldur (artık EntityChanges yüklü)
|
||||||
foreach (var dto in entityDtos)
|
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
|
// Audit Log kayitlarini gormek istiyoruz fakat degistirmek istemiyoruz
|
||||||
[RemoteService(IsEnabled = false)]
|
[RemoteService(IsEnabled = false)]
|
||||||
public override Task<AuditLogDto> CreateAsync(AuditLogDto input)
|
public override Task<AuditLogDto> CreateAsync(AuditLogDto input)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Platform.BlobStoring;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
using Sozsoft.Platform.Extensions;
|
using Sozsoft.Platform.Extensions;
|
||||||
using Sozsoft.Platform.Identity.Dto;
|
using Sozsoft.Platform.Identity.Dto;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using OpenIddict.Abstractions;
|
using OpenIddict.Abstractions;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
@ -32,6 +34,7 @@ public class PlatformIdentityAppService : ApplicationService
|
||||||
public IRepository<WorkHour, Guid> workHourRepository { get; }
|
public IRepository<WorkHour, Guid> workHourRepository { get; }
|
||||||
public IRepository<Department, Guid> departmentRepository { get; }
|
public IRepository<Department, Guid> departmentRepository { get; }
|
||||||
public IRepository<JobPosition, Guid> jobPositionRepository { get; }
|
public IRepository<JobPosition, Guid> jobPositionRepository { get; }
|
||||||
|
public BlobManager BlobCdnManager { get; }
|
||||||
|
|
||||||
public PlatformIdentityAppService(
|
public PlatformIdentityAppService(
|
||||||
IIdentityUserAppService identityUserAppService,
|
IIdentityUserAppService identityUserAppService,
|
||||||
|
|
@ -45,6 +48,7 @@ public class PlatformIdentityAppService : ApplicationService
|
||||||
IRepository<WorkHour, Guid> workHourRepository,
|
IRepository<WorkHour, Guid> workHourRepository,
|
||||||
IRepository<Department, Guid> departmentRepository,
|
IRepository<Department, Guid> departmentRepository,
|
||||||
IRepository<JobPosition, Guid> jobPositionRepository,
|
IRepository<JobPosition, Guid> jobPositionRepository,
|
||||||
|
BlobManager blobCdnManager,
|
||||||
IGuidGenerator guidGenerator
|
IGuidGenerator guidGenerator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
@ -55,6 +59,7 @@ public class PlatformIdentityAppService : ApplicationService
|
||||||
this.workHourRepository = workHourRepository;
|
this.workHourRepository = workHourRepository;
|
||||||
this.departmentRepository = departmentRepository;
|
this.departmentRepository = departmentRepository;
|
||||||
this.jobPositionRepository = jobPositionRepository;
|
this.jobPositionRepository = jobPositionRepository;
|
||||||
|
this.BlobCdnManager = blobCdnManager;
|
||||||
this.permissionRepository = permissionRepository;
|
this.permissionRepository = permissionRepository;
|
||||||
this.branchRepository = branchRepository;
|
this.branchRepository = branchRepository;
|
||||||
this.branchUsersRepository = branchUsersRepository;
|
this.branchUsersRepository = branchUsersRepository;
|
||||||
|
|
@ -273,6 +278,22 @@ public class PlatformIdentityAppService : ApplicationService
|
||||||
await UserManager.UpdateAsync(user);
|
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()
|
public async Task<List<PermissionDefinitionRecord>> GetPermissionList()
|
||||||
{
|
{
|
||||||
var list = await permissionRepository.GetListAsync();
|
var list = await permissionRepository.GetListAsync();
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.PermissionManagement;
|
using Volo.Abp.PermissionManagement;
|
||||||
using Volo.Abp.Uow;
|
using Volo.Abp.Uow;
|
||||||
using static Sozsoft.Platform.PlatformConsts;
|
|
||||||
using System.Data;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Sozsoft.Languages;
|
using Sozsoft.Languages;
|
||||||
using Sozsoft.Platform.DynamicData;
|
using Sozsoft.Platform.DynamicData;
|
||||||
|
|
@ -60,10 +58,21 @@ public class ListFormWizardAppService(
|
||||||
public async Task Create(ListFormWizardDto input)
|
public async Task Create(ListFormWizardDto input)
|
||||||
{
|
{
|
||||||
var wizardName = input.WizardName.Trim();
|
var wizardName = input.WizardName.Trim();
|
||||||
var titleLangKey = WizardConsts.WizardKeyTitle(wizardName);
|
var code = string.IsNullOrWhiteSpace(input.MenuCode)
|
||||||
var nameLangKey = WizardConsts.WizardKey(wizardName);
|
? WizardConsts.WizardKey(wizardName)
|
||||||
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
|
: input.MenuCode.Trim();
|
||||||
var code = WizardConsts.WizardKey(wizardName);
|
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)
|
// Eklenen kayıtları takip et (silme işleminde kullanılır)
|
||||||
var inserted = new WizardInsertedRecordsDto();
|
var inserted = new WizardInsertedRecordsDto();
|
||||||
|
|
@ -78,16 +87,15 @@ public class ListFormWizardAppService(
|
||||||
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
|
if (!await repoPermGroup.AnyAsync(a => a.Name == groupName))
|
||||||
{
|
{
|
||||||
await repoPermGroup.InsertAsync(new PermissionGroupDefinitionRecord(GuidGenerator.Create(), groupName, groupName), autoSave: false);
|
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);
|
inserted.PermissionGroupNames.Add(groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permission'ları tek seferde kontrol et ve oluştur
|
// Permission'ları tek seferde kontrol et ve oluştur
|
||||||
var queryable = await repoPerm.GetQueryableAsync();
|
var existingPerms = await repoPerm.GetListAsync(a => a.GroupName == groupName);
|
||||||
var existingPerms = await AsyncExecuter.ToListAsync(
|
|
||||||
queryable.Where(a => a.GroupName == groupName)
|
|
||||||
);
|
|
||||||
|
|
||||||
var permRead = existingPerms.FirstOrDefault(a => a.Name == code);
|
var permRead = existingPerms.FirstOrDefault(a => a.Name == code);
|
||||||
if (permRead == null)
|
if (permRead == null)
|
||||||
{
|
{
|
||||||
|
|
@ -95,45 +103,45 @@ public class ListFormWizardAppService(
|
||||||
inserted.PermissionNames.Add(permRead.Name);
|
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)
|
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);
|
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)
|
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);
|
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)
|
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);
|
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)
|
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);
|
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)
|
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);
|
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)
|
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);
|
inserted.PermissionNames.Add(permNote.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,12 +169,18 @@ public class ListFormWizardAppService(
|
||||||
if (menuParent == null)
|
if (menuParent == null)
|
||||||
{
|
{
|
||||||
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
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
|
menuParent = await repoMenu.InsertAsync(new Menu
|
||||||
{
|
{
|
||||||
Code = input.MenuParentCode,
|
Code = input.MenuParentCode,
|
||||||
DisplayName = WizardConsts.WizardKeyParent(wizardName),
|
DisplayName = input.MenuParentCode,
|
||||||
IsDisabled = false,
|
IsDisabled = false,
|
||||||
|
Icon = menuParentIcon,
|
||||||
Order = maxRootOrder + 1,
|
Order = maxRootOrder + 1,
|
||||||
}, autoSave: false);
|
}, autoSave: false);
|
||||||
inserted.MenuCodes.Add(input.MenuParentCode);
|
inserted.MenuCodes.Add(input.MenuParentCode);
|
||||||
|
|
@ -218,7 +232,7 @@ public class ListFormWizardAppService(
|
||||||
ColSpan = g.ColCount,
|
ColSpan = g.ColCount,
|
||||||
ItemType = "group",
|
ItemType = "group",
|
||||||
Items = g.Items
|
Items = g.Items
|
||||||
.Where(i => i.DataField != input.KeyFieldName)
|
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
|
||||||
.Select((it, ii) => new EditingFormItemDto
|
.Select((it, ii) => new EditingFormItemDto
|
||||||
{
|
{
|
||||||
Order = ii + 1,
|
Order = ii + 1,
|
||||||
|
|
@ -231,6 +245,7 @@ public class ListFormWizardAppService(
|
||||||
})
|
})
|
||||||
.ToArray()
|
.ToArray()
|
||||||
})
|
})
|
||||||
|
.Where(g => g.Items.Length > 0)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
//ListForm - varsa sil, yeniden ekle
|
//ListForm - varsa sil, yeniden ekle
|
||||||
|
|
@ -257,14 +272,15 @@ public class ListFormWizardAppService(
|
||||||
var isCreated = tableColumns.Contains("CreatorId");
|
var isCreated = tableColumns.Contains("CreatorId");
|
||||||
input.Workflow ??= new WorkflowDto();
|
input.Workflow ??= new WorkflowDto();
|
||||||
input.Workflow.Criteria = input.WorkflowCriteria;
|
input.Workflow.Criteria = input.WorkflowCriteria;
|
||||||
|
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria);
|
||||||
|
|
||||||
var listForm = await repoListForm.InsertAsync(new ListForm
|
await repoListForm.InsertAsync(new ListForm
|
||||||
{
|
{
|
||||||
ListFormType = ListFormTypeEnum.List,
|
ListFormType = ListFormTypeEnum.List,
|
||||||
PageSize = 10,
|
PageSize = 10,
|
||||||
ExportJson = WizardConsts.DefaultExportJson,
|
ExportJson = WizardConsts.DefaultExportJson,
|
||||||
IsSubForm = false,
|
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),
|
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
ListFormCode = input.ListFormCode,
|
ListFormCode = input.ListFormCode,
|
||||||
|
|
@ -280,12 +296,12 @@ public class ListFormWizardAppService(
|
||||||
KeyFieldName = input.KeyFieldName,
|
KeyFieldName = input.KeyFieldName,
|
||||||
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
|
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
|
||||||
DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null,
|
DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null,
|
||||||
SortMode = GridOptions.SortModeSingle,
|
SortMode = PlatformConsts.GridOptions.SortModeSingle,
|
||||||
FilterRowJson = WizardConsts.DefaultFilterRowJson,
|
FilterRowJson = WizardConsts.DefaultFilterRowJson,
|
||||||
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
||||||
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
|
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
|
||||||
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
|
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(),
|
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||||||
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||||||
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||||||
|
|
@ -395,13 +411,65 @@ public class ListFormWizardAppService(
|
||||||
{
|
{
|
||||||
return workflow != null && (
|
return workflow != null && (
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalDateFieldName) ||
|
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
|
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
|
||||||
!string.IsNullOrWhiteSpace(workflow.ApprovalDescriptionFieldName) ||
|
|
||||||
criteria.Count > 0
|
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>
|
/// <summary>
|
||||||
/// Wizard konfigürasyonunu JSON dosyası olarak kaydeder.
|
/// Wizard konfigürasyonunu JSON dosyası olarak kaydeder.
|
||||||
/// Önce ContentRootPath'ten yukarı çıkarak Sozsoft.Platform.DbMigrator/Seeds/WizardData dizinini arar.
|
/// Önce ContentRootPath'ten yukarı çıkarak Sozsoft.Platform.DbMigrator/Seeds/WizardData dizinini arar.
|
||||||
|
|
@ -677,4 +745,16 @@ public class ListFormWizardAppService(
|
||||||
|
|
||||||
return existing;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
using Sozsoft.Platform.Data.Seeds;
|
using Sozsoft.Platform.Data.Seeds;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
using Sozsoft.Platform.Enums;
|
using Sozsoft.Platform.Enums;
|
||||||
using Sozsoft.Platform.ListForms.Select;
|
using Sozsoft.Platform.ListForms.Select;
|
||||||
|
using Sozsoft.Platform.Localization;
|
||||||
using Sozsoft.Platform.Queries;
|
using Sozsoft.Platform.Queries;
|
||||||
using Sozsoft.Sender.Mail;
|
using Sozsoft.Sender.Mail;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
|
|
@ -29,6 +31,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
private const string SystemApprovalDescription = "Sistem tarafından otomatik olarak onaylandı.";
|
private const string SystemApprovalDescription = "Sistem tarafından otomatik olarak onaylandı.";
|
||||||
|
|
||||||
private readonly IRepository<ListFormWorkflow, string> criteriaRepository;
|
private readonly IRepository<ListFormWorkflow, string> criteriaRepository;
|
||||||
|
private readonly IRepository<Note, Guid> noteRepository;
|
||||||
private readonly IListFormManager listFormManager;
|
private readonly IListFormManager listFormManager;
|
||||||
private readonly IListFormAuthorizationManager authManager;
|
private readonly IListFormAuthorizationManager authManager;
|
||||||
private readonly IListFormSelectAppService listFormSelectAppService;
|
private readonly IListFormSelectAppService listFormSelectAppService;
|
||||||
|
|
@ -36,18 +39,22 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
private readonly IdentityUserManager identityUserManager;
|
private readonly IdentityUserManager identityUserManager;
|
||||||
private readonly ISozsoftEmailSender erpEmailSender;
|
private readonly ISozsoftEmailSender erpEmailSender;
|
||||||
private readonly ISettingProvider settingProvider;
|
private readonly ISettingProvider settingProvider;
|
||||||
|
private readonly IStringLocalizer<PlatformResource> localizer;
|
||||||
|
|
||||||
public ListFormWorkflowAppService(
|
public ListFormWorkflowAppService(
|
||||||
IRepository<ListFormWorkflow, string> criteriaRepository,
|
IRepository<ListFormWorkflow, string> criteriaRepository,
|
||||||
|
IRepository<Note, Guid> noteRepository,
|
||||||
IListFormManager listFormManager,
|
IListFormManager listFormManager,
|
||||||
IListFormAuthorizationManager authManager,
|
IListFormAuthorizationManager authManager,
|
||||||
IListFormSelectAppService listFormSelectAppService,
|
IListFormSelectAppService listFormSelectAppService,
|
||||||
IQueryManager queryManager,
|
IQueryManager queryManager,
|
||||||
IdentityUserManager identityUserManager,
|
IdentityUserManager identityUserManager,
|
||||||
ISozsoftEmailSender erpEmailSender,
|
ISozsoftEmailSender erpEmailSender,
|
||||||
ISettingProvider settingProvider)
|
ISettingProvider settingProvider,
|
||||||
|
IStringLocalizer<PlatformResource> localizer)
|
||||||
{
|
{
|
||||||
this.criteriaRepository = criteriaRepository;
|
this.criteriaRepository = criteriaRepository;
|
||||||
|
this.noteRepository = noteRepository;
|
||||||
this.listFormManager = listFormManager;
|
this.listFormManager = listFormManager;
|
||||||
this.authManager = authManager;
|
this.authManager = authManager;
|
||||||
this.listFormSelectAppService = listFormSelectAppService;
|
this.listFormSelectAppService = listFormSelectAppService;
|
||||||
|
|
@ -55,6 +62,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
this.identityUserManager = identityUserManager;
|
this.identityUserManager = identityUserManager;
|
||||||
this.erpEmailSender = erpEmailSender;
|
this.erpEmailSender = erpEmailSender;
|
||||||
this.settingProvider = settingProvider;
|
this.settingProvider = settingProvider;
|
||||||
|
this.localizer = localizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("criteria")]
|
[HttpGet("criteria")]
|
||||||
|
|
@ -188,7 +196,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
|
|
||||||
criteria.ListFormCode = code;
|
criteria.ListFormCode = code;
|
||||||
criteria.Kind = NormalizeRequired(input.Kind, "Compare");
|
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.CompareColumn = NormalizeRequired(input.CompareColumn, "Price");
|
||||||
criteria.CompareOperator = NormalizeRequired(input.CompareOperator, ">");
|
criteria.CompareOperator = NormalizeRequired(input.CompareOperator, ">");
|
||||||
criteria.CompareValue = input.CompareValue;
|
criteria.CompareValue = input.CompareValue;
|
||||||
|
|
@ -300,7 +308,14 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start")
|
var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start")
|
||||||
?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı.");
|
?? 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")]
|
[HttpPost("decision")]
|
||||||
|
|
@ -365,7 +380,15 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
}
|
}
|
||||||
|
|
||||||
var next = FindNextCriteria(context.Criteria, input.Approved ? current.NextOnApprove : current.NextOnReject);
|
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(
|
private async Task<WorkflowRunResultDto> RunForEachKeyAsync(
|
||||||
|
|
@ -387,7 +410,10 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
CurrentNodeTitle = last?.CurrentNodeTitle,
|
CurrentNodeTitle = last?.CurrentNodeTitle,
|
||||||
CurrentNodeKind = last?.CurrentNodeKind,
|
CurrentNodeKind = last?.CurrentNodeKind,
|
||||||
WaitingApproval = results.Any(x => x.WaitingApproval),
|
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();
|
.ToList();
|
||||||
var row = await GetRowAsync(code, listForm.KeyFieldName, keys[0]);
|
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)
|
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);
|
await UpdateRowAsync(context, update);
|
||||||
MergeRowValues(context.Row, update);
|
MergeRowValues(context.Row, update);
|
||||||
|
AddWorkflowNodeRows(context, node);
|
||||||
|
AddWorkflowToastMessage(context, node);
|
||||||
|
|
||||||
if (node.Kind == "Inform")
|
if (node.Kind == "Inform")
|
||||||
{
|
{
|
||||||
|
|
@ -533,7 +561,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
recipientEmail,
|
recipientEmail,
|
||||||
sender,
|
sender,
|
||||||
new { },
|
new { },
|
||||||
BuildInformEmailBody(context, node),
|
BuildInformEmailBody(context, node, await BuildPreviousWorkflowNotesHtmlAsync(context)),
|
||||||
$"Workflow Bilgilendirme: {node.Title}",
|
$"Workflow Bilgilendirme: {node.Title}",
|
||||||
null,
|
null,
|
||||||
true);
|
true);
|
||||||
|
|
@ -542,6 +570,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException($"Bilgilendirme maili gonderilemedi: {result.ErrorMessage}");
|
throw new UserFriendlyException($"Bilgilendirme maili gonderilemedi: {result.ErrorMessage}");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ResolveApproverEmailAsync(string approver)
|
private async Task<string> ResolveApproverEmailAsync(string approver)
|
||||||
|
|
@ -565,22 +594,257 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
return user.Email;
|
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 keyText = string.Join(", ", context.Keys.Select(key => WebUtility.HtmlEncode(key?.ToString() ?? string.Empty)));
|
||||||
var listFormCode = WebUtility.HtmlEncode(context.ListFormCode ?? string.Empty);
|
var listFormCode = WebUtility.HtmlEncode(context.ListFormCode ?? string.Empty);
|
||||||
var nodeTitle = WebUtility.HtmlEncode(node.Title ?? 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 $"""
|
return $"""
|
||||||
<p>Workflow bilgilendirme adimina ulasildi.</p>
|
<div style="font-family: Arial, sans-serif; color: #111827; line-height: 1.45;">
|
||||||
<table>
|
<p>Workflow sürecinde bilgilendirme adımına ulaşıldı.</p>
|
||||||
<tr><td><strong>Liste Formu</strong></td><td>{listFormCode}</td></tr>
|
<table style="border-collapse: collapse; width: 100%; margin-bottom: 16px;">
|
||||||
<tr><td><strong>Adim</strong></td><td>{nodeTitle}</td></tr>
|
<tr><td style="padding: 6px 8px;"><strong>Liste Formu</strong></td><td style="padding: 6px 8px;">{listFormCode}</td></tr>
|
||||||
<tr><td><strong>Kayit</strong></td><td>{keyText}</td></tr>
|
<tr><td style="padding: 6px 8px;"><strong>Kayıt</strong></td><td style="padding: 6px 8px;">{keyText}</td></tr>
|
||||||
</table>
|
<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)
|
private async Task UpdateRowAsync(WorkflowRunContext context, Dictionary<string, object> data)
|
||||||
{
|
{
|
||||||
await queryManager.GenerateAndRunQueryAsync<int>(
|
await queryManager.GenerateAndRunQueryAsync<int>(
|
||||||
|
|
@ -762,7 +1026,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
CurrentNodeTitle = node?.Title,
|
CurrentNodeTitle = node?.Title,
|
||||||
CurrentNodeKind = node?.Kind,
|
CurrentNodeKind = node?.Kind,
|
||||||
WaitingApproval = waitingApproval,
|
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();
|
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)
|
private static string SerializeCompareOutcomes(List<CompareOutcomeDto> outcomes)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(outcomes ?? []);
|
return JsonSerializer.Serialize(outcomes ?? []);
|
||||||
|
|
@ -947,12 +1242,14 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
|
|
||||||
private sealed record WorkflowRunContext(
|
private sealed record WorkflowRunContext(
|
||||||
string ListFormCode,
|
string ListFormCode,
|
||||||
string KeyFieldName,
|
|
||||||
object[] Keys,
|
object[] Keys,
|
||||||
WorkflowDto Workflow,
|
WorkflowDto Workflow,
|
||||||
List<ListFormWorkflow> Criteria,
|
List<ListFormWorkflow> Criteria,
|
||||||
IDictionary<string, object> Row)
|
IDictionary<string, object> Row)
|
||||||
{
|
{
|
||||||
public HashSet<string> UserUpdatedFields { get; } = [];
|
public HashSet<string> UserUpdatedFields { get; } = [];
|
||||||
|
public List<(string Label, string Value)> WorkflowNoteRows { get; } = [];
|
||||||
|
public List<string> ToastMessages { get; } = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,15 @@ public class MenuAppService : CrudAppService<
|
||||||
private readonly IRepository<LanguageText, Guid> _repositoryText;
|
private readonly IRepository<LanguageText, Guid> _repositoryText;
|
||||||
private readonly ITenantRepository _tenantRepository;
|
private readonly ITenantRepository _tenantRepository;
|
||||||
private readonly IPermissionDefinitionRecordRepository _permissionRepository;
|
private readonly IPermissionDefinitionRecordRepository _permissionRepository;
|
||||||
|
private readonly LanguageTextAppService _languageTextAppService;
|
||||||
|
|
||||||
public MenuAppService(
|
public MenuAppService(
|
||||||
IRepository<Menu, Guid> menuRepository,
|
IRepository<Menu, Guid> menuRepository,
|
||||||
IRepository<LanguageKey, Guid> languageKeyRepository,
|
IRepository<LanguageKey, Guid> languageKeyRepository,
|
||||||
IRepository<LanguageText, Guid> languageTextRepository,
|
IRepository<LanguageText, Guid> languageTextRepository,
|
||||||
ITenantRepository tenantRepository,
|
ITenantRepository tenantRepository,
|
||||||
IPermissionDefinitionRecordRepository permissionRepository
|
IPermissionDefinitionRecordRepository permissionRepository,
|
||||||
|
LanguageTextAppService languageTextAppService
|
||||||
) : base(menuRepository)
|
) : base(menuRepository)
|
||||||
{
|
{
|
||||||
_menuRepository = menuRepository;
|
_menuRepository = menuRepository;
|
||||||
|
|
@ -44,6 +46,7 @@ public class MenuAppService : CrudAppService<
|
||||||
_repositoryText = languageTextRepository;
|
_repositoryText = languageTextRepository;
|
||||||
_tenantRepository = tenantRepository;
|
_tenantRepository = tenantRepository;
|
||||||
_permissionRepository = permissionRepository;
|
_permissionRepository = permissionRepository;
|
||||||
|
_languageTextAppService = languageTextAppService;
|
||||||
|
|
||||||
CreatePolicyName = $"{AppCodes.Menus.Menu}.Create";
|
CreatePolicyName = $"{AppCodes.Menus.Menu}.Create";
|
||||||
UpdatePolicyName = $"{AppCodes.Menus.Menu}.Update";
|
UpdatePolicyName = $"{AppCodes.Menus.Menu}.Update";
|
||||||
|
|
@ -275,7 +278,7 @@ public class MenuAppService : CrudAppService<
|
||||||
if (existingEnText != null)
|
if (existingEnText != null)
|
||||||
{
|
{
|
||||||
existingEnText.Value = input.MenuTextEn;
|
existingEnText.Value = input.MenuTextEn;
|
||||||
await _repositoryText.UpdateAsync(existingEnText);
|
await _repositoryText.UpdateAsync(existingEnText, autoSave: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -285,7 +288,7 @@ public class MenuAppService : CrudAppService<
|
||||||
CultureName = "en",
|
CultureName = "en",
|
||||||
Value = input.MenuTextEn,
|
Value = input.MenuTextEn,
|
||||||
ResourceName = PlatformConsts.AppName
|
ResourceName = PlatformConsts.AppName
|
||||||
});
|
}, autoSave: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Türkçe text oluşturuluyor veya güncelleniyor.
|
// Türkçe text oluşturuluyor veya güncelleniyor.
|
||||||
|
|
@ -297,7 +300,7 @@ public class MenuAppService : CrudAppService<
|
||||||
if (existingTrText != null)
|
if (existingTrText != null)
|
||||||
{
|
{
|
||||||
existingTrText.Value = input.MenuTextTr;
|
existingTrText.Value = input.MenuTextTr;
|
||||||
await _repositoryText.UpdateAsync(existingTrText);
|
await _repositoryText.UpdateAsync(existingTrText, autoSave: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -307,9 +310,12 @@ public class MenuAppService : CrudAppService<
|
||||||
CultureName = "tr",
|
CultureName = "tr",
|
||||||
Value = input.MenuTextTr,
|
Value = input.MenuTextTr,
|
||||||
ResourceName = PlatformConsts.AppName
|
ResourceName = PlatformConsts.AppName
|
||||||
});
|
}, autoSave: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear Redis Cache
|
||||||
|
await _languageTextAppService.ClearRedisCacheAsync();
|
||||||
|
|
||||||
return await base.CreateAsync(input);
|
return await base.CreateAsync(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -674,7 +674,7 @@
|
||||||
"code": "Abp.Account.EnableLocalLogin",
|
"code": "Abp.Account.EnableLocalLogin",
|
||||||
"nameKey": "Abp.Account.EnableLocalLogin",
|
"nameKey": "Abp.Account.EnableLocalLogin",
|
||||||
"descriptionKey": "Abp.Account.EnableLocalLogin.Description",
|
"descriptionKey": "Abp.Account.EnableLocalLogin.Description",
|
||||||
"defaultValue": "False",
|
"defaultValue": "True",
|
||||||
"isVisibleToClients": false,
|
"isVisibleToClients": false,
|
||||||
"providers": "G|D",
|
"providers": "G|D",
|
||||||
"isInherited": false,
|
"isInherited": false,
|
||||||
|
|
@ -1071,6 +1071,22 @@
|
||||||
"dataType": "Number",
|
"dataType": "Number",
|
||||||
"selectOptions": {},
|
"selectOptions": {},
|
||||||
"order": 80
|
"order": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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.OrganizationUnits",
|
||||||
|
"requiredPermissionName": "Abp.Identity.OrganizationUnits",
|
||||||
|
"dataType": "Number",
|
||||||
|
"selectOptions": {},
|
||||||
|
"order": 90
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"NotificationTypes": [],
|
"NotificationTypes": [],
|
||||||
|
|
|
||||||
|
|
@ -3642,6 +3642,36 @@
|
||||||
"en": "The record was deleted",
|
"en": "The record was deleted",
|
||||||
"tr": "Kayıt silindi"
|
"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",
|
"resourceName": "Platform",
|
||||||
"key": "KayitEklendi",
|
"key": "KayitEklendi",
|
||||||
|
|
@ -3735,8 +3765,8 @@
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.ListForm.AddNewRecord",
|
"key": "ListForms.ListForm.AddNewRecord",
|
||||||
"en": "Add New Record",
|
"en": "Add",
|
||||||
"tr": "Yeni Kayıt Ekle"
|
"tr": "Ekle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
|
|
@ -4154,9 +4184,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.ListForm.NoteModal.Type.Activity",
|
"key": "ListForms.ListForm.NoteModal.Type.Workflow",
|
||||||
"en": "Activity",
|
"en": "Workflow",
|
||||||
"tr": "Aktivite"
|
"tr": "Akış"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
|
|
@ -16736,6 +16766,24 @@
|
||||||
"en": "Approver",
|
"en": "Approver",
|
||||||
"tr": "Onayla"
|
"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",
|
"resourceName": "Platform",
|
||||||
"key": "App.Listform.ListformField.NextOnStart",
|
"key": "App.Listform.ListformField.NextOnStart",
|
||||||
|
|
@ -17474,6 +17522,12 @@
|
||||||
"en": "Columns load after selecting a Select Command",
|
"en": "Columns load after selecting a Select Command",
|
||||||
"tr": "Select Command seçince sütunlar yüklenir"
|
"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",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.Wizard.Step3.GenerateFromTable",
|
"key": "ListForms.Wizard.Step3.GenerateFromTable",
|
||||||
|
|
@ -17714,6 +17768,12 @@
|
||||||
"en": "Key Field Type",
|
"en": "Key Field Type",
|
||||||
"tr": "Anahtar Alan Tipi"
|
"tr": "Anahtar Alan Tipi"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.Wizard.Step4.ColumnsAndFormLayout",
|
||||||
|
"en": "Columns & Form Layout",
|
||||||
|
"tr": "Sütunlar ve Form Yerleşimi"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.Wizard.Step4.SelectedColumns",
|
"key": "ListForms.Wizard.Step4.SelectedColumns",
|
||||||
|
|
@ -17738,6 +17798,24 @@
|
||||||
"en": "Field",
|
"en": "Field",
|
||||||
"tr": "Alan"
|
"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",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.Wizard.Step4.DeployAndSave",
|
"key": "ListForms.Wizard.Step4.DeployAndSave",
|
||||||
|
|
@ -18038,6 +18116,12 @@
|
||||||
"en": "Add Multi-Tenant Column",
|
"en": "Add Multi-Tenant Column",
|
||||||
"tr": "MultiTenant Sütunları Ekle"
|
"tr": "MultiTenant Sütunları Ekle"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.SqlQueryManager.AddWorkflowColumns",
|
||||||
|
"en": "Add Workflow Column",
|
||||||
|
"tr": "Workflow Sütunları Ekle"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.SqlQueryManager.ClearAllColumns",
|
"key": "App.SqlQueryManager.ClearAllColumns",
|
||||||
|
|
@ -18986,6 +19070,12 @@
|
||||||
"en": "Approval Status Field Name",
|
"en": "Approval Status Field Name",
|
||||||
"tr": "Onay Durumu Alanı Adı"
|
"tr": "Onay Durumu Alanı Adı"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.IsFilterUserName",
|
||||||
|
"en": "Filter User Name?",
|
||||||
|
"tr": "Kullanıcı Adı Filtresin mi?"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName",
|
"key": "ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName",
|
||||||
|
|
@ -19231,6 +19321,84 @@
|
||||||
"key": "FileManager.SortByModifiedDesc",
|
"key": "FileManager.SortByModifiedDesc",
|
||||||
"en": "Modified (Newest)",
|
"en": "Modified (Newest)",
|
||||||
"tr": "Değiştirilme (En Yeni)"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
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",
|
DeleteCommand = $"UPDATE \"AbpUsers\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\"=@Id",
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 730, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 500, 710, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[
|
new () { Order=1,ColCount=1,ColSpan=1,ItemType="group",Items=[
|
||||||
new EditingFormItemDto { Order=1, DataField="Email", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
new EditingFormItemDto { Order=1, DataField="Email", ColSpan=1, IsRequired=true, EditorType2=EditorTypes.dxTextBox },
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ public static class ListFormSeeder_DefaultJsons
|
||||||
{
|
{
|
||||||
public static string DefaultDeleteCommand(string tableName)
|
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[]
|
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid, string newId = "@NEWID") => JsonSerializer.Serialize(new FieldsDefaultValue[]
|
||||||
|
|
|
||||||
|
|
@ -2302,7 +2302,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(DbType.String, "Name"),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
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>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() {
|
new() {
|
||||||
Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
Order=1, ColCount=1, ColSpan=1, ItemType="group", Items=[
|
||||||
|
|
@ -4025,7 +4025,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
HeaderFilterJson = DefaultHeaderFilterJson,
|
HeaderFilterJson = DefaultHeaderFilterJson,
|
||||||
SearchPanelJson = DefaultSearchPanelJson,
|
SearchPanelJson = DefaultSearchPanelJson,
|
||||||
GroupPanelJson = DefaultGroupPanelJson,
|
GroupPanelJson = DefaultGroupPanelJson,
|
||||||
SelectionJson = DefaultSelectionMultipleJson,
|
SelectionJson = DefaultSelectionSingleJson,
|
||||||
ColumnOptionJson = DefaultColumnOptionJson(false),
|
ColumnOptionJson = DefaultColumnOptionJson(false),
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
private readonly IRepository<DataSource, Guid> _repoDataSource;
|
private readonly IRepository<DataSource, Guid> _repoDataSource;
|
||||||
private readonly IRepository<ListForm, Guid> _repoListForm;
|
private readonly IRepository<ListForm, Guid> _repoListForm;
|
||||||
private readonly IRepository<ListFormField, Guid> _repoListFormField;
|
private readonly IRepository<ListFormField, Guid> _repoListFormField;
|
||||||
|
private readonly IRepository<ListFormWorkflow, string> _repoListFormWorkflow;
|
||||||
private readonly ILogger<WizardDataSeeder> _logger;
|
private readonly ILogger<WizardDataSeeder> _logger;
|
||||||
|
|
||||||
private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage;
|
private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage;
|
||||||
|
|
@ -51,6 +52,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
IRepository<DataSource, Guid> repoDataSource,
|
IRepository<DataSource, Guid> repoDataSource,
|
||||||
IRepository<ListForm, Guid> repoListForm,
|
IRepository<ListForm, Guid> repoListForm,
|
||||||
IRepository<ListFormField, Guid> repoListFormField,
|
IRepository<ListFormField, Guid> repoListFormField,
|
||||||
|
IRepository<ListFormWorkflow, string> repoListFormWorkflow,
|
||||||
ILogger<WizardDataSeeder> logger)
|
ILogger<WizardDataSeeder> logger)
|
||||||
{
|
{
|
||||||
_repoLangKey = repoLangKey;
|
_repoLangKey = repoLangKey;
|
||||||
|
|
@ -62,6 +64,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
_repoDataSource = repoDataSource;
|
_repoDataSource = repoDataSource;
|
||||||
_repoListForm = repoListForm;
|
_repoListForm = repoListForm;
|
||||||
_repoListFormField = repoListFormField;
|
_repoListFormField = repoListFormField;
|
||||||
|
_repoListFormWorkflow = repoListFormWorkflow;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,13 +137,29 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
input.Widgets ??= new List<WidgetEditDto>();
|
input.Widgets ??= new List<WidgetEditDto>();
|
||||||
input.Workflow ??= new WorkflowDto();
|
input.Workflow ??= new WorkflowDto();
|
||||||
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>();
|
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;
|
input.Workflow.Criteria = input.WorkflowCriteria;
|
||||||
|
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria);
|
||||||
|
|
||||||
var wizardName = input.WizardName.Trim();
|
var wizardName = input.WizardName.Trim();
|
||||||
var titleLangKey = WizardConsts.WizardKeyTitle(wizardName);
|
var code = string.IsNullOrWhiteSpace(input.MenuCode)
|
||||||
var nameLangKey = WizardConsts.WizardKey(wizardName);
|
? WizardConsts.WizardKey(wizardName)
|
||||||
var descLangKey = WizardConsts.WizardKeyDesc(wizardName);
|
: input.MenuCode.Trim();
|
||||||
var code = WizardConsts.WizardKey(wizardName);
|
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
|
// Dil - Language Keys
|
||||||
await CreateLangKeyAsync(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
|
await CreateLangKeyAsync(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
|
||||||
|
|
@ -153,7 +172,10 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
{
|
{
|
||||||
await _repoPermGroup.InsertAsync(
|
await _repoPermGroup.InsertAsync(
|
||||||
new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true);
|
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
|
// 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(
|
permRead = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||||||
Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: true);
|
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)
|
if (permCreate == null)
|
||||||
permCreate = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
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)
|
if (permUpdate == null)
|
||||||
permUpdate = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
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)
|
if (permDelete == null)
|
||||||
permDelete = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
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)
|
if (permExport == null)
|
||||||
permExport = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
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)
|
if (permImport == null)
|
||||||
permImport = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
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)
|
if (permNote == null)
|
||||||
permNote = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
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
|
// // Permission Grants - Admin role için, sadece eksik olanları ekle
|
||||||
// var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
// var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
||||||
|
|
@ -219,12 +241,18 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
if (menuParent == null)
|
if (menuParent == null)
|
||||||
{
|
{
|
||||||
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
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
|
menuParent = await _repoMenu.InsertAsync(new Menu
|
||||||
{
|
{
|
||||||
Code = input.MenuParentCode,
|
Code = input.MenuParentCode,
|
||||||
DisplayName = WizardConsts.WizardKeyParent(wizardName),
|
DisplayName = input.MenuParentCode,
|
||||||
IsDisabled = false,
|
IsDisabled = false,
|
||||||
|
Icon = menuParentIcon,
|
||||||
Order = maxRootOrder + 1,
|
Order = maxRootOrder + 1,
|
||||||
}, autoSave: true);
|
}, autoSave: true);
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +304,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
ColSpan = g.ColCount,
|
ColSpan = g.ColCount,
|
||||||
ItemType = "group",
|
ItemType = "group",
|
||||||
Items = g.Items
|
Items = g.Items
|
||||||
.Where(i => i.DataField != input.KeyFieldName)
|
.Where(i => i.IncludeInEditingForm && i.DataField != input.KeyFieldName)
|
||||||
.Select((it, ii) => new EditingFormItemDto
|
.Select((it, ii) => new EditingFormItemDto
|
||||||
{
|
{
|
||||||
Order = ii + 1,
|
Order = ii + 1,
|
||||||
|
|
@ -289,6 +317,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
})
|
})
|
||||||
.ToArray()
|
.ToArray()
|
||||||
})
|
})
|
||||||
|
.Where(g => g.Items.Length > 0)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// ListForm - varsa sil, yeniden ekle
|
// ListForm - varsa sil, yeniden ekle
|
||||||
|
|
@ -304,6 +333,12 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
await _repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true);
|
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
|
// ListForm
|
||||||
await _repoListForm.InsertAsync(new ListForm
|
await _repoListForm.InsertAsync(new ListForm
|
||||||
{
|
{
|
||||||
|
|
@ -311,7 +346,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
PageSize = 10,
|
PageSize = 10,
|
||||||
ExportJson = WizardConsts.DefaultExportJson,
|
ExportJson = WizardConsts.DefaultExportJson,
|
||||||
IsSubForm = false,
|
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),
|
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
ListFormCode = input.ListFormCode,
|
ListFormCode = input.ListFormCode,
|
||||||
|
|
@ -332,7 +367,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
||||||
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
|
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
|
||||||
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
|
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
|
||||||
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
|
SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.WorkflowCriteria.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone),
|
||||||
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||||||
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||||||
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||||||
|
|
@ -381,6 +416,34 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
await CreateLangKeyAsync(item.CaptionName, item.EnglishCaption, item.TurkishCaption);
|
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)
|
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)
|
private async Task CreateLangKeyAsync(string key, string textEn, string textTr)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(key)) return;
|
if (string.IsNullOrWhiteSpace(key)) return;
|
||||||
|
|
@ -427,6 +544,16 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
}, autoSave: true);
|
}, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -773,10 +773,10 @@ public static class PlatformConsts
|
||||||
{
|
{
|
||||||
public static class ParameterTypes
|
public static class ParameterTypes
|
||||||
{
|
{
|
||||||
public const string Static = "S";
|
public const string Static = "Static";
|
||||||
public const string Query = "Q";
|
public const string Query = "Query";
|
||||||
public const string Path = "P";
|
public const string Path = "Path";
|
||||||
public const string Body = "B";
|
public const string Body = "Body";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,9 @@ public static class WizardConsts
|
||||||
public static readonly string DefaultSearchPanelJson = JsonSerializer.Serialize(new { Visible = true });
|
public static readonly string DefaultSearchPanelJson = JsonSerializer.Serialize(new { Visible = true });
|
||||||
public static readonly string DefaultGroupPanelJson = 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
|
AllowSelectAll = false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -134,7 +134,7 @@ public static class WizardConsts
|
||||||
R = permissionName,
|
R = permissionName,
|
||||||
U = permissionName + ".Update",
|
U = permissionName + ".Update",
|
||||||
E = true,
|
E = true,
|
||||||
I = false,
|
I = true,
|
||||||
Deny = false
|
Deny = false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +163,7 @@ public static class WizardConsts
|
||||||
|
|
||||||
public static string DefaultDeleteCommand(string tableName)
|
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)
|
public static string DefaultInsertFieldsDefaultValueJson(DbType dbType = DbType.Guid)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,14 @@ namespace Sozsoft.Platform.Entities;
|
||||||
|
|
||||||
public class Note : FullAuditedEntity<Guid>, IMultiTenant
|
public class Note : FullAuditedEntity<Guid>, IMultiTenant
|
||||||
{
|
{
|
||||||
|
public Note()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Note(Guid id) : base(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public Guid? TenantId { get; set; }
|
public Guid? TenantId { get; set; }
|
||||||
public string EntityName { get; set; }
|
public string EntityName { get; set; }
|
||||||
public string EntityId { get; set; }
|
public string EntityId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,9 @@ public class DefaultValueManager : PlatformDomainService, IDefaultValueManager
|
||||||
else if (defaultField.Value == PlatformConsts.DefaultValues.Year)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Year)
|
||||||
value = Clock.Now.Year;
|
value = Clock.Now.Year;
|
||||||
else if (defaultField.Value == PlatformConsts.DefaultValues.Id)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Id)
|
||||||
value = keys?.FirstOrDefault();
|
value = op == OperationEnum.Delete
|
||||||
|
? keys
|
||||||
|
: keys?.FirstOrDefault();
|
||||||
else if (defaultField.Value == PlatformConsts.DefaultValues.NewId)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.NewId)
|
||||||
value = Guid.NewGuid();
|
value = Guid.NewGuid();
|
||||||
else if (defaultField.Value == PlatformConsts.DefaultValues.Selected_Ids)
|
else if (defaultField.Value == PlatformConsts.DefaultValues.Selected_Ids)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sozsoft.Platform.DynamicData;
|
using Sozsoft.Platform.DynamicData;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
|
|
@ -169,7 +170,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
// oncelik Command alanindadir, dolu ise silme islemi buradaki sorguya yonlendirilir
|
// oncelik Command alanindadir, dolu ise silme islemi buradaki sorguya yonlendirilir
|
||||||
if (!string.IsNullOrEmpty(command))
|
if (!string.IsNullOrEmpty(command))
|
||||||
{
|
{
|
||||||
sql = command;
|
sql = NormalizeCollectionParameterCommand(command, parameters, dataSourceType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -189,7 +190,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
{
|
{
|
||||||
var where = dataSourceType switch
|
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})",
|
DataSourceTypeEnum.Postgresql => $"\"{listForm.KeyFieldName}\" = ANY(@{listForm.KeyFieldName})",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
@ -209,7 +210,14 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
string where = string.Empty;
|
string where = string.Empty;
|
||||||
if (parameters.Any())
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -220,7 +228,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
|
|
||||||
where = dataSourceType switch
|
where = dataSourceType switch
|
||||||
{
|
{
|
||||||
DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN (@{listForm.KeyFieldName})",
|
DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN @{listForm.KeyFieldName}",
|
||||||
DataSourceTypeEnum.Postgresql => $"\"{listForm.KeyFieldName}\" = ANY(@{listForm.KeyFieldName})",
|
DataSourceTypeEnum.Postgresql => $"\"{listForm.KeyFieldName}\" = ANY(@{listForm.KeyFieldName})",
|
||||||
_ => "1 = 0",
|
_ => "1 = 0",
|
||||||
};
|
};
|
||||||
|
|
@ -231,5 +239,78 @@ public class QueryManager : PlatformDomainService, IQueryManager
|
||||||
|
|
||||||
return sql;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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())
|
if (!whereParts.Any())
|
||||||
{
|
{
|
||||||
whereParts.Add("1 = 1");
|
whereParts.Add("1 = 1");
|
||||||
|
|
|
||||||
23
api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs
Normal file
23
api/src/Sozsoft.Platform.Domain/Queries/Workflow.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,8 +2,12 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
using Sozsoft.Platform;
|
||||||
using Sozsoft.Platform.DynamicData;
|
using Sozsoft.Platform.DynamicData;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Volo.Abp.DependencyInjection;
|
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)
|
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 dbConnection = await GetOrCreateConnectionAsync(cs);
|
||||||
var transaction = await GetOrCreateTransactionAsync(dbConnection, 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)
|
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 dbConnection = await GetOrCreateConnectionAsync(cs);
|
||||||
var transaction = await GetOrCreateTransactionAsync(dbConnection, 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)
|
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 dbConnection = await GetOrCreateConnectionAsync(cs);
|
||||||
var transaction = await GetOrCreateTransactionAsync(dbConnection, 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)
|
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 dbConnection = await GetOrCreateConnectionAsync(cs);
|
||||||
var transaction = await GetOrCreateTransactionAsync(dbConnection, 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)
|
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 dbConnection = await GetOrCreateConnectionAsync(cs);
|
||||||
var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
|
var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
|
||||||
|
|
||||||
return await dbConnection.ExecuteAsync(sql, param, transaction);
|
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 ------------------
|
// ------------------ Dispose ------------------
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -507,6 +507,7 @@ public class PlatformDbContext :
|
||||||
b.Property(x => x.PositionX).IsRequired();
|
b.Property(x => x.PositionX).IsRequired();
|
||||||
b.Property(x => x.PositionY).IsRequired();
|
b.Property(x => x.PositionY).IsRequired();
|
||||||
b.Property(x => x.CompareOutcomesJson).HasColumnType("text");
|
b.Property(x => x.CompareOutcomesJson).HasColumnType("text");
|
||||||
|
b.HasIndex(x => new { x.ListFormCode, x.Title }).IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<Note>(b =>
|
builder.Entity<Note>(b =>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260602070242_Initial")]
|
[Migration("20260606212623_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -3486,6 +3486,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ListFormCode", "Title")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
|
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3905,6 +3905,12 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table: "Sas_H_ListFormImportLog",
|
table: "Sas_H_ListFormImportLog",
|
||||||
column: "ImportId");
|
column: "ImportId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Sas_H_ListFormWorkflow_ListFormCode_Title",
|
||||||
|
table: "Sas_H_ListFormWorkflow",
|
||||||
|
columns: new[] { "ListFormCode", "Title" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sas_H_Menu_Code",
|
name: "IX_Sas_H_Menu_Code",
|
||||||
table: "Sas_H_Menu",
|
table: "Sas_H_Menu",
|
||||||
|
|
@ -3483,6 +3483,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ListFormCode", "Title")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
|
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -324,16 +324,16 @@
|
||||||
"Url": "/dil/",
|
"Url": "/dil/",
|
||||||
"Method": "GET",
|
"Method": "GET",
|
||||||
"DataSourceCode": "Default",
|
"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": [
|
"ParametersJson": [
|
||||||
{
|
{
|
||||||
"Type": "P",
|
"Type": "Path",
|
||||||
"Name": "CultureName",
|
"Name": "CultureName",
|
||||||
"DefaultValue": "ar",
|
"DefaultValue": "ar",
|
||||||
"Path": "/dil/:CultureName/"
|
"Path": "/dil/:CultureName/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Type": "S",
|
"Type": "Static",
|
||||||
"Name": "IsEnabled",
|
"Name": "IsEnabled",
|
||||||
"DefaultValue": "true"
|
"DefaultValue": "true"
|
||||||
}
|
}
|
||||||
|
|
@ -1456,6 +1456,36 @@
|
||||||
"DepartmentName": "Muhasebe",
|
"DepartmentName": "Muhasebe",
|
||||||
"Name": "Muhasebe Şefi",
|
"Name": "Muhasebe Şefi",
|
||||||
"ParentName": "Muhasebe Müdürü"
|
"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": [
|
"Announcements": [
|
||||||
|
|
|
||||||
|
|
@ -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
|
# sozsoft.com
|
||||||
server {
|
server {
|
||||||
listen 443 ssl http2;
|
listen 443 ssl http2;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ volumes:
|
||||||
rocket_mongodb_data:
|
rocket_mongodb_data:
|
||||||
driver: local
|
driver: local
|
||||||
n8n_data:
|
n8n_data:
|
||||||
|
netdataconfig:
|
||||||
|
netdatalib:
|
||||||
|
netdatacache:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
forgejo:
|
forgejo:
|
||||||
|
|
@ -108,3 +111,30 @@ services:
|
||||||
- /etc/ssl/sozsoft.com:/etc/ssl/sozsoft.com:ro # Sertifikaları mount ettik
|
- /etc/ssl/sozsoft.com:/etc/ssl/sozsoft.com:ro # Sertifikaları mount ettik
|
||||||
- ./logs/coturn:/var/log # Logları dışarı al (opsiyonel)
|
- ./logs/coturn:/var/log # Logları dışarı al (opsiyonel)
|
||||||
command: ["turnserver", "-c", "/etc/coturn/turnserver.conf"]
|
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
|
||||||
|
|
@ -20,6 +20,7 @@ SUBDOMAINS=(
|
||||||
"sozsoft.com"
|
"sozsoft.com"
|
||||||
"www.sozsoft.com"
|
"www.sozsoft.com"
|
||||||
"demo.sozsoft.com"
|
"demo.sozsoft.com"
|
||||||
|
"dashboard.sozsoft.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "Subdomain'ler için SSL sertifikaları alınıyor..."
|
echo "Subdomain'ler için SSL sertifikaları alınıyor..."
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,19 @@
|
||||||
{
|
{
|
||||||
"commit": "0d4703c",
|
"commit": "dc293fc",
|
||||||
"releases": [
|
"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",
|
"version": "1.1.03",
|
||||||
"buildDate": "2026-05-30",
|
"buildDate": "2026-05-30",
|
||||||
|
|
@ -137,4 +150,4 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ export interface ListFormWizardColumnItemDto {
|
||||||
editorScript: string
|
editorScript: string
|
||||||
colSpan: number
|
colSpan: number
|
||||||
isRequired: boolean
|
isRequired: boolean
|
||||||
|
includeInEditingForm: boolean
|
||||||
dbSourceType: number
|
dbSourceType: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +55,7 @@ export interface ListFormWizardDto {
|
||||||
languageTextMenuParentTr: string
|
languageTextMenuParentTr: string
|
||||||
permissionGroupName: string
|
permissionGroupName: string
|
||||||
menuParentCode: string
|
menuParentCode: string
|
||||||
|
menuParentIcon?: string
|
||||||
menuIcon: string
|
menuIcon: string
|
||||||
dataSourceCode: string
|
dataSourceCode: string
|
||||||
dataSourceConnectionString: string
|
dataSourceConnectionString: string
|
||||||
|
|
@ -113,6 +115,7 @@ export interface WizardSeedFileItemDto {
|
||||||
editorScript: string
|
editorScript: string
|
||||||
colSpan: number
|
colSpan: number
|
||||||
isRequired: boolean
|
isRequired: boolean
|
||||||
|
includeInEditingForm?: boolean
|
||||||
dbSourceType: number
|
dbSourceType: number
|
||||||
turkishCaption?: string
|
turkishCaption?: string
|
||||||
englishCaption?: string
|
englishCaption?: string
|
||||||
|
|
|
||||||
|
|
@ -908,6 +908,7 @@ export interface WidgetEditDto {
|
||||||
|
|
||||||
export interface WorkflowDto {
|
export interface WorkflowDto {
|
||||||
approvalUserFieldName: string
|
approvalUserFieldName: string
|
||||||
|
isFilterUserName: boolean
|
||||||
approvalDateFieldName: string
|
approvalDateFieldName: string
|
||||||
approvalStatusFieldName: string
|
approvalStatusFieldName: string
|
||||||
approvalDescriptionFieldName: string
|
approvalDescriptionFieldName: string
|
||||||
|
|
|
||||||
24
ui/src/services/auditLog.service.ts
Normal file
24
ui/src/services/auditLog.service.ts
Normal 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()
|
||||||
|
|
@ -11,6 +11,11 @@ import { ListResultDto, PagedAndSortedResultRequestDto, PagedResultDto } from '.
|
||||||
import { AuditLogDto } from '../proxy/auditLog/audit-log'
|
import { AuditLogDto } from '../proxy/auditLog/audit-log'
|
||||||
import apiService from './api.service'
|
import apiService from './api.service'
|
||||||
|
|
||||||
|
export interface UserAvatarUpdateInput {
|
||||||
|
userId: string
|
||||||
|
avatar?: File
|
||||||
|
}
|
||||||
|
|
||||||
export const getRoles = (skipCount = 0, maxResultCount = 10) =>
|
export const getRoles = (skipCount = 0, maxResultCount = 10) =>
|
||||||
apiService.fetchData<ListResultDto<IdentityRoleDto>>({
|
apiService.fetchData<ListResultDto<IdentityRoleDto>>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -36,6 +41,21 @@ export const putUserDetail = (input: UserInfoViewModel) =>
|
||||||
data: input,
|
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) =>
|
export const putUserLookout = (input: UserInfoViewModel) =>
|
||||||
apiService.fetchData({
|
apiService.fetchData({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export interface WorkflowRunResultDto {
|
||||||
currentNodeKind?: string | null
|
currentNodeKind?: string | null
|
||||||
waitingApproval: boolean
|
waitingApproval: boolean
|
||||||
completed: boolean
|
completed: boolean
|
||||||
|
toastMessages?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {
|
export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm {
|
||||||
const sharedPerson = item.approver || ''
|
const sharedPerson = item.approver || ''
|
||||||
|
|
||||||
|
|
@ -347,7 +432,8 @@ export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
|
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
|
||||||
const sharedPerson = item.approver || ''
|
const sharedPerson =
|
||||||
|
item.kind === 'Approval' || item.kind === 'Inform' ? item.approver || '' : ''
|
||||||
const compareOutcomes = (item.compareOutcomes || [])
|
const compareOutcomes = (item.compareOutcomes || [])
|
||||||
.slice(0, 4)
|
.slice(0, 4)
|
||||||
.filter((outcome) => outcome.label?.trim())
|
.filter((outcome) => outcome.label?.trim())
|
||||||
|
|
@ -504,7 +590,7 @@ export function criteriaSummary(item: WorkflowCriteriaDto) {
|
||||||
if (item.kind === 'Approval' || item.kind === 'Inform') {
|
if (item.kind === 'Approval' || item.kind === 'Inform') {
|
||||||
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
|
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) {
|
export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) {
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,16 @@ import {
|
||||||
emptyCriteria,
|
emptyCriteria,
|
||||||
normalizeCriteria,
|
normalizeCriteria,
|
||||||
toCriteriaForm,
|
toCriteriaForm,
|
||||||
|
uniqueCriteriaTitle,
|
||||||
type WorkflowCriteriaForm,
|
type WorkflowCriteriaForm,
|
||||||
} from '@/utils/workflow/workflowHelpers'
|
} from '@/utils/workflow/workflowHelpers'
|
||||||
import { workflowService, type WorkflowCriteriaDto } from '@/services/workflow.service'
|
import { workflowService, type WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
import { WorkflowDesigner } from '../workflow/WorkflowDesigner'
|
import { WorkflowDesigner } from '../workflow/WorkflowDesigner'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { Field, FieldProps, Form, Formik } from 'formik'
|
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 { ListFormEditTabs } from '@/proxy/admin/list-form/options'
|
||||||
import { object, string } from 'yup'
|
import { bool, object, string } from 'yup'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import { FormEditProps } from './FormEdit'
|
import { FormEditProps } from './FormEdit'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -112,9 +113,17 @@ export function FormTabWorkflow(
|
||||||
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
|
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
|
const normalized = normalizeCriteria(criteriaForm)
|
||||||
|
const preferredTitle = criteriaForm.title || normalized.title
|
||||||
await workflowService.saveCriteria({
|
await workflowService.saveCriteria({
|
||||||
...normalizeCriteria(criteriaForm),
|
...normalized,
|
||||||
listFormCode: props.listFormCode,
|
listFormCode: props.listFormCode,
|
||||||
|
title: uniqueCriteriaTitle(
|
||||||
|
normalized.kind || '',
|
||||||
|
currentCriteria,
|
||||||
|
normalized.id,
|
||||||
|
preferredTitle,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
setSelectedId('')
|
setSelectedId('')
|
||||||
})
|
})
|
||||||
|
|
@ -123,9 +132,11 @@ export function FormTabWorkflow(
|
||||||
const addCriteria = (kind: string) => {
|
const addCriteria = (kind: string) => {
|
||||||
setDesignerTab('flow')
|
setDesignerTab('flow')
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
|
const nextTitle = uniqueCriteriaTitle(kind, currentCriteria)
|
||||||
const saved = await workflowService.saveCriteria({
|
const saved = await workflowService.saveCriteria({
|
||||||
...normalizeCriteria(emptyCriteria(kind, props.listFormCode)),
|
...normalizeCriteria(emptyCriteria(kind, props.listFormCode)),
|
||||||
listFormCode: props.listFormCode,
|
listFormCode: props.listFormCode,
|
||||||
|
title: nextTitle,
|
||||||
positionX: 80 + (currentCriteria.length % 5) * 230,
|
positionX: 80 + (currentCriteria.length % 5) * 230,
|
||||||
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
||||||
})
|
})
|
||||||
|
|
@ -296,10 +307,13 @@ export function FormTabWorkflow(
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = object().shape({
|
const schema = object().shape({
|
||||||
approvalUserFieldName: string().required(),
|
workflowDto: object().shape({
|
||||||
approvalStatusFieldName: string().required(),
|
approvalUserFieldName: string().required(),
|
||||||
approvalDateFieldName: string(),
|
isFilterUserName: bool(),
|
||||||
approvalDescriptionFieldName: string(),
|
approvalStatusFieldName: string().required(),
|
||||||
|
approvalDateFieldName: string(),
|
||||||
|
approvalDescriptionFieldName: string(),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const initialValues = useStoreState((s) => s.admin.lists.values)
|
const initialValues = useStoreState((s) => s.admin.lists.values)
|
||||||
|
|
@ -320,7 +334,7 @@ export function FormTabWorkflow(
|
||||||
<Form>
|
<Form>
|
||||||
<FormContainer size="sm">
|
<FormContainer size="sm">
|
||||||
<Card className="my-2">
|
<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
|
<FormItem
|
||||||
asterisk
|
asterisk
|
||||||
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalUserFieldName')}
|
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalUserFieldName')}
|
||||||
|
|
@ -370,7 +384,7 @@ export function FormTabWorkflow(
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalDateFieldName')}
|
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalDateFieldName')}
|
||||||
invalid={
|
invalid={
|
||||||
|
|
@ -421,9 +435,22 @@ export function FormTabWorkflow(
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
</FormItem>
|
</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>
|
</div>
|
||||||
|
|
||||||
<Button block variant="solid" loading={isSubmitting}>
|
<Button block variant="solid" type="submit" loading={isSubmitting}>
|
||||||
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
{isSubmitting ? translate('::SavingWithThreeDot') : translate('::Save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
import { SelectBoxOption } from '@/types/shared'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { Form, Formik, FormikProps } from 'formik'
|
import { Form, Formik, FormikProps } from 'formik'
|
||||||
|
import type { KeyboardEvent } from 'react'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
@ -70,6 +71,7 @@ const initialValues: ListFormWizardDto = {
|
||||||
languageTextMenuParentTr: '',
|
languageTextMenuParentTr: '',
|
||||||
permissionGroupName: '',
|
permissionGroupName: '',
|
||||||
menuParentCode: '',
|
menuParentCode: '',
|
||||||
|
menuParentIcon: '',
|
||||||
menuIcon: '',
|
menuIcon: '',
|
||||||
dataSourceCode: '',
|
dataSourceCode: '',
|
||||||
dataSourceConnectionString: '',
|
dataSourceConnectionString: '',
|
||||||
|
|
@ -94,6 +96,7 @@ const initialValues: ListFormWizardDto = {
|
||||||
widgets: [],
|
widgets: [],
|
||||||
workflow: {
|
workflow: {
|
||||||
approvalUserFieldName: '',
|
approvalUserFieldName: '',
|
||||||
|
isFilterUserName: false,
|
||||||
approvalDateFieldName: '',
|
approvalDateFieldName: '',
|
||||||
approvalStatusFieldName: '',
|
approvalStatusFieldName: '',
|
||||||
approvalDescriptionFieldName: '',
|
approvalDescriptionFieldName: '',
|
||||||
|
|
@ -221,6 +224,7 @@ const Wizard = () => {
|
||||||
const [widgets, setWidgets] = useState<WidgetEditDto[]>([])
|
const [widgets, setWidgets] = useState<WidgetEditDto[]>([])
|
||||||
const [workflow, setWorkflow] = useState<WorkflowDto>({
|
const [workflow, setWorkflow] = useState<WorkflowDto>({
|
||||||
approvalUserFieldName: '',
|
approvalUserFieldName: '',
|
||||||
|
isFilterUserName: false,
|
||||||
approvalDateFieldName: '',
|
approvalDateFieldName: '',
|
||||||
approvalStatusFieldName: '',
|
approvalStatusFieldName: '',
|
||||||
approvalDescriptionFieldName: '',
|
approvalDescriptionFieldName: '',
|
||||||
|
|
@ -240,6 +244,18 @@ const Wizard = () => {
|
||||||
])
|
])
|
||||||
|
|
||||||
const isAuditColumn = (columnName: string) => AUDIT_COLUMNS.has(columnName.toLowerCase())
|
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) => {
|
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
||||||
if (!dsCode || !name) {
|
if (!dsCode || !name) {
|
||||||
|
|
@ -253,12 +269,13 @@ const Wizard = () => {
|
||||||
const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
|
const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
|
||||||
const cols = res.data ?? []
|
const cols = res.data ?? []
|
||||||
setSelectCommandColumns(cols)
|
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)))
|
setSelectedColumns(new Set(selectableColumns.map((c) => c.columnName)))
|
||||||
setEditingGroups([])
|
setEditingGroups([])
|
||||||
// Auto-check isTenant / isBranch based on column presence
|
// Auto-check isTenant / isBranch based on column presence
|
||||||
const colNames = new Set(cols.map((c) => c.columnName.toLowerCase()))
|
formikRef.current?.setFieldValue('isTenant', hasTenantColumn)
|
||||||
formikRef.current?.setFieldValue('isTenant', colNames.has('tenantid'))
|
|
||||||
formikRef.current?.setFieldValue('isBranch', colNames.has('branchid'))
|
formikRef.current?.setFieldValue('isBranch', colNames.has('branchid'))
|
||||||
// Auto-select first column as key field
|
// Auto-select first column as key field
|
||||||
if (cols.length > 0) {
|
if (cols.length > 0) {
|
||||||
|
|
@ -284,17 +301,23 @@ const Wizard = () => {
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleAllColumns = (all: boolean) =>
|
const toggleAllColumns = (all: boolean, isTenant = formikRef.current?.values.isTenant ?? false) =>
|
||||||
setSelectedColumns(
|
setSelectedColumns(
|
||||||
all
|
all
|
||||||
? new Set(
|
? new Set(
|
||||||
selectCommandColumns
|
selectCommandColumns
|
||||||
.filter((c) => !isAuditColumn(c.columnName))
|
.filter((c) => isAutoSelectedColumn(c.columnName, isTenant))
|
||||||
.map((c) => c.columnName),
|
.map((c) => c.columnName),
|
||||||
)
|
)
|
||||||
: new Set(),
|
: new Set(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleTenantChange = (isTenant: boolean) => {
|
||||||
|
if (!isTenant) return
|
||||||
|
setSelectedColumns((prev) => removeTenantColumn(prev))
|
||||||
|
setEditingGroups((prev) => removeTenantGroupItems(prev))
|
||||||
|
}
|
||||||
|
|
||||||
const getDataSourceList = async () => {
|
const getDataSourceList = async () => {
|
||||||
setIsLoadingDataSource(true)
|
setIsLoadingDataSource(true)
|
||||||
const response = await getDataSources()
|
const response = await getDataSources()
|
||||||
|
|
@ -391,6 +414,7 @@ const Wizard = () => {
|
||||||
languageTextMenuParentTr: w.languageTextMenuParentTr ?? '',
|
languageTextMenuParentTr: w.languageTextMenuParentTr ?? '',
|
||||||
permissionGroupName: w.permissionGroupName ?? '',
|
permissionGroupName: w.permissionGroupName ?? '',
|
||||||
menuParentCode: w.menuParentCode ?? '',
|
menuParentCode: w.menuParentCode ?? '',
|
||||||
|
menuParentIcon: w.menuParentIcon ?? '',
|
||||||
menuIcon: w.menuIcon ?? '',
|
menuIcon: w.menuIcon ?? '',
|
||||||
dataSourceCode: w.dataSourceCode ?? '',
|
dataSourceCode: w.dataSourceCode ?? '',
|
||||||
dataSourceConnectionString: w.dataSourceConnectionString ?? '',
|
dataSourceConnectionString: w.dataSourceConnectionString ?? '',
|
||||||
|
|
@ -415,6 +439,7 @@ const Wizard = () => {
|
||||||
widgets: w.widgets ?? [],
|
widgets: w.widgets ?? [],
|
||||||
workflow: w.workflow ?? {
|
workflow: w.workflow ?? {
|
||||||
approvalUserFieldName: '',
|
approvalUserFieldName: '',
|
||||||
|
isFilterUserName: false,
|
||||||
approvalDateFieldName: '',
|
approvalDateFieldName: '',
|
||||||
approvalStatusFieldName: '',
|
approvalStatusFieldName: '',
|
||||||
approvalDescriptionFieldName: '',
|
approvalDescriptionFieldName: '',
|
||||||
|
|
@ -453,6 +478,7 @@ const Wizard = () => {
|
||||||
editorScript: it.editorScript ?? '',
|
editorScript: it.editorScript ?? '',
|
||||||
colSpan: it.colSpan ?? 1,
|
colSpan: it.colSpan ?? 1,
|
||||||
isRequired: it.isRequired ?? false,
|
isRequired: it.isRequired ?? false,
|
||||||
|
includeInEditingForm: it.includeInEditingForm ?? true,
|
||||||
turkishCaption: it.turkishCaption ?? it.dataField,
|
turkishCaption: it.turkishCaption ?? it.dataField,
|
||||||
englishCaption: it.englishCaption ?? it.dataField,
|
englishCaption: it.englishCaption ?? it.dataField,
|
||||||
captionName: it.captionName ?? `App.Listform.ListformField.${it.dataField}`,
|
captionName: it.captionName ?? `App.Listform.ListformField.${it.dataField}`,
|
||||||
|
|
@ -477,6 +503,7 @@ const Wizard = () => {
|
||||||
setWorkflow(
|
setWorkflow(
|
||||||
w.workflow ?? {
|
w.workflow ?? {
|
||||||
approvalUserFieldName: '',
|
approvalUserFieldName: '',
|
||||||
|
isFilterUserName: false,
|
||||||
approvalDateFieldName: '',
|
approvalDateFieldName: '',
|
||||||
approvalStatusFieldName: '',
|
approvalStatusFieldName: '',
|
||||||
approvalDescriptionFieldName: '',
|
approvalDescriptionFieldName: '',
|
||||||
|
|
@ -516,24 +543,38 @@ const Wizard = () => {
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
const handleWizardNameChange = (name: string) => {
|
const handleWizardNameChange = (name: string) => {
|
||||||
|
const formik = formikRef.current
|
||||||
const spacedLabel = toSpacedLabel(name)
|
const spacedLabel = toSpacedLabel(name)
|
||||||
|
const previousSpacedLabel = toSpacedLabel(formik?.values.wizardName ?? '')
|
||||||
const derived = deriveListFormCode(name)
|
const derived = deriveListFormCode(name)
|
||||||
|
|
||||||
formikRef.current?.setFieldValue('wizardName', name)
|
const setAutoText = (field: keyof Pick<
|
||||||
formikRef.current?.setFieldValue('listFormCode', derived)
|
ListFormWizardDto,
|
||||||
formikRef.current?.setFieldValue('menuCode', derived)
|
| 'languageTextMenuEn'
|
||||||
formikRef.current?.setFieldValue('languageTextMenuEn', spacedLabel)
|
| 'languageTextMenuTr'
|
||||||
formikRef.current?.setFieldValue('languageTextMenuTr', spacedLabel)
|
| 'languageTextTitleEn'
|
||||||
formikRef.current?.setFieldValue('languageTextTitleEn', spacedLabel)
|
| 'languageTextTitleTr'
|
||||||
formikRef.current?.setFieldValue('languageTextTitleTr', spacedLabel)
|
| 'languageTextDescEn'
|
||||||
formikRef.current?.setFieldValue('languageTextDescEn', spacedLabel)
|
| 'languageTextDescTr'
|
||||||
formikRef.current?.setFieldValue('languageTextDescTr', spacedLabel)
|
>) => {
|
||||||
|
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) => {
|
const applyPermissionGroupFromRoot = (rootCode: string) => {
|
||||||
formikRef.current?.setFieldValue('menuParentCode', code)
|
|
||||||
if (!code) return
|
|
||||||
const rootCode = findRootCode(rawMenuItems, code)
|
|
||||||
const rootItem = rawMenuItems.find((i) => i.code === rootCode)
|
const rootItem = rawMenuItems.find((i) => i.code === rootCode)
|
||||||
|
|
||||||
// 1. Use group field if set
|
// 1. Use group field if set
|
||||||
|
|
@ -565,6 +606,32 @@ const Wizard = () => {
|
||||||
formikRef.current?.setFieldValue('permissionGroupName', rootCode)
|
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 () => {
|
const handleNext = async () => {
|
||||||
if (!formikRef.current) return
|
if (!formikRef.current) return
|
||||||
const errors = await formikRef.current.validateForm()
|
const errors = await formikRef.current.validateForm()
|
||||||
|
|
@ -586,6 +653,19 @@ const Wizard = () => {
|
||||||
const handleBack = () => setCurrentStep(0)
|
const handleBack = () => setCurrentStep(0)
|
||||||
const { getConfig } = useStoreActions((a) => a.abpConfig)
|
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 () => {
|
const handleNext2 = async () => {
|
||||||
if (!formikRef.current) return
|
if (!formikRef.current) return
|
||||||
const errors = await formikRef.current.validateForm()
|
const errors = await formikRef.current.validateForm()
|
||||||
|
|
@ -627,6 +707,7 @@ const Wizard = () => {
|
||||||
editorScript: item.editorScript ?? '',
|
editorScript: item.editorScript ?? '',
|
||||||
colSpan: item.colSpan,
|
colSpan: item.colSpan,
|
||||||
isRequired: item.isRequired,
|
isRequired: item.isRequired,
|
||||||
|
includeInEditingForm: item.includeInEditingForm,
|
||||||
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12,
|
dbSourceType: col ? sqlDataTypeToDbType(col.dataType) : 12,
|
||||||
turkishCaption: item.turkishCaption,
|
turkishCaption: item.turkishCaption,
|
||||||
englishCaption: item.englishCaption,
|
englishCaption: item.englishCaption,
|
||||||
|
|
@ -676,7 +757,10 @@ const Wizard = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mb-6 mt-2">
|
<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.MenuInfo') || 'Menu Info'} />
|
||||||
<Steps.Item
|
<Steps.Item
|
||||||
title={translate('::ListForms.Wizard.ListFormSettings') || 'List Form Settings'}
|
title={translate('::ListForms.Wizard.ListFormSettings') || 'List Form Settings'}
|
||||||
|
|
@ -701,40 +785,12 @@ const Wizard = () => {
|
||||||
innerRef={formikRef}
|
innerRef={formikRef}
|
||||||
initialValues={{ ...initialValues }}
|
initialValues={{ ...initialValues }}
|
||||||
validationSchema={listFormValidationSchema}
|
validationSchema={listFormValidationSchema}
|
||||||
onSubmit={async (values, { setSubmitting }) => {
|
onSubmit={(_, { setSubmitting }) => {
|
||||||
setSubmitting(true)
|
setSubmitting(false)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ touched, errors, isSubmitting, values }) => (
|
{({ touched, errors, isSubmitting, values }) => (
|
||||||
<Form>
|
<Form onKeyDown={preventEnterSubmit}>
|
||||||
<FormContainer size={currentStep >= 2 ? undefined : 'sm'}>
|
<FormContainer size={currentStep >= 2 ? undefined : 'sm'}>
|
||||||
{/* ─── Step 1: Basic Info ─────────────────────────────── */}
|
{/* ─── Step 1: Basic Info ─────────────────────────────── */}
|
||||||
{currentStep === 0 && (
|
{currentStep === 0 && (
|
||||||
|
|
@ -748,7 +804,11 @@ const Wizard = () => {
|
||||||
menuTree={menuTree}
|
menuTree={menuTree}
|
||||||
isLoadingMenu={isLoadingMenu}
|
isLoadingMenu={isLoadingMenu}
|
||||||
onMenuParentChange={handleMenuParentChange}
|
onMenuParentChange={handleMenuParentChange}
|
||||||
onClearMenuParent={() => formikRef.current?.setFieldValue('menuParentCode', '')}
|
onClearMenuParent={() => {
|
||||||
|
formikRef.current?.setFieldValue('menuParentCode', '')
|
||||||
|
formikRef.current?.setFieldValue('menuParentIcon', '')
|
||||||
|
}}
|
||||||
|
onMenuCreated={handleMenuCreated}
|
||||||
onReloadMenu={getMenuList}
|
onReloadMenu={getMenuList}
|
||||||
permissionGroupList={permissionGroupList}
|
permissionGroupList={permissionGroupList}
|
||||||
isLoadingPermissionGroup={isLoadingPermissionGroup}
|
isLoadingPermissionGroup={isLoadingPermissionGroup}
|
||||||
|
|
@ -770,6 +830,7 @@ const Wizard = () => {
|
||||||
onDataSourceNewChange={setIsDataSourceNew}
|
onDataSourceNewChange={setIsDataSourceNew}
|
||||||
dbObjects={dbObjects}
|
dbObjects={dbObjects}
|
||||||
isLoadingDbObjects={isLoadingDbObjects}
|
isLoadingDbObjects={isLoadingDbObjects}
|
||||||
|
onDbObjectsRefresh={loadDbObjects}
|
||||||
selectCommandColumns={selectCommandColumns}
|
selectCommandColumns={selectCommandColumns}
|
||||||
isLoadingColumns={isLoadingColumns}
|
isLoadingColumns={isLoadingColumns}
|
||||||
selectedColumns={selectedColumns}
|
selectedColumns={selectedColumns}
|
||||||
|
|
@ -779,7 +840,8 @@ const Wizard = () => {
|
||||||
setSelectedColumns(new Set())
|
setSelectedColumns(new Set())
|
||||||
}}
|
}}
|
||||||
onToggleColumn={toggleColumn}
|
onToggleColumn={toggleColumn}
|
||||||
onToggleAllColumns={toggleAllColumns}
|
onToggleAllColumns={(all) => toggleAllColumns(all, values.isTenant)}
|
||||||
|
onTenantChange={handleTenantChange}
|
||||||
translate={translate}
|
translate={translate}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onNext={handleNext2}
|
onNext={handleNext2}
|
||||||
|
|
|
||||||
|
|
@ -414,6 +414,14 @@ export interface WizardStep1Props {
|
||||||
isLoadingMenu: boolean
|
isLoadingMenu: boolean
|
||||||
onMenuParentChange: (code: string) => void
|
onMenuParentChange: (code: string) => void
|
||||||
onClearMenuParent: () => void
|
onClearMenuParent: () => void
|
||||||
|
onMenuCreated: (menu: {
|
||||||
|
code: string
|
||||||
|
parentCode?: string
|
||||||
|
menuTextEn: string
|
||||||
|
menuTextTr: string
|
||||||
|
icon?: string
|
||||||
|
shortName?: string
|
||||||
|
}) => void | Promise<void>
|
||||||
onReloadMenu: () => void
|
onReloadMenu: () => void
|
||||||
permissionGroupList: SelectBoxOption[]
|
permissionGroupList: SelectBoxOption[]
|
||||||
isLoadingPermissionGroup: boolean
|
isLoadingPermissionGroup: boolean
|
||||||
|
|
@ -432,6 +440,7 @@ const WizardStep1 = ({
|
||||||
isLoadingMenu,
|
isLoadingMenu,
|
||||||
onMenuParentChange,
|
onMenuParentChange,
|
||||||
onClearMenuParent,
|
onClearMenuParent,
|
||||||
|
onMenuCreated,
|
||||||
onReloadMenu,
|
onReloadMenu,
|
||||||
permissionGroupList,
|
permissionGroupList,
|
||||||
isLoadingPermissionGroup,
|
isLoadingPermissionGroup,
|
||||||
|
|
@ -544,7 +553,7 @@ const WizardStep1 = ({
|
||||||
initialParentCode={menuDialogParentCode}
|
initialParentCode={menuDialogParentCode}
|
||||||
initialOrder={menuDialogInitialOrder}
|
initialOrder={menuDialogInitialOrder}
|
||||||
rawItems={rawMenuItems}
|
rawItems={rawMenuItems}
|
||||||
onSaved={onReloadMenu}
|
onSaved={onMenuCreated}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ import { Button, Checkbox, FormItem, Input, Select } from '@/components/ui'
|
||||||
import { SelectCommandTypeEnum } from '@/proxy/form/models'
|
import { SelectCommandTypeEnum } from '@/proxy/form/models'
|
||||||
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto, SqlObjectExplorerDto } from '@/proxy/sql-query-manager/models'
|
||||||
import { SelectBoxOption } from '@/types/shared'
|
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 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 { dbSourceTypeOptions, listFormDefaultLayoutOptions, selectCommandTypeOptions, sqlDataTypeToDbType } from '../edit/options'
|
||||||
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
import { ListFormWizardDto } from '@/proxy/admin/wizard/models'
|
||||||
|
import SqlTableDesignerDialog from '@/views/developerKit/SqlTableDesignerDialog'
|
||||||
|
|
||||||
// ─── Props ────────────────────────────────────────────────────────────────────
|
// ─── Props ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -23,6 +25,7 @@ export interface WizardStep2Props {
|
||||||
// DB Objects
|
// DB Objects
|
||||||
dbObjects: SqlObjectExplorerDto | null
|
dbObjects: SqlObjectExplorerDto | null
|
||||||
isLoadingDbObjects: boolean
|
isLoadingDbObjects: boolean
|
||||||
|
onDbObjectsRefresh: (dsCode: string) => void | Promise<void>
|
||||||
// Columns
|
// Columns
|
||||||
selectCommandColumns: DatabaseColumnDto[]
|
selectCommandColumns: DatabaseColumnDto[]
|
||||||
isLoadingColumns: boolean
|
isLoadingColumns: boolean
|
||||||
|
|
@ -31,6 +34,7 @@ export interface WizardStep2Props {
|
||||||
onClearColumns: () => void
|
onClearColumns: () => void
|
||||||
onToggleColumn: (col: string) => void
|
onToggleColumn: (col: string) => void
|
||||||
onToggleAllColumns: (all: boolean) => void
|
onToggleAllColumns: (all: boolean) => void
|
||||||
|
onTenantChange: (isTenant: boolean) => void
|
||||||
// Navigation
|
// Navigation
|
||||||
translate: (key: string) => string
|
translate: (key: string) => string
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
|
|
@ -50,6 +54,7 @@ const WizardStep2 = ({
|
||||||
onDataSourceNewChange,
|
onDataSourceNewChange,
|
||||||
dbObjects,
|
dbObjects,
|
||||||
isLoadingDbObjects,
|
isLoadingDbObjects,
|
||||||
|
onDbObjectsRefresh,
|
||||||
selectCommandColumns,
|
selectCommandColumns,
|
||||||
isLoadingColumns,
|
isLoadingColumns,
|
||||||
selectedColumns,
|
selectedColumns,
|
||||||
|
|
@ -57,10 +62,23 @@ const WizardStep2 = ({
|
||||||
onClearColumns,
|
onClearColumns,
|
||||||
onToggleColumn,
|
onToggleColumn,
|
||||||
onToggleAllColumns,
|
onToggleAllColumns,
|
||||||
|
onTenantChange,
|
||||||
translate,
|
translate,
|
||||||
onBack,
|
onBack,
|
||||||
onNext,
|
onNext,
|
||||||
}: WizardStep2Props) => {
|
}: 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 = [
|
const step2Missing = [
|
||||||
!values.listFormCode && translate('::App.Listform.ListformField.ListFormCode'),
|
!values.listFormCode && translate('::App.Listform.ListformField.ListFormCode'),
|
||||||
!values.dataSourceCode && translate('::ListForms.Wizard.Step4.DataSource'),
|
!values.dataSourceCode && translate('::ListForms.Wizard.Step4.DataSource'),
|
||||||
|
|
@ -217,7 +235,9 @@ const WizardStep2 = ({
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
return (
|
return (
|
||||||
<Select
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<Select
|
||||||
field={field}
|
field={field}
|
||||||
form={form}
|
form={form}
|
||||||
isClearable
|
isClearable
|
||||||
|
|
@ -256,7 +276,18 @@ const WizardStep2 = ({
|
||||||
form.setFieldTouched('keyFieldName', false)
|
form.setFieldTouched('keyFieldName', false)
|
||||||
onClearColumns()
|
onClearColumns()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="solid"
|
||||||
|
icon={<FaPlus />}
|
||||||
|
disabled={!values.dataSourceCode}
|
||||||
|
onClick={() => setShowTableDesignerDialog(true)}
|
||||||
|
>
|
||||||
|
New Table
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
@ -328,7 +359,18 @@ const WizardStep2 = ({
|
||||||
invalid={!!(errors.isTenant && touched.isTenant)}
|
invalid={!!(errors.isTenant && touched.isTenant)}
|
||||||
errorMessage={errors.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>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|
@ -700,6 +742,7 @@ const WizardStep2 = ({
|
||||||
<div className="flex items-center gap-2 ml-3">
|
<div className="flex items-center gap-2 ml-3">
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
type="button"
|
||||||
onClick={() => onToggleAllColumns(true)}
|
onClick={() => onToggleAllColumns(true)}
|
||||||
className="text-xs px-2 py-0.5 rounded bg-indigo-500 text-white hover:bg-indigo-600"
|
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>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
|
type="button"
|
||||||
onClick={() => onToggleAllColumns(false)}
|
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"
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SqlTableDesignerDialog
|
||||||
|
isOpen={showTableDesignerDialog}
|
||||||
|
onClose={() => setShowTableDesignerDialog(false)}
|
||||||
|
dataSource={values.dataSourceCode}
|
||||||
|
initialTableData={null}
|
||||||
|
onDeployed={handleTableDeployed}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export interface WizardGroupItem {
|
||||||
editorScript: string
|
editorScript: string
|
||||||
colSpan: number
|
colSpan: number
|
||||||
isRequired: boolean
|
isRequired: boolean
|
||||||
|
includeInEditingForm: boolean
|
||||||
turkishCaption?: string
|
turkishCaption?: string
|
||||||
englishCaption?: string
|
englishCaption?: string
|
||||||
captionName?: string
|
captionName?: string
|
||||||
|
|
@ -114,6 +115,7 @@ function newGroupItem(colName: string, meta?: DatabaseColumnDto): WizardGroupIte
|
||||||
editorScript: '',
|
editorScript: '',
|
||||||
colSpan: 1,
|
colSpan: 1,
|
||||||
isRequired: meta?.isNullable === false,
|
isRequired: meta?.isNullable === false,
|
||||||
|
includeInEditingForm: true,
|
||||||
turkishCaption: formatLabel(colName),
|
turkishCaption: formatLabel(colName),
|
||||||
englishCaption: formatLabel(colName),
|
englishCaption: formatLabel(colName),
|
||||||
captionName: `App.Listform.ListformField.${colName}`,
|
captionName: `App.Listform.ListformField.${colName}`,
|
||||||
|
|
@ -179,6 +181,7 @@ interface SortableItemProps {
|
||||||
onLookupQueryChange: (val: string) => void
|
onLookupQueryChange: (val: string) => void
|
||||||
onColSpanChange: (val: number) => void
|
onColSpanChange: (val: number) => void
|
||||||
onRequiredChange: (val: boolean) => void
|
onRequiredChange: (val: boolean) => void
|
||||||
|
onIncludeInEditingFormChange: (val: boolean) => void
|
||||||
onRemove: () => void
|
onRemove: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,6 +197,7 @@ function SortableItem({
|
||||||
onEditorScriptChange,
|
onEditorScriptChange,
|
||||||
onColSpanChange,
|
onColSpanChange,
|
||||||
onRequiredChange,
|
onRequiredChange,
|
||||||
|
onIncludeInEditingFormChange,
|
||||||
onCaptionNameChange,
|
onCaptionNameChange,
|
||||||
onLookupDataSourceTypeChange,
|
onLookupDataSourceTypeChange,
|
||||||
onDisplayExprChange,
|
onDisplayExprChange,
|
||||||
|
|
@ -561,7 +565,7 @@ function SortableItem({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom row: ColSpan + Required */}
|
{/* Bottom row: ColSpan + Editing Form + Required */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span className="text-[10px] text-gray-400">
|
<span className="text-[10px] text-gray-400">
|
||||||
|
|
@ -579,6 +583,20 @@ function SortableItem({
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<label
|
||||||
|
className="flex items-center gap-1 cursor-pointer ml-auto"
|
||||||
|
title={translate('::ListForms.Wizard.Step3.IncludeInEditingForm') || 'Editing form'}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={item.includeInEditingForm}
|
||||||
|
onChange={(e) => onIncludeInEditingFormChange(e.target.checked)}
|
||||||
|
className="w-3 h-3 accent-indigo-500"
|
||||||
|
/>
|
||||||
|
<span className="text-[10px] text-gray-400">
|
||||||
|
{translate('::ListForms.Wizard.Step3.IncludeInEditingForm') || 'Editing form'}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<label className="flex items-center gap-1 cursor-pointer ml-auto" title="Required">
|
<label className="flex items-center gap-1 cursor-pointer ml-auto" title="Required">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -723,6 +741,9 @@ function GroupCard({
|
||||||
onEditorScriptChange={(val) => onItemChange(item.id, { editorScript: val })}
|
onEditorScriptChange={(val) => onItemChange(item.id, { editorScript: val })}
|
||||||
onColSpanChange={(val) => onItemChange(item.id, { colSpan: val })}
|
onColSpanChange={(val) => onItemChange(item.id, { colSpan: val })}
|
||||||
onRequiredChange={(val) => onItemChange(item.id, { isRequired: val })}
|
onRequiredChange={(val) => onItemChange(item.id, { isRequired: val })}
|
||||||
|
onIncludeInEditingFormChange={(val) =>
|
||||||
|
onItemChange(item.id, { includeInEditingForm: val })
|
||||||
|
}
|
||||||
onLookupDataSourceTypeChange={(val: UiLookupDataSourceTypeEnum) =>
|
onLookupDataSourceTypeChange={(val: UiLookupDataSourceTypeEnum) =>
|
||||||
onItemChange(item.id, { lookupDataSourceType: val })
|
onItemChange(item.id, { lookupDataSourceType: val })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -462,10 +462,10 @@ function WizardStep4({
|
||||||
<p>Silmek istediğinize emin misiniz?</p>
|
<p>Silmek istediğinize emin misiniz?</p>
|
||||||
</Dialog.Body>
|
</Dialog.Body>
|
||||||
<Dialog.Footer className="flex justify-end gap-2">
|
<Dialog.Footer className="flex justify-end gap-2">
|
||||||
<Button variant="plain" onClick={() => setDeleteIndex(null)}>
|
<Button type="button" variant="plain" onClick={() => setDeleteIndex(null)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="solid" onClick={removeSubForm}>
|
<Button type="button" variant="solid" onClick={removeSubForm}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
|
|
|
||||||
|
|
@ -340,10 +340,10 @@ function WizardStep5({ widgets, translate, onChange, onBack, onNext }: Props) {
|
||||||
<p>Silmek istediğinize emin misiniz?</p>
|
<p>Silmek istediğinize emin misiniz?</p>
|
||||||
</Dialog.Body>
|
</Dialog.Body>
|
||||||
<Dialog.Footer className="flex justify-end gap-2">
|
<Dialog.Footer className="flex justify-end gap-2">
|
||||||
<Button variant="plain" onClick={() => setDeleteIndex(null)}>
|
<Button type="button" variant="plain" onClick={() => setDeleteIndex(null)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="solid" onClick={removeWidget}>
|
<Button type="button" variant="solid" onClick={removeWidget}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button, Card, FormContainer, FormItem, Select } from '@/components/ui'
|
import { Button, Card, Checkbox, FormContainer, FormItem, Select } from '@/components/ui'
|
||||||
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
import type { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
import { ListFormWorkflowCriteriaDto, WorkflowDto } from '@/proxy/form/models'
|
import { ListFormWorkflowCriteriaDto, WorkflowDto } from '@/proxy/form/models'
|
||||||
import { getUsers } from '@/services/identity.service'
|
import { getUsers } from '@/services/identity.service'
|
||||||
|
|
@ -9,6 +9,8 @@ import {
|
||||||
emptyCriteria,
|
emptyCriteria,
|
||||||
normalizeCriteria,
|
normalizeCriteria,
|
||||||
toCriteriaForm,
|
toCriteriaForm,
|
||||||
|
uniqueCriteriaId,
|
||||||
|
uniqueCriteriaTitle,
|
||||||
type WorkflowCriteriaForm,
|
type WorkflowCriteriaForm,
|
||||||
} from '@/utils/workflow/workflowHelpers'
|
} from '@/utils/workflow/workflowHelpers'
|
||||||
import { Field, FieldProps, Form, Formik } from 'formik'
|
import { Field, FieldProps, Form, Formik } from 'formik'
|
||||||
|
|
@ -16,6 +18,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import type { FormEvent } from 'react'
|
import type { FormEvent } from 'react'
|
||||||
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa'
|
||||||
import { WorkflowDesigner } from '../workflow/WorkflowDesigner'
|
import { WorkflowDesigner } from '../workflow/WorkflowDesigner'
|
||||||
|
import { IdentityUserDto } from '@/proxy/admin/models'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -44,8 +47,6 @@ const toDesignerCriteria = (items: ListFormWorkflowCriteriaDto[]): WorkflowCrite
|
||||||
const toWizardCriteria = (items: WorkflowCriteriaDto[]): ListFormWorkflowCriteriaDto[] =>
|
const toWizardCriteria = (items: WorkflowCriteriaDto[]): ListFormWorkflowCriteriaDto[] =>
|
||||||
items.map(({ nodeId: _nodeId, ...item }) => item)
|
items.map(({ nodeId: _nodeId, ...item }) => item)
|
||||||
|
|
||||||
const nextId = () => `WF${Date.now()}${Math.floor(Math.random() * 1000)}`
|
|
||||||
|
|
||||||
function WizardStep6({
|
function WizardStep6({
|
||||||
listFormCode,
|
listFormCode,
|
||||||
workflow,
|
workflow,
|
||||||
|
|
@ -77,7 +78,7 @@ function WizardStep6({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUsers(0, 1000).then((response) => {
|
getUsers(0, 1000).then((response) => {
|
||||||
setUserList(
|
setUserList(
|
||||||
(response.data?.items ?? []).map((user: any) => ({
|
(response.data?.items ?? []).map((user: IdentityUserDto) => ({
|
||||||
value: user.userName,
|
value: user.userName,
|
||||||
label: `${user.userName} (${user.name} ${user.surname})`,
|
label: `${user.userName} (${user.name} ${user.surname})`,
|
||||||
})),
|
})),
|
||||||
|
|
@ -95,9 +96,10 @@ function WizardStep6({
|
||||||
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
|
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const normalized = normalizeCriteria({ ...criteriaForm, listFormCode })
|
const normalized = normalizeCriteria({ ...criteriaForm, listFormCode })
|
||||||
const id = normalized.id || nextId()
|
const id = normalized.id || uniqueCriteriaId(currentCriteria)
|
||||||
const nextItem = { ...normalized, id, nodeId: id } as WorkflowCriteriaDto
|
|
||||||
const exists = currentCriteria.some((item) => item.id === id)
|
const exists = currentCriteria.some((item) => item.id === id)
|
||||||
|
const title = uniqueCriteriaTitle(normalized.kind || '', currentCriteria, id, normalized.title)
|
||||||
|
const nextItem = { ...normalized, id, nodeId: id, title } as WorkflowCriteriaDto
|
||||||
updateCriteria(
|
updateCriteria(
|
||||||
exists
|
exists
|
||||||
? currentCriteria.map((item) => (item.id === id ? nextItem : item))
|
? currentCriteria.map((item) => (item.id === id ? nextItem : item))
|
||||||
|
|
@ -108,11 +110,12 @@ function WizardStep6({
|
||||||
}
|
}
|
||||||
|
|
||||||
const addCriteria = (kind: string) => {
|
const addCriteria = (kind: string) => {
|
||||||
const id = nextId()
|
const id = uniqueCriteriaId(currentCriteria)
|
||||||
const nextItem = {
|
const nextItem = {
|
||||||
...normalizeCriteria(emptyCriteria(kind, listFormCode)),
|
...normalizeCriteria(emptyCriteria(kind, listFormCode)),
|
||||||
id,
|
id,
|
||||||
nodeId: id,
|
nodeId: id,
|
||||||
|
title: uniqueCriteriaTitle(kind, currentCriteria),
|
||||||
positionX: 80 + (currentCriteria.length % 5) * 230,
|
positionX: 80 + (currentCriteria.length % 5) * 230,
|
||||||
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
||||||
} as WorkflowCriteriaDto
|
} as WorkflowCriteriaDto
|
||||||
|
|
@ -192,14 +195,15 @@ function WizardStep6({
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetDemo = () => {
|
const resetDemo = () => {
|
||||||
const startId = nextId()
|
const startId = uniqueCriteriaId([])
|
||||||
const approvalId = nextId()
|
const approvalId = uniqueCriteriaId([], [startId])
|
||||||
const endId = nextId()
|
const endId = uniqueCriteriaId([], [startId, approvalId])
|
||||||
updateCriteria([
|
updateCriteria([
|
||||||
{
|
{
|
||||||
...normalizeCriteria(emptyCriteria('Start', listFormCode)),
|
...normalizeCriteria(emptyCriteria('Start', listFormCode)),
|
||||||
id: startId,
|
id: startId,
|
||||||
nodeId: startId,
|
nodeId: startId,
|
||||||
|
title: uniqueCriteriaTitle('Start', []),
|
||||||
nextOnStart: approvalId,
|
nextOnStart: approvalId,
|
||||||
positionX: 72,
|
positionX: 72,
|
||||||
positionY: 160,
|
positionY: 160,
|
||||||
|
|
@ -208,6 +212,7 @@ function WizardStep6({
|
||||||
...normalizeCriteria(emptyCriteria('Approval', listFormCode)),
|
...normalizeCriteria(emptyCriteria('Approval', listFormCode)),
|
||||||
id: approvalId,
|
id: approvalId,
|
||||||
nodeId: approvalId,
|
nodeId: approvalId,
|
||||||
|
title: uniqueCriteriaTitle('Approval', []),
|
||||||
nextOnApprove: endId,
|
nextOnApprove: endId,
|
||||||
positionX: 360,
|
positionX: 360,
|
||||||
positionY: 160,
|
positionY: 160,
|
||||||
|
|
@ -216,6 +221,7 @@ function WizardStep6({
|
||||||
...normalizeCriteria(emptyCriteria('End', listFormCode)),
|
...normalizeCriteria(emptyCriteria('End', listFormCode)),
|
||||||
id: endId,
|
id: endId,
|
||||||
nodeId: endId,
|
nodeId: endId,
|
||||||
|
title: uniqueCriteriaTitle('End', []),
|
||||||
positionX: 650,
|
positionX: 650,
|
||||||
positionY: 160,
|
positionY: 160,
|
||||||
} as WorkflowCriteriaDto,
|
} as WorkflowCriteriaDto,
|
||||||
|
|
@ -234,7 +240,7 @@ function WizardStep6({
|
||||||
<Form>
|
<Form>
|
||||||
<Card className="mb-4" header={translate('::ListForms.ListFormEdit.TabWorkflow')}>
|
<Card className="mb-4" header={translate('::ListForms.ListFormEdit.TabWorkflow')}>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<div className="grid grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||||
{[
|
{[
|
||||||
[
|
[
|
||||||
'approvalUserFieldName',
|
'approvalUserFieldName',
|
||||||
|
|
@ -277,6 +283,26 @@ function WizardStep6({
|
||||||
</Field>
|
</Field>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
))}
|
))}
|
||||||
|
<FormItem
|
||||||
|
label={translate('::ListForms.ListFormEdit.Workflow.IsFilterUserName')}
|
||||||
|
>
|
||||||
|
<Field name="isFilterUserName">
|
||||||
|
{({ field, form }: FieldProps<boolean>) => (
|
||||||
|
<Checkbox
|
||||||
|
name={field.name}
|
||||||
|
checked={Boolean(values.isFilterUserName)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
form.setFieldValue(field.name, checked)
|
||||||
|
onWorkflowChange({
|
||||||
|
...values,
|
||||||
|
isFilterUserName: checked,
|
||||||
|
criteria,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,13 @@ const WizardStep7 = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
|
const totalFields = groups.reduce((acc, g) => acc + g.items.length, 0)
|
||||||
|
const editingFormFields = groups.flatMap((g) =>
|
||||||
|
g.items
|
||||||
|
.filter((item) => item.includeInEditingForm && item.dataField !== values.keyFieldName)
|
||||||
|
.map((item) => ({ ...item, groupCaption: g.caption })),
|
||||||
|
)
|
||||||
|
const groupedFieldNames = new Set(groups.flatMap((g) => g.items.map((item) => item.dataField)))
|
||||||
|
const ungroupedSelectedColumns = [...selectedColumns].filter((col) => !groupedFieldNames.has(col))
|
||||||
const hasWorkflowFields = Boolean(
|
const hasWorkflowFields = Boolean(
|
||||||
workflow.approvalUserFieldName ||
|
workflow.approvalUserFieldName ||
|
||||||
workflow.approvalDateFieldName ||
|
workflow.approvalDateFieldName ||
|
||||||
|
|
@ -298,91 +305,157 @@ const WizardStep7 = ({
|
||||||
label={translate('::App.Listform.ListformField.ConnectionString')}
|
label={translate('::App.Listform.ListformField.ConnectionString')}
|
||||||
value={values.dataSourceConnectionString}
|
value={values.dataSourceConnectionString}
|
||||||
/>
|
/>
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.CommandType')}
|
|
||||||
value={
|
|
||||||
selectCommandTypeOptions.find((o) => o.value === values.selectCommandType)?.label ||
|
|
||||||
values.selectCommandType
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Row
|
<Row
|
||||||
label={translate('::ListForms.Wizard.Step4.SelectCommand')}
|
label={translate('::ListForms.Wizard.Step4.SelectCommand')}
|
||||||
value={values.selectCommand}
|
value={`${values.selectCommand} (${
|
||||||
|
selectCommandTypeOptions.find((o: any) => o.value === values.selectCommandType)
|
||||||
|
?.label ?? String(values.selectCommandType)
|
||||||
|
})`}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
label={translate('::ListForms.Wizard.Step4.KeyField')}
|
label={translate('::ListForms.Wizard.Step4.KeyField')}
|
||||||
value={values.keyFieldName}
|
value={`${values.keyFieldName} (${
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={translate('::ListForms.Wizard.Step4.KeyFieldType')}
|
|
||||||
value={
|
|
||||||
dbSourceTypeOptions.find((o: any) => o.value === values.keyFieldDbSourceType)
|
dbSourceTypeOptions.find((o: any) => o.value === values.keyFieldDbSourceType)
|
||||||
?.label ?? String(values.keyFieldDbSourceType)
|
?.label ?? String(values.keyFieldDbSourceType)
|
||||||
}
|
})`}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Section
|
<Section
|
||||||
title={translate('::ListForms.Wizard.Step4.SelectedColumns')}
|
title={
|
||||||
badge={selectedColumns.size}
|
translate('::ListForms.Wizard.Step4.ColumnsAndFormLayout') || 'Columns & Form Layout'
|
||||||
|
}
|
||||||
|
badge={`${selectedColumns.size} ${translate('::App.Listform.ListformField.Column')} / ${editingFormFields.length} ${translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="mb-3 grid grid-cols-3 gap-2">
|
||||||
{[...selectedColumns].map((col) => {
|
{[
|
||||||
const meta = selectCommandColumns.find((c) => c.columnName === col)
|
{
|
||||||
return (
|
label: translate('::ListForms.Wizard.Step4.SelectedColumns'),
|
||||||
<span
|
value: selectedColumns.size,
|
||||||
key={col}
|
className: 'text-indigo-600 dark:text-indigo-400',
|
||||||
className="inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded-full bg-indigo-50 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-700"
|
},
|
||||||
>
|
{
|
||||||
{col}
|
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
||||||
{meta?.dataType && (
|
value: editingFormFields.length,
|
||||||
<span className="text-[10px] text-indigo-400 opacity-70">{meta.dataType}</span>
|
className: 'text-emerald-600 dark:text-emerald-400',
|
||||||
)}
|
},
|
||||||
</span>
|
{
|
||||||
)
|
label: translate('::ListForms.Wizard.Step4.FormGroups'),
|
||||||
})}
|
value: groups.length,
|
||||||
</div>
|
className: 'text-gray-700 dark:text-gray-200',
|
||||||
</Section>
|
},
|
||||||
|
].map((item) => (
|
||||||
<Section title={translate('::ListForms.Wizard.Step4.FormGroups')} badge={groups.length}>
|
<div
|
||||||
<div className="flex flex-col gap-3">
|
key={item.label}
|
||||||
{groups.map((g) => (
|
className="rounded-lg border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-800 px-3 py-2"
|
||||||
<Section
|
|
||||||
key={g.id}
|
|
||||||
title={g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
|
|
||||||
badge={`${g.items.length} ${translate('::ListForms.Wizard.Step4.StatField')} · ${g.colCount} ${translate('::App.Listform.ListformField.Column')}`}
|
|
||||||
defaultOpen={false}
|
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className={`text-lg font-semibold leading-none ${item.className}`}>
|
||||||
|
{item.value}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-[11px] text-gray-400 truncate">{item.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{groups.map((g) => (
|
||||||
|
<div
|
||||||
|
key={g.id}
|
||||||
|
className="overflow-hidden rounded-lg border border-gray-100 dark:border-gray-800"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-3 bg-gray-50 dark:bg-gray-800 px-3 py-2">
|
||||||
|
<span className="text-xs font-semibold text-gray-700 dark:text-gray-200 truncate">
|
||||||
|
{g.caption || `(${translate('::ListForms.Wizard.Step4.StatGroup')})`}
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-1.5 text-[10px] text-gray-400 shrink-0">
|
||||||
|
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
|
||||||
|
{g.items.length} {translate('::ListForms.Wizard.Step4.StatField')}
|
||||||
|
</span>
|
||||||
|
<span className="rounded bg-white dark:bg-gray-900 px-1.5 py-0.5">
|
||||||
|
{g.colCount} {translate('::App.Listform.ListformField.Column')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||||
{g.items.length === 0 ? (
|
{g.items.length === 0 ? (
|
||||||
<span className="text-xs text-gray-300 italic">
|
<span className="block px-3 py-2 text-xs text-gray-300 italic">
|
||||||
{translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'}
|
{translate('::ListForms.Wizard.Step4.NoFields') || 'Alan yok'}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
g.items.map((item) => (
|
g.items.map((item) => {
|
||||||
<div
|
const meta = selectCommandColumns.find((c) => c.columnName === item.dataField)
|
||||||
key={item.id}
|
const isKeyField = item.dataField === values.keyFieldName
|
||||||
className="flex items-center gap-2 py-1 border-b border-gray-100 dark:border-gray-800 last:border-0"
|
const isPopupField = item.includeInEditingForm && !isKeyField
|
||||||
>
|
return (
|
||||||
<span className="text-xs font-medium text-indigo-600 dark:text-indigo-400 w-36 shrink-0 truncate">
|
<div
|
||||||
{item.dataField}
|
key={item.id}
|
||||||
</span>
|
className="grid grid-cols-[minmax(150px,1fr)_auto] items-center gap-3 px-3 py-1.5"
|
||||||
<span className="text-[10px] text-gray-400 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">
|
>
|
||||||
{item.editorType}
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
</span>
|
<span className="text-xs font-medium text-indigo-600 dark:text-indigo-400 truncate">
|
||||||
<span className="text-[10px] text-gray-400 mr-auto shrink-0 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">
|
{item.dataField}
|
||||||
col-span-{item.colSpan}
|
</span>
|
||||||
{item.isRequired && (
|
{meta?.dataType && (
|
||||||
<span className="ml-1 text-red-400 font-semibold">*</span>
|
<span className="text-[10px] text-gray-400 truncate">
|
||||||
)}
|
{meta.dataType}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
)}
|
||||||
))
|
</div>
|
||||||
|
<div className="flex flex-wrap justify-end gap-1">
|
||||||
|
<span className="rounded bg-indigo-50 dark:bg-indigo-900/30 px-1.5 py-0.5 text-[10px] text-indigo-600 dark:text-indigo-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.SelectedColumns') || 'List'}
|
||||||
|
</span>
|
||||||
|
{isPopupField && (
|
||||||
|
<span className="rounded bg-emerald-50 dark:bg-emerald-900/30 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isKeyField && (
|
||||||
|
<span className="rounded bg-amber-50 dark:bg-amber-900/30 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.KeyField')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
|
||||||
|
{item.editorType}
|
||||||
|
</span>
|
||||||
|
<span className="rounded bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 text-[10px] text-gray-400">
|
||||||
|
span-{item.colSpan}
|
||||||
|
{item.isRequired && <span className="ml-1 text-red-400">*</span>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{ungroupedSelectedColumns.length > 0 && (
|
||||||
|
<div className="rounded-lg border border-amber-100 dark:border-amber-900/40 bg-amber-50/50 dark:bg-amber-900/10 px-3 py-2">
|
||||||
|
<div className="mb-1.5 text-xs font-semibold text-amber-700 dark:text-amber-300">
|
||||||
|
{translate('::ListForms.Wizard.Step4.UngroupedColumns') || 'Ungrouped columns'}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{ungroupedSelectedColumns.map((col) => {
|
||||||
|
const meta = selectCommandColumns.find((c) => c.columnName === col)
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={col}
|
||||||
|
className="inline-flex items-center gap-1 rounded-full border border-amber-200 dark:border-amber-800 bg-white dark:bg-gray-900 px-2 py-0.5 text-xs text-amber-700 dark:text-amber-300"
|
||||||
|
>
|
||||||
|
{col}
|
||||||
|
{meta?.dataType && (
|
||||||
|
<span className="text-[10px] text-amber-500 opacity-70">
|
||||||
|
{meta.dataType}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
|
@ -460,6 +533,7 @@ const WizardStep7 = ({
|
||||||
<Row label="Approval Date" value={workflow.approvalDateFieldName} />
|
<Row label="Approval Date" value={workflow.approvalDateFieldName} />
|
||||||
<Row label="Approval Status" value={workflow.approvalStatusFieldName} />
|
<Row label="Approval Status" value={workflow.approvalStatusFieldName} />
|
||||||
<Row label="Approval Description" value={workflow.approvalDescriptionFieldName} />
|
<Row label="Approval Description" value={workflow.approvalDescriptionFieldName} />
|
||||||
|
<Row label="Is Filter User Name?" value={workflow.isFilterUserName === true ? 'Yes' : 'No'} />
|
||||||
</div>
|
</div>
|
||||||
{workflowItems.length > 0 && (
|
{workflowItems.length > 0 && (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
|
|
@ -471,12 +545,18 @@ const WizardStep7 = ({
|
||||||
<div className="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
<div className="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
||||||
{criteria.title || criteria.kind || `Criteria ${index + 1}`}
|
{criteria.title || criteria.kind || `Criteria ${index + 1}`}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
{criteria.kind === 'Compare' && (
|
||||||
{criteria.compareColumn} {criteria.compareOperator} {criteria.compareValue}
|
<div className="mt-1 text-[11px] text-gray-500 dark:text-gray-400">
|
||||||
</div>
|
{criteria.compareColumn} {criteria.compareOperator}{' '}
|
||||||
<div className="text-[11px] text-gray-500 dark:text-gray-400">
|
{criteria.compareValue}
|
||||||
Approver: {criteria.approver}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
{(criteria.kind === 'Approval' || criteria.kind === 'Inform') &&
|
||||||
|
criteria.approver && (
|
||||||
|
<div className="text-[11px] text-gray-500 dark:text-gray-400">
|
||||||
|
Approver: {criteria.approver}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -489,10 +569,14 @@ const WizardStep7 = ({
|
||||||
{/* ── Right: Deploy ──────────────────────────────────────────── */}
|
{/* ── Right: Deploy ──────────────────────────────────────────── */}
|
||||||
<div className="sticky top-4 flex flex-col gap-3 max-h-[calc(100vh-200px)]">
|
<div className="sticky top-4 flex flex-col gap-3 max-h-[calc(100vh-200px)]">
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-7 gap-2">
|
||||||
{[
|
{[
|
||||||
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
|
{ label: translate('::ListForms.Wizard.Step4.StatGroup'), value: groups.length },
|
||||||
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
|
{ label: translate('::ListForms.Wizard.Step4.StatField'), value: totalFields },
|
||||||
|
{
|
||||||
|
label: translate('::ListForms.Wizard.Step4.EditingForm') || 'Popup Form',
|
||||||
|
value: editingFormFields.length,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: translate('::App.Listform.ListformField.Column'),
|
label: translate('::App.Listform.ListformField.Column'),
|
||||||
value: selectedColumns.size,
|
value: selectedColumns.size,
|
||||||
|
|
|
||||||
|
|
@ -257,17 +257,19 @@ export function WorkflowCriteria({
|
||||||
onChange={(event) => setField('title', event.target.value)}
|
onChange={(event) => setField('title', event.target.value)}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem
|
{(formValues.kind === 'Approval' || formValues.kind === 'Inform') && (
|
||||||
label={translate('::App.Listform.ListformField.Approver')}
|
<FormItem
|
||||||
asterisk={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
|
label={translate('::App.Listform.ListformField.Approver')}
|
||||||
>
|
asterisk
|
||||||
<SelectField
|
>
|
||||||
required={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
|
<SelectField
|
||||||
options={userList}
|
required
|
||||||
value={formValues.approver}
|
options={userList}
|
||||||
onChange={(value) => setField('approver', value)}
|
value={formValues.approver}
|
||||||
/>
|
onChange={(value) => setField('approver', value)}
|
||||||
</FormItem>
|
/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{(formValues.kind === 'Start' || formValues.kind === 'Inform') && (
|
{(formValues.kind === 'Start' || formValues.kind === 'Inform') && (
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,11 @@ import {
|
||||||
deleteClaimUser,
|
deleteClaimUser,
|
||||||
getUserDetail,
|
getUserDetail,
|
||||||
postClaimUser,
|
postClaimUser,
|
||||||
|
putUserAvatar,
|
||||||
putUserDetail,
|
putUserDetail,
|
||||||
putUserLookout,
|
putUserLookout,
|
||||||
putUserPermission,
|
putUserPermission,
|
||||||
} from '@/services/identity.service'
|
} from '@/services/identity.service'
|
||||||
import { updateProfile } from '@/services/account.service'
|
|
||||||
import { CountryDto, getCountry } from '@/services/home.service'
|
import { CountryDto, getCountry } from '@/services/home.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
@ -97,6 +97,7 @@ function UserDetails() {
|
||||||
const [confirmDeleteClaim, setConfirmDeleteClaim] = useState<AssignedClaimViewModel | null>(null)
|
const [confirmDeleteClaim, setConfirmDeleteClaim] = useState<AssignedClaimViewModel | null>(null)
|
||||||
const [countries, setCountries] = useState<CountryDto[]>([])
|
const [countries, setCountries] = useState<CountryDto[]>([])
|
||||||
const [image, setImage] = useState<string | undefined>()
|
const [image, setImage] = useState<string | undefined>()
|
||||||
|
const [hasAvatarChange, setHasAvatarChange] = useState(false)
|
||||||
const cropperRef = useRef<CropperRef>(null)
|
const cropperRef = useRef<CropperRef>(null)
|
||||||
const previewRef = useRef<CropperPreviewRef>(null)
|
const previewRef = useRef<CropperPreviewRef>(null)
|
||||||
const auth = useStoreState((state) => state.auth)
|
const auth = useStoreState((state) => state.auth)
|
||||||
|
|
@ -111,15 +112,26 @@ function UserDetails() {
|
||||||
const isTwoFactorEnabled = setting('Abp.Account.TwoFactor.Enabled')
|
const isTwoFactorEnabled = setting('Abp.Account.TwoFactor.Enabled')
|
||||||
|
|
||||||
const getUser = async (syncAvatar = true) => {
|
const getUser = async (syncAvatar = true) => {
|
||||||
const { data } = await getUserDetail(userId || '')
|
if (!userId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await getUserDetail(userId)
|
||||||
setUserDetails(data)
|
setUserDetails(data)
|
||||||
if (syncAvatar) {
|
if (syncAvatar) {
|
||||||
setImage(`${AVATAR_URL(data.id, data.tenantId)}?${dayjs().unix()}`)
|
setImage(`${AVATAR_URL(data.id, data.tenantId)}?${dayjs().unix()}`)
|
||||||
|
setHasAvatarChange(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setUserDetails(undefined)
|
||||||
|
setImage(undefined)
|
||||||
|
setHasAvatarChange(false)
|
||||||
getUser()
|
getUser()
|
||||||
|
}, [userId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
getCountry().then(({ data }) => setCountries(data))
|
getCountry().then(({ data }) => setCountries(data))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
@ -131,8 +143,10 @@ function UserDetails() {
|
||||||
const onChooseImage = async (file: File[]) => {
|
const onChooseImage = async (file: File[]) => {
|
||||||
if (file[0]) {
|
if (file[0]) {
|
||||||
setImage(URL.createObjectURL(file[0]))
|
setImage(URL.createObjectURL(file[0]))
|
||||||
|
setHasAvatarChange(true)
|
||||||
} else {
|
} else {
|
||||||
setImage(undefined)
|
setImage(undefined)
|
||||||
|
setHasAvatarChange(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,34 +260,49 @@ function UserDetails() {
|
||||||
<TabContent value="user">
|
<TabContent value="user">
|
||||||
<div className="px-4 py-6">
|
<div className="px-4 py-6">
|
||||||
<Formik
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
initialValues={userDetails}
|
initialValues={userDetails}
|
||||||
onSubmit={async (values, { resetForm, setSubmitting }) => {
|
onSubmit={async (values, { resetForm, setSubmitting }) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
await putUserDetail({ ...values })
|
await putUserDetail({ ...values })
|
||||||
let keepCurrentAvatar = false
|
if (values.id === auth.user.id) {
|
||||||
|
|
||||||
const avatar = await getCroppedAvatar()
|
|
||||||
const resp = await updateProfile({
|
|
||||||
name: values.name ?? '',
|
|
||||||
surname: values.surname ?? '',
|
|
||||||
avatar: avatar ? new File([avatar], 'avatar') : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (resp.status === 200) {
|
|
||||||
const avatarUrl =
|
|
||||||
AVATAR_URL(auth.user.id, auth.tenant?.tenantId) + `?${dayjs().unix()}`
|
|
||||||
|
|
||||||
setUser({
|
setUser({
|
||||||
...auth.user,
|
...auth.user,
|
||||||
name: `${resp.data.name} ${resp.data.surname}`.trim(),
|
name: `${values.name ?? ''} ${values.surname ?? ''}`.trim(),
|
||||||
avatar: avatarUrl,
|
|
||||||
})
|
})
|
||||||
setImage(avatarUrl)
|
}
|
||||||
keepCurrentAvatar = true
|
let keepCurrentAvatar = false
|
||||||
} else {
|
|
||||||
toast.push(<Notification title={resp?.error?.message} type="danger" />, {
|
if (hasAvatarChange) {
|
||||||
placement: 'top-end',
|
const avatar = await getCroppedAvatar()
|
||||||
|
const resp = await putUserAvatar({
|
||||||
|
userId: values.id!,
|
||||||
|
avatar: avatar ? new File([avatar], 'avatar') : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const avatarUrl =
|
||||||
|
AVATAR_URL(values.id, values.tenantId) + `?${dayjs().unix()}`
|
||||||
|
|
||||||
|
if (values.id === auth.user.id) {
|
||||||
|
setUser({
|
||||||
|
...auth.user,
|
||||||
|
name: `${values.name ?? ''} ${values.surname ?? ''}`.trim(),
|
||||||
|
avatar: avatarUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setImage(avatarUrl)
|
||||||
|
setHasAvatarChange(false)
|
||||||
|
keepCurrentAvatar = true
|
||||||
|
} else {
|
||||||
|
const errorMessage =
|
||||||
|
(resp as { error?: { message?: string } })?.error?.message || 'Hata'
|
||||||
|
|
||||||
|
toast.push(<Notification title={errorMessage} type="danger" />, {
|
||||||
|
placement: 'top-end',
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -542,7 +571,10 @@ function UserDetails() {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
icon={<FaTrashAlt />}
|
icon={<FaTrashAlt />}
|
||||||
onClick={() => setImage(undefined)}
|
onClick={() => {
|
||||||
|
setImage(undefined)
|
||||||
|
setHasAvatarChange(true)
|
||||||
|
}}
|
||||||
></Button>
|
></Button>
|
||||||
</div>
|
</div>
|
||||||
{image && (
|
{image && (
|
||||||
|
|
@ -574,6 +606,7 @@ function UserDetails() {
|
||||||
<TabContent value="permission">
|
<TabContent value="permission">
|
||||||
<div className="px-4 py-6">
|
<div className="px-4 py-6">
|
||||||
<Formik
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
initialValues={userDetails}
|
initialValues={userDetails}
|
||||||
onSubmit={async (values, { setSubmitting }) => {
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
@ -685,6 +718,7 @@ function UserDetails() {
|
||||||
<TabContent value="work">
|
<TabContent value="work">
|
||||||
<div className="px-4 py-6">
|
<div className="px-4 py-6">
|
||||||
<Formik
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
initialValues={userDetails}
|
initialValues={userDetails}
|
||||||
onSubmit={async (values, { resetForm, setSubmitting }) => {
|
onSubmit={async (values, { resetForm, setSubmitting }) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
@ -866,6 +900,7 @@ function UserDetails() {
|
||||||
<TabContent value="identity">
|
<TabContent value="identity">
|
||||||
<div className="px-4 py-6">
|
<div className="px-4 py-6">
|
||||||
<Formik
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
initialValues={userDetails}
|
initialValues={userDetails}
|
||||||
onSubmit={async (values, { resetForm, setSubmitting }) => {
|
onSubmit={async (values, { resetForm, setSubmitting }) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
@ -1222,6 +1257,7 @@ function UserDetails() {
|
||||||
<TabContent value="lockout">
|
<TabContent value="lockout">
|
||||||
<div className="px-4 py-6">
|
<div className="px-4 py-6">
|
||||||
<Formik
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
initialValues={userDetails}
|
initialValues={userDetails}
|
||||||
onSubmit={async (values, { setSubmitting }) => {
|
onSubmit={async (values, { setSubmitting }) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ type SqlDataType =
|
||||||
| 'decimal'
|
| 'decimal'
|
||||||
| 'float'
|
| 'float'
|
||||||
| 'bit'
|
| 'bit'
|
||||||
|
| 'datetime'
|
||||||
| 'datetime2'
|
| 'datetime2'
|
||||||
| 'date'
|
| 'date'
|
||||||
| 'uniqueidentifier'
|
| 'uniqueidentifier'
|
||||||
|
|
@ -68,7 +69,7 @@ interface TableDesignerDialogProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
dataSource: string | null
|
dataSource: string | null
|
||||||
onDeployed?: () => void
|
onDeployed?: (table: { schemaName: string; tableName: string }) => void | Promise<void>
|
||||||
initialTableData?: { schemaName: string; tableName: string } | null
|
initialTableData?: { schemaName: string; tableName: string } | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +99,7 @@ const DATA_TYPES: { value: SqlDataType; label: string }[] = [
|
||||||
{ value: 'decimal', label: 'Decimal (decimal 18,4)' },
|
{ value: 'decimal', label: 'Decimal (decimal 18,4)' },
|
||||||
{ value: 'float', label: 'Float (float)' },
|
{ value: 'float', label: 'Float (float)' },
|
||||||
{ value: 'bit', label: 'Bool (bit)' },
|
{ value: 'bit', label: 'Bool (bit)' },
|
||||||
|
{ value: 'datetime', label: 'DateTime (datetime)' },
|
||||||
{ value: 'datetime2', label: 'DateTime (datetime2)' },
|
{ value: 'datetime2', label: 'DateTime (datetime2)' },
|
||||||
{ value: 'date', label: 'Date (date)' },
|
{ value: 'date', label: 'Date (date)' },
|
||||||
{ value: 'uniqueidentifier', label: 'Guid (uniqueidentifier)' },
|
{ value: 'uniqueidentifier', label: 'Guid (uniqueidentifier)' },
|
||||||
|
|
@ -226,6 +228,45 @@ const TENANT_COLUMN: ColumnDefinition = {
|
||||||
description: 'Tenant ID for multi-tenancy',
|
description: 'Tenant ID for multi-tenancy',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WORKFLOW_COLUMNS: ColumnDefinition[] = [
|
||||||
|
{
|
||||||
|
id: '__ApprovalUserId',
|
||||||
|
columnName: 'ApprovalUserName',
|
||||||
|
dataType: 'nvarchar',
|
||||||
|
maxLength: '256',
|
||||||
|
isNullable: true,
|
||||||
|
defaultValue: '',
|
||||||
|
description: 'Workflow approval user name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '__ApprovalStatus',
|
||||||
|
columnName: 'ApprovalStatus',
|
||||||
|
dataType: 'nvarchar',
|
||||||
|
maxLength: '50',
|
||||||
|
isNullable: true,
|
||||||
|
defaultValue: '',
|
||||||
|
description: 'Workflow approval status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '__ApprovalDate',
|
||||||
|
columnName: 'ApprovalDate',
|
||||||
|
dataType: 'datetime',
|
||||||
|
maxLength: '',
|
||||||
|
isNullable: true,
|
||||||
|
defaultValue: '',
|
||||||
|
description: 'Workflow approval date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '__ApprovalDescription',
|
||||||
|
columnName: 'ApprovalDescription',
|
||||||
|
dataType: 'nvarchar',
|
||||||
|
maxLength: '200',
|
||||||
|
isNullable: true,
|
||||||
|
defaultValue: '',
|
||||||
|
description: 'Workflow approval description',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
|
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
|
||||||
|
|
||||||
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
|
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
|
||||||
|
|
@ -405,6 +446,8 @@ function dbColToColumnDef(col: {
|
||||||
dataType = 'float'
|
dataType = 'float'
|
||||||
} else if (dt === 'bit') {
|
} else if (dt === 'bit') {
|
||||||
dataType = 'bit'
|
dataType = 'bit'
|
||||||
|
} else if (dt === 'datetime') {
|
||||||
|
dataType = 'datetime'
|
||||||
} else if (dt.startsWith('datetime') || dt === 'smalldatetime') {
|
} else if (dt.startsWith('datetime') || dt === 'smalldatetime') {
|
||||||
dataType = 'datetime2'
|
dataType = 'datetime2'
|
||||||
} else if (dt === 'date') {
|
} else if (dt === 'date') {
|
||||||
|
|
@ -476,6 +519,7 @@ function mapSqlTypeToDesigner(dataTypeRaw: string, lengthRaw: string): {
|
||||||
if (dt === 'decimal' || dt === 'numeric') return { dataType: 'decimal', maxLength: '' }
|
if (dt === 'decimal' || dt === 'numeric') return { dataType: 'decimal', maxLength: '' }
|
||||||
if (dt === 'float' || dt === 'real') return { dataType: 'float', maxLength: '' }
|
if (dt === 'float' || dt === 'real') return { dataType: 'float', maxLength: '' }
|
||||||
if (dt === 'bit') return { dataType: 'bit', maxLength: '' }
|
if (dt === 'bit') return { dataType: 'bit', maxLength: '' }
|
||||||
|
if (dt === 'datetime') return { dataType: 'datetime', maxLength: '' }
|
||||||
if (dt.startsWith('datetime') || dt === 'smalldatetime' || dt === 'time') {
|
if (dt.startsWith('datetime') || dt === 'smalldatetime' || dt === 'time') {
|
||||||
return { dataType: 'datetime2', maxLength: '' }
|
return { dataType: 'datetime2', maxLength: '' }
|
||||||
}
|
}
|
||||||
|
|
@ -1224,6 +1268,18 @@ const SqlTableDesignerDialog = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addWorkflowColumns = () => {
|
||||||
|
const existingNames = new Set(columns.map((c) => c.columnName.trim().toLowerCase()))
|
||||||
|
const toAdd = WORKFLOW_COLUMNS.filter((c) => !existingNames.has(c.columnName.toLowerCase()))
|
||||||
|
|
||||||
|
if (toAdd.length > 0) {
|
||||||
|
setColumns((prev) => {
|
||||||
|
const nonEmpty = prev.filter((c) => c.columnName.trim() !== '')
|
||||||
|
return [...nonEmpty, ...toAdd.map((c) => ({ ...c })), createEmptyColumn()]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const importColumnsFromRememberedCreateTable = async () => {
|
const importColumnsFromRememberedCreateTable = async () => {
|
||||||
let script = ''
|
let script = ''
|
||||||
|
|
||||||
|
|
@ -1591,7 +1647,10 @@ const SqlTableDesignerDialog = ({
|
||||||
} catch {
|
} catch {
|
||||||
// Non-blocking: seed file save failure does not affect deploy success
|
// Non-blocking: seed file save failure does not affect deploy success
|
||||||
}
|
}
|
||||||
onDeployed?.()
|
await onDeployed?.({
|
||||||
|
schemaName: initialTableData?.schemaName || 'dbo',
|
||||||
|
tableName: deployedTable,
|
||||||
|
})
|
||||||
handleClose()
|
handleClose()
|
||||||
} else {
|
} else {
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -1690,6 +1749,9 @@ const SqlTableDesignerDialog = ({
|
||||||
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
|
<Button size="xs" variant="solid" color="green-600" onClick={addMultiTenantColumns}>
|
||||||
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
|
{translate('::App.SqlQueryManager.AddMultiTenantColumns')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button size="xs" variant="solid" color="indigo-600" onClick={addWorkflowColumns}>
|
||||||
|
{translate('::App.SqlQueryManager.AddWorkflowColumns')}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
|
|
@ -1943,7 +2005,14 @@ const SqlTableDesignerDialog = ({
|
||||||
initialParentCode={selectedMenuCode}
|
initialParentCode={selectedMenuCode}
|
||||||
initialOrder={999}
|
initialOrder={999}
|
||||||
rawItems={rawMenuItems}
|
rawItems={rawMenuItems}
|
||||||
onSaved={() => reloadMenus()}
|
onSaved={(menu) =>
|
||||||
|
reloadMenus((items) => {
|
||||||
|
const savedMenu = items.find((item) => item.code === menu.code)
|
||||||
|
if (savedMenu?.code && savedMenu.shortName) {
|
||||||
|
onMenuCodeSelect(savedMenu.code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2671,7 +2740,7 @@ const SqlTableDesignerDialog = ({
|
||||||
// ── Render ─────────────────────────────────────────────────────────────────
|
// ── Render ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={900}>
|
<Dialog isOpen={isOpen} onClose={handleClose} onRequestClose={handleClose} width={1100}>
|
||||||
<Dialog.Body className="flex flex-col gap-2">
|
<Dialog.Body className="flex flex-col gap-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0">
|
<div className="flex items-center gap-3 border-b pb-3 flex-shrink-0">
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,17 @@ import TabNav from '@/components/ui/Tabs/TabNav'
|
||||||
import { NoteDto } from '@/proxy/note/models'
|
import { NoteDto } from '@/proxy/note/models'
|
||||||
import { AVATAR_URL } from '@/constants/app.constant'
|
import { AVATAR_URL } from '@/constants/app.constant'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
import apiService from '@/services/api.service'
|
|
||||||
import { PagedResultDto } from '@/proxy'
|
|
||||||
import { AuditLogActionDto, AuditLogDto } from '@/proxy/auditLog/audit-log'
|
import { AuditLogActionDto, AuditLogDto } from '@/proxy/auditLog/audit-log'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { auditLogService } from '@/services/auditLog.service'
|
||||||
|
|
||||||
interface NoteListProps {
|
interface NoteListProps {
|
||||||
notes: NoteDto[]
|
notes: NoteDto[]
|
||||||
entityName: string
|
entityName: string
|
||||||
entityId: string
|
entityId: string
|
||||||
|
isVisible?: boolean
|
||||||
onAddNote?: () => void
|
onAddNote?: () => void
|
||||||
|
onRefreshNotes?: () => void
|
||||||
onDeleteNote?: (noteId: string) => void
|
onDeleteNote?: (noteId: string) => void
|
||||||
onDownloadFile?: (fileData: any) => void
|
onDownloadFile?: (fileData: any) => void
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +36,9 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
notes,
|
notes,
|
||||||
entityName,
|
entityName,
|
||||||
entityId,
|
entityId,
|
||||||
|
isVisible = true,
|
||||||
onAddNote,
|
onAddNote,
|
||||||
|
onRefreshNotes,
|
||||||
onDeleteNote,
|
onDeleteNote,
|
||||||
onDownloadFile,
|
onDownloadFile,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -70,6 +73,11 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
icon: <FaEnvelope className="text-green-500" />,
|
icon: <FaEnvelope className="text-green-500" />,
|
||||||
border: 'border-green-400',
|
border: 'border-green-400',
|
||||||
}
|
}
|
||||||
|
case 'workflow':
|
||||||
|
return {
|
||||||
|
icon: <FaHistory className="text-purple-500" />,
|
||||||
|
border: 'border-purple-400',
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
icon: <FaStickyNote className="text-gray-400" />,
|
icon: <FaStickyNote className="text-gray-400" />,
|
||||||
|
|
@ -215,7 +223,18 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
const getRowLabelIfMatches = (input: any): string | null => {
|
const getRowLabelIfMatches = (input: any): string | null => {
|
||||||
if (!entityIdNormalized) return null
|
if (!entityIdNormalized) return null
|
||||||
const inputFormCode = normalize(input?.listFormCode)
|
const inputFormCode = normalize(input?.listFormCode)
|
||||||
if (!inputFormCode || inputFormCode !== listFormCodeNormalized) return null
|
if (!inputFormCode) return null
|
||||||
|
const data = input?.data
|
||||||
|
const isMainListForm = inputFormCode === listFormCodeNormalized
|
||||||
|
|
||||||
|
if (!isMainListForm) {
|
||||||
|
if (!data || typeof data !== 'object') return null
|
||||||
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
|
if (!hit) return null
|
||||||
|
|
||||||
|
const nameValue = (data as any)?.Name ?? (data as any)?.name
|
||||||
|
return nameValue ? String(nameValue) : String(hit.value)
|
||||||
|
}
|
||||||
|
|
||||||
const keys = getKeysFromInput(input)
|
const keys = getKeysFromInput(input)
|
||||||
.map((k) => normalize(k))
|
.map((k) => normalize(k))
|
||||||
|
|
@ -227,7 +246,6 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some entities may use a different PK than the visible row key; allow strict match via input.data too.
|
// Some entities may use a different PK than the visible row key; allow strict match via input.data too.
|
||||||
const data = input?.data
|
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
const hit = findMatchingValueInData(data, entityIdNormalized)
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
if (hit) {
|
if (hit) {
|
||||||
|
|
@ -240,7 +258,6 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert: keys is null, match by scanning input.data for entity id/name/code/etc.
|
// insert: keys is null, match by scanning input.data for entity id/name/code/etc.
|
||||||
const data = input?.data
|
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
const hit = findMatchingValueInData(data, entityIdNormalized)
|
const hit = findMatchingValueInData(data, entityIdNormalized)
|
||||||
if (!hit) return null
|
if (!hit) return null
|
||||||
|
|
@ -277,17 +294,15 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
setAuditLoading(true)
|
setAuditLoading(true)
|
||||||
setAuditError(null)
|
setAuditError(null)
|
||||||
try {
|
try {
|
||||||
const response = await apiService.fetchData<PagedResultDto<AuditLogDto>>({
|
const response = await auditLogService.getList({
|
||||||
method: 'GET',
|
skipCount: 0,
|
||||||
url: '/api/app/audit-log',
|
maxResultCount: 200,
|
||||||
params: {
|
sorting: 'ExecutionTime DESC',
|
||||||
skipCount: 0,
|
listFormCode: entityName,
|
||||||
maxResultCount: 200,
|
entityId,
|
||||||
sorting: 'ExecutionTime DESC',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const items = response.data?.items ?? []
|
const items = response?.items ?? []
|
||||||
const filtered = items
|
const filtered = items
|
||||||
.map((log) => ({ log, matchedActions: buildMatchedActions(log) }))
|
.map((log) => ({ log, matchedActions: buildMatchedActions(log) }))
|
||||||
.filter((x) => x.matchedActions.length > 0)
|
.filter((x) => x.matchedActions.length > 0)
|
||||||
|
|
@ -305,13 +320,13 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentTab !== 'audit') return
|
if (!isVisible) return
|
||||||
if (!listFormCodeNormalized && !entityIdNormalized) return
|
if (!listFormCodeNormalized && !entityIdNormalized) return
|
||||||
const key = `${listFormCodeNormalized}|${entityIdNormalized}`
|
const key = `${listFormCodeNormalized}|${entityIdNormalized}`
|
||||||
if (auditLoadedKey === key) return
|
if (auditLoadedKey === key) return
|
||||||
loadAuditLogs()
|
loadAuditLogs()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentTab, listFormCodeNormalized, entityIdNormalized])
|
}, [isVisible, listFormCodeNormalized, entityIdNormalized])
|
||||||
|
|
||||||
const getStatusBadge = (statusCode?: number) => {
|
const getStatusBadge = (statusCode?: number) => {
|
||||||
if (!statusCode) return <Badge className="bg-gray-500" content="?" />
|
if (!statusCode) return <Badge className="bg-gray-500" content="?" />
|
||||||
|
|
@ -330,7 +345,7 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
onChange={(val) => setCurrentTab(val as 'notes' | 'audit')}
|
onChange={(val) => setCurrentTab(val as 'notes' | 'audit')}
|
||||||
variant="underline"
|
variant="underline"
|
||||||
>
|
>
|
||||||
<TabList className="mb-4 border-0 dark:bg-gray-800">
|
<TabList className="mb-2 border-0 dark:bg-gray-800">
|
||||||
<TabNav value="notes">
|
<TabNav value="notes">
|
||||||
{translate('::ListForms.ListForm.Notes')}
|
{translate('::ListForms.ListForm.Notes')}
|
||||||
<Badge className="ml-2 bg-blue-500" content={`${notes?.length ?? 0}`} />
|
<Badge className="ml-2 bg-blue-500" content={`${notes?.length ?? 0}`} />
|
||||||
|
|
@ -341,21 +356,48 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
</TabNav>
|
</TabNav>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabContent value="notes">
|
<div className="mb-2 flex min-h-10 items-center justify-end border-y border-gray-200 bg-gray-50 px-1 py-1 dark:border-gray-700 dark:bg-gray-900">
|
||||||
<div className="flex items-center justify-end mb-2">
|
{currentTab === 'notes' ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="xs"
|
||||||
|
icon={<FaPlus />}
|
||||||
|
type="button"
|
||||||
|
onClick={onAddNote}
|
||||||
|
disabled={!onAddNote}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
{translate('::ListForms.ListForm.AddNote')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="default"
|
||||||
|
type="button"
|
||||||
|
icon={<FaSyncAlt />}
|
||||||
|
onClick={onRefreshNotes}
|
||||||
|
disabled={!onRefreshNotes}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
{translate('::ListForms.ListForm.Refresh')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
size="xs"
|
||||||
variant="default"
|
variant="default"
|
||||||
size="sm"
|
|
||||||
icon={<FaPlus className="mr-1" />}
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onAddNote}
|
icon={<FaSyncAlt />}
|
||||||
disabled={!onAddNote}
|
onClick={loadAuditLogs}
|
||||||
|
disabled={auditLoading}
|
||||||
className="flex items-center"
|
className="flex items-center"
|
||||||
>
|
>
|
||||||
{translate('::ListForms.ListForm.AddNote')}
|
{translate('::ListForms.ListForm.Refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TabContent value="notes">
|
||||||
{(notes?.length ?? 0) === 0 ? (
|
{(notes?.length ?? 0) === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-32 text-gray-500">
|
<div className="flex flex-col items-center justify-center h-32 text-gray-500">
|
||||||
<FaStickyNote className="text-4xl mb-2 opacity-50" />
|
<FaStickyNote className="text-4xl mb-2 opacity-50" />
|
||||||
|
|
@ -398,7 +440,7 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sil butonu */}
|
{/* Sil butonu */}
|
||||||
{user?.id === note.creatorId && (
|
{user?.id === note.creatorId && note.type !== 'workflow' && (
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -449,20 +491,6 @@ export const NoteList: React.FC<NoteListProps> = ({
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|
||||||
<TabContent value="audit">
|
<TabContent value="audit">
|
||||||
<div className="flex items-center justify-end mb-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="default"
|
|
||||||
type="button"
|
|
||||||
icon={<FaSyncAlt className="mr-1" />}
|
|
||||||
onClick={loadAuditLogs}
|
|
||||||
disabled={auditLoading}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
{translate('::ListForms.ListForm.Refresh')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{auditLoading ? (
|
{auditLoading ? (
|
||||||
<div className="flex items-center justify-center py-10">
|
<div className="flex items-center justify-center py-10">
|
||||||
<Spinner size={32} />
|
<Spinner size={32} />
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ function NoteModalContent({
|
||||||
const types = [
|
const types = [
|
||||||
{ value: 'note', label: translate('::ListForms.ListForm.NoteModal.Type.Note') },
|
{ value: 'note', label: translate('::ListForms.ListForm.NoteModal.Type.Note') },
|
||||||
{ value: 'message', label: translate('::ListForms.ListForm.NoteModal.Type.Message') },
|
{ value: 'message', label: translate('::ListForms.ListForm.NoteModal.Type.Message') },
|
||||||
{ value: 'activity', label: translate('::ListForms.ListForm.NoteModal.Type.Activity') },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const handleSave = async (values: any) => {
|
const handleSave = async (values: any) => {
|
||||||
|
|
@ -92,7 +91,7 @@ function NoteModalContent({
|
||||||
{/* Başlık */}
|
{/* Başlık */}
|
||||||
<div className="flex items-center justify-between mb-5 flex-shrink-0">
|
<div className="flex items-center justify-between mb-5 flex-shrink-0">
|
||||||
<h3 className="text-xl font-semibold flex items-center gap-3">
|
<h3 className="text-xl font-semibold flex items-center gap-3">
|
||||||
<div className="p-2 bg-purple-100 rounded-full">
|
<div className="p-1 bg-purple-100 rounded-full">
|
||||||
<FaPlus className="text-purple-600 text-lg" />
|
<FaPlus className="text-purple-600 text-lg" />
|
||||||
</div>
|
</div>
|
||||||
{translate('::ListForms.ListForm.AddNote')}
|
{translate('::ListForms.ListForm.AddNote')}
|
||||||
|
|
@ -116,7 +115,7 @@ function NoteModalContent({
|
||||||
{types.map((t) => (
|
{types.map((t) => (
|
||||||
<label
|
<label
|
||||||
key={t.value}
|
key={t.value}
|
||||||
className="flex items-center gap-2 px-2 py-1 text-black rounded-md cursor-pointer transition-all duration-200 dark:bg-gray-800 dark:text-gray-300"
|
className="flex items-center gap-2 px-1 py-1 text-black rounded-md cursor-pointer transition-all duration-200 dark:bg-gray-800 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
<Radio
|
<Radio
|
||||||
value={t.value}
|
value={t.value}
|
||||||
|
|
@ -139,8 +138,9 @@ function NoteModalContent({
|
||||||
<Field
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
name="subject"
|
name="subject"
|
||||||
as={Input}
|
|
||||||
placeholder={translate('::ListForms.ListForm.NoteModal.Subject')}
|
placeholder={translate('::ListForms.ListForm.NoteModal.Subject')}
|
||||||
|
component={Input}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|
@ -198,7 +198,7 @@ function NoteModalContent({
|
||||||
|
|
||||||
{/* DOSYA YÜKLEME */}
|
{/* DOSYA YÜKLEME */}
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg p-3 text-center hover:border-purple-400 transition-colors duration-200">
|
<div className="border-2 border-dashed border-gray-300 rounded-lg p-2 text-center hover:border-purple-400 transition-colors duration-200">
|
||||||
<Upload
|
<Upload
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
showList={false}
|
showList={false}
|
||||||
|
|
@ -255,7 +255,7 @@ function NoteModalContent({
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
|
|
||||||
{/* ALT BUTONLAR */}
|
{/* ALT BUTONLAR */}
|
||||||
<Dialog.Footer className="mt-5 flex justify-between items-center pt-4 border-t border-gray-200">
|
<Dialog.Footer className="mt-2 flex justify-between items-center pt-4 border-t border-gray-200">
|
||||||
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
<Button variant="default" size="md" onClick={onClose} disabled={uploading}>
|
||||||
{translate('::Cancel')}
|
{translate('::Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isVisible) fetchActivities()
|
if (isVisible) fetchActivities()
|
||||||
}, [isVisible])
|
}, [isVisible, entityName, entityId])
|
||||||
|
|
||||||
const handleDownloadFile = async (fileData: any) => {
|
const handleDownloadFile = async (fileData: any) => {
|
||||||
if (!fileData?.SavedFileName) return
|
if (!fileData?.SavedFileName) return
|
||||||
|
|
@ -62,8 +62,6 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTotalCount = () => activities.length
|
|
||||||
|
|
||||||
// Draggable button handlers
|
// Draggable button handlers
|
||||||
const handleMouseDown = (e: React.MouseEvent) => {
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
if (!buttonRef.current) return
|
if (!buttonRef.current) return
|
||||||
|
|
@ -134,7 +132,6 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
|
{isVisible ? <FaChevronRight /> : <FaChevronLeft />}
|
||||||
{getTotalCount() > 0 && <Badge content={getTotalCount()} />}
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -187,7 +184,9 @@ export const NotePanel: React.FC<NotePanelProps> = ({
|
||||||
notes={activities}
|
notes={activities}
|
||||||
entityName={entityName}
|
entityName={entityName}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
isVisible={isVisible}
|
||||||
onAddNote={() => setShowAddModal(true)}
|
onAddNote={() => setShowAddModal(true)}
|
||||||
|
onRefreshNotes={fetchActivities}
|
||||||
onDeleteNote={handleDeleteActivity}
|
onDeleteNote={handleDeleteActivity}
|
||||||
onDownloadFile={handleDownloadFile}
|
onDownloadFile={handleDownloadFile}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ const IntranetDashboard: React.FC = () => {
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
style={{
|
style={{
|
||||||
touchAction: 'none',
|
touchAction: isDesignMode ? 'none' : 'pan-y',
|
||||||
transition:
|
transition:
|
||||||
'border-color 0.3s ease-out, opacity 0.3s ease-out, box-shadow 0.3s ease-out',
|
'border-color 0.3s ease-out, opacity 0.3s ease-out, box-shadow 0.3s ease-out',
|
||||||
willChange: isDragging ? 'opacity' : 'auto',
|
willChange: isDragging ? 'opacity' : 'auto',
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ import { useListFormColumns } from './useListFormColumns'
|
||||||
import { Loading } from '@/components/shared'
|
import { Loading } from '@/components/shared'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
import { workflowService } from '@/services/workflow.service'
|
import { workflowService } from '@/services/workflow.service'
|
||||||
|
import { NotePanel } from '../form/notes/NotePanel'
|
||||||
|
|
||||||
interface GridProps {
|
interface GridProps {
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -244,7 +245,7 @@ const Grid = (props: GridProps) => {
|
||||||
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const { smaller } = useResponsive()
|
const { smaller } = useResponsive()
|
||||||
const config = useStoreState((state) => state.abpConfig.config)
|
const currentUser = useStoreState((state) => state.auth.user)
|
||||||
|
|
||||||
const gridRef = useRef<DataGridRef>()
|
const gridRef = useRef<DataGridRef>()
|
||||||
const refListFormCode = useRef('')
|
const refListFormCode = useRef('')
|
||||||
|
|
@ -262,6 +263,10 @@ const Grid = (props: GridProps) => {
|
||||||
const [gridDto, setGridDto] = useState<GridDto>()
|
const [gridDto, setGridDto] = useState<GridDto>()
|
||||||
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
||||||
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
|
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
|
||||||
|
const [notePanelTarget, setNotePanelTarget] = useState<{
|
||||||
|
entityName: string
|
||||||
|
entityId: string
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
type EditorOptionsWithButtons = {
|
type EditorOptionsWithButtons = {
|
||||||
buttons?: any[]
|
buttons?: any[]
|
||||||
|
|
@ -338,11 +343,29 @@ const Grid = (props: GridProps) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
|
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
|
||||||
|
|
||||||
|
const openNotePanel = useCallback(
|
||||||
|
(rowData: Record<string, any>) => {
|
||||||
|
const keyFieldName = gridDto?.gridOptions.keyFieldName
|
||||||
|
const entityId = getValueByField(rowData, keyFieldName)
|
||||||
|
if (entityId === undefined || entityId === null || entityId === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotePanelTarget({
|
||||||
|
entityName: gridDto?.gridOptions.listFormCode ?? listFormCode,
|
||||||
|
entityId: String(entityId),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[gridDto, listFormCode],
|
||||||
|
)
|
||||||
|
|
||||||
const { getBandedColumns } = useListFormColumns({
|
const { getBandedColumns } = useListFormColumns({
|
||||||
gridDto,
|
gridDto,
|
||||||
listFormCode,
|
listFormCode,
|
||||||
isSubForm,
|
isSubForm,
|
||||||
gridRef,
|
gridRef,
|
||||||
|
onShowNote: openNotePanel,
|
||||||
})
|
})
|
||||||
|
|
||||||
const getSelectedRowKeys = useCallback(async () => {
|
const getSelectedRowKeys = useCallback(async () => {
|
||||||
|
|
@ -374,10 +397,10 @@ const Grid = (props: GridProps) => {
|
||||||
grd,
|
grd,
|
||||||
gridDto?.gridOptions.workflowDto,
|
gridDto?.gridOptions.workflowDto,
|
||||||
selectedRowsData ?? grd.getSelectedRowsData(),
|
selectedRowsData ?? grd.getSelectedRowsData(),
|
||||||
config?.currentUser,
|
currentUser,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[config?.currentUser, gridDto],
|
[currentUser, gridDto],
|
||||||
)
|
)
|
||||||
|
|
||||||
const refreshData = useCallback(() => {
|
const refreshData = useCallback(() => {
|
||||||
|
|
@ -997,7 +1020,7 @@ const Grid = (props: GridProps) => {
|
||||||
|
|
||||||
// Kolonları oluştur - dil değiştiğinde güncelle
|
// Kolonları oluştur - dil değiştiğinde güncelle
|
||||||
const memoizedColumns = useMemo(() => {
|
const memoizedColumns = useMemo(() => {
|
||||||
if (!gridDto || !config) return undefined
|
if (!gridDto) return undefined
|
||||||
|
|
||||||
const cols = getBandedColumns()
|
const cols = getBandedColumns()
|
||||||
|
|
||||||
|
|
@ -1038,7 +1061,7 @@ const Grid = (props: GridProps) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
return cols
|
return cols
|
||||||
}, [gridDto, config])
|
}, [gridDto])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setColumnData(memoizedColumns)
|
setColumnData(memoizedColumns)
|
||||||
|
|
@ -1451,7 +1474,27 @@ const Grid = (props: GridProps) => {
|
||||||
) {
|
) {
|
||||||
workflowService
|
workflowService
|
||||||
.startWorkflow(listFormCode, [insertedKey])
|
.startWorkflow(listFormCode, [insertedKey])
|
||||||
.then(() => gridRef.current?.instance()?.refresh())
|
.then((result) => {
|
||||||
|
const messages = result.toastMessages ?? []
|
||||||
|
if (messages.length > 0) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="info" duration={7000}>
|
||||||
|
{messages.map((message, messageIndex) => (
|
||||||
|
<div
|
||||||
|
key={messageIndex}
|
||||||
|
className={messageIndex > 0 ? 'mt-2 border-t pt-2' : undefined}
|
||||||
|
>
|
||||||
|
{message.split('\n').map((line, lineIndex) => (
|
||||||
|
<div key={lineIndex}>{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
gridRef.current?.instance()?.refresh()
|
||||||
|
})
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
props.refreshData?.()
|
props.refreshData?.()
|
||||||
|
|
@ -1874,6 +1917,14 @@ const Grid = (props: GridProps) => {
|
||||||
{toolbarModalData?.content}
|
{toolbarModalData?.content}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<GridFilterDialogs gridRef={gridRef} listFormCode={listFormCode} {...filterData} />
|
<GridFilterDialogs gridRef={gridRef} listFormCode={listFormCode} {...filterData} />
|
||||||
|
{notePanelTarget && (
|
||||||
|
<NotePanel
|
||||||
|
entityName={notePanelTarget.entityName}
|
||||||
|
entityId={notePanelTarget.entityId}
|
||||||
|
isVisible
|
||||||
|
onToggle={() => setNotePanelTarget(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ import { useStoreState } from '@/store/store'
|
||||||
import SubForms from '../form/SubForms'
|
import SubForms from '../form/SubForms'
|
||||||
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
||||||
import { workflowService } from '@/services/workflow.service'
|
import { workflowService } from '@/services/workflow.service'
|
||||||
|
import { NotePanel } from '../form/notes/NotePanel'
|
||||||
|
|
||||||
interface TreeProps {
|
interface TreeProps {
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -232,6 +233,7 @@ const Tree = (props: TreeProps) => {
|
||||||
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const { smaller } = useResponsive()
|
const { smaller } = useResponsive()
|
||||||
|
const currentUser = useStoreState((state) => state.auth.user)
|
||||||
|
|
||||||
const gridRef = useRef<TreeListRef>()
|
const gridRef = useRef<TreeListRef>()
|
||||||
const refListFormCode = useRef('')
|
const refListFormCode = useRef('')
|
||||||
|
|
@ -249,9 +251,12 @@ const Tree = (props: TreeProps) => {
|
||||||
const [gridDto, setGridDto] = useState<GridDto>()
|
const [gridDto, setGridDto] = useState<GridDto>()
|
||||||
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
const [isPopupFullScreen, setIsPopupFullScreen] = useState(false)
|
||||||
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
|
const [widgetGroupHeight, setWidgetGroupHeight] = useState(0)
|
||||||
|
const [notePanelTarget, setNotePanelTarget] = useState<{
|
||||||
|
entityName: string
|
||||||
|
entityId: string
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
const [expandedRowKeys, setExpandedRowKeys] = useState<any[]>([])
|
const [expandedRowKeys, setExpandedRowKeys] = useState<any[]>([])
|
||||||
const config = useStoreState((state) => state.abpConfig.config)
|
|
||||||
|
|
||||||
type EditorOptionsWithButtons = {
|
type EditorOptionsWithButtons = {
|
||||||
buttons?: any[]
|
buttons?: any[]
|
||||||
|
|
@ -343,11 +348,29 @@ const Tree = (props: TreeProps) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
|
const { createSelectDataSource } = useListFormCustomDataSource({ gridRef })
|
||||||
|
|
||||||
|
const openNotePanel = useCallback(
|
||||||
|
(rowData: Record<string, any>) => {
|
||||||
|
const keyFieldName = gridDto?.gridOptions.keyFieldName
|
||||||
|
const entityId = getValueByField(rowData, keyFieldName)
|
||||||
|
if (entityId === undefined || entityId === null || entityId === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotePanelTarget({
|
||||||
|
entityName: gridDto?.gridOptions.listFormCode ?? listFormCode,
|
||||||
|
entityId: String(entityId),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[gridDto, listFormCode],
|
||||||
|
)
|
||||||
|
|
||||||
const { getBandedColumns } = useListFormColumns({
|
const { getBandedColumns } = useListFormColumns({
|
||||||
gridDto,
|
gridDto,
|
||||||
listFormCode,
|
listFormCode,
|
||||||
isSubForm,
|
isSubForm,
|
||||||
gridRef,
|
gridRef,
|
||||||
|
onShowNote: openNotePanel,
|
||||||
})
|
})
|
||||||
|
|
||||||
const extractSearchParamsFields = useCallback((filter: any): [string, string, any][] => {
|
const extractSearchParamsFields = useCallback((filter: any): [string, string, any][] => {
|
||||||
|
|
@ -409,10 +432,10 @@ const Tree = (props: TreeProps) => {
|
||||||
tree,
|
tree,
|
||||||
gridDto?.gridOptions.workflowDto,
|
gridDto?.gridOptions.workflowDto,
|
||||||
selectedRowsData ?? tree.getSelectedRowsData(),
|
selectedRowsData ?? tree.getSelectedRowsData(),
|
||||||
config?.currentUser,
|
currentUser,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[config?.currentUser, gridDto],
|
[currentUser, gridDto],
|
||||||
)
|
)
|
||||||
|
|
||||||
const refreshData = useCallback(() => {
|
const refreshData = useCallback(() => {
|
||||||
|
|
@ -900,7 +923,7 @@ const Tree = (props: TreeProps) => {
|
||||||
}, [gridDto])
|
}, [gridDto])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gridDto || !config) return
|
if (!gridDto) return
|
||||||
|
|
||||||
const cols = getBandedColumns()
|
const cols = getBandedColumns()
|
||||||
setColumnData(cols)
|
setColumnData(cols)
|
||||||
|
|
@ -913,7 +936,7 @@ const Tree = (props: TreeProps) => {
|
||||||
cols,
|
cols,
|
||||||
)
|
)
|
||||||
setTreeListDataSource(dataSource)
|
setTreeListDataSource(dataSource)
|
||||||
}, [gridDto, searchParams, config])
|
}, [gridDto, searchParams])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const activeFilters = extraFilters.filter((f) => f.value)
|
const activeFilters = extraFilters.filter((f) => f.value)
|
||||||
|
|
@ -1110,7 +1133,27 @@ const Tree = (props: TreeProps) => {
|
||||||
) {
|
) {
|
||||||
workflowService
|
workflowService
|
||||||
.startWorkflow(listFormCode, [insertedKey])
|
.startWorkflow(listFormCode, [insertedKey])
|
||||||
.then(() => gridRef.current?.instance()?.refresh())
|
.then((result) => {
|
||||||
|
const messages = result.toastMessages ?? []
|
||||||
|
if (messages.length > 0) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="info" duration={7000}>
|
||||||
|
{messages.map((message, messageIndex) => (
|
||||||
|
<div
|
||||||
|
key={messageIndex}
|
||||||
|
className={messageIndex > 0 ? 'mt-2 border-t pt-2' : undefined}
|
||||||
|
>
|
||||||
|
{message.split('\n').map((line, lineIndex) => (
|
||||||
|
<div key={lineIndex}>{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
gridRef.current?.instance()?.refresh()
|
||||||
|
})
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
props.refreshData?.()
|
props.refreshData?.()
|
||||||
|
|
@ -1613,6 +1656,14 @@ const Tree = (props: TreeProps) => {
|
||||||
{toolbarModalData?.content}
|
{toolbarModalData?.content}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<GridFilterDialogs gridRef={gridRef as any} listFormCode={listFormCode} {...filterData} />
|
<GridFilterDialogs gridRef={gridRef as any} listFormCode={listFormCode} {...filterData} />
|
||||||
|
{notePanelTarget && (
|
||||||
|
<NotePanel
|
||||||
|
entityName={notePanelTarget.entityName}
|
||||||
|
entityId={notePanelTarget.entityId}
|
||||||
|
isVisible
|
||||||
|
onToggle={() => setNotePanelTarget(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -247,11 +247,13 @@ const useListFormColumns = ({
|
||||||
listFormCode,
|
listFormCode,
|
||||||
isSubForm,
|
isSubForm,
|
||||||
gridRef,
|
gridRef,
|
||||||
|
onShowNote,
|
||||||
}: {
|
}: {
|
||||||
gridDto?: GridDto
|
gridDto?: GridDto
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
isSubForm?: boolean
|
isSubForm?: boolean
|
||||||
gridRef?: any
|
gridRef?: any
|
||||||
|
onShowNote?: (rowData: Record<string, any>) => void
|
||||||
}) => {
|
}) => {
|
||||||
const dialog: any = useDialogContext()
|
const dialog: any = useDialogContext()
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
@ -435,6 +437,11 @@ const useListFormColumns = ({
|
||||||
gridDto.gridOptions.editingOptionDto.allowDetail &&
|
gridDto.gridOptions.editingOptionDto.allowDetail &&
|
||||||
checkPermission(gridDto.gridOptions.permissionDto.u)
|
checkPermission(gridDto.gridOptions.permissionDto.u)
|
||||||
|
|
||||||
|
const hasShowNote =
|
||||||
|
gridDto.gridOptions.showNote &&
|
||||||
|
checkPermission(gridDto.gridOptions.permissionDto.n) &&
|
||||||
|
typeof onShowNote === 'function'
|
||||||
|
|
||||||
const hasDuplicate =
|
const hasDuplicate =
|
||||||
gridDto.gridOptions.editingOptionDto.allowDuplicate &&
|
gridDto.gridOptions.editingOptionDto.allowDuplicate &&
|
||||||
checkPermission(gridDto.gridOptions.permissionDto.i)
|
checkPermission(gridDto.gridOptions.permissionDto.i)
|
||||||
|
|
@ -442,7 +449,15 @@ const useListFormColumns = ({
|
||||||
const hasCommandButtons = gridDto.gridOptions.commandColumnDto.length > 0
|
const hasCommandButtons = gridDto.gridOptions.commandColumnDto.length > 0
|
||||||
|
|
||||||
// Eğer hiçbir buton eklenecek durumda değilse: çık
|
// Eğer hiçbir buton eklenecek durumda değilse: çık
|
||||||
if (!hasUpdate && !hasDelete && !hasCreate && !hasCommandButtons) {
|
if (
|
||||||
|
!hasUpdate &&
|
||||||
|
!hasDelete &&
|
||||||
|
!hasCreate &&
|
||||||
|
!hasDetail &&
|
||||||
|
!hasShowNote &&
|
||||||
|
!hasDuplicate &&
|
||||||
|
!hasCommandButtons
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -485,6 +500,20 @@ const useListFormColumns = ({
|
||||||
buttons.push(item)
|
buttons.push(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasShowNote) {
|
||||||
|
buttons.push({
|
||||||
|
name: 'note',
|
||||||
|
text: translate('::ListForms.ListForm.NoteModal.Type.Note'),
|
||||||
|
onClick: (e: any) => {
|
||||||
|
if (typeof e.event?.preventDefault === 'function') {
|
||||||
|
e.event.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowNote?.(e.row.data)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDuplicate) {
|
if (hasDuplicate) {
|
||||||
const item = {
|
const item = {
|
||||||
name: 'duplicate',
|
name: 'duplicate',
|
||||||
|
|
@ -600,7 +629,7 @@ const useListFormColumns = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return column as GridColumnData
|
return column as GridColumnData
|
||||||
}, [gridDto, checkPermission, translate, listFormCode, isPwaMode, dialog, gridRef])
|
}, [gridDto, checkPermission, translate, listFormCode, isPwaMode, dialog, gridRef, onShowNote])
|
||||||
|
|
||||||
const getColumns = useCallback(
|
const getColumns = useCallback(
|
||||||
(columnFormats: ColumnFormatDto[]) => {
|
(columnFormats: ColumnFormatDto[]) => {
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,35 @@ import { usePWA } from '@/utils/hooks/usePWA'
|
||||||
import { layoutTypes, ListViewLayoutType } from '../admin/listForm/edit/types'
|
import { layoutTypes, ListViewLayoutType } from '../admin/listForm/edit/types'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
import { workflowService } from '@/services/workflow.service'
|
import { workflowService } from '@/services/workflow.service'
|
||||||
|
import type { WorkflowRunResultDto } from '@/services/workflow.service'
|
||||||
|
|
||||||
type ToolbarModalData = {
|
type ToolbarModalData = {
|
||||||
open: boolean
|
open: boolean
|
||||||
content?: JSX.Element
|
content?: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showWorkflowToastMessages = (results: WorkflowRunResultDto | WorkflowRunResultDto[]) => {
|
||||||
|
const list = Array.isArray(results) ? results : [results]
|
||||||
|
const messages = list.flatMap((result) => result.toastMessages ?? [])
|
||||||
|
|
||||||
|
if (!messages.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.push(
|
||||||
|
<Notification type="info" duration={7000}>
|
||||||
|
{messages.map((message, messageIndex) => (
|
||||||
|
<div key={messageIndex} className={messageIndex > 0 ? 'mt-2 border-t pt-2' : undefined}>
|
||||||
|
{message.split('\n').map((line, lineIndex) => (
|
||||||
|
<div key={lineIndex}>{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// https://js.devexpress.com/Documentation/ApiReference/UI_Components/dxDataGrid/Configuration/toolbar/
|
// https://js.devexpress.com/Documentation/ApiReference/UI_Components/dxDataGrid/Configuration/toolbar/
|
||||||
// item.name > Accepted Values: 'addRowButton', 'applyFilterButton', 'columnChooserButton', 'exportButton', 'groupPanel', 'revertButton', 'saveButton', 'searchPanel'
|
// item.name > Accepted Values: 'addRowButton', 'applyFilterButton', 'columnChooserButton', 'exportButton', 'groupPanel', 'revertButton', 'saveButton', 'searchPanel'
|
||||||
const useToolbar = ({
|
const useToolbar = ({
|
||||||
|
|
@ -32,7 +55,7 @@ const useToolbar = ({
|
||||||
}: {
|
}: {
|
||||||
gridDto?: GridDto
|
gridDto?: GridDto
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
getSelectedRowKeys: () => void
|
getSelectedRowKeys: () => unknown[] | Promise<unknown[]>
|
||||||
getSelectedRowsData: () => any
|
getSelectedRowsData: () => any
|
||||||
refreshData: () => void
|
refreshData: () => void
|
||||||
getFilter: () => void
|
getFilter: () => void
|
||||||
|
|
@ -48,7 +71,7 @@ const useToolbar = ({
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const { checkPermission } = usePermission()
|
const { checkPermission } = usePermission()
|
||||||
const isPwaMode = usePWA()
|
const isPwaMode = usePWA()
|
||||||
const config = useStoreState((state) => state.abpConfig.config)
|
const currentUser = useStoreState((state) => state.auth.user)
|
||||||
|
|
||||||
const [toolbarData, setToolbarData] = useState<ToolbarItem[]>([])
|
const [toolbarData, setToolbarData] = useState<ToolbarItem[]>([])
|
||||||
const [toolbarModalData, setToolbarModalData] = useState<ToolbarModalData>()
|
const [toolbarModalData, setToolbarModalData] = useState<ToolbarModalData>()
|
||||||
|
|
@ -113,7 +136,8 @@ const useToolbar = ({
|
||||||
})
|
})
|
||||||
|
|
||||||
const workflowOptions = grdOpt.workflowDto
|
const workflowOptions = grdOpt.workflowDto
|
||||||
const approvalCriteria = workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? []
|
const approvalCriteria =
|
||||||
|
workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? []
|
||||||
if (
|
if (
|
||||||
workflowOptions?.approvalStatusFieldName &&
|
workflowOptions?.approvalStatusFieldName &&
|
||||||
approvalCriteria.length > 0 &&
|
approvalCriteria.length > 0 &&
|
||||||
|
|
@ -149,7 +173,7 @@ const useToolbar = ({
|
||||||
) {
|
) {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="warning" duration={2500}>
|
<Notification type="warning" duration={2500}>
|
||||||
Secili kayit icin workflow zaten baslamis.
|
{translate('::WorkflowAlreadyStarted')}
|
||||||
</Notification>,
|
</Notification>,
|
||||||
{ placement: 'top-end' },
|
{ placement: 'top-end' },
|
||||||
)
|
)
|
||||||
|
|
@ -157,7 +181,8 @@ const useToolbar = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await workflowService.startWorkflow(listFormCode, keys)
|
const result = await workflowService.startWorkflow(listFormCode, keys)
|
||||||
|
showWorkflowToastMessages(result)
|
||||||
refreshData()
|
refreshData()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -204,14 +229,14 @@ const useToolbar = ({
|
||||||
row,
|
row,
|
||||||
workflowOptions,
|
workflowOptions,
|
||||||
criteria.title,
|
criteria.title,
|
||||||
getCurrentUserWorkflowIdentities(config?.currentUser),
|
getCurrentUserWorkflowIdentities(currentUser),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (activeRows.length !== selectedRows.length) {
|
if (activeRows.length !== selectedRows.length) {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="warning" duration={2500}>
|
<Notification type="warning" duration={2500}>
|
||||||
Secili kayit bu onay adiminda veya onay kullanicisinda beklemiyor.
|
{translate('::SeciliKayitBekliyor')}
|
||||||
</Notification>,
|
</Notification>,
|
||||||
{ placement: 'top-end' },
|
{ placement: 'top-end' },
|
||||||
)
|
)
|
||||||
|
|
@ -302,16 +327,75 @@ const useToolbar = ({
|
||||||
text: translate('::ListForms.ListForm.DeleteSelectedRecords'),
|
text: translate('::ListForms.ListForm.DeleteSelectedRecords'),
|
||||||
icon: 'trash',
|
icon: 'trash',
|
||||||
visible: false,
|
visible: false,
|
||||||
onClick() {
|
async onClick() {
|
||||||
if (!grdOpt.deleteServiceAddress) {
|
if (!grdOpt.deleteServiceAddress) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamicFetch(grdOpt.deleteServiceAddress, 'POST', null, {
|
const selectedKeys = await Promise.resolve(getSelectedRowKeys())
|
||||||
keys: getSelectedRowKeys(),
|
const keys = Array.isArray(selectedKeys) ? [...selectedKeys] : []
|
||||||
listFormCode,
|
|
||||||
}).then(() => {
|
if (!keys.length) {
|
||||||
refreshData()
|
toast.push(
|
||||||
|
<Notification type="warning" duration={2000}>
|
||||||
|
{translate('::ListForms.ListForm.SelectRecord')}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setToolbarModalData({
|
||||||
|
open: true,
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<h5 className="mb-4">
|
||||||
|
{translate('::ListForms.ListForm.DeleteSelectedRecords')}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{translate('::SeciliKayitlarSilmekIstiyormusunuz', {
|
||||||
|
0: keys.length,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="text-right mt-6">
|
||||||
|
<Button
|
||||||
|
className="ltr:mr-2 rtl:ml-2"
|
||||||
|
variant="plain"
|
||||||
|
onClick={() => setToolbarModalData(undefined)}
|
||||||
|
>
|
||||||
|
{translate('::Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
onClick={() => {
|
||||||
|
dynamicFetch(grdOpt.deleteServiceAddress!, 'POST', null, {
|
||||||
|
keys,
|
||||||
|
listFormCode,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
refreshData()
|
||||||
|
setToolbarModalData(undefined)
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger" duration={3000}>
|
||||||
|
{error?.response?.data?.error?.message ||
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error?.message ||
|
||||||
|
translate('::SilmeIslemiBasarisiz')}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{translate('::Delete')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -342,9 +426,13 @@ const useToolbar = ({
|
||||||
open: true,
|
open: true,
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<h5 className="mb-4">Delete All Records</h5>
|
<h5 className="mb-4">{translate('::ListForms.ListForm.DeleteAllRecords')}</h5>
|
||||||
|
|
||||||
<p>Are you sure to delete all {r.data.totalCount} records?</p>
|
<p>
|
||||||
|
{translate('::TumKayitlariSilmekIstiyormusunuz', {
|
||||||
|
0: r.data.totalCount,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="text-right mt-6">
|
<div className="text-right mt-6">
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -362,7 +450,7 @@ const useToolbar = ({
|
||||||
dynamicFetch('list-form-select/select', 'GET', parameters).then(() => {
|
dynamicFetch('list-form-select/select', 'GET', parameters).then(() => {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification type="success" duration={2000}>
|
<Notification type="success" duration={2000}>
|
||||||
{'Tüm kayıtlar silindi.'}
|
{translate('::TumKayitlarSilindi')}
|
||||||
</Notification>,
|
</Notification>,
|
||||||
{
|
{
|
||||||
placement: 'top-end',
|
placement: 'top-end',
|
||||||
|
|
@ -483,10 +571,9 @@ const useToolbar = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gridDto && !listFormCode) return
|
if (!gridDto && !listFormCode) return
|
||||||
if (!config) return
|
|
||||||
|
|
||||||
getToolbarData()
|
getToolbarData()
|
||||||
}, [gridDto, listFormCode, config])
|
}, [gridDto, listFormCode, currentUser])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toolbarData,
|
toolbarData,
|
||||||
|
|
@ -522,7 +609,9 @@ function isWorkflowApprovalCriteriaActive(
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeWorkflowValue(value: unknown) {
|
function normalizeWorkflowValue(value: unknown) {
|
||||||
return String(value ?? '').trim().toLocaleLowerCase('tr-TR')
|
return String(value ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase('tr-TR')
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWorkflowNotStarted(row: Record<string, unknown>, workflowOptions: WorkflowDto) {
|
function isWorkflowNotStarted(row: Record<string, unknown>, workflowOptions: WorkflowDto) {
|
||||||
|
|
@ -547,7 +636,8 @@ export function updateWorkflowApprovalToolbarItems(
|
||||||
name?: string
|
name?: string
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const approvalCriteria = workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? []
|
const approvalCriteria =
|
||||||
|
workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? []
|
||||||
if (!component || !workflowOptions?.approvalStatusFieldName || !approvalCriteria.length) {
|
if (!component || !workflowOptions?.approvalStatusFieldName || !approvalCriteria.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -586,7 +676,12 @@ export function updateWorkflowApprovalToolbarItems(
|
||||||
const enabled =
|
const enabled =
|
||||||
selectedRowsData.length > 0 &&
|
selectedRowsData.length > 0 &&
|
||||||
selectedRowsData.every((row) =>
|
selectedRowsData.every((row) =>
|
||||||
isWorkflowApprovalCriteriaActive(row, workflowOptions, criteria.title, currentUserIdentities),
|
isWorkflowApprovalCriteriaActive(
|
||||||
|
row,
|
||||||
|
workflowOptions,
|
||||||
|
criteria.title,
|
||||||
|
currentUserIdentities,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const optionPath = `toolbar.items[${toolbarItemIndex}].options.disabled`
|
const optionPath = `toolbar.items[${toolbarItemIndex}].options.disabled`
|
||||||
|
|
@ -619,17 +714,12 @@ function WorkflowApprovalDecisionDialog({
|
||||||
const decide = async (approved: boolean) => {
|
const decide = async (approved: boolean) => {
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
const results = await Promise.all(
|
||||||
keys.map((key) =>
|
keys.map((key) =>
|
||||||
workflowService.decideWorkflow(
|
workflowService.decideWorkflow(listFormCode, [key], approved, note, criteriaId),
|
||||||
listFormCode,
|
|
||||||
[key],
|
|
||||||
approved,
|
|
||||||
note,
|
|
||||||
criteriaId,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
showWorkflowToastMessages(results)
|
||||||
onCompleted()
|
onCompleted()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.push(
|
toast.push(
|
||||||
|
|
@ -649,24 +739,33 @@ function WorkflowApprovalDecisionDialog({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h5 className="mb-4">{criteriaTitle}</h5>
|
<h5 className="mb-4">{criteriaTitle}</h5>
|
||||||
<p>{keys.length} kayit icin workflow karari verilecek.</p>
|
<p className="mb-4">
|
||||||
<label className="mb-2 block font-semibold">Not</label>
|
{translate('::App.Listform.ListformField.WorkflowDecisionMessage', {
|
||||||
|
0: keys.length,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
<textarea
|
<textarea
|
||||||
className="input input-textarea mb-4 min-h-[96px] w-full resize-y"
|
className="input input-textarea mb-4 min-h-[96px] w-full resize-y"
|
||||||
rows={4}
|
rows={4}
|
||||||
value={note}
|
value={note}
|
||||||
placeholder="Onay veya red aciklamasi"
|
autoFocus
|
||||||
|
placeholder={translate('::App.Listform.ListformField.ApprovalComment')}
|
||||||
onChange={(event) => setNote(event.target.value)}
|
onChange={(event) => setNote(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="text-right mt-6">
|
<div className="text-right mt-6">
|
||||||
<Button className="ltr:mr-2 rtl:ml-2" variant="plain" disabled={submitting} onClick={onCancel}>
|
<Button
|
||||||
|
className="ltr:mr-2 rtl:ml-2"
|
||||||
|
variant="plain"
|
||||||
|
disabled={submitting}
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
{translate('::Cancel')}
|
{translate('::Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="ltr:mr-2 rtl:ml-2" disabled={submitting} onClick={() => decide(false)}>
|
<Button className="ltr:mr-2 rtl:ml-2" disabled={submitting} onClick={() => decide(false)}>
|
||||||
Reddet
|
{translate('::App.Listform.ListformField.Rejecter')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="solid" disabled={submitting} onClick={() => decide(true)}>
|
<Button variant="solid" disabled={submitting} onClick={() => decide(true)}>
|
||||||
Onayla
|
{translate('::App.Listform.ListformField.Approver')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,14 @@ export interface MenuAddDialogProps {
|
||||||
initialParentCode: string
|
initialParentCode: string
|
||||||
initialOrder: number
|
initialOrder: number
|
||||||
rawItems: (MenuItem & { id?: string })[]
|
rawItems: (MenuItem & { id?: string })[]
|
||||||
onSaved: () => void
|
onSaved: (menu: {
|
||||||
|
code: string
|
||||||
|
parentCode?: string
|
||||||
|
menuTextEn: string
|
||||||
|
menuTextTr: string
|
||||||
|
icon?: string
|
||||||
|
shortName?: string
|
||||||
|
}) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MenuAddDialog({
|
export function MenuAddDialog({
|
||||||
|
|
@ -172,7 +179,7 @@ export function MenuAddDialog({
|
||||||
if (shortNameRequired && !form.shortName.trim()) return
|
if (shortNameRequired && !form.shortName.trim()) return
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
await menuService.createWithLanguageKeyText({
|
const savedMenu = {
|
||||||
code: form.code.trim(),
|
code: form.code.trim(),
|
||||||
displayName: form.code.trim(),
|
displayName: form.code.trim(),
|
||||||
parentCode: form.parentCode.trim() || undefined,
|
parentCode: form.parentCode.trim() || undefined,
|
||||||
|
|
@ -182,9 +189,18 @@ export function MenuAddDialog({
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
menuTextTr: form.menuTextTr.trim(),
|
menuTextTr: form.menuTextTr.trim(),
|
||||||
menuTextEn: form.menuTextEn.trim(),
|
menuTextEn: form.menuTextEn.trim(),
|
||||||
} as MenuDto)
|
} as MenuDto
|
||||||
|
|
||||||
onSaved()
|
await menuService.createWithLanguageKeyText(savedMenu)
|
||||||
|
|
||||||
|
await onSaved({
|
||||||
|
code: savedMenu.code!,
|
||||||
|
parentCode: savedMenu.parentCode,
|
||||||
|
menuTextEn: savedMenu.menuTextEn!,
|
||||||
|
menuTextTr: savedMenu.menuTextTr!,
|
||||||
|
icon: savedMenu.icon,
|
||||||
|
shortName: savedMenu.shortName,
|
||||||
|
})
|
||||||
onClose()
|
onClose()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.push(<Notification title={e.message} type="danger" />, { placement: 'top-end' })
|
toast.push(<Notification title={e.message} type="danger" />, { placement: 'top-end' })
|
||||||
|
|
@ -342,10 +358,11 @@ export function MenuAddDialog({
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex justify-end gap-2 pt-1 border-t border-gray-100 dark:border-gray-700">
|
<div className="flex justify-end gap-2 pt-1 border-t border-gray-100 dark:border-gray-700">
|
||||||
<Button size="sm" variant="plain" onClick={onClose}>
|
<Button type="button" size="sm" variant="plain" onClick={onClose}>
|
||||||
{translate('::Cancel') || 'İptal'}
|
{translate('::Cancel') || 'İptal'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
loading={saving}
|
loading={saving}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue