Workflow ve WizardSeeder düzenlemeleri yapıldı.

This commit is contained in:
Sedat Öztürk 2026-06-07 22:42:02 +03:00
parent d0cccde53f
commit 12f046f262
11 changed files with 416 additions and 84 deletions

View file

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

View file

@ -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
);
}

View file

@ -7,10 +7,12 @@ using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Sozsoft.Platform.Data.Seeds;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.ListForms.Select;
using Sozsoft.Platform.Localization;
using Sozsoft.Platform.Queries;
using Sozsoft.Sender.Mail;
using Volo.Abp;
@ -37,6 +39,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
private readonly IdentityUserManager identityUserManager;
private readonly ISozsoftEmailSender erpEmailSender;
private readonly ISettingProvider settingProvider;
private readonly IStringLocalizer<PlatformResource> localizer;
public ListFormWorkflowAppService(
IRepository<ListFormWorkflow, string> criteriaRepository,
@ -47,7 +50,8 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
IQueryManager queryManager,
IdentityUserManager identityUserManager,
ISozsoftEmailSender erpEmailSender,
ISettingProvider settingProvider)
ISettingProvider settingProvider,
IStringLocalizer<PlatformResource> 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<IDictionary<string, object>> 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<string> 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<string> 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<string> BuildPreviousWorkflowNotesHtmlAsync(WorkflowRunContext context)
{
var key = context.Keys?.FirstOrDefault()?.ToString();
if (key.IsNullOrWhiteSpace())
{
return string.Empty;
}
var notes = (await noteRepository.GetListAsync(note =>
note.EntityName == context.ListFormCode &&
note.EntityId == key &&
note.Type == "workflow"))
.OrderBy(note => note.CreationTime)
.ToList();
if (notes.Count == 0)
{
return string.Empty;
}
var noteItems = notes.Select(note =>
$"""
<div style="margin: 0 0 12px 0; padding: 10px 12px; border: 1px solid #e5e7eb; border-radius: 6px;">
<div style="font-weight: 600; margin-bottom: 6px;">{Encode(note.Subject)}</div>
<div>{note.Content}</div>
</div>
""");
return string.Join(string.Empty, noteItems);
}
private static string BuildInformEmailBody(
WorkflowRunContext context,
ListFormWorkflow node,
string previousWorkflowNotesHtml)
{
var keyText = string.Join(", ", context.Keys.Select(key => WebUtility.HtmlEncode(key?.ToString() ?? string.Empty)));
var listFormCode = WebUtility.HtmlEncode(context.ListFormCode ?? string.Empty);
var nodeTitle = WebUtility.HtmlEncode(node.Title ?? string.Empty);
var recipient = WebUtility.HtmlEncode(node.Approver ?? string.Empty);
var processRows = BuildWorkflowNoteContent(context.WorkflowNoteRows);
var previousNotesSection = previousWorkflowNotesHtml.IsNullOrWhiteSpace()
? string.Empty
: $"""
<h3 style="margin: 18px 0 8px 0;">Önceki Workflow Notları</h3>
{previousWorkflowNotesHtml}
""";
return $"""
<p>Workflow bilgilendirme adimina ulasildi.</p>
<table>
<tr><td><strong>Liste Formu</strong></td><td>{listFormCode}</td></tr>
<tr><td><strong>Adim</strong></td><td>{nodeTitle}</td></tr>
<tr><td><strong>Kayit</strong></td><td>{keyText}</td></tr>
</table>
<div style="font-family: Arial, sans-serif; color: #111827; line-height: 1.45;">
<p>Workflow sürecinde bilgilendirme adımına ulaşıldı.</p>
<table style="border-collapse: collapse; width: 100%; margin-bottom: 16px;">
<tr><td style="padding: 6px 8px;"><strong>Liste Formu</strong></td><td style="padding: 6px 8px;">{listFormCode}</td></tr>
<tr><td style="padding: 6px 8px;"><strong>Kayıt</strong></td><td style="padding: 6px 8px;">{keyText}</td></tr>
<tr><td style="padding: 6px 8px;"><strong>Bilgilendirme Adımı</strong></td><td style="padding: 6px 8px;">{nodeTitle}</td></tr>
<tr><td style="padding: 6px 8px;"><strong>Alıcı</strong></td><td style="padding: 6px 8px;">{recipient}</td></tr>
</table>
<h3 style="margin: 18px 0 8px 0;">Bu İşlemdeki Süreç Özeti</h3>
{processRows}
{previousNotesSection}
</div>
""";
}
private string ResolveCurrentUserDisplayName()
{
return CurrentUser.UserName
?? CurrentUser.Name
?? CurrentUser.Id?.ToString()
?? "System";
}
private static void AddWorkflowDecisionRows(
WorkflowRunContext context,
ListFormWorkflow current,
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<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,
@ -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 $"<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)
@ -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<string, object> data)
{
await queryManager.GenerateAndRunQueryAsync<int>(
@ -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<ListFormWorkflow> Criteria,
@ -1137,5 +1250,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
{
public HashSet<string> UserUpdatedFields { get; } = [];
public List<(string Label, string Value)> WorkflowNoteRows { get; } = [];
public List<string> ToastMessages { get; } = [];
}
}

View file

@ -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"
}
]
}

View file

@ -36,6 +36,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<DataSource, Guid> _repoDataSource;
private readonly IRepository<ListForm, Guid> _repoListForm;
private readonly IRepository<ListFormField, Guid> _repoListFormField;
private readonly IRepository<ListFormWorkflow, string> _repoListFormWorkflow;
private readonly ILogger<WizardDataSeeder> _logger;
private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage;
@ -51,6 +52,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<DataSource, Guid> repoDataSource,
IRepository<ListForm, Guid> repoListForm,
IRepository<ListFormField, Guid> repoListFormField,
IRepository<ListFormWorkflow, string> repoListFormWorkflow,
ILogger<WizardDataSeeder> logger)
{
_repoLangKey = repoLangKey;
@ -62,6 +64,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
_repoDataSource = repoDataSource;
_repoListForm = repoListForm;
_repoListFormField = repoListFormField;
_repoListFormWorkflow = repoListFormWorkflow;
_logger = logger;
}
@ -134,6 +137,10 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
input.Widgets ??= new List<WidgetEditDto>();
input.Workflow ??= new WorkflowDto();
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>();
if (input.WorkflowCriteria.Count == 0 && input.Workflow.Criteria?.Count > 0)
{
input.WorkflowCriteria = input.Workflow.Criteria;
}
input.Workflow.Criteria = input.WorkflowCriteria;
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria);
@ -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<ListFormWorkflowCriteriaDto> criteria)

View file

@ -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}\" = '')"
);
}
}

View file

@ -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": [

View file

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

View file

@ -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(
<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)
}
props.refreshData?.()

View file

@ -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(
<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)
}
props.refreshData?.()

View file

@ -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(
<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/
// 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(