From 12f046f2621b449298fedddef6f71d0658a9d9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sun, 7 Jun 2026 22:42:02 +0300 Subject: [PATCH] =?UTF-8?q?Workflow=20ve=20WizardSeeder=20d=C3=BCzenlemele?= =?UTF-8?q?ri=20yap=C4=B1ld=C4=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Workflow/WorkflowRunResultDto.cs | 3 + .../ListForms/ListFormWizardAppService.cs | 16 +- .../ListForms/ListFormWorkflowAppService.cs | 248 +++++++++++++----- .../Seeds/LanguagesData.json | 78 ++++++ .../Seeds/WizardDataSeeder.cs | 41 +++ .../Queries/SelectQueryManager.cs | 10 +- .../Seeds/TenantData.json | 30 +++ ui/src/services/workflow.service.ts | 1 + ui/src/views/list/Grid.tsx | 22 +- ui/src/views/list/Tree.tsx | 22 +- ui/src/views/list/useToolbar.tsx | 29 +- 11 files changed, 416 insertions(+), 84 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowRunResultDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowRunResultDto.cs index 0436c15..d52efdf 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowRunResultDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowRunResultDto.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Sozsoft.Platform.ListForms; public class WorkflowRunResultDto @@ -8,5 +10,6 @@ public class WorkflowRunResultDto public string CurrentNodeKind { get; set; } public bool WaitingApproval { get; set; } public bool Completed { get; set; } + public List ToastMessages { get; set; } = []; } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 8529036..ec21d31 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -12,8 +12,6 @@ using Volo.Abp.Domain.Repositories; using Volo.Abp.MultiTenancy; using Volo.Abp.PermissionManagement; using Volo.Abp.Uow; -using static Sozsoft.Platform.PlatformConsts; -using System.Data; using Microsoft.Extensions.Hosting; using Sozsoft.Languages; using Sozsoft.Platform.DynamicData; @@ -97,11 +95,7 @@ public class ListFormWizardAppService( } // Permission'ları tek seferde kontrol et ve oluştur - var queryable = await repoPerm.GetQueryableAsync(); - var existingPerms = await AsyncExecuter.ToListAsync( - queryable.Where(a => a.GroupName == groupName) - ); - + var existingPerms = await repoPerm.GetListAsync(a => a.GroupName == groupName); var permRead = existingPerms.FirstOrDefault(a => a.Name == code); if (permRead == null) { @@ -280,7 +274,7 @@ public class ListFormWizardAppService( input.Workflow.Criteria = input.WorkflowCriteria; EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria); - var listForm = await repoListForm.InsertAsync(new ListForm + await repoListForm.InsertAsync(new ListForm { ListFormType = ListFormTypeEnum.List, PageSize = 10, @@ -302,12 +296,12 @@ public class ListFormWizardAppService( KeyFieldName = input.KeyFieldName, KeyFieldDbSourceType = input.KeyFieldDbSourceType, DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null, - SortMode = GridOptions.SortModeSingle, + SortMode = PlatformConsts.GridOptions.SortModeSingle, FilterRowJson = WizardConsts.DefaultFilterRowJson, HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson, SearchPanelJson = WizardConsts.DefaultSearchPanelJson, GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }), - SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.WorkflowCriteria.Count > 0 ? GridOptions.SelectionModeSingle : GridOptions.SelectionModeNone), + SelectionJson = WizardConsts.DefaultSelectionSingleJson(input.WorkflowCriteria.Count > 0 ? PlatformConsts.GridOptions.SelectionModeSingle : PlatformConsts.GridOptions.SelectionModeNone), ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(), PermissionJson = WizardConsts.DefaultPermissionJson(code), DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null, @@ -417,9 +411,7 @@ public class ListFormWizardAppService( { return workflow != null && ( !string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) || - !string.IsNullOrWhiteSpace(workflow.ApprovalDateFieldName) || !string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) || - !string.IsNullOrWhiteSpace(workflow.ApprovalDescriptionFieldName) || criteria.Count > 0 ); } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs index 8655dab..cb21480 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs @@ -7,10 +7,12 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; using Sozsoft.Platform.ListForms.Select; +using Sozsoft.Platform.Localization; using Sozsoft.Platform.Queries; using Sozsoft.Sender.Mail; using Volo.Abp; @@ -37,6 +39,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA private readonly IdentityUserManager identityUserManager; private readonly ISozsoftEmailSender erpEmailSender; private readonly ISettingProvider settingProvider; + private readonly IStringLocalizer localizer; public ListFormWorkflowAppService( IRepository criteriaRepository, @@ -47,7 +50,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA IQueryManager queryManager, IdentityUserManager identityUserManager, ISozsoftEmailSender erpEmailSender, - ISettingProvider settingProvider) + ISettingProvider settingProvider, + IStringLocalizer localizer) { this.criteriaRepository = criteriaRepository; this.noteRepository = noteRepository; @@ -58,6 +62,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA this.identityUserManager = identityUserManager; this.erpEmailSender = erpEmailSender; this.settingProvider = settingProvider; + this.localizer = localizer; } [HttpGet("criteria")] @@ -303,6 +308,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start") ?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı."); + context.WorkflowNoteRows.Add(("Started By: ", ResolveCurrentUserDisplayName())); var result = await RunUntilWaitAsync(context, start); await InsertWorkflowNoteAsync( context, @@ -374,7 +380,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA } var next = FindNextCriteria(context.Criteria, input.Approved ? current.NextOnApprove : current.NextOnReject); - AddWorkflowDecisionRows(context, current, next, input.Approved, input.Note ?? string.Empty); + context.WorkflowNoteRows.Add(("Decision By: ", ResolveCurrentUserDisplayName())); + AddWorkflowDecisionRows(context, current, input.Approved, input.Note ?? string.Empty); var result = await RunUntilWaitAsync(context, next); await InsertWorkflowNoteAsync( context, @@ -403,7 +410,10 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA CurrentNodeTitle = last?.CurrentNodeTitle, CurrentNodeKind = last?.CurrentNodeKind, WaitingApproval = results.Any(x => x.WaitingApproval), - Completed = results.All(x => x.Completed) + Completed = results.All(x => x.Completed), + ToastMessages = results + .SelectMany(result => result.ToastMessages ?? []) + .ToList() }; } @@ -435,7 +445,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA .ToList(); var row = await GetRowAsync(code, listForm.KeyFieldName, keys[0]); - return new WorkflowRunContext(code, listForm.KeyFieldName, keys, workflow, criteria, row); + return new WorkflowRunContext(code, keys, workflow, criteria, row); } private async Task> GetRowAsync(string listFormCode, string keyFieldName, object key) @@ -527,17 +537,16 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA await UpdateRowAsync(context, update); MergeRowValues(context.Row, update); + AddWorkflowNodeRows(context, node); + AddWorkflowToastMessage(context, node); - string informedRecipient = null; if (node.Kind == "Inform") { - informedRecipient = await SendInformEmailAsync(context, node); + await SendInformEmailAsync(context, node); } - - AddWorkflowNodeRows(context, node, informedRecipient ?? string.Empty); } - private async Task SendInformEmailAsync(WorkflowRunContext context, ListFormWorkflow node) + private async Task SendInformEmailAsync(WorkflowRunContext context, ListFormWorkflow node) { var recipientEmail = await ResolveApproverEmailAsync(node.Approver); var senderName = await settingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName); @@ -552,7 +561,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA recipientEmail, sender, new { }, - BuildInformEmailBody(context, node), + BuildInformEmailBody(context, node, await BuildPreviousWorkflowNotesHtmlAsync(context)), $"Workflow Bilgilendirme: {node.Title}", null, true); @@ -562,7 +571,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA throw new UserFriendlyException($"Bilgilendirme maili gonderilemedi: {result.ErrorMessage}"); } - return recipientEmail; } private async Task ResolveApproverEmailAsync(string approver) @@ -586,26 +594,83 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA return user.Email; } - private static string BuildInformEmailBody(WorkflowRunContext context, ListFormWorkflow node) + private async Task 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 => + $""" +
+
{Encode(note.Subject)}
+
{note.Content}
+
+ """); + + return string.Join(string.Empty, noteItems); + } + + private static string BuildInformEmailBody( + WorkflowRunContext context, + ListFormWorkflow node, + string previousWorkflowNotesHtml) { var keyText = string.Join(", ", context.Keys.Select(key => WebUtility.HtmlEncode(key?.ToString() ?? string.Empty))); var listFormCode = WebUtility.HtmlEncode(context.ListFormCode ?? string.Empty); var nodeTitle = WebUtility.HtmlEncode(node.Title ?? string.Empty); + var recipient = WebUtility.HtmlEncode(node.Approver ?? string.Empty); + var processRows = BuildWorkflowNoteContent(context.WorkflowNoteRows); + var previousNotesSection = previousWorkflowNotesHtml.IsNullOrWhiteSpace() + ? string.Empty + : $""" +

Önceki Workflow Notları

+ {previousWorkflowNotesHtml} + """; return $""" -

Workflow bilgilendirme adimina ulasildi.

- - - - -
Liste Formu{listFormCode}
Adim{nodeTitle}
Kayit{keyText}
+
+

Workflow sürecinde bilgilendirme adımına ulaşıldı.

+ + + + + +
Liste Formu{listFormCode}
Kayıt{keyText}
Bilgilendirme Adımı{nodeTitle}
Alıcı{recipient}
+ +

Bu İşlemdeki Süreç Özeti

+ {processRows} + + {previousNotesSection} +
"""; } + private string ResolveCurrentUserDisplayName() + { + return CurrentUser.UserName + ?? CurrentUser.Name + ?? CurrentUser.Id?.ToString() + ?? "System"; + } + private static void AddWorkflowDecisionRows( WorkflowRunContext context, ListFormWorkflow current, - ListFormWorkflow next, bool approved, string description) { @@ -616,8 +681,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA private static void AddWorkflowNodeRows( WorkflowRunContext context, - ListFormWorkflow node, - string informedRecipient) + ListFormWorkflow node) { var action = node.Kind switch { @@ -637,6 +701,98 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA } } + 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(); + + 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 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, @@ -662,20 +818,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA 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 @@ -686,14 +828,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA return $"{string.Join(string.Empty, tableRows)}
"; } - private string ResolveCurrentUserDisplayName() - { - return CurrentUser.UserName - ?? CurrentUser.Name - ?? CurrentUser.Id?.ToString() - ?? "System"; - } - private static string FormatNode(ListFormWorkflow node) { if (node == null) @@ -706,33 +840,11 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA 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 data) { await queryManager.GenerateAndRunQueryAsync( @@ -914,7 +1026,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA CurrentNodeTitle = node?.Title, CurrentNodeKind = node?.Kind, WaitingApproval = waitingApproval, - Completed = completed + Completed = completed, + ToastMessages = context.ToastMessages.ToList() }; } @@ -1129,7 +1242,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA private sealed record WorkflowRunContext( string ListFormCode, - string KeyFieldName, + object[] Keys, WorkflowDto Workflow, List Criteria, @@ -1137,5 +1250,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA { public HashSet UserUpdatedFields { get; } = []; public List<(string Label, string Value)> WorkflowNoteRows { get; } = []; + public List ToastMessages { get; } = []; } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 1443502..c11576a 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -19321,6 +19321,84 @@ "key": "FileManager.SortByModifiedDesc", "en": "Modified (Newest)", "tr": "Değiştirilme (En Yeni)" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.WorkflowStarted", + "en": "Operation: Workflow started", + "tr": "İşlem: Workflow başlatıldı" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.InformReached", + "en": "Operation: Workflow inform step reached", + "tr": "İşlem: Workflow bilgilendirme adımına ulaştı" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.WorkflowCompleted", + "en": "Operation: Workflow completed", + "tr": "İşlem: Workflow tamamlandı" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.Step", + "en": "Step: {0}", + "tr": "Adım: {0}" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.PerformedBy", + "en": "Performed by: {0}", + "tr": "İşlemi yapan: {0}" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.InformUser", + "en": "User to inform: {0}", + "tr": "Bilgilendirilecek kullanıcı: {0}" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.ApproverUser", + "en": "Approver user: {0}", + "tr": "Onaylayacak kullanıcı: {0}" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.NextStep.Approval", + "en": "Next step: Approval", + "tr": "Sonraki adım: Onay" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.NextStep.Inform", + "en": "Next step: Inform", + "tr": "Sonraki adım: Bilgilendirme" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.NextStep.End", + "en": "Next step: Workflow end", + "tr": "Sonraki adım: Workflow bitiş" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.NextStepName", + "en": "Next step name: {0}", + "tr": "Sonraki adım adı: {0}" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.NextStep", + "en": "Next step: {0}", + "tr": "Sonraki adım: {0}" + }, + { + "resourceName": "Platform", + "key": "ListForms.ListForm.Workflow.UndefinedUser", + "en": "Undefined", + "tr": "Tanımsız" } ] } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs index a51e043..08db5fa 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/WizardDataSeeder.cs @@ -36,6 +36,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency private readonly IRepository _repoDataSource; private readonly IRepository _repoListForm; private readonly IRepository _repoListFormField; + private readonly IRepository _repoListFormWorkflow; private readonly ILogger _logger; private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage; @@ -51,6 +52,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency IRepository repoDataSource, IRepository repoListForm, IRepository repoListFormField, + IRepository repoListFormWorkflow, ILogger logger) { _repoLangKey = repoLangKey; @@ -62,6 +64,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency _repoDataSource = repoDataSource; _repoListForm = repoListForm; _repoListFormField = repoListFormField; + _repoListFormWorkflow = repoListFormWorkflow; _logger = logger; } @@ -134,6 +137,10 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency input.Widgets ??= new List(); input.Workflow ??= new WorkflowDto(); input.WorkflowCriteria ??= new List(); + if (input.WorkflowCriteria.Count == 0 && input.Workflow.Criteria?.Count > 0) + { + input.WorkflowCriteria = input.Workflow.Criteria; + } input.Workflow.Criteria = input.WorkflowCriteria; EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria); @@ -326,6 +333,12 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency await _repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true); } + var existingWorkflowCriteria = await _repoListFormWorkflow.GetListAsync(a => a.ListFormCode == input.ListFormCode); + if (existingWorkflowCriteria.Count > 0) + { + await _repoListFormWorkflow.DeleteManyAsync(existingWorkflowCriteria, autoSave: true); + } + // ListForm await _repoListForm.InsertAsync(new ListForm { @@ -403,6 +416,34 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency await CreateLangKeyAsync(item.CaptionName, item.EnglishCaption, item.TurkishCaption); } } + + foreach (var criteria in input.WorkflowCriteria) + { + if (string.IsNullOrWhiteSpace(criteria.Id)) + { + _logger.LogWarning("Workflow criteria skipped because Id is empty. ListFormCode: {ListFormCode}, Title: {Title}", input.ListFormCode, criteria.Title); + continue; + } + + await _repoListFormWorkflow.InsertAsync(new ListFormWorkflow(criteria.Id) + { + ListFormCode = string.IsNullOrWhiteSpace(criteria.ListFormCode) ? input.ListFormCode : criteria.ListFormCode, + Kind = criteria.Kind, + Title = criteria.Title, + CompareColumn = criteria.CompareColumn, + CompareOperator = criteria.CompareOperator, + CompareValue = criteria.CompareValue, + Approver = criteria.Approver, + NextOnStart = criteria.NextOnStart, + NextOnTrue = criteria.NextOnTrue, + NextOnFalse = criteria.NextOnFalse, + NextOnApprove = criteria.NextOnApprove, + NextOnReject = criteria.NextOnReject, + PositionX = criteria.PositionX, + PositionY = criteria.PositionY, + CompareOutcomesJson = JsonSerializer.Serialize(criteria.CompareOutcomes ?? []), + }, autoSave: true); + } } private static bool HasWorkflow(WorkflowDto workflow, List criteria) diff --git a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs index c4d1355..f03101f 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs @@ -475,7 +475,15 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager whereParts.Add("AND"); } - whereParts.Add($"\"{workflow.ApprovalUserFieldName}\" = '{CurrentUser.UserName}'"); + // 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}\" = '')" + ); } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json index 91b047a..8c50fdf 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json @@ -1456,6 +1456,36 @@ "DepartmentName": "Muhasebe", "Name": "Muhasebe Şefi", "ParentName": "Muhasebe Müdürü" + }, + { + "Id": "b7c8d9e0-f1a2-4b3c-8d9e-0f1a2b3c4d2e", + "DepartmentName": "Bilgi İşlem", + "Name": "Bilgi İşlem Müdürü", + "ParentName": "Genel Müdür" + }, + { + "Id": "b7c8d9e0-f1a2-4b3c-1d9e-0f1a2b3c4d2e", + "DepartmentName": "Finans", + "Name": "Finans Müdürü", + "ParentName": "Genel Müdür" + }, + { + "Id": "b7c8d9e0-f1b2-4b3c-1d9e-0f1a2b3c4d2e", + "DepartmentName": "Satış", + "Name": "İhracat Müdürü", + "ParentName": "Genel Müdür" + }, + { + "Id": "b2c8d9e0-f1b2-4b3c-1d9e-0f1a2b3c4d2e", + "DepartmentName": "Satış", + "Name": "İç Piyasa Müdürü", + "ParentName": "Genel Müdür" + }, + { + "Id": "b2c8d9e0-f1b2-2b3c-1d9e-0f1a2b3c4d2e", + "DepartmentName": "Üretim", + "Name": "Üretim Müdürü", + "ParentName": "Genel Müdür" } ], "Announcements": [ diff --git a/ui/src/services/workflow.service.ts b/ui/src/services/workflow.service.ts index 19934f1..bc39e23 100644 --- a/ui/src/services/workflow.service.ts +++ b/ui/src/services/workflow.service.ts @@ -43,6 +43,7 @@ export interface WorkflowRunResultDto { currentNodeKind?: string | null waitingApproval: boolean completed: boolean + toastMessages?: string[] } export type SaveCriteriaInput = Omit, 'id'> & { diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index 3776769..614d38a 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -1474,7 +1474,27 @@ const Grid = (props: GridProps) => { ) { workflowService .startWorkflow(listFormCode, [insertedKey]) - .then(() => gridRef.current?.instance()?.refresh()) + .then((result) => { + const messages = result.toastMessages ?? [] + if (messages.length > 0) { + toast.push( + + {messages.map((message, messageIndex) => ( +
0 ? 'mt-2 border-t pt-2' : undefined} + > + {message.split('\n').map((line, lineIndex) => ( +
{line}
+ ))} +
+ ))} +
, + { placement: 'top-end' }, + ) + } + gridRef.current?.instance()?.refresh() + }) .catch(console.error) } props.refreshData?.() diff --git a/ui/src/views/list/Tree.tsx b/ui/src/views/list/Tree.tsx index 5196003..ef4ffa8 100644 --- a/ui/src/views/list/Tree.tsx +++ b/ui/src/views/list/Tree.tsx @@ -1133,7 +1133,27 @@ const Tree = (props: TreeProps) => { ) { workflowService .startWorkflow(listFormCode, [insertedKey]) - .then(() => gridRef.current?.instance()?.refresh()) + .then((result) => { + const messages = result.toastMessages ?? [] + if (messages.length > 0) { + toast.push( + + {messages.map((message, messageIndex) => ( +
0 ? 'mt-2 border-t pt-2' : undefined} + > + {message.split('\n').map((line, lineIndex) => ( +
{line}
+ ))} +
+ ))} +
, + { placement: 'top-end' }, + ) + } + gridRef.current?.instance()?.refresh() + }) .catch(console.error) } props.refreshData?.() diff --git a/ui/src/views/list/useToolbar.tsx b/ui/src/views/list/useToolbar.tsx index edb16e5..9dae41d 100644 --- a/ui/src/views/list/useToolbar.tsx +++ b/ui/src/views/list/useToolbar.tsx @@ -11,12 +11,35 @@ import { usePWA } from '@/utils/hooks/usePWA' import { layoutTypes, ListViewLayoutType } from '../admin/listForm/edit/types' import { useStoreState } from '@/store' import { workflowService } from '@/services/workflow.service' +import type { WorkflowRunResultDto } from '@/services/workflow.service' type ToolbarModalData = { open: boolean 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( + + {messages.map((message, messageIndex) => ( +
0 ? 'mt-2 border-t pt-2' : undefined}> + {message.split('\n').map((line, lineIndex) => ( +
{line}
+ ))} +
+ ))} +
, + { placement: 'top-end' }, + ) +} + // https://js.devexpress.com/Documentation/ApiReference/UI_Components/dxDataGrid/Configuration/toolbar/ // item.name > Accepted Values: 'addRowButton', 'applyFilterButton', 'columnChooserButton', 'exportButton', 'groupPanel', 'revertButton', 'saveButton', 'searchPanel' const useToolbar = ({ @@ -158,7 +181,8 @@ const useToolbar = ({ } try { - await workflowService.startWorkflow(listFormCode, keys) + const result = await workflowService.startWorkflow(listFormCode, keys) + showWorkflowToastMessages(result) refreshData() } catch (error: any) { toast.push( @@ -690,11 +714,12 @@ function WorkflowApprovalDecisionDialog({ const decide = async (approved: boolean) => { setSubmitting(true) try { - await Promise.all( + const results = await Promise.all( keys.map((key) => workflowService.decideWorkflow(listFormCode, [key], approved, note, criteriaId), ), ) + showWorkflowToastMessages(results) onCompleted() } catch (error: any) { toast.push(