ListFormWorkflowun Grid ve Tree üzerinden çalıştır
This commit is contained in:
parent
8f3932bc6e
commit
6262baa6f1
32 changed files with 1643 additions and 135 deletions
|
|
@ -394,6 +394,19 @@ public class GridOptionsDto : AuditedEntityDto<Guid>
|
||||||
set { LayoutJson = JsonSerializer.Serialize(value); }
|
set { LayoutJson = JsonSerializer.Serialize(value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string WorkflowJson { get; set; }
|
||||||
|
public WorkflowDto WorkflowDto
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(WorkflowJson))
|
||||||
|
return JsonSerializer.Deserialize<WorkflowDto>(WorkflowJson);
|
||||||
|
return new WorkflowDto();
|
||||||
|
}
|
||||||
|
set { WorkflowJson = JsonSerializer.Serialize(value); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Chart a yetkili kullanıcı. UserId ve RoleId boş ise herkes yetkilidir
|
/// <summary> Chart a yetkili kullanıcı. UserId ve RoleId boş ise herkes yetkilidir
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -123,18 +123,5 @@ public class GridOptionsEditDto : GridOptionsDto
|
||||||
}
|
}
|
||||||
set { ExtraFilterJson = JsonSerializer.Serialize(value); }
|
set { ExtraFilterJson = JsonSerializer.Serialize(value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string WorkflowJson { get; set; }
|
|
||||||
public WorkflowDto WorkflowDto
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(WorkflowJson))
|
|
||||||
return JsonSerializer.Deserialize<WorkflowDto>(WorkflowJson);
|
|
||||||
return new WorkflowDto();
|
|
||||||
}
|
|
||||||
set { WorkflowJson = JsonSerializer.Serialize(value); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
namespace Sozsoft.Platform.ListForms;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class WorkflowDto
|
public class WorkflowDto
|
||||||
{
|
{
|
||||||
public string ApprovalUserFieldName { get; set; }
|
public string ApprovalUserFieldName { get; set; }
|
||||||
public string ApprovalDateFieldName { get; set; }
|
public string ApprovalDateFieldName { get; set; }
|
||||||
public string ApprovalStatusFieldName { get; set; }
|
public string ApprovalStatusFieldName { get; set; }
|
||||||
|
public string ApprovalDescriptionFieldName { get; set; }
|
||||||
|
|
||||||
|
public List<ListFormWorkflowCriteriaDto> Criteria { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class CompareOutcomeDto
|
public class CompareOutcomeDto
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class CreateUpdateListFormWorkflowCriteriaDto
|
public class CreateUpdateListFormWorkflowCriteriaDto
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class DecisionWorkflowDto
|
public class DecisionWorkflowDto
|
||||||
{
|
{
|
||||||
|
public string ListFormCode { get; set; }
|
||||||
|
public object[] Keys { get; set; }
|
||||||
|
public string CurrentNodeId { get; set; }
|
||||||
public bool Approved { get; set; }
|
public bool Approved { get; set; }
|
||||||
public string Note { get; set; }
|
public string Note { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public interface IListFormWorkflowAppService : IApplicationService
|
public interface IListFormWorkflowAppService : IApplicationService
|
||||||
{
|
{
|
||||||
Task<ListFormWorkflowStateDto> GetStateAsync(string listFormCode = null);
|
Task<ListFormWorkflowStateDto> GetCriteriaAsync(string listFormCode = null);
|
||||||
Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input);
|
Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input);
|
||||||
Task DeleteCriteriaAsync(string id);
|
Task DeleteCriteriaAsync(string id);
|
||||||
Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null);
|
Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null);
|
||||||
|
Task<WorkflowRunResultDto> StartAsync(StartWorkflowDto input);
|
||||||
|
Task<WorkflowRunResultDto> DecisionAsync(DecisionWorkflowDto input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class ListFormWorkflowCriteriaDto : AuditedEntityDto<string>
|
public class ListFormWorkflowCriteriaDto : EntityDto<string>
|
||||||
{
|
{
|
||||||
public string ListFormCode { get; set; }
|
public string ListFormCode { get; set; }
|
||||||
public string Kind { get; set; }
|
public string Kind { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class ListFormWorkflowStateDto
|
public class ListFormWorkflowStateDto
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
|
public class StartWorkflowDto
|
||||||
|
{
|
||||||
|
public string ListFormCode { get; set; }
|
||||||
|
public object[] Keys { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class WorkflowConditionDto
|
public class WorkflowConditionDto
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
public class WorkflowHistoryDto
|
public class WorkflowHistoryDto
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
|
public class WorkflowRunResultDto
|
||||||
|
{
|
||||||
|
public object[] Keys { get; set; }
|
||||||
|
public string CurrentNodeId { get; set; }
|
||||||
|
public string CurrentNodeTitle { get; set; }
|
||||||
|
public string CurrentNodeKind { get; set; }
|
||||||
|
public bool WaitingApproval { get; set; }
|
||||||
|
public bool Completed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -21,6 +21,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
{
|
{
|
||||||
private readonly IRepository<ListForm, Guid> listFormRepository;
|
private readonly IRepository<ListForm, Guid> listFormRepository;
|
||||||
private readonly IRepository<ListFormField, Guid> listFormFieldRepository;
|
private readonly IRepository<ListFormField, Guid> listFormFieldRepository;
|
||||||
|
private readonly IRepository<ListFormWorkflow, string> listFormWorkflowRepository;
|
||||||
private readonly ICurrentUser currentUser;
|
private readonly ICurrentUser currentUser;
|
||||||
private readonly ISelectQueryManager selectQueryManager;
|
private readonly ISelectQueryManager selectQueryManager;
|
||||||
private readonly IListFormAuthorizationManager authManager;
|
private readonly IListFormAuthorizationManager authManager;
|
||||||
|
|
@ -39,6 +40,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
public ListFormSelectAppService(
|
public ListFormSelectAppService(
|
||||||
IRepository<ListForm, Guid> listFormRepository,
|
IRepository<ListForm, Guid> listFormRepository,
|
||||||
IRepository<ListFormField, Guid> listFormFieldRepository,
|
IRepository<ListFormField, Guid> listFormFieldRepository,
|
||||||
|
IRepository<ListFormWorkflow, string> listFormWorkflowRepository,
|
||||||
ICurrentUser currentUser,
|
ICurrentUser currentUser,
|
||||||
ISelectQueryManager selectQueryManager,
|
ISelectQueryManager selectQueryManager,
|
||||||
IListFormAuthorizationManager authManager,
|
IListFormAuthorizationManager authManager,
|
||||||
|
|
@ -54,6 +56,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
{
|
{
|
||||||
this.listFormRepository = listFormRepository;
|
this.listFormRepository = listFormRepository;
|
||||||
this.listFormFieldRepository = listFormFieldRepository;
|
this.listFormFieldRepository = listFormFieldRepository;
|
||||||
|
this.listFormWorkflowRepository = listFormWorkflowRepository;
|
||||||
this.currentUser = currentUser;
|
this.currentUser = currentUser;
|
||||||
this.selectQueryManager = selectQueryManager;
|
this.selectQueryManager = selectQueryManager;
|
||||||
this.authManager = authManager;
|
this.authManager = authManager;
|
||||||
|
|
@ -165,6 +168,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
var data = await dynamicDataRepository.QueryAsync(selectQueryManager.GroupQuery, connectionString, param);
|
var data = await dynamicDataRepository.QueryAsync(selectQueryManager.GroupQuery, connectionString, param);
|
||||||
var dataQueryable = data.AsQueryable();
|
var dataQueryable = data.AsQueryable();
|
||||||
|
|
||||||
|
|
||||||
var groups = new List<(string, int, List<dynamic>)>(selectQueryManager.GroupTuples.Count);
|
var groups = new List<(string, int, List<dynamic>)>(selectQueryManager.GroupTuples.Count);
|
||||||
for (int i = 0; i < selectQueryManager.GroupTuples.Count; i++)
|
for (int i = 0; i < selectQueryManager.GroupTuples.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -231,7 +235,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
{
|
{
|
||||||
var widgetList = JsonSerializer.Deserialize<WidgetEditDto[]>(listForm.WidgetsJson) ?? [];
|
var widgetList = JsonSerializer.Deserialize<WidgetEditDto[]>(listForm.WidgetsJson) ?? [];
|
||||||
var activeWidgets = widgetList.Where(w => w.IsActive && !string.IsNullOrWhiteSpace(w.SqlQuery)).ToList();
|
var activeWidgets = widgetList.Where(w => w.IsActive && !string.IsNullOrWhiteSpace(w.SqlQuery)).ToList();
|
||||||
|
|
||||||
if (activeWidgets.Count == 0)
|
if (activeWidgets.Count == 0)
|
||||||
return Widgets;
|
return Widgets;
|
||||||
|
|
||||||
|
|
@ -305,6 +309,11 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
Widgets = await GetWidgetsAsync(listForm)
|
Widgets = await GetWidgetsAsync(listForm)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Workflow kriterlerini alıp GridOptionsDto içine atıyoruz ki frontend'de kullanabilelim
|
||||||
|
var workflowDto = result.GridOptions.WorkflowDto;
|
||||||
|
workflowDto.Criteria = await GetWorkflowCriteriaAsync(ListFormCode);
|
||||||
|
result.GridOptions.WorkflowDto = workflowDto;
|
||||||
|
|
||||||
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
var queryParameters = httpContextAccessor.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
|
||||||
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, fields, Enums.OperationEnum.Select, queryParameters: queryParameters);
|
var defaultFields = await defaultValueManager.GenerateDefaultValuesAsync(listForm, fields, Enums.OperationEnum.Select, queryParameters: queryParameters);
|
||||||
|
|
||||||
|
|
@ -491,5 +500,153 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
|
||||||
|
|
||||||
return sql + " " + insertText;
|
return sql + " " + insertText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Workflow kriterlerini alma
|
||||||
|
private async Task<List<ListFormWorkflowCriteriaDto>> GetWorkflowCriteriaAsync(string listFormCode)
|
||||||
|
{
|
||||||
|
var criteria = await listFormWorkflowRepository.GetListAsync(x => x.ListFormCode == listFormCode);
|
||||||
|
return OrderWorkflowCriteria(criteria.Select(MapWorkflowCriteria).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ListFormWorkflowCriteriaDto> OrderWorkflowCriteria(List<ListFormWorkflowCriteriaDto> criteria)
|
||||||
|
{
|
||||||
|
if (criteria.Count <= 1)
|
||||||
|
{
|
||||||
|
return criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
var criteriaById = criteria.ToDictionary(x => x.Id);
|
||||||
|
var ordered = new List<ListFormWorkflowCriteriaDto>();
|
||||||
|
var visited = new HashSet<string>();
|
||||||
|
var visiting = new HashSet<string>();
|
||||||
|
|
||||||
|
var roots = criteria
|
||||||
|
.Where(x => x.Kind == "Start")
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (roots.Count == 0)
|
||||||
|
{
|
||||||
|
var referencedIds = criteria
|
||||||
|
.SelectMany(GetWorkflowTargetIds)
|
||||||
|
.Where(criteriaById.ContainsKey)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
roots = criteria
|
||||||
|
.Where(x => !referencedIds.Contains(x.Id))
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var root in roots)
|
||||||
|
{
|
||||||
|
VisitWorkflowCriteria(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in criteria.OrderBy(x => x.Id))
|
||||||
|
{
|
||||||
|
VisitWorkflowCriteria(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
|
||||||
|
void VisitWorkflowCriteria(ListFormWorkflowCriteriaDto item)
|
||||||
|
{
|
||||||
|
if (visited.Contains(item.Id) || visiting.Contains(item.Id))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visiting.Add(item.Id);
|
||||||
|
ordered.Add(item);
|
||||||
|
|
||||||
|
foreach (var targetId in GetWorkflowTargetIds(item))
|
||||||
|
{
|
||||||
|
if (criteriaById.TryGetValue(targetId, out var target))
|
||||||
|
{
|
||||||
|
VisitWorkflowCriteria(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visiting.Remove(item.Id);
|
||||||
|
visited.Add(item.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetWorkflowTargetIds(ListFormWorkflowCriteriaDto criteria)
|
||||||
|
{
|
||||||
|
if (!criteria.NextOnStart.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var outcome in criteria.CompareOutcomes ?? [])
|
||||||
|
{
|
||||||
|
if (!outcome.TargetId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return outcome.TargetId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnTrue.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnTrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnFalse.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnFalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnApprove.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnApprove;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnReject.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnReject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListFormWorkflowCriteriaDto MapWorkflowCriteria(ListFormWorkflow criteria)
|
||||||
|
{
|
||||||
|
return new ListFormWorkflowCriteriaDto
|
||||||
|
{
|
||||||
|
Id = criteria.Id,
|
||||||
|
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,
|
||||||
|
CompareOutcomes = DeserializeCompareOutcomes(criteria.CompareOutcomesJson)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<CompareOutcomeDto> DeserializeCompareOutcomes(string json)
|
||||||
|
{
|
||||||
|
if (json.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<List<CompareOutcomeDto>>(json) ?? [];
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Sozsoft.Platform.ListForms.Select;
|
||||||
|
using Sozsoft.Platform.Queries;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
|
@ -15,36 +19,149 @@ namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
[Route("api/app/list-form-workflow")]
|
[Route("api/app/list-form-workflow")]
|
||||||
public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowAppService
|
public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowAppService
|
||||||
{
|
{
|
||||||
private const string DefaultListFormCode = "workflow";
|
|
||||||
private const string CriteriaIdPrefix = "N";
|
private const string CriteriaIdPrefix = "N";
|
||||||
private const int CriteriaIdPadding = 3;
|
private const int CriteriaIdPadding = 3;
|
||||||
|
private const string SystemApprovalDescription = "Sistem tarafından otomatik olarak onaylandı.";
|
||||||
|
|
||||||
private readonly IRepository<ListFormWorkflow, string> criteriaRepository;
|
private readonly IRepository<ListFormWorkflow, string> criteriaRepository;
|
||||||
|
private readonly IListFormManager listFormManager;
|
||||||
|
private readonly IListFormAuthorizationManager authManager;
|
||||||
|
private readonly IListFormSelectAppService listFormSelectAppService;
|
||||||
|
private readonly IQueryManager queryManager;
|
||||||
|
|
||||||
public ListFormWorkflowAppService(IRepository<ListFormWorkflow, string> criteriaRepository)
|
public ListFormWorkflowAppService(
|
||||||
|
IRepository<ListFormWorkflow, string> criteriaRepository,
|
||||||
|
IListFormManager listFormManager,
|
||||||
|
IListFormAuthorizationManager authManager,
|
||||||
|
IListFormSelectAppService listFormSelectAppService,
|
||||||
|
IQueryManager queryManager)
|
||||||
{
|
{
|
||||||
this.criteriaRepository = criteriaRepository;
|
this.criteriaRepository = criteriaRepository;
|
||||||
|
this.listFormManager = listFormManager;
|
||||||
|
this.authManager = authManager;
|
||||||
|
this.listFormSelectAppService = listFormSelectAppService;
|
||||||
|
this.queryManager = queryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("state")]
|
[HttpGet("criteria")]
|
||||||
public async Task<ListFormWorkflowStateDto> GetStateAsync(string listFormCode = null)
|
public async Task<ListFormWorkflowStateDto> GetCriteriaAsync(string listFormCode = null)
|
||||||
{
|
{
|
||||||
var code = NormalizeListFormCode(listFormCode);
|
var code = listFormCode;
|
||||||
var criteria = (await criteriaRepository.GetListAsync(x => x.ListFormCode == code))
|
var criteria = (await criteriaRepository.GetListAsync(x => x.ListFormCode == code))
|
||||||
.OrderBy(x => x.PositionX)
|
.OrderBy(x => x.Id)
|
||||||
.ThenBy(x => x.PositionY)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return new ListFormWorkflowStateDto
|
return new ListFormWorkflowStateDto
|
||||||
{
|
{
|
||||||
Criteria = criteria.Select(MapCriteria).ToList()
|
Criteria = OrderWorkflowCriteria(criteria.Select(MapCriteria).ToList())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<ListFormWorkflowCriteriaDto> OrderWorkflowCriteria(List<ListFormWorkflowCriteriaDto> criteria)
|
||||||
|
{
|
||||||
|
if (criteria.Count <= 1)
|
||||||
|
{
|
||||||
|
return criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
var criteriaById = criteria.ToDictionary(x => x.Id);
|
||||||
|
var ordered = new List<ListFormWorkflowCriteriaDto>();
|
||||||
|
var visited = new HashSet<string>();
|
||||||
|
var visiting = new HashSet<string>();
|
||||||
|
|
||||||
|
var roots = criteria
|
||||||
|
.Where(x => x.Kind == "Start")
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (roots.Count == 0)
|
||||||
|
{
|
||||||
|
var referencedIds = criteria
|
||||||
|
.SelectMany(GetWorkflowTargetIds)
|
||||||
|
.Where(criteriaById.ContainsKey)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
roots = criteria
|
||||||
|
.Where(x => !referencedIds.Contains(x.Id))
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var root in roots)
|
||||||
|
{
|
||||||
|
VisitWorkflowCriteria(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in criteria.OrderBy(x => x.Id))
|
||||||
|
{
|
||||||
|
VisitWorkflowCriteria(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
|
||||||
|
void VisitWorkflowCriteria(ListFormWorkflowCriteriaDto item)
|
||||||
|
{
|
||||||
|
if (visited.Contains(item.Id) || visiting.Contains(item.Id))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visiting.Add(item.Id);
|
||||||
|
ordered.Add(item);
|
||||||
|
|
||||||
|
foreach (var targetId in GetWorkflowTargetIds(item))
|
||||||
|
{
|
||||||
|
if (criteriaById.TryGetValue(targetId, out var target))
|
||||||
|
{
|
||||||
|
VisitWorkflowCriteria(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visiting.Remove(item.Id);
|
||||||
|
visited.Add(item.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetWorkflowTargetIds(ListFormWorkflowCriteriaDto criteria)
|
||||||
|
{
|
||||||
|
if (!criteria.NextOnStart.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var outcome in criteria.CompareOutcomes ?? [])
|
||||||
|
{
|
||||||
|
if (!outcome.TargetId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return outcome.TargetId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnTrue.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnTrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnFalse.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnFalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnApprove.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnApprove;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!criteria.NextOnReject.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
yield return criteria.NextOnReject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("criteria")]
|
[HttpPost("criteria")]
|
||||||
public async Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input)
|
public async Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input)
|
||||||
{
|
{
|
||||||
var code = NormalizeListFormCode(input.ListFormCode);
|
var code = input.ListFormCode;
|
||||||
var isNew = input.Id.IsNullOrWhiteSpace();
|
var isNew = input.Id.IsNullOrWhiteSpace();
|
||||||
var criteria = isNew
|
var criteria = isNew
|
||||||
? new ListFormWorkflow(await GenerateNextCriteriaIdAsync())
|
? new ListFormWorkflow(await GenerateNextCriteriaIdAsync())
|
||||||
|
|
@ -58,7 +175,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 = NormalizeRequired(input.Title, criteria.Kind);
|
||||||
criteria.CompareColumn = NormalizeRequired(input.CompareColumn, "Tutar");
|
criteria.CompareColumn = NormalizeRequired(input.CompareColumn, "Price");
|
||||||
criteria.CompareOperator = NormalizeRequired(input.CompareOperator, ">");
|
criteria.CompareOperator = NormalizeRequired(input.CompareOperator, ">");
|
||||||
criteria.CompareValue = input.CompareValue;
|
criteria.CompareValue = input.CompareValue;
|
||||||
criteria.Approver = input.Approver ?? string.Empty;
|
criteria.Approver = input.Approver ?? string.Empty;
|
||||||
|
|
@ -110,7 +227,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
[HttpPost("reset-demo")]
|
[HttpPost("reset-demo")]
|
||||||
public async Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null)
|
public async Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null)
|
||||||
{
|
{
|
||||||
var code = NormalizeListFormCode(listFormCode);
|
var code = listFormCode;
|
||||||
var existing = await criteriaRepository.GetListAsync(x => x.ListFormCode == code);
|
var existing = await criteriaRepository.GetListAsync(x => x.ListFormCode == code);
|
||||||
foreach (var item in existing)
|
foreach (var item in existing)
|
||||||
{
|
{
|
||||||
|
|
@ -121,7 +238,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
var compare = await CreateCriteriaAsync(code, "Compare", "Tutar kontrolü", 330, 130);
|
var compare = await CreateCriteriaAsync(code, "Compare", "Tutar kontrolü", 330, 130);
|
||||||
var approval = await CreateCriteriaAsync(code, "Approval", "Yönetici Onayı", 590, 60, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
|
var approval = await CreateCriteriaAsync(code, "Approval", "Yönetici Onayı", 590, 60, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
|
||||||
var inform = await CreateCriteriaAsync(code, "Inform", "Muhasebe Bilgilendirme", 590, 230, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
|
var inform = await CreateCriteriaAsync(code, "Inform", "Muhasebe Bilgilendirme", 590, 230, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue);
|
||||||
var end = await CreateCriteriaAsync(code, "End", "Akışı Bitir", 850, 150);
|
var end = await CreateCriteriaAsync(code, "End", "İş Akışı Bitir", 850, 150);
|
||||||
|
|
||||||
start.NextOnStart = compare.Id;
|
start.NextOnStart = compare.Id;
|
||||||
compare.NextOnTrue = approval.Id;
|
compare.NextOnTrue = approval.Id;
|
||||||
|
|
@ -131,13 +248,13 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
{
|
{
|
||||||
Label = "Onay gerekir",
|
Label = "Onay gerekir",
|
||||||
TargetId = approval.Id,
|
TargetId = approval.Id,
|
||||||
Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = ">", CompareValue = 5000 }]
|
Conditions = [new WorkflowConditionDto { CompareColumn = "Price", CompareOperator = ">", CompareValue = 5000 }]
|
||||||
},
|
},
|
||||||
new CompareOutcomeDto
|
new CompareOutcomeDto
|
||||||
{
|
{
|
||||||
Label = "Bilgilendir",
|
Label = "Bilgilendir",
|
||||||
TargetId = inform.Id,
|
TargetId = inform.Id,
|
||||||
Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = "<=", CompareValue = 5000 }]
|
Conditions = [new WorkflowConditionDto { CompareColumn = "Price", CompareOperator = "<=", CompareValue = 5000 }]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
approval.NextOnApprove = inform.Id;
|
approval.NextOnApprove = inform.Id;
|
||||||
|
|
@ -149,7 +266,422 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
await criteriaRepository.UpdateAsync(approval, autoSave: true);
|
await criteriaRepository.UpdateAsync(approval, autoSave: true);
|
||||||
await criteriaRepository.UpdateAsync(inform, autoSave: true);
|
await criteriaRepository.UpdateAsync(inform, autoSave: true);
|
||||||
|
|
||||||
return await GetStateAsync(code);
|
return await GetCriteriaAsync(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("start")]
|
||||||
|
public async Task<WorkflowRunResultDto> StartAsync(StartWorkflowDto input)
|
||||||
|
{
|
||||||
|
if (input.Keys?.Length > 1)
|
||||||
|
{
|
||||||
|
return await RunForEachKeyAsync(input.Keys, keys => StartSingleAsync(input.ListFormCode, keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await StartSingleAsync(input.ListFormCode, input.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<WorkflowRunResultDto> StartSingleAsync(string listFormCode, object[] keys)
|
||||||
|
{
|
||||||
|
var context = await CreateRunContextAsync(listFormCode, keys);
|
||||||
|
var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start")
|
||||||
|
?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı.");
|
||||||
|
|
||||||
|
return await RunUntilWaitAsync(context, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("decision")]
|
||||||
|
public async Task<WorkflowRunResultDto> DecisionAsync(DecisionWorkflowDto input)
|
||||||
|
{
|
||||||
|
if (input.Keys?.Length > 1)
|
||||||
|
{
|
||||||
|
return await RunForEachKeyAsync(input.Keys, keys => DecisionSingleAsync(input, keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await DecisionSingleAsync(input, input.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<WorkflowRunResultDto> DecisionSingleAsync(DecisionWorkflowDto input, object[] keys)
|
||||||
|
{
|
||||||
|
var context = await CreateRunContextAsync(input.ListFormCode, keys);
|
||||||
|
var currentNodeId = GetRowValue(context.Row, context.Workflow.ApprovalStatusFieldName)?.ToString();
|
||||||
|
var current = FindCurrentCriteria(context.Criteria, currentNodeId);
|
||||||
|
|
||||||
|
if (current == null && currentNodeId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start")
|
||||||
|
?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı.");
|
||||||
|
|
||||||
|
var started = await RunUntilWaitAsync(context, start);
|
||||||
|
current = FindCurrentCriteria(context.Criteria, started.CurrentNodeId);
|
||||||
|
}
|
||||||
|
else if (current != null && current.Kind != "Approval" && current.Kind != "End")
|
||||||
|
{
|
||||||
|
var progressed = await RunUntilWaitAsync(context, current);
|
||||||
|
current = FindCurrentCriteria(context.Criteria, progressed.CurrentNodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current?.Kind != "Approval")
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Seçili kayıt onay adımında beklemiyor.");
|
||||||
|
}
|
||||||
|
if (!input.CurrentNodeId.IsNullOrWhiteSpace() && input.CurrentNodeId != current.Id)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Seçili kayıt bu onay adımında beklemiyor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var update = new Dictionary<string, object>();
|
||||||
|
if (!context.Workflow.ApprovalUserFieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
update[context.Workflow.ApprovalUserFieldName] = CurrentUser.UserName ?? CurrentUser.Name ?? string.Empty;
|
||||||
|
}
|
||||||
|
if (!context.Workflow.ApprovalDateFieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
update[context.Workflow.ApprovalDateFieldName] = Clock.Now;
|
||||||
|
}
|
||||||
|
if (!context.Workflow.ApprovalDescriptionFieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
update[context.Workflow.ApprovalDescriptionFieldName] = input.Note ?? string.Empty;
|
||||||
|
context.UserUpdatedFields.Add(context.Workflow.ApprovalDescriptionFieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.Count > 0)
|
||||||
|
{
|
||||||
|
await UpdateRowAsync(context, update);
|
||||||
|
MergeRowValues(context.Row, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = FindNextCriteria(context.Criteria, input.Approved ? current.NextOnApprove : current.NextOnReject);
|
||||||
|
return await RunUntilWaitAsync(context, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<WorkflowRunResultDto> RunForEachKeyAsync(
|
||||||
|
object[] keys,
|
||||||
|
Func<object[], Task<WorkflowRunResultDto>> action)
|
||||||
|
{
|
||||||
|
var results = new List<WorkflowRunResultDto>();
|
||||||
|
|
||||||
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
|
results.Add(await action([key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
var last = results.LastOrDefault();
|
||||||
|
return new WorkflowRunResultDto
|
||||||
|
{
|
||||||
|
Keys = keys,
|
||||||
|
CurrentNodeId = last?.CurrentNodeId,
|
||||||
|
CurrentNodeTitle = last?.CurrentNodeTitle,
|
||||||
|
CurrentNodeKind = last?.CurrentNodeKind,
|
||||||
|
WaitingApproval = results.Any(x => x.WaitingApproval),
|
||||||
|
Completed = results.All(x => x.Completed)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<WorkflowRunContext> CreateRunContextAsync(string listFormCode, object[] keys)
|
||||||
|
{
|
||||||
|
var code = listFormCode;
|
||||||
|
if (!await authManager.CanAccess(code, AuthorizationTypeEnum.Update))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Workflow işlemi için güncelleme yetkiniz yok.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var listForm = await listFormManager.GetUserListForm(code);
|
||||||
|
var workflow = DeserializeWorkflow(listForm.WorkflowJson);
|
||||||
|
if (workflow.ApprovalStatusFieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Workflow durum alanı tanımlı değil.");
|
||||||
|
}
|
||||||
|
if (listForm.KeyFieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Liste formu anahtar alanı tanımlı değil.");
|
||||||
|
}
|
||||||
|
if (keys == null || keys.Length == 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Workflow için kayıt anahtarı seçilmelidir.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var criteria = (await criteriaRepository.GetListAsync(x => x.ListFormCode == code))
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
var row = await GetRowAsync(code, listForm.KeyFieldName, keys[0]);
|
||||||
|
|
||||||
|
return new WorkflowRunContext(code, listForm.KeyFieldName, keys, workflow, criteria, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IDictionary<string, object>> GetRowAsync(string listFormCode, string keyFieldName, object key)
|
||||||
|
{
|
||||||
|
var filter = JsonSerializer.Serialize(new object[] { keyFieldName, "=", key });
|
||||||
|
var result = await listFormSelectAppService.GetSelectAsync(new SelectRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Filter = filter,
|
||||||
|
Skip = 0,
|
||||||
|
Take = 1,
|
||||||
|
RequireTotalCount = false,
|
||||||
|
RequireGroupCount = false
|
||||||
|
});
|
||||||
|
|
||||||
|
var row = ((result?.Data as System.Collections.IEnumerable)?.Cast<object>()?.FirstOrDefault())
|
||||||
|
?? throw new UserFriendlyException("Workflow kaydı bulunamadı.");
|
||||||
|
|
||||||
|
if (row is IDictionary<string, object> dictionary)
|
||||||
|
{
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UserFriendlyException("Workflow kaydı okunamadı.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<WorkflowRunResultDto> RunUntilWaitAsync(WorkflowRunContext context, ListFormWorkflow current)
|
||||||
|
{
|
||||||
|
var visited = new HashSet<string>();
|
||||||
|
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
if (!visited.Add(current.Id))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("Workflow adımlarında döngü tespit edildi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await ApplyNodeAsync(context, current);
|
||||||
|
|
||||||
|
if (current.Kind == "Approval")
|
||||||
|
{
|
||||||
|
return ToRunResult(context, current, waitingApproval: true, completed: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.Kind == "End")
|
||||||
|
{
|
||||||
|
return ToRunResult(context, current, waitingApproval: false, completed: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
current = FindNextCriteria(context.Criteria, ResolveNextNodeId(context, current));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToRunResult(context, null, waitingApproval: false, completed: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyNodeAsync(WorkflowRunContext context, ListFormWorkflow node)
|
||||||
|
{
|
||||||
|
var update = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
[context.Workflow.ApprovalStatusFieldName] = node.Title
|
||||||
|
};
|
||||||
|
|
||||||
|
if (node.Kind == "Approval")
|
||||||
|
{
|
||||||
|
if (!context.Workflow.ApprovalUserFieldName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
update[context.Workflow.ApprovalUserFieldName] = node.Approver ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node.Kind == "End")
|
||||||
|
{
|
||||||
|
if (!context.Workflow.ApprovalUserFieldName.IsNullOrWhiteSpace() &&
|
||||||
|
IsRowValueEmpty(context.Row, context.Workflow.ApprovalUserFieldName))
|
||||||
|
{
|
||||||
|
update[context.Workflow.ApprovalUserFieldName] = PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue;
|
||||||
|
}
|
||||||
|
if (!context.Workflow.ApprovalDateFieldName.IsNullOrWhiteSpace() &&
|
||||||
|
IsRowValueEmpty(context.Row, context.Workflow.ApprovalDateFieldName))
|
||||||
|
{
|
||||||
|
update[context.Workflow.ApprovalDateFieldName] = Clock.Now;
|
||||||
|
}
|
||||||
|
if (!context.Workflow.ApprovalDescriptionFieldName.IsNullOrWhiteSpace() &&
|
||||||
|
!context.UserUpdatedFields.Contains(context.Workflow.ApprovalDescriptionFieldName) &&
|
||||||
|
IsRowValueEmpty(context.Row, context.Workflow.ApprovalDescriptionFieldName))
|
||||||
|
{
|
||||||
|
update[context.Workflow.ApprovalDescriptionFieldName] = SystemApprovalDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateRowAsync(context, update);
|
||||||
|
MergeRowValues(context.Row, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateRowAsync(WorkflowRunContext context, Dictionary<string, object> data)
|
||||||
|
{
|
||||||
|
await queryManager.GenerateAndRunQueryAsync<int>(
|
||||||
|
context.ListFormCode,
|
||||||
|
OperationEnum.Update,
|
||||||
|
JsonSerializer.Serialize(data),
|
||||||
|
context.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ResolveNextNodeId(WorkflowRunContext context, ListFormWorkflow current)
|
||||||
|
{
|
||||||
|
if (current.Kind == "Start" || current.Kind == "Inform")
|
||||||
|
{
|
||||||
|
return current.NextOnStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.Kind == "Compare")
|
||||||
|
{
|
||||||
|
var outcomes = DeserializeCompareOutcomes(current.CompareOutcomesJson);
|
||||||
|
var matched = outcomes.FirstOrDefault(outcome =>
|
||||||
|
outcome.Conditions == null ||
|
||||||
|
outcome.Conditions.Count == 0 ||
|
||||||
|
outcome.Conditions.All(condition => EvaluateCondition(context.Row, condition, current.CompareColumn)));
|
||||||
|
|
||||||
|
if (matched != null && !matched.TargetId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return matched.TargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EvaluateCondition(context.Row, new WorkflowConditionDto
|
||||||
|
{
|
||||||
|
CompareColumn = current.CompareColumn,
|
||||||
|
CompareOperator = current.CompareOperator,
|
||||||
|
CompareValue = current.CompareValue
|
||||||
|
}, current.CompareColumn)
|
||||||
|
? current.NextOnTrue
|
||||||
|
: current.NextOnFalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool EvaluateCondition(
|
||||||
|
IDictionary<string, object> row,
|
||||||
|
WorkflowConditionDto condition,
|
||||||
|
string fallbackCompareColumn = null)
|
||||||
|
{
|
||||||
|
if (condition == null || condition.CompareColumn.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var compareColumn = ResolveCompareColumn(row, condition.CompareColumn, fallbackCompareColumn);
|
||||||
|
if (!TryGetRowValue(row, compareColumn, out var rawValue))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(
|
||||||
|
$"Workflow karşılaştırma alanı bulunamadı: {condition.CompareColumn}. Mevcut alanlar: {string.Join(", ", row.Keys)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawValue == null ||
|
||||||
|
(!decimal.TryParse(rawValue.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var value) &&
|
||||||
|
!decimal.TryParse(rawValue.ToString(), NumberStyles.Any, CultureInfo.CurrentCulture, out value)))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(
|
||||||
|
$"Workflow karşılaştırma alanı sayısal değer olarak okunamadı: {compareColumn} = {rawValue}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition.CompareOperator switch
|
||||||
|
{
|
||||||
|
">" => value > condition.CompareValue,
|
||||||
|
">=" => value >= condition.CompareValue,
|
||||||
|
"<" => value < condition.CompareValue,
|
||||||
|
"<=" => value <= condition.CompareValue,
|
||||||
|
"==" or "=" => value == condition.CompareValue,
|
||||||
|
"!=" or "<>" => value != condition.CompareValue,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveCompareColumn(
|
||||||
|
IDictionary<string, object> row,
|
||||||
|
string compareColumn,
|
||||||
|
string fallbackCompareColumn)
|
||||||
|
{
|
||||||
|
if (TryGetRowValue(row, compareColumn, out _))
|
||||||
|
{
|
||||||
|
return compareColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fallbackCompareColumn.IsNullOrWhiteSpace() &&
|
||||||
|
TryGetRowValue(row, fallbackCompareColumn, out _))
|
||||||
|
{
|
||||||
|
return fallbackCompareColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
var numericKeys = row
|
||||||
|
.Where(item =>
|
||||||
|
item.Value != null &&
|
||||||
|
decimal.TryParse(item.Value.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out _))
|
||||||
|
.Select(item => item.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (numericKeys.Count == 1)
|
||||||
|
{
|
||||||
|
return numericKeys[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UserFriendlyException(
|
||||||
|
$"Workflow karşılaştırma alanı bulunamadı: {compareColumn}. Mevcut alanlar: {string.Join(", ", row.Keys)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object GetRowValue(IDictionary<string, object> row, string fieldName)
|
||||||
|
{
|
||||||
|
return TryGetRowValue(row, fieldName, out var value) ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRowValueEmpty(IDictionary<string, object> row, string fieldName)
|
||||||
|
{
|
||||||
|
if (!TryGetRowValue(row, fieldName, out var value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value == null || value == DBNull.Value || value.ToString().IsNullOrWhiteSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetRowValue(IDictionary<string, object> row, string fieldName, out object value)
|
||||||
|
{
|
||||||
|
if (row.TryGetValue(fieldName, out value))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = row.Keys.FirstOrDefault(x => string.Equals(x, fieldName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = row[key];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MergeRowValues(IDictionary<string, object> row, Dictionary<string, object> values)
|
||||||
|
{
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
row[value.Key] = value.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListFormWorkflow FindNextCriteria(List<ListFormWorkflow> criteria, string id)
|
||||||
|
{
|
||||||
|
return id.IsNullOrWhiteSpace() ? null : criteria.FirstOrDefault(x => x.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListFormWorkflow FindCurrentCriteria(List<ListFormWorkflow> criteria, string currentNodeId)
|
||||||
|
{
|
||||||
|
if (currentNodeId.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return criteria.FirstOrDefault(x => string.Equals(x.Id, currentNodeId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? criteria.FirstOrDefault(x => string.Equals(x.Title, currentNodeId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WorkflowRunResultDto ToRunResult(
|
||||||
|
WorkflowRunContext context,
|
||||||
|
ListFormWorkflow node,
|
||||||
|
bool waitingApproval,
|
||||||
|
bool completed)
|
||||||
|
{
|
||||||
|
return new WorkflowRunResultDto
|
||||||
|
{
|
||||||
|
Keys = context.Keys,
|
||||||
|
CurrentNodeId = node?.Id,
|
||||||
|
CurrentNodeTitle = node?.Title,
|
||||||
|
CurrentNodeKind = node?.Kind,
|
||||||
|
WaitingApproval = waitingApproval,
|
||||||
|
Completed = completed
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ListFormWorkflow> CreateCriteriaAsync(
|
private async Task<ListFormWorkflow> CreateCriteriaAsync(
|
||||||
|
|
@ -165,7 +697,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
ListFormCode = listFormCode,
|
ListFormCode = listFormCode,
|
||||||
Kind = kind,
|
Kind = kind,
|
||||||
Title = title,
|
Title = title,
|
||||||
CompareColumn = "Tutar",
|
CompareColumn = "Price",
|
||||||
CompareOperator = ">",
|
CompareOperator = ">",
|
||||||
CompareValue = 5000,
|
CompareValue = 5000,
|
||||||
Approver = approver,
|
Approver = approver,
|
||||||
|
|
@ -287,11 +819,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeListFormCode(string listFormCode)
|
|
||||||
{
|
|
||||||
return listFormCode.IsNullOrWhiteSpace() ? DefaultListFormCode : listFormCode.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeRequired(string value, string fallback)
|
private static string NormalizeRequired(string value, string fallback)
|
||||||
{
|
{
|
||||||
return value.IsNullOrWhiteSpace() ? fallback : value.Trim();
|
return value.IsNullOrWhiteSpace() ? fallback : value.Trim();
|
||||||
|
|
@ -318,4 +845,32 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static WorkflowDto DeserializeWorkflow(string json)
|
||||||
|
{
|
||||||
|
if (json.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return new WorkflowDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<WorkflowDto>(json) ?? new WorkflowDto();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new WorkflowDto();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record WorkflowRunContext(
|
||||||
|
string ListFormCode,
|
||||||
|
string KeyFieldName,
|
||||||
|
object[] Keys,
|
||||||
|
WorkflowDto Workflow,
|
||||||
|
List<ListFormWorkflow> Criteria,
|
||||||
|
IDictionary<string, object> Row)
|
||||||
|
{
|
||||||
|
public HashSet<string> UserUpdatedFields { get; } = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3624,6 +3624,12 @@
|
||||||
"en": "MENU",
|
"en": "MENU",
|
||||||
"tr": "MENÜ"
|
"tr": "MENÜ"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListForm.SelectRecord",
|
||||||
|
"en": "Please select a row.",
|
||||||
|
"tr": "Lütfen bir satır seçin."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "ListForms.ListForm.ClearRedisCache",
|
"key": "ListForms.ListForm.ClearRedisCache",
|
||||||
|
|
@ -16292,6 +16298,12 @@
|
||||||
"en": "Status",
|
"en": "Status",
|
||||||
"tr": "Durum"
|
"tr": "Durum"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.Connection",
|
||||||
|
"en": "Connection",
|
||||||
|
"tr": "Bağlantı"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Listform.ListformField.FailureReason",
|
"key": "App.Listform.ListformField.FailureReason",
|
||||||
|
|
@ -16580,6 +16592,48 @@
|
||||||
"en": "Title",
|
"en": "Title",
|
||||||
"tr": "Başlık"
|
"tr": "Başlık"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.Approver",
|
||||||
|
"en": "Approver",
|
||||||
|
"tr": "Onayla"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.NextOnStart",
|
||||||
|
"en": "Next on Start",
|
||||||
|
"tr": "Sonraki Adım"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.NextOnApprove",
|
||||||
|
"en": "Next on Approve",
|
||||||
|
"tr": "Onay Adımı"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.NextOnReject",
|
||||||
|
"en": "Next on Reject",
|
||||||
|
"tr": "Red Adımı"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.AddCompareOutcome",
|
||||||
|
"en": "Add Compare Outcome",
|
||||||
|
"tr": "Karşılaştırma Adımı Ekle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.CompareOutcomes",
|
||||||
|
"en": "Compare Outcomes",
|
||||||
|
"tr": "Karşılaştırma Adımları"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.Listform.ListformField.LoadingColumns",
|
||||||
|
"en": "Loading Columns...",
|
||||||
|
"tr": "Sütunlar Yükleniyor..."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.Listform.ListformField.Total",
|
"key": "App.Listform.ListformField.Total",
|
||||||
|
|
@ -18745,6 +18799,12 @@
|
||||||
"key": "ListForms.ListFormEdit.Workflow.ApprovalStatusFieldName",
|
"key": "ListForms.ListFormEdit.Workflow.ApprovalStatusFieldName",
|
||||||
"en": "Approval Status Field Name",
|
"en": "Approval Status Field Name",
|
||||||
"tr": "Onay Durumu Alanı Adı"
|
"tr": "Onay Durumu Alanı Adı"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName",
|
||||||
|
"en": "Approval Description Field Name",
|
||||||
|
"tr": "Onay Açıklaması Alanı Adı"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +135,8 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
||||||
List<ListFormCustomization> listFormCustomizations = null,
|
List<ListFormCustomization> listFormCustomizations = null,
|
||||||
QueryParameters queryParams = null)
|
QueryParameters queryParams = null)
|
||||||
{
|
{
|
||||||
|
ResetQueryState();
|
||||||
|
|
||||||
if (listform == null)
|
if (listform == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -213,6 +215,28 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResetQueryState()
|
||||||
|
{
|
||||||
|
SelectCommand = null;
|
||||||
|
TableName = null;
|
||||||
|
KeyFieldName = null;
|
||||||
|
SelectFields = [];
|
||||||
|
JoinParts = [];
|
||||||
|
WhereParts = [];
|
||||||
|
SortParts = [];
|
||||||
|
SelectQuery = null;
|
||||||
|
SelectQueryParameters = [];
|
||||||
|
TotalCountQuery = null;
|
||||||
|
GroupQuery = null;
|
||||||
|
DeleteQuery = null;
|
||||||
|
ChartQuery = null;
|
||||||
|
GroupTuples = [];
|
||||||
|
GroupSummaryTuples = [];
|
||||||
|
SummaryQueries = [];
|
||||||
|
IsAppliedGridFilter = false;
|
||||||
|
IsAppliedServerFilter = false;
|
||||||
|
}
|
||||||
|
|
||||||
private List<SelectField> GetSelectAndJoinFields(List<ListFormField> listFormFields)
|
private List<SelectField> GetSelectAndJoinFields(List<ListFormField> listFormFields)
|
||||||
{
|
{
|
||||||
List<SelectField> selectFields = [];
|
List<SelectField> selectFields = [];
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260523104659_Initial")]
|
[Migration("20260523160811_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -1,6 +1,17 @@
|
||||||
{
|
{
|
||||||
"commit": "e9d8f5e",
|
"commit": "8f3932b",
|
||||||
"releases": [
|
"releases": [
|
||||||
|
{
|
||||||
|
"version": "1.0.10",
|
||||||
|
"buildDate": "2026-05-11",
|
||||||
|
"commit": "414006204e324018be597a598d3dd102868c5cca",
|
||||||
|
"changeLog": [
|
||||||
|
"- Backup dosyalarını son 5 günün kalması",
|
||||||
|
"- Grid için Fit Columns özelliği eklendi.",
|
||||||
|
"- Notification Desktop, UiActivity, UiToast özelliği eklendi",
|
||||||
|
"- Versiyon güncellemeleri için \"System Updating\" mesajı"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"buildDate": "2026-05-09",
|
"buildDate": "2026-05-09",
|
||||||
|
|
@ -93,4 +104,4 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -586,6 +586,7 @@ export interface GridOptionsDto extends AuditedEntityDto<string> {
|
||||||
extraFilterJson?: string
|
extraFilterJson?: string
|
||||||
extraFilterDto: ExtraFilterDto[]
|
extraFilterDto: ExtraFilterDto[]
|
||||||
layoutDto: LayoutDto
|
layoutDto: LayoutDto
|
||||||
|
workflowDto: WorkflowDto
|
||||||
|
|
||||||
//ChartEditDto
|
//ChartEditDto
|
||||||
userId?: string
|
userId?: string
|
||||||
|
|
@ -663,7 +664,6 @@ export interface GridOptionsEditDto extends GridOptionsDto, Record<string, any>
|
||||||
formFieldsDefaultValueDto: FieldsDefaultValueDto[]
|
formFieldsDefaultValueDto: FieldsDefaultValueDto[]
|
||||||
widgetsJson?: string
|
widgetsJson?: string
|
||||||
widgetsDto: WidgetEditDto[]
|
widgetsDto: WidgetEditDto[]
|
||||||
workflowDto: WorkflowDto
|
|
||||||
extraFilterEditDto: ExtraFilterEditDto[]
|
extraFilterEditDto: ExtraFilterEditDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -910,6 +910,39 @@ export interface WorkflowDto {
|
||||||
approvalUserFieldName: string
|
approvalUserFieldName: string
|
||||||
approvalDateFieldName: string
|
approvalDateFieldName: string
|
||||||
approvalStatusFieldName: string
|
approvalStatusFieldName: string
|
||||||
|
approvalDescriptionFieldName: string
|
||||||
|
criteria: ListFormWorkflowCriteriaDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowConditionDto {
|
||||||
|
compareColumn: string
|
||||||
|
compareOperator: string
|
||||||
|
compareValue: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompareOutcomeDto {
|
||||||
|
label: string
|
||||||
|
targetId?: string | null
|
||||||
|
conditions: WorkflowConditionDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListFormWorkflowCriteriaDto {
|
||||||
|
id: string
|
||||||
|
listFormCode: string
|
||||||
|
kind: string
|
||||||
|
title: string
|
||||||
|
compareColumn: string
|
||||||
|
compareOperator: string
|
||||||
|
compareValue: number
|
||||||
|
approver: string
|
||||||
|
nextOnStart?: string | null
|
||||||
|
nextOnTrue?: string | null
|
||||||
|
nextOnFalse?: string | null
|
||||||
|
nextOnApprove?: string | null
|
||||||
|
nextOnReject?: string | null
|
||||||
|
positionX: number
|
||||||
|
positionY: number
|
||||||
|
compareOutcomes: CompareOutcomeDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayoutDto {
|
export interface LayoutDto {
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,19 @@ export interface WorkflowCriteriaDto {
|
||||||
compareOutcomes: CompareOutcomeDto[]
|
compareOutcomes: CompareOutcomeDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowStateDto {
|
export interface WorkflowCriteriasDto {
|
||||||
criteria: WorkflowCriteriaDto[]
|
criteria: WorkflowCriteriaDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WorkflowRunResultDto {
|
||||||
|
keys: unknown[]
|
||||||
|
currentNodeId?: string | null
|
||||||
|
currentNodeTitle?: string | null
|
||||||
|
currentNodeKind?: string | null
|
||||||
|
waitingApproval: boolean
|
||||||
|
completed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {
|
export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {
|
||||||
id?: string | null
|
id?: string | null
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -44,10 +53,10 @@ export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {
|
||||||
const baseUrl = '/api/app/list-form-workflow'
|
const baseUrl = '/api/app/list-form-workflow'
|
||||||
|
|
||||||
export const workflowService = {
|
export const workflowService = {
|
||||||
async getState(listFormCode?: string) {
|
async getCriteria(listFormCode?: string) {
|
||||||
const response = await apiService.fetchData<WorkflowStateDto>({
|
const response = await apiService.fetchData<WorkflowCriteriasDto>({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${baseUrl}/state`,
|
url: `${baseUrl}/criteria`,
|
||||||
params: { listFormCode },
|
params: { listFormCode },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -72,7 +81,7 @@ export const workflowService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async resetDemo(listFormCode?: string) {
|
async resetDemo(listFormCode?: string) {
|
||||||
const response = await apiService.fetchData<WorkflowStateDto>({
|
const response = await apiService.fetchData<WorkflowCriteriasDto>({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${baseUrl}/reset-demo`,
|
url: `${baseUrl}/reset-demo`,
|
||||||
params: { listFormCode },
|
params: { listFormCode },
|
||||||
|
|
@ -80,4 +89,30 @@ export const workflowService = {
|
||||||
|
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async startWorkflow(listFormCode: string, keys: unknown[]) {
|
||||||
|
const response = await apiService.fetchData<WorkflowRunResultDto>({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${baseUrl}/start`,
|
||||||
|
data: { listFormCode, keys },
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
|
||||||
|
async decideWorkflow(
|
||||||
|
listFormCode: string,
|
||||||
|
keys: unknown[],
|
||||||
|
approved: boolean,
|
||||||
|
note?: string,
|
||||||
|
currentNodeId?: string,
|
||||||
|
) {
|
||||||
|
const response = await apiService.fetchData<WorkflowRunResultDto>({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${baseUrl}/decision`,
|
||||||
|
data: { listFormCode, keys, approved, note, currentNodeId },
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useLocalization } from '../hooks/useLocalization'
|
||||||
import { getNodeHeight, nodeSize } from './workflowConstants'
|
import { getNodeHeight, nodeSize } from './workflowConstants'
|
||||||
import type {
|
import type {
|
||||||
CompareOutcomeDto,
|
CompareOutcomeDto,
|
||||||
|
|
@ -311,7 +312,7 @@ export function emptyCriteria(kind = 'Compare', listFormCode = ''): WorkflowCrit
|
||||||
listFormCode,
|
listFormCode,
|
||||||
kind,
|
kind,
|
||||||
title: defaultTitle(kind),
|
title: defaultTitle(kind),
|
||||||
compareColumn: 'Tutar',
|
compareColumn: 'Price',
|
||||||
compareOperator: '>',
|
compareOperator: '>',
|
||||||
compareValue: 5000,
|
compareValue: 5000,
|
||||||
approver: '',
|
approver: '',
|
||||||
|
|
@ -347,19 +348,31 @@ export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm
|
||||||
|
|
||||||
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
|
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
|
||||||
const sharedPerson = item.approver || ''
|
const sharedPerson = item.approver || ''
|
||||||
|
const compareOutcomes = (item.compareOutcomes || [])
|
||||||
|
.slice(0, 4)
|
||||||
|
.filter((outcome) => outcome.label?.trim())
|
||||||
|
.map(normalizeCompareOutcome)
|
||||||
|
const firstCompareColumn = compareOutcomes
|
||||||
|
.flatMap((outcome) => outcome.conditions || [])
|
||||||
|
.find((condition) => condition.compareColumn)?.compareColumn
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
|
||||||
id: item.id || null,
|
id: item.id || null,
|
||||||
listFormCode: item.listFormCode || '',
|
listFormCode: item.listFormCode || '',
|
||||||
|
kind: item.kind || 'Compare',
|
||||||
|
title: item.title || defaultTitle(item.kind || 'Compare'),
|
||||||
|
compareColumn: firstCompareColumn || item.compareColumn || 'Price',
|
||||||
|
compareOperator: item.compareOperator || '>',
|
||||||
compareValue: Number(item.compareValue || 0),
|
compareValue: Number(item.compareValue || 0),
|
||||||
approver: sharedPerson,
|
approver: sharedPerson,
|
||||||
|
nextOnStart: item.nextOnStart || '',
|
||||||
|
nextOnTrue: compareOutcomes[0]?.targetId || item.nextOnTrue || '',
|
||||||
|
nextOnFalse: compareOutcomes[1]?.targetId || item.nextOnFalse || '',
|
||||||
|
nextOnApprove: item.nextOnApprove || '',
|
||||||
|
nextOnReject: item.nextOnReject || '',
|
||||||
positionX: Number(item.positionX || 32),
|
positionX: Number(item.positionX || 32),
|
||||||
positionY: Number(item.positionY || 150),
|
positionY: Number(item.positionY || 150),
|
||||||
compareOutcomes: (item.compareOutcomes || [])
|
compareOutcomes,
|
||||||
.slice(0, 4)
|
|
||||||
.filter((outcome) => outcome.label?.trim())
|
|
||||||
.map(normalizeCompareOutcome),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,27 +380,27 @@ export function defaultTitle(kind: string) {
|
||||||
return (
|
return (
|
||||||
{
|
{
|
||||||
Start: 'İş Akışı Başlat',
|
Start: 'İş Akışı Başlat',
|
||||||
Compare: 'Tutar > 5000 TL',
|
Compare: 'Karşılaştırma',
|
||||||
Approval: 'Onaylanacak Kişi',
|
Approval: 'Onay',
|
||||||
Inform: 'Bilgilendirme Yapılacak Personel',
|
Inform: 'Bilgilendirme',
|
||||||
End: 'Akışı Bitir',
|
End: 'İş Akışı Bitir',
|
||||||
}[kind] ?? 'İş Akışı Adımı'
|
}[kind] ?? 'İş Akışı Adımı'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyCompareOutcome1(label = 'Durum'): CompareOutcomeDto {
|
export function emptyCompareOutcome1(label = 'Durum', compareColumn = 'Price'): CompareOutcomeDto {
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
targetId: '',
|
targetId: '',
|
||||||
conditions: [{ compareColumn: 'Tutar', compareOperator: '>', compareValue: 5000 }],
|
conditions: [{ compareColumn, compareOperator: '>', compareValue: 5000 }],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyCompareOutcome2(label = 'Durum'): CompareOutcomeDto {
|
export function emptyCompareOutcome2(label = 'Durum', compareColumn = 'Price'): CompareOutcomeDto {
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
targetId: '',
|
targetId: '',
|
||||||
conditions: [{ compareColumn: 'Tutar', compareOperator: '<=', compareValue: 5000 }],
|
conditions: [{ compareColumn, compareOperator: '<=', compareValue: 5000 }],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,7 +414,7 @@ export function toCompareOutcomeForm(
|
||||||
? outcome.conditions
|
? outcome.conditions
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
compareColumn: outcome.compareColumn || 'Tutar',
|
compareColumn: outcome.compareColumn || 'Price',
|
||||||
compareOperator: outcome.compareOperator || '>',
|
compareOperator: outcome.compareOperator || '>',
|
||||||
compareValue: outcome.compareValue || 0,
|
compareValue: outcome.compareValue || 0,
|
||||||
},
|
},
|
||||||
|
|
@ -411,7 +424,7 @@ export function toCompareOutcomeForm(
|
||||||
label: outcome.label || '',
|
label: outcome.label || '',
|
||||||
targetId: outcome.targetId || '',
|
targetId: outcome.targetId || '',
|
||||||
conditions: conditions.map((condition) => ({
|
conditions: conditions.map((condition) => ({
|
||||||
compareColumn: condition.compareColumn || 'Tutar',
|
compareColumn: condition.compareColumn || 'Price',
|
||||||
compareOperator: condition.compareOperator || '>',
|
compareOperator: condition.compareOperator || '>',
|
||||||
compareValue: condition.compareValue ?? 0,
|
compareValue: condition.compareValue ?? 0,
|
||||||
})),
|
})),
|
||||||
|
|
@ -429,7 +442,7 @@ export function normalizeCompareOutcome(
|
||||||
? outcome.conditions
|
? outcome.conditions
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
compareColumn: outcome.compareColumn || 'Tutar',
|
compareColumn: outcome.compareColumn || 'Price',
|
||||||
compareOperator: outcome.compareOperator || '>',
|
compareOperator: outcome.compareOperator || '>',
|
||||||
compareValue: outcome.compareValue || 0,
|
compareValue: outcome.compareValue || 0,
|
||||||
},
|
},
|
||||||
|
|
@ -437,7 +450,7 @@ export function normalizeCompareOutcome(
|
||||||
)
|
)
|
||||||
.filter((condition) => condition.compareOperator && String(condition.compareValue ?? '') !== '')
|
.filter((condition) => condition.compareOperator && String(condition.compareValue ?? '') !== '')
|
||||||
.map((condition) => ({
|
.map((condition) => ({
|
||||||
compareColumn: condition.compareColumn || 'Tutar',
|
compareColumn: condition.compareColumn || 'Price',
|
||||||
compareOperator: condition.compareOperator || '>',
|
compareOperator: condition.compareOperator || '>',
|
||||||
compareValue: Number(condition.compareValue || 0),
|
compareValue: Number(condition.compareValue || 0),
|
||||||
}))
|
}))
|
||||||
|
|
@ -488,9 +501,10 @@ export function criteriaSummary(item: WorkflowCriteriaDto) {
|
||||||
.join(' / ') || '-'
|
.join(' / ') || '-'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (item.kind === 'Approval') return item.approver || '-'
|
if (item.kind === 'Approval' || item.kind === 'Inform') {
|
||||||
if (item.kind === 'Inform') return item.approver || '-'
|
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
|
||||||
return item.title
|
}
|
||||||
|
return `${item.title} ${item.approver ? `- ${item.approver}` : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) {
|
export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export function FormTabWorkflow(
|
||||||
)
|
)
|
||||||
|
|
||||||
const loadState = useCallback(async () => {
|
const loadState = useCallback(async () => {
|
||||||
const data = await workflowService.getState(props.listFormCode)
|
const data = await workflowService.getCriteria(props.listFormCode)
|
||||||
setCriteria(data.criteria)
|
setCriteria(data.criteria)
|
||||||
return data
|
return data
|
||||||
}, [props.listFormCode])
|
}, [props.listFormCode])
|
||||||
|
|
@ -299,6 +299,7 @@ export function FormTabWorkflow(
|
||||||
approvalUserFieldName: string(),
|
approvalUserFieldName: string(),
|
||||||
approvalDateFieldName: string(),
|
approvalDateFieldName: string(),
|
||||||
approvalStatusFieldName: string(),
|
approvalStatusFieldName: string(),
|
||||||
|
approvalDescriptionFieldName: string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const initialValues = useStoreState((s) => s.admin.lists.values)
|
const initialValues = useStoreState((s) => s.admin.lists.values)
|
||||||
|
|
@ -319,7 +320,7 @@ export function FormTabWorkflow(
|
||||||
<Form>
|
<Form>
|
||||||
<FormContainer size="sm">
|
<FormContainer size="sm">
|
||||||
<Card className="my-2">
|
<Card className="my-2">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-2">
|
||||||
<FormItem
|
<FormItem
|
||||||
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalUserFieldName')}
|
label={translate('::ListForms.ListFormEdit.Workflow.ApprovalUserFieldName')}
|
||||||
invalid={
|
invalid={
|
||||||
|
|
@ -391,6 +392,33 @@ export function FormTabWorkflow(
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem
|
||||||
|
label={translate(
|
||||||
|
'::ListForms.ListFormEdit.Workflow.ApprovalDescriptionFieldName',
|
||||||
|
)}
|
||||||
|
invalid={
|
||||||
|
errors.workflowDto?.approvalDescriptionFieldName &&
|
||||||
|
touched.workflowDto?.approvalDescriptionFieldName
|
||||||
|
}
|
||||||
|
errorMessage={errors.workflowDto?.approvalDescriptionFieldName}
|
||||||
|
>
|
||||||
|
<Field type="text" name="workflowDto.approvalDescriptionFieldName">
|
||||||
|
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||||||
|
<Select
|
||||||
|
field={field}
|
||||||
|
form={form}
|
||||||
|
options={columnOptions}
|
||||||
|
isClearable={true}
|
||||||
|
value={columnOptions.filter(
|
||||||
|
(option) =>
|
||||||
|
option.value === values.workflowDto.approvalDescriptionFieldName,
|
||||||
|
)}
|
||||||
|
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button block variant="solid" loading={isSubmitting}>
|
<Button block variant="solid" loading={isSubmitting}>
|
||||||
|
|
|
||||||
|
|
@ -54,13 +54,55 @@ export function WorkflowCriteria({
|
||||||
.filter((item) => item.id !== formValues.id)
|
.filter((item) => item.id !== formValues.id)
|
||||||
.map((item) => ({ value: item.id, label: `${item.id} - ${item.title}` })),
|
.map((item) => ({ value: item.id, label: `${item.id} - ${item.title}` })),
|
||||||
]
|
]
|
||||||
|
const numericCompareColumn = selectCommandColumns.find((column) =>
|
||||||
|
isNumericDataType(column.dataType),
|
||||||
|
)
|
||||||
const compareColumnOptions = selectCommandColumns.length
|
const compareColumnOptions = selectCommandColumns.length
|
||||||
? selectCommandColumns.map((column) => ({
|
? selectCommandColumns.map((column) => ({
|
||||||
value: column.columnName,
|
value: column.columnName,
|
||||||
label: `${column.columnName} (${column.dataType})`,
|
label: `${column.columnName} (${column.dataType})`,
|
||||||
}))
|
}))
|
||||||
: []
|
: []
|
||||||
const defaultCompareColumn = compareColumnOptions[0]?.value ?? 'Tutar'
|
const defaultCompareColumn =
|
||||||
|
numericCompareColumn?.columnName ?? compareColumnOptions[0]?.value ?? 'Price'
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (formValues.kind !== 'Compare' || !compareColumnOptions.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const validColumns = new Set(compareColumnOptions.map((option) => option.value))
|
||||||
|
let changed = false
|
||||||
|
const nextOutcomes = (formValues.compareOutcomes || []).map(
|
||||||
|
(outcome: CompareOutcomeDto) => ({
|
||||||
|
...outcome,
|
||||||
|
conditions: (outcome.conditions || []).map((condition) => {
|
||||||
|
if (validColumns.has(condition.compareColumn)) {
|
||||||
|
return condition
|
||||||
|
}
|
||||||
|
|
||||||
|
changed = true
|
||||||
|
return { ...condition, compareColumn: defaultCompareColumn }
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const nextCompareColumn = validColumns.has(formValues.compareColumn)
|
||||||
|
? formValues.compareColumn
|
||||||
|
: defaultCompareColumn
|
||||||
|
|
||||||
|
if (nextCompareColumn !== formValues.compareColumn) {
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
onChange({
|
||||||
|
...formValues,
|
||||||
|
compareColumn: nextCompareColumn,
|
||||||
|
compareOutcomes: nextOutcomes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [compareColumnOptions, defaultCompareColumn, formValues, onChange])
|
||||||
const updateCompareOutcome = (index: number, patch: Partial<CompareOutcomeDto>) => {
|
const updateCompareOutcome = (index: number, patch: Partial<CompareOutcomeDto>) => {
|
||||||
const next = [...(formValues.compareOutcomes || [])]
|
const next = [...(formValues.compareOutcomes || [])]
|
||||||
next[index] = { ...next[index], ...patch }
|
next[index] = { ...next[index], ...patch }
|
||||||
|
|
@ -199,7 +241,7 @@ export function WorkflowCriteria({
|
||||||
<div className="flex-1 min-h-0 overflow-y-auto pr-1">
|
<div className="flex-1 min-h-0 overflow-y-auto pr-1">
|
||||||
<FormContainer size="sm">
|
<FormContainer size="sm">
|
||||||
<div className="grid grid-cols-2 gap-1">
|
<div className="grid grid-cols-2 gap-1">
|
||||||
<FormItem label="Tip" asterisk>
|
<FormItem label={translate('::App.Platform.Type')} asterisk>
|
||||||
<SelectField
|
<SelectField
|
||||||
required
|
required
|
||||||
options={kindOptions}
|
options={kindOptions}
|
||||||
|
|
@ -207,7 +249,7 @@ export function WorkflowCriteria({
|
||||||
onChange={(value) => setField('kind', value)}
|
onChange={(value) => setField('kind', value)}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="Başlık" asterisk>
|
<FormItem label={translate('::App.Listform.ListformField.Title')} asterisk>
|
||||||
<Input
|
<Input
|
||||||
required
|
required
|
||||||
value={formValues.title}
|
value={formValues.title}
|
||||||
|
|
@ -215,11 +257,11 @@ export function WorkflowCriteria({
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem
|
<FormItem
|
||||||
label="Onaylayacak Kişi"
|
label={translate('::App.Listform.ListformField.Approver')}
|
||||||
asterisk={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
|
asterisk={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
|
||||||
>
|
>
|
||||||
<SelectField
|
<SelectField
|
||||||
required
|
required={formValues.kind === 'Approval' || formValues.kind === 'Inform'}
|
||||||
options={userList}
|
options={userList}
|
||||||
value={formValues.approver}
|
value={formValues.approver}
|
||||||
onChange={(value) => setField('approver', value)}
|
onChange={(value) => setField('approver', value)}
|
||||||
|
|
@ -227,7 +269,7 @@ export function WorkflowCriteria({
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{(formValues.kind === 'Start' || formValues.kind === 'Inform') && (
|
{(formValues.kind === 'Start' || formValues.kind === 'Inform') && (
|
||||||
<FormItem label="Sonraki adım" asterisk>
|
<FormItem label={translate('::App.Listform.ListformField.NextOnStart')} asterisk>
|
||||||
{targetSelect(
|
{targetSelect(
|
||||||
formValues.nextOnStart,
|
formValues.nextOnStart,
|
||||||
(value) => setField('nextOnStart', value),
|
(value) => setField('nextOnStart', value),
|
||||||
|
|
@ -238,14 +280,14 @@ export function WorkflowCriteria({
|
||||||
|
|
||||||
{formValues.kind === 'Approval' && (
|
{formValues.kind === 'Approval' && (
|
||||||
<>
|
<>
|
||||||
<FormItem label="Onay adımı" asterisk>
|
<FormItem label={translate('::App.Listform.ListformField.NextOnApprove')} asterisk>
|
||||||
{targetSelect(
|
{targetSelect(
|
||||||
formValues.nextOnApprove,
|
formValues.nextOnApprove,
|
||||||
(value) => setField('nextOnApprove', value),
|
(value) => setField('nextOnApprove', value),
|
||||||
true,
|
true,
|
||||||
)}
|
)}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="Red adımı" asterisk>
|
<FormItem label={translate('::App.Listform.ListformField.NextOnReject')} asterisk>
|
||||||
{targetSelect(
|
{targetSelect(
|
||||||
formValues.nextOnReject,
|
formValues.nextOnReject,
|
||||||
(value) => setField('nextOnReject', value),
|
(value) => setField('nextOnReject', value),
|
||||||
|
|
@ -260,10 +302,10 @@ export function WorkflowCriteria({
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="mb-3 flex items-center justify-between gap-2">
|
<div className="mb-3 flex items-center justify-between gap-2">
|
||||||
<h6>
|
<h6>
|
||||||
Karşılaştırma durumları
|
{translate('::App.Listform.ListformField.CompareOutcomes')}
|
||||||
{isLoadingSelectCommandColumns && (
|
{isLoadingSelectCommandColumns && (
|
||||||
<span className="ml-2 text-xs font-normal text-gray-400">
|
<span className="ml-2 text-xs font-normal text-gray-400">
|
||||||
Sütunlar yükleniyor...
|
{translate('::App.Listform.ListformField.LoadingColumns')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h6>
|
</h6>
|
||||||
|
|
@ -276,11 +318,12 @@ export function WorkflowCriteria({
|
||||||
...(formValues.compareOutcomes || []),
|
...(formValues.compareOutcomes || []),
|
||||||
emptyCompareOutcome1(
|
emptyCompareOutcome1(
|
||||||
`Durum ${(formValues.compareOutcomes || []).length + 1}`,
|
`Durum ${(formValues.compareOutcomes || []).length + 1}`,
|
||||||
|
defaultCompareColumn,
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Karşılaştırma Ekle
|
{translate('::App.Listform.ListformField.AddCompareOutcome')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -289,8 +332,8 @@ export function WorkflowCriteria({
|
||||||
(outcome: CompareOutcomeDto, index: number) => (
|
(outcome: CompareOutcomeDto, index: number) => (
|
||||||
<div key={index} className="rounded border border-gray-200 p-3">
|
<div key={index} className="rounded border border-gray-200 p-3">
|
||||||
<div className="flex flex-col-11 items-center gap-2 mb-2">
|
<div className="flex flex-col-11 items-center gap-2 mb-2">
|
||||||
<strong className="flex-[5]">Durum {index + 1}</strong>
|
<strong className="flex-[5]">{translate('::App.Listform.ListformField.Status')} {index + 1}</strong>
|
||||||
<strong className="flex-[5]">Bağlantı</strong>
|
<strong className="flex-[5]">{translate('::App.Listform.ListformField.Connection')}</strong>
|
||||||
<span className="flex-1" />
|
<span className="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col-11 items-center gap-2">
|
<div className="flex flex-col-11 items-center gap-2">
|
||||||
|
|
@ -322,7 +365,7 @@ export function WorkflowCriteria({
|
||||||
disabled={(formValues.compareOutcomes || []).length <= 2}
|
disabled={(formValues.compareOutcomes || []).length <= 2}
|
||||||
onClick={() => removeCompareOutcome(index)}
|
onClick={() => removeCompareOutcome(index)}
|
||||||
>
|
>
|
||||||
Sil
|
{translate('::Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -381,7 +424,7 @@ export function WorkflowCriteria({
|
||||||
onClick={() => addCompareCondition(index)}
|
onClick={() => addCompareCondition(index)}
|
||||||
className="flex-[1]"
|
className="flex-[1]"
|
||||||
>
|
>
|
||||||
Ekle
|
{translate('::Insert')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -398,7 +441,7 @@ export function WorkflowCriteria({
|
||||||
|
|
||||||
<Dialog.Footer className="flex justify-end gap-2 border-t pt-3 mt-1">
|
<Dialog.Footer className="flex justify-end gap-2 border-t pt-3 mt-1">
|
||||||
<Button type="button" variant="plain" disabled={busy} onClick={closeDialog}>
|
<Button type="button" variant="plain" disabled={busy} onClick={closeDialog}>
|
||||||
Cancel
|
{translate('::Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -406,10 +449,10 @@ export function WorkflowCriteria({
|
||||||
disabled={busy || !formValues.id}
|
disabled={busy || !formValues.id}
|
||||||
onClick={() => onDelete(formValues.id)}
|
onClick={() => onDelete(formValues.id)}
|
||||||
>
|
>
|
||||||
Sil
|
{translate('::Delete')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="solid" loading={busy} type="submit">
|
<Button variant="solid" loading={busy} type="submit">
|
||||||
Kaydet
|
{translate('::Save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -418,6 +461,20 @@ export function WorkflowCriteria({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNumericDataType(dataType?: string | null) {
|
||||||
|
const normalized = (dataType || '').toLowerCase()
|
||||||
|
return [
|
||||||
|
'int',
|
||||||
|
'decimal',
|
||||||
|
'numeric',
|
||||||
|
'money',
|
||||||
|
'float',
|
||||||
|
'real',
|
||||||
|
'double',
|
||||||
|
'number',
|
||||||
|
].some((typeName) => normalized.includes(typeName))
|
||||||
|
}
|
||||||
|
|
||||||
function SelectField({
|
function SelectField({
|
||||||
options,
|
options,
|
||||||
value,
|
value,
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,23 @@ function colToSqlLine(col: ColumnDefinition, addComma = true): string {
|
||||||
return ` [${col.columnName}] ${typeSql} ${nullPart}${defaultPart}${addComma ? ',' : ''}`
|
return ` [${col.columnName}] ${typeSql} ${nullPart}${defaultPart}${addComma ? ',' : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCreateIndexIfNotExistsSql(
|
||||||
|
fullTableName: string,
|
||||||
|
indexName: string,
|
||||||
|
isClustered: boolean,
|
||||||
|
colsSql: string,
|
||||||
|
): string[] {
|
||||||
|
const escapedFullTableName = fullTableName.replace(/'/g, "''")
|
||||||
|
const escapedIndexName = indexName.replace(/'/g, "''")
|
||||||
|
|
||||||
|
return [
|
||||||
|
`IF INDEXPROPERTY(OBJECT_ID(N'${escapedFullTableName}'), '${escapedIndexName}', 'IndexID') IS NULL`,
|
||||||
|
`BEGIN`,
|
||||||
|
` CREATE ${isClustered ? 'CLUSTERED ' : ''}INDEX [${indexName}] ON ${fullTableName} (${colsSql});`,
|
||||||
|
`END`,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
function generateCreateTableSql(
|
function generateCreateTableSql(
|
||||||
columns: ColumnDefinition[],
|
columns: ColumnDefinition[],
|
||||||
settings: TableSettings,
|
settings: TableSettings,
|
||||||
|
|
@ -305,7 +322,12 @@ function generateCreateTableSql(
|
||||||
} else {
|
} else {
|
||||||
extraIndexLines.push(`-- 📋 Index: [${idx.indexName}]`)
|
extraIndexLines.push(`-- 📋 Index: [${idx.indexName}]`)
|
||||||
extraIndexLines.push(
|
extraIndexLines.push(
|
||||||
`CREATE ${idx.isClustered ? 'CLUSTERED ' : ''}INDEX [${idx.indexName}] ON ${fullTableName} (${colsSql});`,
|
...buildCreateIndexIfNotExistsSql(
|
||||||
|
fullTableName,
|
||||||
|
idx.indexName,
|
||||||
|
idx.isClustered,
|
||||||
|
colsSql,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -715,7 +737,12 @@ function generateAlterTableSql(
|
||||||
} else {
|
} else {
|
||||||
lines.push(`-- 📋 Index: [${ix.indexName}]`)
|
lines.push(`-- 📋 Index: [${ix.indexName}]`)
|
||||||
lines.push(
|
lines.push(
|
||||||
`CREATE ${ix.isClustered ? 'CLUSTERED ' : ''}INDEX [${ix.indexName}] ON ${fullTableName} (${colsSql});`,
|
...buildCreateIndexIfNotExistsSql(
|
||||||
|
fullTableName,
|
||||||
|
ix.indexName,
|
||||||
|
ix.isClustered,
|
||||||
|
colsSql,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
lines.push('')
|
lines.push('')
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,11 @@ const useGridData = (props: {
|
||||||
name: i.dataField,
|
name: i.dataField,
|
||||||
editorType2: i.editorType2,
|
editorType2: i.editorType2,
|
||||||
editorType:
|
editorType:
|
||||||
i.editorType2 == PlatformEditorTypes.dxGridBox ? 'dxDropDownBox' : i.editorType2,
|
i.editorType2 == PlatformEditorTypes.dxGridBox
|
||||||
|
? 'dxDropDownBox'
|
||||||
|
: i.editorType2 == PlatformEditorTypes.dxImageUpload
|
||||||
|
? undefined
|
||||||
|
: i.editorType2,
|
||||||
colSpan: i.colSpan,
|
colSpan: i.colSpan,
|
||||||
isRequired: i.isRequired,
|
isRequired: i.isRequired,
|
||||||
editorOptions: {
|
editorOptions: {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
||||||
import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent'
|
import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent'
|
||||||
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
|
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
|
||||||
import { useFilters } from './useFilters'
|
import { useFilters } from './useFilters'
|
||||||
import { useToolbar } from './useToolbar'
|
import { updateWorkflowApprovalToolbarItems, useToolbar } from './useToolbar'
|
||||||
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
import { ImportDashboard } from '@/components/importManager/ImportDashboard'
|
||||||
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
|
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
|
||||||
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
|
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
|
||||||
|
|
@ -75,6 +75,7 @@ import { useListFormCustomDataSource } from './useListFormCustomDataSource'
|
||||||
import { useListFormColumns } from './useListFormColumns'
|
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'
|
||||||
|
|
||||||
interface GridProps {
|
interface GridProps {
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -87,6 +88,24 @@ interface GridProps {
|
||||||
|
|
||||||
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)' // kullanici tanimli gridState ile islem gormus gridin paneline ait renk
|
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)' // kullanici tanimli gridState ile islem gormus gridin paneline ait renk
|
||||||
|
|
||||||
|
const isTemporaryDxKey = (key: unknown) => typeof key === 'string' && key.startsWith('_DX_KEY_')
|
||||||
|
|
||||||
|
const getPersistedInsertedKey = (
|
||||||
|
e: DataGridTypes.RowInsertedEvent<any, any>,
|
||||||
|
keyFieldName?: string,
|
||||||
|
) => {
|
||||||
|
const dataKey = keyFieldName ? e.data?.[keyFieldName] : undefined
|
||||||
|
if (dataKey !== undefined && dataKey !== null && !isTemporaryDxKey(dataKey)) {
|
||||||
|
return dataKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key !== undefined && e.key !== null && !isTemporaryDxKey(e.key)) {
|
||||||
|
return e.key
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const Grid = (props: GridProps) => {
|
const Grid = (props: GridProps) => {
|
||||||
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
@ -208,9 +227,28 @@ const Grid = (props: GridProps) => {
|
||||||
return grd.getSelectedRowsData()
|
return grd.getSelectedRowsData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const updateWorkflowApprovalButtons = useCallback(
|
||||||
|
(component?: any, selectedRowsData?: Record<string, unknown>[]) => {
|
||||||
|
const grd = component ?? gridRef.current?.instance()
|
||||||
|
if (!grd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWorkflowApprovalToolbarItems(
|
||||||
|
grd,
|
||||||
|
gridDto?.gridOptions.workflowDto,
|
||||||
|
selectedRowsData ?? grd.getSelectedRowsData(),
|
||||||
|
config?.currentUser,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[config?.currentUser, gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
const refreshData = useCallback(() => {
|
const refreshData = useCallback(() => {
|
||||||
gridRef.current?.instance()?.refresh()
|
const grd = gridRef.current?.instance()
|
||||||
}, [])
|
const refreshResult = grd?.refresh()
|
||||||
|
Promise.resolve(refreshResult).finally(() => updateWorkflowApprovalButtons(grd))
|
||||||
|
}, [updateWorkflowApprovalButtons])
|
||||||
|
|
||||||
const getFilter = useCallback(() => {
|
const getFilter = useCallback(() => {
|
||||||
const grd = gridRef.current?.instance()
|
const grd = gridRef.current?.instance()
|
||||||
|
|
@ -257,6 +295,8 @@ const Grid = (props: GridProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubForm'ları gösterebilmek için secili satiri formData'ya at
|
// SubForm'ları gösterebilmek için secili satiri formData'ya at
|
||||||
|
updateWorkflowApprovalButtons(grd, data.selectedRowsData)
|
||||||
|
|
||||||
if (data.selectedRowsData.length) {
|
if (data.selectedRowsData.length) {
|
||||||
setFormData(data.selectedRowsData[0])
|
setFormData(data.selectedRowsData[0])
|
||||||
}
|
}
|
||||||
|
|
@ -401,25 +441,32 @@ const Grid = (props: GridProps) => {
|
||||||
[gridDto, searchParams, extraFilters, getNextSequenceValue],
|
[gridDto, searchParams, extraFilters, getNextSequenceValue],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onRowInserting = useCallback((e: DataGridTypes.RowInsertingEvent<any, any>) => {
|
const onRowInserting = useCallback(
|
||||||
if (!gridDto?.columnFormats) {
|
(e: DataGridTypes.RowInsertingEvent<any, any>) => {
|
||||||
e.data = setFormEditingExtraItemValues(e.data)
|
if (!gridDto?.columnFormats) {
|
||||||
return
|
e.data = setFormEditingExtraItemValues(e.data)
|
||||||
}
|
return
|
||||||
const allowedFields = gridDto.columnFormats.filter(f => f.allowAdding).map(f => f.fieldName)
|
|
||||||
const filteredData: any = {}
|
|
||||||
for (const key of allowedFields) {
|
|
||||||
if (e.data.hasOwnProperty(key) && key) {
|
|
||||||
filteredData[key] = e.data[key]
|
|
||||||
}
|
}
|
||||||
}
|
const allowedFields = gridDto.columnFormats
|
||||||
e.data = setFormEditingExtraItemValues(filteredData)
|
.filter((f) => f.allowAdding)
|
||||||
}, [gridDto])
|
.map((f) => f.fieldName)
|
||||||
|
const filteredData: any = {}
|
||||||
|
for (const key of allowedFields) {
|
||||||
|
if (e.data.hasOwnProperty(key) && key) {
|
||||||
|
filteredData[key] = e.data[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.data = setFormEditingExtraItemValues(filteredData)
|
||||||
|
},
|
||||||
|
[gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
const onRowUpdating = useCallback(
|
const onRowUpdating = useCallback(
|
||||||
(e: DataGridTypes.RowUpdatingEvent<any, any>) => {
|
(e: DataGridTypes.RowUpdatingEvent<any, any>) => {
|
||||||
if (!gridDto?.columnFormats) return
|
if (!gridDto?.columnFormats) return
|
||||||
const allowedFields = gridDto.columnFormats.filter(f => f.allowEditing).map(f => f.fieldName)
|
const allowedFields = gridDto.columnFormats
|
||||||
|
.filter((f) => f.allowEditing)
|
||||||
|
.map((f) => f.fieldName)
|
||||||
let newData = { ...e.oldData, ...e.newData }
|
let newData = { ...e.oldData, ...e.newData }
|
||||||
// Remove keys not allowed
|
// Remove keys not allowed
|
||||||
Object.keys(newData).forEach((key) => {
|
Object.keys(newData).forEach((key) => {
|
||||||
|
|
@ -452,7 +499,7 @@ const Grid = (props: GridProps) => {
|
||||||
if (!e.data[field[0]]) {
|
if (!e.data[field[0]]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = JSON.parse(e.data[field[0]])
|
const json = JSON.parse(e.data[field[0]])
|
||||||
e.data[col.dataField] = json[field[1]]
|
e.data[col.dataField] = json[field[1]]
|
||||||
})
|
})
|
||||||
|
|
@ -521,6 +568,19 @@ const Grid = (props: GridProps) => {
|
||||||
const formItem = gridDto.gridOptions.editingFormDto
|
const formItem = gridDto.gridOptions.editingFormDto
|
||||||
.flatMap((group) => group.items || [])
|
.flatMap((group) => group.items || [])
|
||||||
.find((i) => i.dataField === editor.dataField)
|
.find((i) => i.dataField === editor.dataField)
|
||||||
|
const fieldName = editor.dataField.split(':')[0]
|
||||||
|
const columnFormat = gridDto.columnFormats.find((column) => column.fieldName === fieldName)
|
||||||
|
const isNewRow = Boolean((editor as any).row?.isNewRow) || mode === 'new'
|
||||||
|
|
||||||
|
if (
|
||||||
|
(isNewRow && columnFormat?.allowAdding === false) ||
|
||||||
|
(!isNewRow && columnFormat?.allowEditing === false)
|
||||||
|
) {
|
||||||
|
editor.editorOptions.readOnly = true
|
||||||
|
} else if (isNewRow && columnFormat?.allowAdding === true) {
|
||||||
|
editor.editorOptions.readOnly = false
|
||||||
|
editor.editorOptions.disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
// Cascade mantığı
|
// Cascade mantığı
|
||||||
const cascadeInfo = cascadeFieldsMap.get(editor.dataField)
|
const cascadeInfo = cascadeFieldsMap.get(editor.dataField)
|
||||||
|
|
@ -1192,6 +1252,7 @@ const Grid = (props: GridProps) => {
|
||||||
showColumnHeaders={gridDto.gridOptions.columnOptionDto?.showColumnHeaders}
|
showColumnHeaders={gridDto.gridOptions.columnOptionDto?.showColumnHeaders}
|
||||||
filterSyncEnabled={true}
|
filterSyncEnabled={true}
|
||||||
onSelectionChanged={onSelectionChanged}
|
onSelectionChanged={onSelectionChanged}
|
||||||
|
onContentReady={(e) => updateWorkflowApprovalButtons(e.component)}
|
||||||
onInitNewRow={onInitNewRow}
|
onInitNewRow={onInitNewRow}
|
||||||
onCellPrepared={onCellPrepared}
|
onCellPrepared={onCellPrepared}
|
||||||
onRowInserting={onRowInserting}
|
onRowInserting={onRowInserting}
|
||||||
|
|
@ -1212,7 +1273,18 @@ const Grid = (props: GridProps) => {
|
||||||
setMode('view')
|
setMode('view')
|
||||||
setIsPopupFullScreen(false)
|
setIsPopupFullScreen(false)
|
||||||
}}
|
}}
|
||||||
onRowInserted={() => {
|
onRowInserted={(e) => {
|
||||||
|
const insertedKey = getPersistedInsertedKey(e, gridDto.gridOptions.keyFieldName)
|
||||||
|
|
||||||
|
if (
|
||||||
|
gridDto.gridOptions.workflowDto?.approvalStatusFieldName &&
|
||||||
|
insertedKey !== undefined
|
||||||
|
) {
|
||||||
|
workflowService
|
||||||
|
.startWorkflow(listFormCode, [insertedKey])
|
||||||
|
.then(() => gridRef.current?.instance()?.refresh())
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
props.refreshData?.()
|
props.refreshData?.()
|
||||||
}}
|
}}
|
||||||
onRowUpdated={() => {
|
onRowUpdated={() => {
|
||||||
|
|
@ -1342,9 +1414,9 @@ const Grid = (props: GridProps) => {
|
||||||
if (mode === 'view') {
|
if (mode === 'view') {
|
||||||
return a.canRead
|
return a.canRead
|
||||||
} else if (mode === 'new') {
|
} else if (mode === 'new') {
|
||||||
return (a.canCreate || a.canRead) && a.allowAdding
|
return a.canCreate && a.allowAdding
|
||||||
} else if (mode === 'edit') {
|
} else if (mode === 'edit') {
|
||||||
return (a.canUpdate || a.canRead) && a.allowEditing
|
return a.canUpdate && a.allowEditing
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1371,9 +1443,9 @@ const Grid = (props: GridProps) => {
|
||||||
if (mode === 'view') {
|
if (mode === 'view') {
|
||||||
return a.canRead
|
return a.canRead
|
||||||
} else if (mode === 'new') {
|
} else if (mode === 'new') {
|
||||||
return (a.canCreate || a.canRead) && a.allowAdding
|
return a.canCreate && a.allowAdding
|
||||||
} else if (mode === 'edit') {
|
} else if (mode === 'edit') {
|
||||||
return (a.canUpdate || a.canRead) && a.allowEditing
|
return a.canUpdate && a.allowEditing
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ import {
|
||||||
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
|
||||||
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
|
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
|
||||||
import { useFilters } from './useFilters'
|
import { useFilters } from './useFilters'
|
||||||
import { useToolbar } from './useToolbar'
|
import { updateWorkflowApprovalToolbarItems, useToolbar } from './useToolbar'
|
||||||
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
|
import WidgetGroup from '@/components/ui/Widget/WidgetGroup'
|
||||||
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
|
import { GridExtraFilterToolbar } from './GridExtraFilterToolbar'
|
||||||
import { getList } from '@/services/form.service'
|
import { getList } from '@/services/form.service'
|
||||||
|
|
@ -66,6 +66,7 @@ import { DataType } from 'devextreme/common'
|
||||||
import { useStoreState } from '@/store/store'
|
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'
|
||||||
|
|
||||||
interface TreeProps {
|
interface TreeProps {
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
|
|
@ -78,6 +79,21 @@ interface TreeProps {
|
||||||
|
|
||||||
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)'
|
const statedGridPanelColor = 'rgba(50, 200, 200, 0.5)'
|
||||||
|
|
||||||
|
const isTemporaryDxKey = (key: unknown) => typeof key === 'string' && key.startsWith('_DX_KEY_')
|
||||||
|
|
||||||
|
const getPersistedInsertedKey = (e: any, keyFieldName?: string) => {
|
||||||
|
const dataKey = keyFieldName ? e.data?.[keyFieldName] : undefined
|
||||||
|
if (dataKey !== undefined && dataKey !== null && !isTemporaryDxKey(dataKey)) {
|
||||||
|
return dataKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key !== undefined && e.key !== null && !isTemporaryDxKey(e.key)) {
|
||||||
|
return e.key
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const Tree = (props: TreeProps) => {
|
const Tree = (props: TreeProps) => {
|
||||||
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
const { listFormCode, searchParams, isSubForm, level, gridDto: extGridDto } = props
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
@ -246,9 +262,28 @@ const Tree = (props: TreeProps) => {
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const updateWorkflowApprovalButtons = useCallback(
|
||||||
|
(component?: any, selectedRowsData?: Record<string, unknown>[]) => {
|
||||||
|
const tree = component ?? gridRef.current?.instance()
|
||||||
|
if (!tree) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWorkflowApprovalToolbarItems(
|
||||||
|
tree,
|
||||||
|
gridDto?.gridOptions.workflowDto,
|
||||||
|
selectedRowsData ?? tree.getSelectedRowsData(),
|
||||||
|
config?.currentUser,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[config?.currentUser, gridDto],
|
||||||
|
)
|
||||||
|
|
||||||
const refreshData = useCallback(() => {
|
const refreshData = useCallback(() => {
|
||||||
gridRef.current?.instance().refresh()
|
const tree = gridRef.current?.instance()
|
||||||
}, [])
|
const refreshResult = tree?.refresh()
|
||||||
|
Promise.resolve(refreshResult).finally(() => updateWorkflowApprovalButtons(tree))
|
||||||
|
}, [updateWorkflowApprovalButtons])
|
||||||
|
|
||||||
const getFilter = useCallback(() => {
|
const getFilter = useCallback(() => {
|
||||||
const tree = gridRef.current?.instance()
|
const tree = gridRef.current?.instance()
|
||||||
|
|
@ -300,6 +335,8 @@ const Tree = (props: TreeProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateWorkflowApprovalButtons(tree, data.selectedRowsData)
|
||||||
|
|
||||||
if (data.selectedRowsData.length) {
|
if (data.selectedRowsData.length) {
|
||||||
setFormData(data.selectedRowsData[0])
|
setFormData(data.selectedRowsData[0])
|
||||||
}
|
}
|
||||||
|
|
@ -480,6 +517,19 @@ const Tree = (props: TreeProps) => {
|
||||||
const formItem = gridDto.gridOptions.editingFormDto
|
const formItem = gridDto.gridOptions.editingFormDto
|
||||||
.flatMap((group) => group.items || [])
|
.flatMap((group) => group.items || [])
|
||||||
.find((i) => i.dataField === editor.dataField)
|
.find((i) => i.dataField === editor.dataField)
|
||||||
|
const fieldName = editor.dataField.split(':')[0]
|
||||||
|
const columnFormat = gridDto.columnFormats.find((column) => column.fieldName === fieldName)
|
||||||
|
const isNewRow = Boolean((editor as any).row?.isNewRow) || mode === 'new'
|
||||||
|
|
||||||
|
if (
|
||||||
|
(isNewRow && columnFormat?.allowAdding === false) ||
|
||||||
|
(!isNewRow && columnFormat?.allowEditing === false)
|
||||||
|
) {
|
||||||
|
editor.editorOptions.readOnly = true
|
||||||
|
} else if (isNewRow && columnFormat?.allowAdding === true) {
|
||||||
|
editor.editorOptions.readOnly = false
|
||||||
|
editor.editorOptions.disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
// Cascade disabled mantığı
|
// Cascade disabled mantığı
|
||||||
const colFormat = gridDto.columnFormats.find((c) => c.fieldName === editor.dataField)
|
const colFormat = gridDto.columnFormats.find((c) => c.fieldName === editor.dataField)
|
||||||
|
|
@ -878,7 +928,18 @@ const Tree = (props: TreeProps) => {
|
||||||
setMode('view')
|
setMode('view')
|
||||||
setIsPopupFullScreen(false)
|
setIsPopupFullScreen(false)
|
||||||
}}
|
}}
|
||||||
onRowInserted={() => {
|
onRowInserted={(e) => {
|
||||||
|
const insertedKey = getPersistedInsertedKey(e, gridDto.gridOptions.keyFieldName)
|
||||||
|
|
||||||
|
if (
|
||||||
|
gridDto.gridOptions.workflowDto?.approvalStatusFieldName &&
|
||||||
|
insertedKey !== undefined
|
||||||
|
) {
|
||||||
|
workflowService
|
||||||
|
.startWorkflow(listFormCode, [insertedKey])
|
||||||
|
.then(() => gridRef.current?.instance()?.refresh())
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
props.refreshData?.()
|
props.refreshData?.()
|
||||||
}}
|
}}
|
||||||
onRowUpdated={() => {
|
onRowUpdated={() => {
|
||||||
|
|
@ -889,6 +950,8 @@ const Tree = (props: TreeProps) => {
|
||||||
}}
|
}}
|
||||||
onEditorPreparing={onEditorPreparing}
|
onEditorPreparing={onEditorPreparing}
|
||||||
onContentReady={(e) => {
|
onContentReady={(e) => {
|
||||||
|
updateWorkflowApprovalButtons(e.component)
|
||||||
|
|
||||||
// Restore expanded keys after data refresh (only if autoExpandAll is false)
|
// Restore expanded keys after data refresh (only if autoExpandAll is false)
|
||||||
if (
|
if (
|
||||||
!gridDto.gridOptions.treeOptionDto?.autoExpandAll &&
|
!gridDto.gridOptions.treeOptionDto?.autoExpandAll &&
|
||||||
|
|
@ -1117,9 +1180,9 @@ const Tree = (props: TreeProps) => {
|
||||||
if (mode === 'view') {
|
if (mode === 'view') {
|
||||||
return a.canRead
|
return a.canRead
|
||||||
} else if (mode === 'new') {
|
} else if (mode === 'new') {
|
||||||
return a.canCreate || a.canRead
|
return a.canCreate && a.allowAdding
|
||||||
} else if (mode === 'edit') {
|
} else if (mode === 'edit') {
|
||||||
return a.canUpdate || a.canRead
|
return a.canUpdate && a.allowEditing
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1146,9 +1209,9 @@ const Tree = (props: TreeProps) => {
|
||||||
if (mode === 'view') {
|
if (mode === 'view') {
|
||||||
return a.canRead
|
return a.canRead
|
||||||
} else if (mode === 'new') {
|
} else if (mode === 'new') {
|
||||||
return a.canCreate || a.canRead
|
return a.canCreate && a.allowAdding
|
||||||
} else if (mode === 'edit') {
|
} else if (mode === 'edit') {
|
||||||
return a.canUpdate || a.canRead
|
return a.canUpdate && a.allowEditing
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -691,7 +691,7 @@ const useListFormColumns = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
column.allowEditing = colData?.allowEditing
|
column.allowEditing = colData?.allowEditing || colData?.allowAdding
|
||||||
|
|
||||||
// #region lookup ayarlari
|
// #region lookup ayarlari
|
||||||
if (colData.lookupDto?.dataSourceType) {
|
if (colData.lookupDto?.dataSourceType) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,34 @@ import { CardViewRef, CardViewTypes } from 'devextreme-react/cjs/card-view'
|
||||||
|
|
||||||
const filteredGridPanelColor = 'rgba(10, 200, 10, 0.5)' // kullanici tanimli filtre ile filtrelenmis gridin paneline ait renk
|
const filteredGridPanelColor = 'rgba(10, 200, 10, 0.5)' // kullanici tanimli filtre ile filtrelenmis gridin paneline ait renk
|
||||||
|
|
||||||
|
const toInsertedRowData = (values: any, responseData: any, keyFieldName?: string | null) => {
|
||||||
|
if (!keyFieldName) {
|
||||||
|
return responseData ?? values
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseData && typeof responseData === 'object' && !Array.isArray(responseData)) {
|
||||||
|
if (responseData[keyFieldName] !== undefined && responseData[keyFieldName] !== null) {
|
||||||
|
return { ...values, ...responseData }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
responseData.data &&
|
||||||
|
typeof responseData.data === 'object' &&
|
||||||
|
!Array.isArray(responseData.data) &&
|
||||||
|
responseData.data[keyFieldName] !== undefined &&
|
||||||
|
responseData.data[keyFieldName] !== null
|
||||||
|
) {
|
||||||
|
return { ...values, ...responseData.data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseData !== undefined && responseData !== null) {
|
||||||
|
return { ...values, [keyFieldName]: responseData }
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
const useListFormCustomDataSource = ({
|
const useListFormCustomDataSource = ({
|
||||||
gridRef,
|
gridRef,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -372,7 +400,10 @@ const useListFormCustomDataSource = ({
|
||||||
}
|
}
|
||||||
const insertUrl = getServiceAddress(gridOptions.insertServiceAddress)
|
const insertUrl = getServiceAddress(gridOptions.insertServiceAddress)
|
||||||
|
|
||||||
return dynamicFetch(insertUrl, 'POST', searchParams, { data: values, listFormCode })
|
return dynamicFetch(insertUrl, 'POST', searchParams, { data: values, listFormCode }).then(
|
||||||
|
(response: any) =>
|
||||||
|
toInsertedRowData(values, response?.data, gridOptions.keyFieldName),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
errorHandler: (error: any) => {
|
errorHandler: (error: any) => {
|
||||||
console.log(error.message)
|
console.log(error.message)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Button, Notification, toast } from '@/components/ui'
|
import { Button, Notification, toast } from '@/components/ui'
|
||||||
import { GridDto, UiCommandButtonPositionTypeEnum } from '@/proxy/form/models'
|
import { GridDto, UiCommandButtonPositionTypeEnum, WorkflowDto } from '@/proxy/form/models'
|
||||||
import { dynamicFetch } from '@/services/form.service'
|
import { dynamicFetch } from '@/services/form.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { usePermission } from '@/utils/hooks/usePermission'
|
import { usePermission } from '@/utils/hooks/usePermission'
|
||||||
|
|
@ -10,6 +10,7 @@ import { useDialogContext } from '../shared/DialogContext'
|
||||||
import { usePWA } from '@/utils/hooks/usePWA'
|
import { usePWA } from '@/utils/hooks/usePWA'
|
||||||
import { layoutTypes, ListViewLayoutType } from '../admin/listForm/edit/types'
|
import { layoutTypes, ListViewLayoutType } from '../admin/listForm/edit/types'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreState } from '@/store'
|
||||||
|
import { workflowService } from '@/services/workflow.service'
|
||||||
|
|
||||||
type ToolbarModalData = {
|
type ToolbarModalData = {
|
||||||
open: boolean
|
open: boolean
|
||||||
|
|
@ -51,7 +52,6 @@ const useToolbar = ({
|
||||||
|
|
||||||
const [toolbarData, setToolbarData] = useState<ToolbarItem[]>([])
|
const [toolbarData, setToolbarData] = useState<ToolbarItem[]>([])
|
||||||
const [toolbarModalData, setToolbarModalData] = useState<ToolbarModalData>()
|
const [toolbarModalData, setToolbarModalData] = useState<ToolbarModalData>()
|
||||||
|
|
||||||
const grdOpt = gridDto?.gridOptions
|
const grdOpt = gridDto?.gridOptions
|
||||||
|
|
||||||
function getToolbarData() {
|
function getToolbarData() {
|
||||||
|
|
@ -112,6 +112,136 @@ const useToolbar = ({
|
||||||
location: 'after',
|
location: 'after',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const workflowOptions = grdOpt.workflowDto
|
||||||
|
const approvalCriteria = workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? []
|
||||||
|
if (
|
||||||
|
workflowOptions?.approvalStatusFieldName &&
|
||||||
|
approvalCriteria.length > 0 &&
|
||||||
|
grdOpt.updateServiceAddress
|
||||||
|
) {
|
||||||
|
items.push({
|
||||||
|
widget: 'dxButton',
|
||||||
|
name: 'workflowStart',
|
||||||
|
location: 'after',
|
||||||
|
options: {
|
||||||
|
icon: 'play',
|
||||||
|
text: 'Workflow Start',
|
||||||
|
hint: 'Workflow Start',
|
||||||
|
visible: true,
|
||||||
|
disabled: true,
|
||||||
|
onClick: async () => {
|
||||||
|
const keys = (await Promise.resolve(getSelectedRowKeys() as any)) as unknown[]
|
||||||
|
if (!keys?.length) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="warning" duration={2000}>
|
||||||
|
{translate('::ListForms.ListForm.SelectRecord')}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedRows = ((await Promise.resolve(getSelectedRowsData() as any)) ||
|
||||||
|
[]) as Record<string, unknown>[]
|
||||||
|
if (
|
||||||
|
selectedRows.length === 0 ||
|
||||||
|
!selectedRows.every((row) => isWorkflowNotStarted(row, workflowOptions))
|
||||||
|
) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="warning" duration={2500}>
|
||||||
|
Secili kayit icin workflow zaten baslamis.
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await workflowService.startWorkflow(listFormCode, keys)
|
||||||
|
refreshData()
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger" duration={3000}>
|
||||||
|
{error?.response?.data?.error?.message ||
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error?.message ||
|
||||||
|
'Workflow baslatilamadi.'}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
approvalCriteria.forEach((criteria) => {
|
||||||
|
items.push({
|
||||||
|
widget: 'dxButton',
|
||||||
|
name: `workflowApproval_${criteria.id}`,
|
||||||
|
location: 'after',
|
||||||
|
options: {
|
||||||
|
icon: 'check',
|
||||||
|
text: criteria.title,
|
||||||
|
hint: criteria.title,
|
||||||
|
visible: true,
|
||||||
|
disabled: true,
|
||||||
|
onClick: async () => {
|
||||||
|
const keys = (await Promise.resolve(getSelectedRowKeys() as any)) as unknown[]
|
||||||
|
if (!keys?.length) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="warning" duration={2000}>
|
||||||
|
{translate('::ListForms.ListForm.SelectRecord')}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedRows = ((await Promise.resolve(getSelectedRowsData() as any)) ||
|
||||||
|
[]) as Record<string, unknown>[]
|
||||||
|
const activeRows = selectedRows.filter((row) =>
|
||||||
|
isWorkflowApprovalCriteriaActive(
|
||||||
|
row,
|
||||||
|
workflowOptions,
|
||||||
|
criteria.title,
|
||||||
|
getCurrentUserWorkflowIdentities(config?.currentUser),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (activeRows.length !== selectedRows.length) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="warning" duration={2500}>
|
||||||
|
Secili kayit bu onay adiminda veya onay kullanicisinda beklemiyor.
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setToolbarModalData({
|
||||||
|
open: true,
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<WorkflowApprovalDecisionDialog
|
||||||
|
criteriaTitle={criteria.title}
|
||||||
|
keys={keys}
|
||||||
|
listFormCode={listFormCode}
|
||||||
|
criteriaId={criteria.id}
|
||||||
|
onCancel={() => setToolbarModalData(undefined)}
|
||||||
|
onCompleted={() => {
|
||||||
|
refreshData()
|
||||||
|
setToolbarModalData(undefined)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Add Expand All button for TreeList
|
// Add Expand All button for TreeList
|
||||||
if (layout === layoutTypes.tree && grdOpt.treeOptionDto?.parentIdExpr) {
|
if (layout === layoutTypes.tree && grdOpt.treeOptionDto?.parentIdExpr) {
|
||||||
items.push({
|
items.push({
|
||||||
|
|
@ -365,4 +495,182 @@ const useToolbar = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isWorkflowApprovalCriteriaActive(
|
||||||
|
row: Record<string, unknown>,
|
||||||
|
workflowOptions: WorkflowDto,
|
||||||
|
criteriaTitle: string,
|
||||||
|
currentUserIdentities: string[] = [],
|
||||||
|
) {
|
||||||
|
if (!workflowOptions.approvalStatusFieldName || !criteriaTitle) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusMatches =
|
||||||
|
normalizeWorkflowValue(row?.[workflowOptions.approvalStatusFieldName]) ===
|
||||||
|
normalizeWorkflowValue(criteriaTitle)
|
||||||
|
|
||||||
|
if (!statusMatches) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!workflowOptions.approvalUserFieldName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const approver = normalizeWorkflowValue(row?.[workflowOptions.approvalUserFieldName])
|
||||||
|
return currentUserIdentities.some((identity) => normalizeWorkflowValue(identity) === approver)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeWorkflowValue(value: unknown) {
|
||||||
|
return String(value ?? '').trim().toLocaleLowerCase('tr-TR')
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWorkflowNotStarted(row: Record<string, unknown>, workflowOptions: WorkflowDto) {
|
||||||
|
return normalizeWorkflowValue(row?.[workflowOptions.approvalStatusFieldName]) === ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentUserWorkflowIdentities(currentUser?: {
|
||||||
|
userName?: string
|
||||||
|
email?: string
|
||||||
|
name?: string
|
||||||
|
}) {
|
||||||
|
return [currentUser?.email, currentUser?.userName, currentUser?.name].filter(Boolean) as string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateWorkflowApprovalToolbarItems(
|
||||||
|
component: any,
|
||||||
|
workflowOptions: WorkflowDto | undefined,
|
||||||
|
selectedRowsData: Record<string, unknown>[] = [],
|
||||||
|
currentUser?: {
|
||||||
|
userName?: string
|
||||||
|
email?: string
|
||||||
|
name?: string
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const approvalCriteria = workflowOptions?.criteria?.filter((item) => item.kind === 'Approval') ?? []
|
||||||
|
if (!component || !workflowOptions?.approvalStatusFieldName || !approvalCriteria.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolbarOptions = component.option('toolbar')
|
||||||
|
if (!toolbarOptions?.items || !Array.isArray(toolbarOptions.items)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowStartItemIndex = toolbarOptions.items
|
||||||
|
.map((item: any) => item.name)
|
||||||
|
.indexOf('workflowStart')
|
||||||
|
|
||||||
|
if (workflowStartItemIndex >= 0) {
|
||||||
|
const startEnabled =
|
||||||
|
selectedRowsData.length > 0 &&
|
||||||
|
selectedRowsData.every((row) => isWorkflowNotStarted(row, workflowOptions))
|
||||||
|
const startOptionPath = `toolbar.items[${workflowStartItemIndex}].options.disabled`
|
||||||
|
const nextStartDisabled = !startEnabled
|
||||||
|
if (component.option(startOptionPath) !== nextStartDisabled) {
|
||||||
|
component.option(startOptionPath, nextStartDisabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUserIdentities = getCurrentUserWorkflowIdentities(currentUser)
|
||||||
|
|
||||||
|
approvalCriteria.forEach((criteria) => {
|
||||||
|
const toolbarItemIndex = toolbarOptions.items
|
||||||
|
.map((item: any) => item.name)
|
||||||
|
.indexOf(`workflowApproval_${criteria.id}`)
|
||||||
|
|
||||||
|
if (toolbarItemIndex < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabled =
|
||||||
|
selectedRowsData.length > 0 &&
|
||||||
|
selectedRowsData.every((row) =>
|
||||||
|
isWorkflowApprovalCriteriaActive(row, workflowOptions, criteria.title, currentUserIdentities),
|
||||||
|
)
|
||||||
|
|
||||||
|
const optionPath = `toolbar.items[${toolbarItemIndex}].options.disabled`
|
||||||
|
const nextDisabled = !enabled
|
||||||
|
if (component.option(optionPath) !== nextDisabled) {
|
||||||
|
component.option(optionPath, nextDisabled)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function WorkflowApprovalDecisionDialog({
|
||||||
|
criteriaTitle,
|
||||||
|
keys,
|
||||||
|
listFormCode,
|
||||||
|
criteriaId,
|
||||||
|
onCancel,
|
||||||
|
onCompleted,
|
||||||
|
}: {
|
||||||
|
criteriaTitle: string
|
||||||
|
keys: unknown[]
|
||||||
|
listFormCode: string
|
||||||
|
criteriaId: string
|
||||||
|
onCancel: () => void
|
||||||
|
onCompleted: () => void
|
||||||
|
}) {
|
||||||
|
const { translate } = useLocalization()
|
||||||
|
const [note, setNote] = useState('')
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
|
const decide = async (approved: boolean) => {
|
||||||
|
setSubmitting(true)
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
keys.map((key) =>
|
||||||
|
workflowService.decideWorkflow(
|
||||||
|
listFormCode,
|
||||||
|
[key],
|
||||||
|
approved,
|
||||||
|
note,
|
||||||
|
criteriaId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onCompleted()
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.push(
|
||||||
|
<Notification type="danger" duration={3000}>
|
||||||
|
{error?.response?.data?.error?.message ||
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error?.message ||
|
||||||
|
'Workflow karari verilemedi.'}
|
||||||
|
</Notification>,
|
||||||
|
{ placement: 'top-end' },
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h5 className="mb-4">{criteriaTitle}</h5>
|
||||||
|
<p>{keys.length} kayit icin workflow karari verilecek.</p>
|
||||||
|
<label className="mb-2 block font-semibold">Not</label>
|
||||||
|
<textarea
|
||||||
|
className="input input-textarea mb-4 min-h-[96px] w-full resize-y"
|
||||||
|
rows={4}
|
||||||
|
value={note}
|
||||||
|
placeholder="Onay veya red aciklamasi"
|
||||||
|
onChange={(event) => setNote(event.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="text-right mt-6">
|
||||||
|
<Button className="ltr:mr-2 rtl:ml-2" variant="plain" disabled={submitting} onClick={onCancel}>
|
||||||
|
{translate('::Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button className="ltr:mr-2 rtl:ml-2" disabled={submitting} onClick={() => decide(false)}>
|
||||||
|
Reddet
|
||||||
|
</Button>
|
||||||
|
<Button variant="solid" disabled={submitting} onClick={() => decide(true)}>
|
||||||
|
Onayla
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export { useToolbar }
|
export { useToolbar }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue