Workflow Listlerinin Note Özelliği eklendi
This commit is contained in:
parent
27e65f05f0
commit
c204eef755
20 changed files with 532 additions and 432 deletions
|
|
@ -278,6 +278,7 @@ 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
|
var listForm = await repoListForm.InsertAsync(new ListForm
|
||||||
{
|
{
|
||||||
|
|
@ -285,7 +286,7 @@ public class ListFormWizardAppService(
|
||||||
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,
|
||||||
|
|
@ -423,6 +424,60 @@ public class ListFormWizardAppService(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,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;
|
||||||
|
|
@ -39,6 +40,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
|
|
||||||
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,
|
||||||
|
|
@ -48,6 +50,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
ISettingProvider settingProvider)
|
ISettingProvider settingProvider)
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
@ -188,7 +191,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;
|
||||||
|
|
@ -365,6 +368,7 @@ 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);
|
||||||
|
await LogWorkflowDecisionAsync(context, current, next, input.Approved, input.Note);
|
||||||
return await RunUntilWaitAsync(context, next);
|
return await RunUntilWaitAsync(context, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -512,13 +516,16 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
await UpdateRowAsync(context, update);
|
await UpdateRowAsync(context, update);
|
||||||
MergeRowValues(context.Row, update);
|
MergeRowValues(context.Row, update);
|
||||||
|
|
||||||
|
string informedRecipient = null;
|
||||||
if (node.Kind == "Inform")
|
if (node.Kind == "Inform")
|
||||||
{
|
{
|
||||||
await SendInformEmailAsync(context, node);
|
informedRecipient = await SendInformEmailAsync(context, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await LogWorkflowNodeAsync(context, node, informedRecipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendInformEmailAsync(WorkflowRunContext context, ListFormWorkflow node)
|
private async Task<string> SendInformEmailAsync(WorkflowRunContext context, ListFormWorkflow node)
|
||||||
{
|
{
|
||||||
var recipientEmail = await ResolveApproverEmailAsync(node.Approver);
|
var recipientEmail = await ResolveApproverEmailAsync(node.Approver);
|
||||||
var senderName = await settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName);
|
var senderName = await settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName);
|
||||||
|
|
@ -542,6 +549,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException($"Bilgilendirme maili gonderilemedi: {result.ErrorMessage}");
|
throw new UserFriendlyException($"Bilgilendirme maili gonderilemedi: {result.ErrorMessage}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return recipientEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ResolveApproverEmailAsync(string approver)
|
private async Task<string> ResolveApproverEmailAsync(string approver)
|
||||||
|
|
@ -581,6 +590,149 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LogWorkflowDecisionAsync(
|
||||||
|
WorkflowRunContext context,
|
||||||
|
ListFormWorkflow current,
|
||||||
|
ListFormWorkflow next,
|
||||||
|
bool approved,
|
||||||
|
string description)
|
||||||
|
{
|
||||||
|
var action = approved ? "Approved" : "Rejected";
|
||||||
|
var subject = $"Workflow {action}: {current.Title}";
|
||||||
|
var rows = new List<(string Label, string Value)>();
|
||||||
|
|
||||||
|
rows.Add(("Description", description ?? string.Empty));
|
||||||
|
rows.Add(("Next Step", FormatNode(next)));
|
||||||
|
|
||||||
|
await InsertWorkflowNoteAsync(context, subject, BuildWorkflowNoteContent(rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogWorkflowNodeAsync(
|
||||||
|
WorkflowRunContext context,
|
||||||
|
ListFormWorkflow node,
|
||||||
|
string informedRecipient)
|
||||||
|
{
|
||||||
|
var action = node.Kind switch
|
||||||
|
{
|
||||||
|
"Start" => "Started",
|
||||||
|
"Compare" => "Evaluated",
|
||||||
|
"Approval" => "Waiting Approval",
|
||||||
|
"Inform" => "Informed",
|
||||||
|
"End" => "Completed",
|
||||||
|
_ => "Processed"
|
||||||
|
};
|
||||||
|
|
||||||
|
var subject = $"Workflow {action}: {node.Title}";
|
||||||
|
var rows = new List<(string Label, string Value)>();
|
||||||
|
|
||||||
|
if (!node.Approver.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
rows.Add((node.Kind == "Inform" ? "Inform Target" : "Approver", node.Approver));
|
||||||
|
}
|
||||||
|
if (!informedRecipient.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
rows.Add(("Informed Email", informedRecipient));
|
||||||
|
}
|
||||||
|
|
||||||
|
await InsertWorkflowNoteAsync(context, subject, BuildWorkflowNoteContent(rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 void AddWorkflowFieldRow(
|
||||||
|
List<(string Label, string Value)> rows,
|
||||||
|
WorkflowRunContext context,
|
||||||
|
string label,
|
||||||
|
string fieldName)
|
||||||
|
{
|
||||||
|
if (fieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.Add((label, FormatRowValue(GetRowValue(context.Row, fieldName))));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 string ResolveCurrentUserDisplayName()
|
||||||
|
{
|
||||||
|
return CurrentUser.UserName
|
||||||
|
?? CurrentUser.Name
|
||||||
|
?? CurrentUser.Id?.ToString()
|
||||||
|
?? "System";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 FormatRowValue(object value)
|
||||||
|
{
|
||||||
|
if (value == null || value == DBNull.Value)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value is DateTime dateTime
|
||||||
|
? dateTime.ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
: value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Encode(string value)
|
||||||
|
{
|
||||||
|
return WebUtility.HtmlEncode(value ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Truncate(string value, int maxLength)
|
||||||
|
{
|
||||||
|
if (value == null || value.Length <= maxLength)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value[..maxLength];
|
||||||
|
}
|
||||||
|
|
||||||
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>(
|
||||||
|
|
@ -906,6 +1058,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 ?? []);
|
||||||
|
|
|
||||||
|
|
@ -4154,9 +4154,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",
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,384 +0,0 @@
|
||||||
{
|
|
||||||
"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": "Approval",
|
|
||||||
"LanguageTextDescEn": "Approval",
|
|
||||||
"LanguageTextDescTr": "Approval",
|
|
||||||
"LanguageTextMenuParentEn": "",
|
|
||||||
"LanguageTextMenuParentTr": "",
|
|
||||||
"PermissionGroupName": "App.Wizard.Sales",
|
|
||||||
"MenuParentCode": "App.Wizard.Sales",
|
|
||||||
"MenuParentIcon": "FcAssistant",
|
|
||||||
"MenuIcon": "FcBrokenLink",
|
|
||||||
"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",
|
|
||||||
"ApprovalDateFieldName": "ApprovalDate",
|
|
||||||
"ApprovalStatusFieldName": "ApprovalStatus",
|
|
||||||
"ApprovalDescriptionFieldName": "ApprovalDescription",
|
|
||||||
"Criteria": [
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Start",
|
|
||||||
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Ba\u015Flat",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "",
|
|
||||||
"NextOnStart": "WF1780768059929317",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "",
|
|
||||||
"NextOnReject": "",
|
|
||||||
"PositionX": 53,
|
|
||||||
"PositionY": 42,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768057089996"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Approval",
|
|
||||||
"Title": "Onay1",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "admin@sozsoft.com",
|
|
||||||
"NextOnStart": "",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "WF1780768062793138",
|
|
||||||
"NextOnReject": "WF1780768065817524",
|
|
||||||
"PositionX": 356,
|
|
||||||
"PositionY": 41,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768059929317"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Approval",
|
|
||||||
"Title": "Onay2",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "demo@sozsoft.com",
|
|
||||||
"NextOnStart": "",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "WF1780768065817524",
|
|
||||||
"NextOnReject": "WF1780768065817524",
|
|
||||||
"PositionX": 632,
|
|
||||||
"PositionY": 38,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768062793138"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Inform",
|
|
||||||
"Title": "Bilgilendirme",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "system@sozsoft.com",
|
|
||||||
"NextOnStart": "WF1780768071444394",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "",
|
|
||||||
"NextOnReject": "",
|
|
||||||
"PositionX": 472,
|
|
||||||
"PositionY": 417,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768065817524"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "End",
|
|
||||||
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Bitir",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "",
|
|
||||||
"NextOnStart": "",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "",
|
|
||||||
"NextOnReject": "",
|
|
||||||
"PositionX": 850,
|
|
||||||
"PositionY": 417,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768071444394"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"WorkflowCriteria": [
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Start",
|
|
||||||
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Ba\u015Flat",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "",
|
|
||||||
"NextOnStart": "WF1780768059929317",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "",
|
|
||||||
"NextOnReject": "",
|
|
||||||
"PositionX": 53,
|
|
||||||
"PositionY": 42,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768057089996"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Approval",
|
|
||||||
"Title": "Onay1",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "admin@sozsoft.com",
|
|
||||||
"NextOnStart": "",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "WF1780768062793138",
|
|
||||||
"NextOnReject": "WF1780768065817524",
|
|
||||||
"PositionX": 356,
|
|
||||||
"PositionY": 41,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768059929317"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Approval",
|
|
||||||
"Title": "Onay2",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "demo@sozsoft.com",
|
|
||||||
"NextOnStart": "",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "WF1780768065817524",
|
|
||||||
"NextOnReject": "WF1780768065817524",
|
|
||||||
"PositionX": 632,
|
|
||||||
"PositionY": 38,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768062793138"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "Inform",
|
|
||||||
"Title": "Bilgilendirme",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "system@sozsoft.com",
|
|
||||||
"NextOnStart": "WF1780768071444394",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "",
|
|
||||||
"NextOnReject": "",
|
|
||||||
"PositionX": 472,
|
|
||||||
"PositionY": 417,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768065817524"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ListFormCode": "App.Wizard.Approval",
|
|
||||||
"Kind": "End",
|
|
||||||
"Title": "\u0130\u015F Ak\u0131\u015F\u0131 Bitir",
|
|
||||||
"CompareColumn": "Price",
|
|
||||||
"CompareOperator": "\u003E",
|
|
||||||
"CompareValue": 5000,
|
|
||||||
"Approver": "",
|
|
||||||
"NextOnStart": "",
|
|
||||||
"NextOnTrue": "",
|
|
||||||
"NextOnFalse": "",
|
|
||||||
"NextOnApprove": "",
|
|
||||||
"NextOnReject": "",
|
|
||||||
"PositionX": 850,
|
|
||||||
"PositionY": 417,
|
|
||||||
"CompareOutcomes": [],
|
|
||||||
"Id": "WF1780768071444394"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"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": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -135,6 +135,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
input.Workflow ??= new WorkflowDto();
|
input.Workflow ??= new WorkflowDto();
|
||||||
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>();
|
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>();
|
||||||
input.Workflow.Criteria = input.WorkflowCriteria;
|
input.Workflow.Criteria = input.WorkflowCriteria;
|
||||||
|
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria);
|
||||||
|
|
||||||
var wizardName = input.WizardName.Trim();
|
var wizardName = input.WizardName.Trim();
|
||||||
var code = string.IsNullOrWhiteSpace(input.MenuCode)
|
var code = string.IsNullOrWhiteSpace(input.MenuCode)
|
||||||
|
|
@ -332,7 +333,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,
|
||||||
|
|
@ -415,6 +416,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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 || ''
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ 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'
|
||||||
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -45,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,
|
||||||
|
|
@ -96,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))
|
||||||
|
|
@ -109,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
|
||||||
|
|
@ -193,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,
|
||||||
|
|
@ -209,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,
|
||||||
|
|
@ -217,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,
|
||||||
|
|
|
||||||
|
|
@ -73,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" />,
|
||||||
|
|
@ -435,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"
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
@ -1874,6 +1897,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
|
||||||
|
|
@ -250,6 +251,10 @@ 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[]>([])
|
||||||
|
|
||||||
|
|
@ -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][] => {
|
||||||
|
|
@ -1613,6 +1636,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[]) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue