diff --git a/.github/instructions/ai.instructions.md b/.github/instructions/ai.instructions.md index 7e1100a..8f3fa9f 100644 --- a/.github/instructions/ai.instructions.md +++ b/.github/instructions/ai.instructions.md @@ -102,7 +102,7 @@ Driven by: - ListFormFields - ListFormCustomization (UserUiFilter, GridState, ServerJoin, ServerWhere) - ListFormImport and ListFormImportLog -- ListFormWorkflow and ListFormWorkflowCriteria +- ListFormWorkflow - ListFormJsonRow operations Capabilities: diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs index dba2162..d16680d 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsDto.cs @@ -365,6 +365,19 @@ public class GridOptionsDto : AuditedEntityDto } set { SubFormsJson = JsonSerializer.Serialize(value); } } + + [JsonIgnore] + public string WorkflowJson { get; set; } + public WorkflowDto WorkflowDto + { + get + { + if (!string.IsNullOrEmpty(WorkflowJson)) + return JsonSerializer.Deserialize(WorkflowJson); + return new WorkflowDto(); + } + set { WorkflowJson = JsonSerializer.Serialize(value); } + } [JsonIgnore] public string ExtraFilterJson { get; set; } // Cagrilacak Extra Filters diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs new file mode 100644 index 0000000..2c67380 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/WorkflowDto.cs @@ -0,0 +1,9 @@ +using System; + +namespace Sozsoft.Platform.ListForms; + +public class WorkflowDto +{ + public string ApprovalFieldName { get; set; } + public DateTime ApprovalDateFieldName { get; set; } +} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs index 7a628bf..d013a7c 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ListFormEditTabs.cs @@ -53,6 +53,7 @@ public class ListFormEditTabs public const string StateForm = "state"; public const string SubFormJsonRow = "subForm"; public const string WidgetForm = "widget"; + public const string WorkflowForm = "workflow"; public const string Fields = "fields"; public const string Customization = "customization"; public const string ExtraFilterForm = "extraFilter"; diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/CreateUpdateListFormWorkflowCriteriaDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/CreateUpdateListFormWorkflowCriteriaDto.cs index 232dabe..3cb3f7c 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/CreateUpdateListFormWorkflowCriteriaDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/CreateUpdateListFormWorkflowCriteriaDto.cs @@ -7,15 +7,12 @@ public class CreateUpdateListFormWorkflowCriteriaDto { public Guid? Id { get; set; } public string ListFormCode { get; set; } - public Guid WorkflowItemId { get; set; } - public string NodeId { get; set; } public string Kind { get; set; } public string Title { get; set; } - public string Column { get; set; } - public string Operator { get; set; } + public string CompareColumn { get; set; } + public string CompareOperator { get; set; } public decimal CompareValue { get; set; } public string Approver { get; set; } - public string InformPerson { get; set; } public string NextOnStart { get; set; } public string NextOnTrue { get; set; } public string NextOnFalse { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/CreateUpdateListFormWorkflowDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/CreateUpdateListFormWorkflowDto.cs deleted file mode 100644 index 501f5d2..0000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/CreateUpdateListFormWorkflowDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Sozsoft.Platform.ListForms.Workflow; - -public class CreateUpdateListFormWorkflowDto -{ - public string ListFormCode { get; set; } - public string Sorumlu { get; set; } - public DateTime? Tarih { get; set; } - public decimal Amount { get; set; } -} - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs index a276668..86e0c5f 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/IListFormWorkflowAppService.cs @@ -7,10 +7,6 @@ namespace Sozsoft.Platform.ListForms.Workflow; public interface IListFormWorkflowAppService : IApplicationService { Task GetStateAsync(string listFormCode = null); - Task CreateWorkflowAsync(CreateUpdateListFormWorkflowDto input); - Task UpdateWorkflowAsync(Guid id, CreateUpdateListFormWorkflowDto input); - Task StartWorkflowAsync(Guid id); - Task DecideWorkflowAsync(Guid id, DecisionWorkflowDto input); Task SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input); Task DeleteCriteriaAsync(Guid id); Task ResetDemoAsync(string listFormCode = null); diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowCriteriaDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowCriteriaDto.cs index db2a10e..f479070 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowCriteriaDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowCriteriaDto.cs @@ -7,15 +7,12 @@ namespace Sozsoft.Platform.ListForms.Workflow; public class ListFormWorkflowCriteriaDto : AuditedEntityDto { public string ListFormCode { get; set; } - public Guid WorkflowItemId { get; set; } - public string NodeId { get; set; } public string Kind { get; set; } public string Title { get; set; } - public string Column { get; set; } - public string Operator { get; set; } + public string CompareColumn { get; set; } + public string CompareOperator { get; set; } public decimal CompareValue { get; set; } public string Approver { get; set; } - public string InformPerson { get; set; } public string NextOnStart { get; set; } public string NextOnTrue { get; set; } public string NextOnFalse { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowDto.cs deleted file mode 100644 index fa45215..0000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowDto.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using Volo.Abp.Application.Dtos; - -namespace Sozsoft.Platform.ListForms.Workflow; - -public class ListFormWorkflowDto : AuditedEntityDto -{ - public string ListFormCode { get; set; } - public int OrderNo { get; set; } - public string Title { get; set; } - public string Sorumlu { get; set; } - public DateTime Tarih { get; set; } - public string Status { get; set; } - public string Durum { get; set; } - public decimal Amount { get; set; } - public string CurrentNodeId { get; set; } - public string AssignedApprover { get; set; } - public string InformedPerson { get; set; } - public List History { get; set; } = []; -} - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowStateDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowStateDto.cs index 9eb3e03..2c1a4a7 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowStateDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/ListFormWorkflowStateDto.cs @@ -4,7 +4,6 @@ namespace Sozsoft.Platform.ListForms.Workflow; public class ListFormWorkflowStateDto { - public List WorkflowItems { get; set; } = []; public List Criteria { get; set; } = []; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowConditionDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowConditionDto.cs index 9d7e90d..25ab9c9 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowConditionDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Workflow/WorkflowConditionDto.cs @@ -2,8 +2,8 @@ namespace Sozsoft.Platform.ListForms.Workflow; public class WorkflowConditionDto { - public string Column { get; set; } - public string Operator { get; set; } + public string CompareColumn { get; set; } + public string CompareOperator { get; set; } public decimal CompareValue { get; set; } } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs index 45f5814..6c8ff4d 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormsAppService.cs @@ -179,6 +179,10 @@ public class ListFormsAppService : CrudAppService< { item.SubFormsJson = JsonSerializer.Serialize(input.SubFormsDto); } + else if (input.EditType == ListFormEditTabs.WorkflowForm) + { + item.WorkflowJson = JsonSerializer.Serialize(input.WorkflowDto); + } /*Chart*/ else if (input.EditType == ListFormEditTabs.ChartCommonForm) diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs new file mode 100644 index 0000000..2d912d0 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Sozsoft.Platform.Entities; +using Volo.Abp; +using Volo.Abp.Domain.Repositories; + +namespace Sozsoft.Platform.ListForms.Workflow; + +[Authorize] +[Route("api/app/list-form-workflow")] +public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowAppService +{ + private const string DefaultListFormCode = "workflow"; + + private readonly IRepository criteriaRepository; + + public ListFormWorkflowAppService(IRepository criteriaRepository) + { + this.criteriaRepository = criteriaRepository; + } + + [HttpGet("state")] + public async Task GetStateAsync(string listFormCode = null) + { + var code = NormalizeListFormCode(listFormCode); + var criteria = (await criteriaRepository.GetListAsync(x => x.ListFormCode == code)) + .OrderBy(x => x.PositionX) + .ThenBy(x => x.PositionY) + .ToList(); + + return new ListFormWorkflowStateDto + { + Criteria = criteria.Select(MapCriteria).ToList() + }; + } + + [HttpPost("criteria")] + public async Task SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input) + { + var code = NormalizeListFormCode(input.ListFormCode); + var isNew = !input.Id.HasValue || input.Id.Value == Guid.Empty; + var criteria = isNew + ? new ListFormWorkflow(GuidGenerator.Create()) + : await criteriaRepository.GetAsync(input.Id.Value); + + if (!isNew && criteria.ListFormCode != code) + { + throw new UserFriendlyException("Workflow adımı seçili liste formuna ait değil."); + } + + criteria.ListFormCode = code; + criteria.Kind = NormalizeRequired(input.Kind, "Compare"); + criteria.Title = NormalizeRequired(input.Title, criteria.Kind); + criteria.CompareColumn = NormalizeRequired(input.CompareColumn, "Tutar"); + criteria.CompareOperator = NormalizeRequired(input.CompareOperator, ">"); + criteria.CompareValue = input.CompareValue; + criteria.Approver = input.Approver ?? string.Empty; + criteria.NextOnStart = input.NextOnStart ?? string.Empty; + criteria.NextOnTrue = input.NextOnTrue ?? string.Empty; + criteria.NextOnFalse = input.NextOnFalse ?? string.Empty; + criteria.NextOnApprove = input.NextOnApprove ?? string.Empty; + criteria.NextOnReject = input.NextOnReject ?? string.Empty; + criteria.PositionX = input.PositionX <= 0 ? 32 : input.PositionX; + criteria.PositionY = input.PositionY <= 0 ? 150 : input.PositionY; + criteria.CompareOutcomesJson = SerializeCompareOutcomes(input.CompareOutcomes); + + var outcomes = input.CompareOutcomes ?? []; + if (criteria.Kind == "Compare" && outcomes.Count > 0) + { + criteria.NextOnTrue = outcomes.ElementAtOrDefault(0)?.TargetId ?? criteria.NextOnTrue; + criteria.NextOnFalse = outcomes.ElementAtOrDefault(1)?.TargetId ?? criteria.NextOnFalse; + } + + if (isNew) + { + await criteriaRepository.InsertAsync(criteria, autoSave: true); + } + else + { + await criteriaRepository.UpdateAsync(criteria, autoSave: true); + } + + return MapCriteria(criteria); + } + + [HttpDelete("criteria/{id}")] + public async Task DeleteCriteriaAsync(Guid id) + { + var criteria = await criteriaRepository.GetAsync(id); + await criteriaRepository.DeleteAsync(criteria, autoSave: true); + + var remaining = await criteriaRepository.GetListAsync(x => x.ListFormCode == criteria.ListFormCode); + foreach (var item in remaining) + { + var changed = ClearDeletedTarget(item, id.ToString()); + if (changed) + { + await criteriaRepository.UpdateAsync(item, autoSave: true); + } + } + } + + [HttpPost("reset-demo")] + public async Task ResetDemoAsync(string listFormCode = null) + { + var code = NormalizeListFormCode(listFormCode); + var existing = await criteriaRepository.GetListAsync(x => x.ListFormCode == code); + foreach (var item in existing) + { + await criteriaRepository.DeleteAsync(item, autoSave: true); + } + + var start = await CreateCriteriaAsync(code, "Start", "İş Akışı Başlat", 80, 150); + var compare = await CreateCriteriaAsync(code, "Compare", "Tutar kontrolü", 330, 130); + var approval = await CreateCriteriaAsync(code, "Approval", "Yönetici Onayı", 590, 60, "ayse.yilmaz"); + var inform = await CreateCriteriaAsync(code, "Inform", "Muhasebe Bilgilendirme", 590, 230, "muhasebe"); + var end = await CreateCriteriaAsync(code, "End", "Akışı Bitir", 850, 150); + + start.NextOnStart = compare.Id.ToString(); + compare.NextOnTrue = approval.Id.ToString(); + compare.NextOnFalse = inform.Id.ToString(); + compare.CompareOutcomesJson = SerializeCompareOutcomes([ + new CompareOutcomeDto + { + Label = "Onay gerekir", + TargetId = approval.Id.ToString(), + Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = ">", CompareValue = 5000 }] + }, + new CompareOutcomeDto + { + Label = "Bilgilendir", + TargetId = inform.Id.ToString(), + Conditions = [new WorkflowConditionDto { CompareColumn = "Tutar", CompareOperator = "<=", CompareValue = 5000 }] + } + ]); + approval.NextOnApprove = inform.Id.ToString(); + approval.NextOnReject = end.Id.ToString(); + inform.NextOnStart = end.Id.ToString(); + + await criteriaRepository.UpdateAsync(start, autoSave: true); + await criteriaRepository.UpdateAsync(compare, autoSave: true); + await criteriaRepository.UpdateAsync(approval, autoSave: true); + await criteriaRepository.UpdateAsync(inform, autoSave: true); + + return await GetStateAsync(code); + } + + private async Task CreateCriteriaAsync( + string listFormCode, + string kind, + string title, + int positionX, + int positionY, + string approver = "") + { + var criteria = new ListFormWorkflow(GuidGenerator.Create()) + { + ListFormCode = listFormCode, + Kind = kind, + Title = title, + CompareColumn = "Tutar", + CompareOperator = ">", + CompareValue = 5000, + Approver = approver, + NextOnStart = string.Empty, + NextOnTrue = string.Empty, + NextOnFalse = string.Empty, + NextOnApprove = string.Empty, + NextOnReject = string.Empty, + PositionX = positionX, + PositionY = positionY, + CompareOutcomesJson = SerializeCompareOutcomes([]) + }; + + await criteriaRepository.InsertAsync(criteria, autoSave: true); + return criteria; + } + + private static bool ClearDeletedTarget(ListFormWorkflow criteria, string deletedId) + { + var changed = false; + if (criteria.NextOnStart == deletedId) + { + criteria.NextOnStart = string.Empty; + changed = true; + } + if (criteria.NextOnTrue == deletedId) + { + criteria.NextOnTrue = string.Empty; + changed = true; + } + if (criteria.NextOnFalse == deletedId) + { + criteria.NextOnFalse = string.Empty; + changed = true; + } + if (criteria.NextOnApprove == deletedId) + { + criteria.NextOnApprove = string.Empty; + changed = true; + } + if (criteria.NextOnReject == deletedId) + { + criteria.NextOnReject = string.Empty; + changed = true; + } + + var outcomes = DeserializeCompareOutcomes(criteria.CompareOutcomesJson); + foreach (var outcome in outcomes.Where(x => x.TargetId == deletedId)) + { + outcome.TargetId = null; + changed = true; + } + + if (changed) + { + criteria.CompareOutcomesJson = SerializeCompareOutcomes(outcomes); + } + + return changed; + } + + private static ListFormWorkflowCriteriaDto MapCriteria(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 string NormalizeListFormCode(string listFormCode) + { + return listFormCode.IsNullOrWhiteSpace() ? DefaultListFormCode : listFormCode.Trim(); + } + + private static string NormalizeRequired(string value, string fallback) + { + return value.IsNullOrWhiteSpace() ? fallback : value.Trim(); + } + + private static string SerializeCompareOutcomes(List outcomes) + { + return JsonSerializer.Serialize(outcomes ?? []); + } + + private static List DeserializeCompareOutcomes(string json) + { + if (json.IsNullOrWhiteSpace()) + { + return []; + } + + try + { + return JsonSerializer.Deserialize>(json) ?? []; + } + catch + { + return []; + } + } +} diff --git a/api/src/Sozsoft.Platform.Application/ListForms/Workflow/ListFormWorkflowAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/Workflow/ListFormWorkflowAppService.cs deleted file mode 100644 index fe5e7a0..0000000 --- a/api/src/Sozsoft.Platform.Application/ListForms/Workflow/ListFormWorkflowAppService.cs +++ /dev/null @@ -1,595 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Sozsoft.Platform.Entities; -using Volo.Abp; -using Volo.Abp.Domain.Repositories; - -namespace Sozsoft.Platform.ListForms.Workflow; - -[Authorize] -[Route("api/app/list-form-workflow")] -public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowAppService -{ - private const string DefaultListFormCode = "workflow"; - private const string StatusNew = "Yeni"; - private const string StatusPending = "Onay Bekliyor"; - private const string StatusFinished = "Bitti"; - private const string StatusInformed = "Bilgilendirildi"; - - private readonly IRepository workflowRepository; - private readonly IRepository criteriaRepository; - - public ListFormWorkflowAppService( - IRepository workflowRepository, - IRepository criteriaRepository) - { - this.workflowRepository = workflowRepository; - this.criteriaRepository = criteriaRepository; - } - - [HttpGet("state")] - public async Task GetStateAsync(string listFormCode = null) - { - var code = NormalizeListFormCode(listFormCode); - var workflows = (await workflowRepository.GetListAsync(x => x.ListFormCode == code)) - .OrderBy(x => x.OrderNo) - .ThenBy(x => x.CreationTime) - .ToList(); - var criteria = (await criteriaRepository.GetListAsync(x => x.ListFormCode == code)) - .OrderBy(x => x.WorkflowItemId) - .ThenBy(x => x.PositionX) - .ThenBy(x => x.PositionY) - .ToList(); - - return new ListFormWorkflowStateDto - { - WorkflowItems = workflows.Select(MapWorkflow).ToList(), - Criteria = criteria.Select(MapCriteria).ToList() - }; - } - - [HttpPost("workflows")] - public async Task CreateWorkflowAsync(CreateUpdateListFormWorkflowDto input) - { - var code = NormalizeListFormCode(input.ListFormCode); - var nextOrderNo = (await workflowRepository.GetListAsync(x => x.ListFormCode == code)) - .Select(x => x.OrderNo) - .DefaultIfEmpty() - .Max() + 1; - - var workflow = new ListFormWorkflow(GuidGenerator.Create()) - { - ListFormCode = code, - OrderNo = nextOrderNo, - Title = NormalizeRequired(input.Sorumlu, "Yeni Workflow"), - Status = StatusNew, - Amount = input.Amount, - CurrentNodeId = string.Empty, - AssignedApprover = string.Empty, - InformedPerson = string.Empty, - HistoryJson = SerializeHistory([]) - }; - - await workflowRepository.InsertAsync(workflow, autoSave: true); - - var start = await CreateDefaultStartCriteriaAsync(workflow); - workflow.CurrentNodeId = start.Id.ToString(); - await workflowRepository.UpdateAsync(workflow, autoSave: true); - - return MapWorkflow(workflow); - } - - [HttpPut("workflows/{id}")] - public async Task UpdateWorkflowAsync(Guid id, CreateUpdateListFormWorkflowDto input) - { - var workflow = await workflowRepository.GetAsync(id); - workflow.ListFormCode = NormalizeListFormCode(input.ListFormCode ?? workflow.ListFormCode); - workflow.Title = NormalizeRequired(input.Sorumlu, workflow.Title); - workflow.Amount = input.Amount; - - await workflowRepository.UpdateAsync(workflow, autoSave: true); - return MapWorkflow(workflow); - } - - [HttpPost("workflows/{id}/start")] - public async Task StartWorkflowAsync(Guid id) - { - var workflow = await workflowRepository.GetAsync(id); - var criteria = await GetCriteriaForWorkflowAsync(workflow); - var current = criteria.FirstOrDefault(x => x.Id.ToString() == workflow.CurrentNodeId) - ?? criteria.FirstOrDefault(x => x.Kind == "Start") - ?? throw new UserFriendlyException("Bu workflow için başlangıç adımı bulunamadı."); - - workflow.CurrentNodeId = current.Id.ToString(); - AddHistory(workflow, "Başlatıldı", "Workflow başlatıldı."); - await AdvanceWorkflowAsync(workflow, criteria, current); - await workflowRepository.UpdateAsync(workflow, autoSave: true); - - return MapWorkflow(workflow); - } - - [HttpPost("workflows/{id}/decision")] - public async Task DecideWorkflowAsync(Guid id, DecisionWorkflowDto input) - { - var workflow = await workflowRepository.GetAsync(id); - var criteria = await GetCriteriaForWorkflowAsync(workflow); - var current = criteria.FirstOrDefault(x => x.Id.ToString() == workflow.CurrentNodeId) - ?? throw new UserFriendlyException("Aktif workflow adımı bulunamadı."); - - if (current.Kind != "Approval") - { - throw new UserFriendlyException("Workflow aktif olarak onay adımında değil."); - } - - var nextId = input.Approved ? current.NextOnApprove : current.NextOnReject; - AddHistory( - workflow, - input.Approved ? "Onaylandı" : "Reddedildi", - NormalizeRequired(input.Note, input.Approved ? "Onay verildi." : "Red edildi.")); - - var next = criteria.FirstOrDefault(x => x.Id.ToString() == nextId); - if (next == null) - { - workflow.Status = StatusFinished; - workflow.CurrentNodeId = current.Id.ToString(); - } - else - { - await AdvanceWorkflowAsync(workflow, criteria, next); - } - - await workflowRepository.UpdateAsync(workflow, autoSave: true); - return MapWorkflow(workflow); - } - - [HttpPost("criteria")] - public async Task SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input) - { - var workflow = await workflowRepository.GetAsync(input.WorkflowItemId); - var isNew = !input.Id.HasValue || input.Id.Value == Guid.Empty; - var criteria = isNew - ? new ListFormWorkflowCriteria(GuidGenerator.Create()) { WorkflowItemId = workflow.Id } - : await criteriaRepository.GetAsync(input.Id.Value); - - criteria.ListFormCode = NormalizeListFormCode(input.ListFormCode ?? workflow.ListFormCode); - criteria.WorkflowItemId = workflow.Id; - criteria.NodeId = NormalizeRequired(input.NodeId, criteria.Id.ToString()); - criteria.Kind = NormalizeRequired(input.Kind, "Compare"); - criteria.Title = NormalizeRequired(input.Title, criteria.Kind); - criteria.Column = NormalizeRequired(input.Column, "Tutar"); - criteria.Operator = NormalizeRequired(input.Operator, ">"); - criteria.CompareValue = input.CompareValue; - criteria.Approver = input.Approver ?? string.Empty; - criteria.InformPerson = input.InformPerson ?? string.Empty; - criteria.NextOnStart = input.NextOnStart ?? string.Empty; - criteria.NextOnTrue = input.NextOnTrue ?? string.Empty; - criteria.NextOnFalse = input.NextOnFalse ?? string.Empty; - criteria.NextOnApprove = input.NextOnApprove ?? string.Empty; - criteria.NextOnReject = input.NextOnReject ?? string.Empty; - criteria.PositionX = input.PositionX <= 0 ? 32 : input.PositionX; - criteria.PositionY = input.PositionY <= 0 ? 150 : input.PositionY; - criteria.CompareOutcomesJson = SerializeCompareOutcomes(input.CompareOutcomes); - - var outcomes = input.CompareOutcomes ?? []; - if (criteria.Kind == "Compare" && outcomes.Count > 0) - { - criteria.NextOnTrue = outcomes.ElementAtOrDefault(0)?.TargetId ?? criteria.NextOnTrue; - criteria.NextOnFalse = outcomes.ElementAtOrDefault(1)?.TargetId ?? criteria.NextOnFalse; - } - - if (isNew) - { - await criteriaRepository.InsertAsync(criteria, autoSave: true); - if (workflow.CurrentNodeId.IsNullOrWhiteSpace() || criteria.Kind == "Start") - { - workflow.CurrentNodeId = criteria.Id.ToString(); - await workflowRepository.UpdateAsync(workflow, autoSave: true); - } - } - else - { - await criteriaRepository.UpdateAsync(criteria, autoSave: true); - } - - return MapCriteria(criteria); - } - - [HttpDelete("criteria/{id}")] - public async Task DeleteCriteriaAsync(Guid id) - { - var criteria = await criteriaRepository.GetAsync(id); - var workflow = await workflowRepository.GetAsync(criteria.WorkflowItemId); - await criteriaRepository.DeleteAsync(criteria, autoSave: true); - - var remaining = await GetCriteriaForWorkflowAsync(workflow); - foreach (var item in remaining) - { - var changed = ClearDeletedTarget(item, id.ToString()); - if (changed) - { - await criteriaRepository.UpdateAsync(item, autoSave: true); - } - } - - if (workflow.CurrentNodeId == id.ToString()) - { - workflow.CurrentNodeId = remaining.FirstOrDefault(x => x.Kind == "Start")?.Id.ToString() ?? string.Empty; - await workflowRepository.UpdateAsync(workflow, autoSave: true); - } - } - - [HttpPost("reset-demo")] - public async Task ResetDemoAsync(string listFormCode = null) - { - var code = NormalizeListFormCode(listFormCode); - var workflows = await workflowRepository.GetListAsync(x => x.ListFormCode == code); - var workflowIds = workflows.Select(x => x.Id).ToList(); - var criteria = await criteriaRepository.GetListAsync(x => x.ListFormCode == code && workflowIds.Contains(x.WorkflowItemId)); - foreach (var item in criteria) - { - await criteriaRepository.DeleteAsync(item, autoSave: true); - } - - foreach (var workflow in workflows) - { - await workflowRepository.DeleteAsync(workflow, autoSave: true); - } - - var demo = new ListFormWorkflow(GuidGenerator.Create()) - { - ListFormCode = code, - OrderNo = 1, - Title = "Üretim Süreci", - Status = StatusNew, - Amount = 7200, - CurrentNodeId = string.Empty, - AssignedApprover = string.Empty, - InformedPerson = string.Empty, - HistoryJson = SerializeHistory([]) - }; - await workflowRepository.InsertAsync(demo, autoSave: true); - - var start = await CreateCriteriaAsync(demo, "Start", "İş Akışı Başlat", 80, 150); - var compare = await CreateCriteriaAsync(demo, "Compare", "Tutar kontrolü", 330, 130); - var approval = await CreateCriteriaAsync(demo, "Approval", "Yönetici Onayı", 590, 60, "ayse.yilmaz"); - var inform = await CreateCriteriaAsync(demo, "Inform", "Muhasebe Bilgilendirme", 590, 230, "muhasebe"); - var end = await CreateCriteriaAsync(demo, "End", "Akışı Bitir", 850, 150); - - start.NextOnStart = compare.Id.ToString(); - compare.NextOnTrue = approval.Id.ToString(); - compare.NextOnFalse = inform.Id.ToString(); - compare.CompareOutcomesJson = SerializeCompareOutcomes([ - new CompareOutcomeDto - { - Label = "Onay gerekir", - TargetId = approval.Id.ToString(), - Conditions = [new WorkflowConditionDto { Column = "Tutar", Operator = ">", CompareValue = 5000 }] - }, - new CompareOutcomeDto - { - Label = "Bilgilendir", - TargetId = inform.Id.ToString(), - Conditions = [new WorkflowConditionDto { Column = "Tutar", Operator = "<=", CompareValue = 5000 }] - } - ]); - approval.NextOnApprove = inform.Id.ToString(); - approval.NextOnReject = end.Id.ToString(); - inform.NextOnStart = end.Id.ToString(); - - await criteriaRepository.UpdateAsync(start, autoSave: true); - await criteriaRepository.UpdateAsync(compare, autoSave: true); - await criteriaRepository.UpdateAsync(approval, autoSave: true); - await criteriaRepository.UpdateAsync(inform, autoSave: true); - - demo.CurrentNodeId = start.Id.ToString(); - await workflowRepository.UpdateAsync(demo, autoSave: true); - - return await GetStateAsync(code); - } - - private async Task CreateDefaultStartCriteriaAsync(ListFormWorkflow workflow) - { - return await CreateCriteriaAsync(workflow, "Start", "İş Akışı Başlat", 32, 150); - } - - private async Task CreateCriteriaAsync( - ListFormWorkflow workflow, - string kind, - string title, - int positionX, - int positionY, - string approver = "") - { - var criteria = new ListFormWorkflowCriteria(GuidGenerator.Create()) - { - ListFormCode = workflow.ListFormCode, - WorkflowItemId = workflow.Id, - NodeId = GuidGenerator.Create().ToString(), - Kind = kind, - Title = title, - Column = "Tutar", - Operator = ">", - CompareValue = 5000, - Approver = approver, - InformPerson = approver, - NextOnStart = string.Empty, - NextOnTrue = string.Empty, - NextOnFalse = string.Empty, - NextOnApprove = string.Empty, - NextOnReject = string.Empty, - PositionX = positionX, - PositionY = positionY, - CompareOutcomesJson = SerializeCompareOutcomes([]) - }; - - await criteriaRepository.InsertAsync(criteria, autoSave: true); - return criteria; - } - - private async Task> GetCriteriaForWorkflowAsync(ListFormWorkflow workflow) - { - return (await criteriaRepository.GetListAsync(x => x.WorkflowItemId == workflow.Id)) - .OrderBy(x => x.PositionX) - .ThenBy(x => x.PositionY) - .ToList(); - } - - private async Task AdvanceWorkflowAsync( - ListFormWorkflow workflow, - List criteria, - ListFormWorkflowCriteria current) - { - var visited = new HashSet(); - var step = current; - - while (step != null && visited.Add(step.Id)) - { - workflow.CurrentNodeId = step.Id.ToString(); - - if (step.Kind == "Approval") - { - workflow.Status = StatusPending; - workflow.AssignedApprover = step.Approver; - workflow.InformedPerson = step.InformPerson; - return; - } - - if (step.Kind == "End") - { - workflow.Status = StatusFinished; - AddHistory(workflow, "Tamamlandı", "Workflow tamamlandı."); - return; - } - - if (step.Kind == "Inform") - { - workflow.Status = StatusInformed; - workflow.InformedPerson = step.InformPerson; - AddHistory(workflow, "Bilgilendirildi", $"{step.InformPerson} bilgilendirildi."); - step = FindNext(criteria, step.NextOnStart); - continue; - } - - if (step.Kind == "Compare") - { - step = FindNext(criteria, ResolveCompareTarget(workflow, step)); - continue; - } - - workflow.Status = StatusNew; - step = FindNext(criteria, step.NextOnStart); - } - - await Task.CompletedTask; - } - - private string ResolveCompareTarget(ListFormWorkflow workflow, ListFormWorkflowCriteria criteria) - { - var outcomes = DeserializeCompareOutcomes(criteria.CompareOutcomesJson); - foreach (var outcome in outcomes) - { - if (outcome.Conditions.Count > 0 && outcome.Conditions.All(condition => EvaluateCondition(workflow, condition))) - { - return outcome.TargetId; - } - } - - return EvaluateCondition(workflow, new WorkflowConditionDto - { - Column = criteria.Column, - Operator = criteria.Operator, - CompareValue = criteria.CompareValue - }) - ? criteria.NextOnTrue - : criteria.NextOnFalse; - } - - private static bool EvaluateCondition(ListFormWorkflow workflow, WorkflowConditionDto condition) - { - var value = condition.Column == "Id" ? workflow.OrderNo : workflow.Amount; - return condition.Operator switch - { - ">" => value > condition.CompareValue, - ">=" => value >= condition.CompareValue, - "<" => value < condition.CompareValue, - "<=" => value <= condition.CompareValue, - "=" => value == condition.CompareValue, - "!=" => value != condition.CompareValue, - _ => false - }; - } - - private static ListFormWorkflowCriteria FindNext(List criteria, string id) - { - return id.IsNullOrWhiteSpace() ? null : criteria.FirstOrDefault(x => x.Id.ToString() == id); - } - - private static bool ClearDeletedTarget(ListFormWorkflowCriteria criteria, string deletedId) - { - var changed = false; - if (criteria.NextOnStart == deletedId) - { - criteria.NextOnStart = string.Empty; - changed = true; - } - if (criteria.NextOnTrue == deletedId) - { - criteria.NextOnTrue = string.Empty; - changed = true; - } - if (criteria.NextOnFalse == deletedId) - { - criteria.NextOnFalse = string.Empty; - changed = true; - } - if (criteria.NextOnApprove == deletedId) - { - criteria.NextOnApprove = string.Empty; - changed = true; - } - if (criteria.NextOnReject == deletedId) - { - criteria.NextOnReject = string.Empty; - changed = true; - } - - var outcomes = DeserializeCompareOutcomes(criteria.CompareOutcomesJson); - foreach (var outcome in outcomes.Where(x => x.TargetId == deletedId)) - { - outcome.TargetId = null; - changed = true; - } - - if (changed) - { - criteria.CompareOutcomesJson = SerializeCompareOutcomes(outcomes); - } - - return changed; - } - - private static void AddHistory(ListFormWorkflow workflow, string action, string note) - { - var history = DeserializeHistory(workflow.HistoryJson); - history.Add(new WorkflowHistoryDto - { - Time = DateTime.Now, - Action = action, - Note = note - }); - workflow.HistoryJson = SerializeHistory(history); - } - - private static ListFormWorkflowDto MapWorkflow(ListFormWorkflow workflow) - { - return new ListFormWorkflowDto - { - Id = workflow.Id, - CreationTime = workflow.CreationTime, - CreatorId = workflow.CreatorId, - LastModificationTime = workflow.LastModificationTime, - LastModifierId = workflow.LastModifierId, - ListFormCode = workflow.ListFormCode, - OrderNo = workflow.OrderNo, - Title = workflow.Title, - Sorumlu = workflow.Title, - Tarih = workflow.CreationTime == default ? DateTime.Now : workflow.CreationTime, - Status = workflow.Status, - Durum = workflow.Status, - Amount = workflow.Amount, - CurrentNodeId = workflow.CurrentNodeId, - AssignedApprover = workflow.AssignedApprover, - InformedPerson = workflow.InformedPerson, - History = DeserializeHistory(workflow.HistoryJson) - }; - } - - private static ListFormWorkflowCriteriaDto MapCriteria(ListFormWorkflowCriteria criteria) - { - return new ListFormWorkflowCriteriaDto - { - Id = criteria.Id, - CreationTime = criteria.CreationTime, - CreatorId = criteria.CreatorId, - LastModificationTime = criteria.LastModificationTime, - LastModifierId = criteria.LastModifierId, - ListFormCode = criteria.ListFormCode, - WorkflowItemId = criteria.WorkflowItemId, - NodeId = criteria.NodeId, - Kind = criteria.Kind, - Title = criteria.Title, - Column = criteria.Column, - Operator = criteria.Operator, - CompareValue = criteria.CompareValue, - Approver = criteria.Approver, - InformPerson = criteria.InformPerson, - 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 string NormalizeListFormCode(string listFormCode) - { - return listFormCode.IsNullOrWhiteSpace() ? DefaultListFormCode : listFormCode.Trim(); - } - - private static string NormalizeRequired(string value, string fallback) - { - return value.IsNullOrWhiteSpace() ? fallback : value.Trim(); - } - - private static string SerializeCompareOutcomes(List outcomes) - { - return JsonSerializer.Serialize(outcomes ?? []); - } - - private static List DeserializeCompareOutcomes(string json) - { - if (json.IsNullOrWhiteSpace()) - { - return []; - } - - try - { - return JsonSerializer.Deserialize>(json) ?? []; - } - catch - { - return []; - } - } - - private static string SerializeHistory(List history) - { - return JsonSerializer.Serialize(history ?? []); - } - - private static List DeserializeHistory(string json) - { - if (json.IsNullOrWhiteSpace()) - { - return []; - } - - try - { - return JsonSerializer.Deserialize>(json) ?? []; - } - catch - { - return []; - } - } -} diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Utils.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Utils.cs index 21b30ba..e0bfa74 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Utils.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Utils.cs @@ -59,6 +59,8 @@ public class ListFormSeeder_Utils { ListFormCode = newListFormCode, SubFormsJson = listForm.SubFormsJson, + WidgetsJson = listForm.WidgetsJson, + WorkflowJson = listForm.WorkflowJson, ListFormType = listForm.ListFormType, IsSubForm = listForm.IsSubForm, ShowNote = listForm.ShowNote, diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs index 513479a..e15e1ba 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs @@ -29,7 +29,6 @@ public enum TableNameEnum ListFormImport, ListFormImportLog, ListFormWorkflow, - ListFormWorkflowCriteria, Note, ForumCategory, ForumTopic, diff --git a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs index 123a92f..9e4c8ed 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs @@ -39,7 +39,6 @@ public static class TableNameResolver { nameof(TableNameEnum.ListFormImport), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ListFormImportLog), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.ListFormWorkflow), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, - { nameof(TableNameEnum.ListFormWorkflowCriteria), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.Notification), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.NotificationRule), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.NotificationType), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs index ac96b5f..d71c327 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs @@ -59,7 +59,7 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou { using var uow = LazyServiceProvider.LazyGetRequiredService().Begin(requiresNew: true, isTransactional: false); - var Worker = await Repository.FirstOrDefaultAsync(a => a.Id == WorkerId); + var Worker = await Repository.FirstOrDefaultAsync(a => a.Id == WorkerId, cancellationToken: cancellationToken); var LogPrefix = $"{Clock.Now:s}_{Worker.Name}: {{0}}"; var DistributedLockName = Worker.Name; diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs index ec1ea2b..f0e28e9 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs @@ -137,10 +137,13 @@ public class ListForm : Entity /// bu listform'un üstünde yer alan widgetların listesidir public string WidgetsJson { get; set; } - /// bu listform'un üstünde yer alan widgetların listesidir + /// bu listform'un üstünde yer alan workflowların listesidir + public string WorkflowJson { get; set; } + + /// bu listform'un üstünde yer alan extra filterların listesidir public string ExtraFilterJson { get; set; } - /// bu listform'un üstünde yer alan widgetların listesidir + /// bu listform'un üstünde yer alan layoutların listesidir public string LayoutJson { get; set; } /* diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListFormWorkflow.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListFormWorkflow.cs index 788fd29..338fe6e 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListFormWorkflow.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListFormWorkflow.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; -using Volo.Abp.Domain.Entities.Auditing; +using Volo.Abp.Domain.Entities; namespace Sozsoft.Platform.Entities; -public class ListFormWorkflow : FullAuditedEntity +public class ListFormWorkflow : Entity { protected ListFormWorkflow() { @@ -15,14 +14,18 @@ public class ListFormWorkflow : FullAuditedEntity } public string ListFormCode { get; set; } - public int OrderNo { get; set; } + public string Kind { get; set; } public string Title { get; set; } - public string Status { get; set; } - public decimal Amount { get; set; } - public string CurrentNodeId { get; set; } - public string AssignedApprover { get; set; } - public string InformedPerson { get; set; } - public string HistoryJson { get; set; } - - public ICollection Criteria { get; set; } + public string CompareColumn { get; set; } + public string CompareOperator { get; set; } + public decimal CompareValue { get; set; } + public string Approver { get; set; } + public string NextOnStart { get; set; } + public string NextOnTrue { get; set; } + public string NextOnFalse { get; set; } + public string NextOnApprove { get; set; } + public string NextOnReject { get; set; } + public int PositionX { get; set; } + public int PositionY { get; set; } + public string CompareOutcomesJson { get; set; } } diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListFormWorkflowCriteria.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListFormWorkflowCriteria.cs deleted file mode 100644 index 4f2fe4f..0000000 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListFormWorkflowCriteria.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Volo.Abp.Domain.Entities.Auditing; - -namespace Sozsoft.Platform.Entities; - -public class ListFormWorkflowCriteria : FullAuditedEntity -{ - protected ListFormWorkflowCriteria() - { - } - - public ListFormWorkflowCriteria(Guid id) : base(id) - { - } - - public string ListFormCode { get; set; } - public Guid WorkflowItemId { get; set; } - public ListFormWorkflow WorkflowItem { get; set; } - public string NodeId { get; set; } - public string Kind { get; set; } - public string Title { get; set; } - public string Column { get; set; } - public string Operator { get; set; } - public decimal CompareValue { get; set; } - public string Approver { get; set; } - public string InformPerson { get; set; } - public string NextOnStart { get; set; } - public string NextOnTrue { get; set; } - public string NextOnFalse { get; set; } - public string NextOnApprove { get; set; } - public string NextOnReject { get; set; } - public int PositionX { get; set; } - public int PositionY { get; set; } - public string CompareOutcomesJson { get; set; } -} diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 08f7285..6f6efd6 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -55,8 +55,7 @@ public class PlatformDbContext : public DbSet ListFormCustomization { get; set; } public DbSet ListFormImports { get; set; } public DbSet ListFormImportLogs { get; set; } - public DbSet ListFormWorkflows { get; set; } - public DbSet ListFormWorkflowCriteria { get; set; } + public DbSet ListFormWorkflow { get; set; } public DbSet BackgroundWorkers { get; set; } public DbSet ForumCategories { get; set; } public DbSet ForumTopics { get; set; } @@ -365,6 +364,7 @@ public class PlatformDbContext : b.Property(a => a.FormFieldsDefaultValueJson).HasColumnType("text"); b.Property(a => a.SubFormsJson).HasColumnType("text"); b.Property(a => a.WidgetsJson).HasColumnType("text"); + b.Property(a => a.WorkflowJson).HasColumnType("text"); b.Property(a => a.ExtraFilterJson).HasColumnType("text"); b.Property(a => a.LayoutJson).HasColumnType("text"); b.Property(a => a.CommonJson).HasColumnType("text"); @@ -488,36 +488,12 @@ public class PlatformDbContext : b.ConfigureByConvention(); b.Property(x => x.ListFormCode).IsRequired().HasMaxLength(64); - b.Property(x => x.OrderNo).IsRequired(); - b.Property(x => x.Title).IsRequired().HasMaxLength(150); - b.Property(x => x.Status).IsRequired().HasMaxLength(100); - b.Property(x => x.Amount).HasPrecision(18, 2); - b.Property(x => x.CurrentNodeId).IsRequired().HasMaxLength(50); - b.Property(x => x.AssignedApprover).IsRequired().HasMaxLength(250); - b.Property(x => x.InformedPerson).IsRequired().HasMaxLength(250); - b.Property(x => x.HistoryJson).HasColumnType("text"); - - b.HasMany(x => x.Criteria) - .WithOne(x => x.WorkflowItem) - .HasForeignKey(x => x.WorkflowItemId) - .OnDelete(DeleteBehavior.Cascade); - }); - - builder.Entity(b => - { - b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ListFormWorkflowCriteria)), Prefix.DbSchema); - b.ConfigureByConvention(); - - b.Property(x => x.ListFormCode).IsRequired().HasMaxLength(64); - b.Property(x => x.WorkflowItemId).IsRequired(); - b.Property(x => x.NodeId).IsRequired().HasMaxLength(50); b.Property(x => x.Kind).IsRequired().HasMaxLength(50); b.Property(x => x.Title).IsRequired().HasMaxLength(250); - b.Property(x => x.Column).IsRequired().HasMaxLength(100); - b.Property(x => x.Operator).IsRequired().HasMaxLength(20); + b.Property(x => x.CompareColumn).IsRequired().HasMaxLength(100); + b.Property(x => x.CompareOperator).IsRequired().HasMaxLength(20); b.Property(x => x.CompareValue).HasPrecision(18, 2); b.Property(x => x.Approver).IsRequired().HasMaxLength(250); - b.Property(x => x.InformPerson).IsRequired().HasMaxLength(250); b.Property(x => x.NextOnStart).IsRequired().HasMaxLength(50); b.Property(x => x.NextOnTrue).IsRequired().HasMaxLength(50); b.Property(x => x.NextOnFalse).IsRequired().HasMaxLength(50); @@ -526,13 +502,6 @@ public class PlatformDbContext : b.Property(x => x.PositionX).IsRequired(); b.Property(x => x.PositionY).IsRequired(); b.Property(x => x.CompareOutcomesJson).HasColumnType("text"); - - b.HasIndex(x => new - { - x.ListFormCode, - x.WorkflowItemId, - x.NodeId - }); }); builder.Entity(b => diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522085648_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522200739_Initial.Designer.cs similarity index 98% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522085648_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522200739_Initial.Designer.cs index 77293d5..ef48921 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522085648_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522200739_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260522085648_Initial")] + [Migration("20260522200739_Initial")] partial class Initial { /// @@ -3053,6 +3053,9 @@ namespace Sozsoft.Platform.Migrations b.Property("Width") .HasColumnType("int"); + b.Property("WorkflowJson") + .HasColumnType("text"); + b.Property("ZoomAndPanJson") .HasColumnType("text"); @@ -3410,86 +3413,6 @@ namespace Sozsoft.Platform.Migrations }); modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("Amount") - .HasPrecision(18, 2) - .HasColumnType("decimal(18,2)"); - - b.Property("AssignedApprover") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("CreationTime") - .HasColumnType("datetime2") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uniqueidentifier") - .HasColumnName("CreatorId"); - - b.Property("CurrentNodeId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("DeleterId") - .HasColumnType("uniqueidentifier") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("datetime2") - .HasColumnName("DeletionTime"); - - b.Property("HistoryJson") - .HasColumnType("text"); - - b.Property("InformedPerson") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("datetime2") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uniqueidentifier") - .HasColumnName("LastModifierId"); - - b.Property("ListFormCode") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("nvarchar(64)"); - - b.Property("OrderNo") - .HasColumnType("int"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("nvarchar(150)"); - - b.HasKey("Id"); - - b.ToTable("Sas_H_ListFormWorkflow", (string)null); - }); - - modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflowCriteria", b => { b.Property("Id") .HasColumnType("uniqueidentifier"); @@ -3499,11 +3422,16 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(250) .HasColumnType("nvarchar(250)"); - b.Property("Column") + b.Property("CompareColumn") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("CompareOperator") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + b.Property("CompareOutcomesJson") .HasColumnType("text"); @@ -3511,46 +3439,11 @@ namespace Sozsoft.Platform.Migrations .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); - b.Property("CreationTime") - .HasColumnType("datetime2") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uniqueidentifier") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uniqueidentifier") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("datetime2") - .HasColumnName("DeletionTime"); - - b.Property("InformPerson") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - b.Property("Kind") .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("LastModificationTime") - .HasColumnType("datetime2") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uniqueidentifier") - .HasColumnName("LastModifierId"); - b.Property("ListFormCode") .IsRequired() .HasMaxLength(64) @@ -3581,16 +3474,6 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("NodeId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Operator") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - b.Property("PositionX") .HasColumnType("int"); @@ -3602,16 +3485,9 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(250) .HasColumnType("nvarchar(250)"); - b.Property("WorkflowItemId") - .HasColumnType("uniqueidentifier"); - b.HasKey("Id"); - b.HasIndex("WorkflowItemId"); - - b.HasIndex("ListFormCode", "WorkflowItemId", "NodeId"); - - b.ToTable("Sas_H_ListFormWorkflowCriteria", (string)null); + b.ToTable("Sas_H_ListFormWorkflow", (string)null); }); modelBuilder.Entity("Sozsoft.Platform.Entities.LogEntry", b => @@ -8344,17 +8220,6 @@ namespace Sozsoft.Platform.Migrations .IsRequired(); }); - modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflowCriteria", b => - { - b.HasOne("Sozsoft.Platform.Entities.ListFormWorkflow", "WorkflowItem") - .WithMany("Criteria") - .HasForeignKey("WorkflowItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("WorkflowItem"); - }); - modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b => { b.HasOne("Sozsoft.Platform.Entities.Order", "Order") @@ -8790,11 +8655,6 @@ namespace Sozsoft.Platform.Migrations b.Navigation("Events"); }); - modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b => - { - b.Navigation("Criteria"); - }); - modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b => { b.Navigation("Items"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522085648_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522200739_Initial.cs similarity index 98% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522085648_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522200739_Initial.cs index 2fc8753..71b932e 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522085648_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260522200739_Initial.cs @@ -1355,6 +1355,7 @@ namespace Sozsoft.Platform.Migrations ShowNote = table.Column(type: "bit", nullable: false), SubFormsJson = table.Column(type: "text", nullable: true), WidgetsJson = table.Column(type: "text", nullable: true), + WorkflowJson = table.Column(type: "text", nullable: true), ExtraFilterJson = table.Column(type: "text", nullable: true), LayoutJson = table.Column(type: "text", nullable: true), UserId = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), @@ -1394,21 +1395,20 @@ namespace Sozsoft.Platform.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), ListFormCode = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - OrderNo = table.Column(type: "int", nullable: false), - Title = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), - Status = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Amount = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), - CurrentNodeId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - AssignedApprover = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - InformedPerson = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - HistoryJson = table.Column(type: "text", nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) + Kind = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Title = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), + CompareColumn = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + CompareOperator = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + CompareValue = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), + Approver = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), + NextOnStart = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + NextOnTrue = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + NextOnFalse = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + NextOnApprove = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + NextOnReject = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + PositionX = table.Column(type: "int", nullable: false), + PositionY = table.Column(type: "int", nullable: false), + CompareOutcomesJson = table.Column(type: "text", nullable: true) }, constraints: table => { @@ -2661,48 +2661,6 @@ namespace Sozsoft.Platform.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "Sas_H_ListFormWorkflowCriteria", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ListFormCode = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), - WorkflowItemId = table.Column(type: "uniqueidentifier", nullable: false), - NodeId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Kind = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Title = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - Column = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Operator = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), - CompareValue = table.Column(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false), - Approver = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - InformPerson = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - NextOnStart = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - NextOnTrue = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - NextOnFalse = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - NextOnApprove = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - NextOnReject = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - PositionX = table.Column(type: "int", nullable: false), - PositionY = table.Column(type: "int", nullable: false), - CompareOutcomesJson = table.Column(type: "text", nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true), - LastModificationTime = table.Column(type: "datetime2", nullable: true), - LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), - IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), - DeleterId = table.Column(type: "uniqueidentifier", nullable: true), - DeletionTime = table.Column(type: "datetime2", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Sas_H_ListFormWorkflowCriteria", x => x.Id); - table.ForeignKey( - name: "FK_Sas_H_ListFormWorkflowCriteria_Sas_H_ListFormWorkflow_WorkflowItemId", - column: x => x.WorkflowItemId, - principalTable: "Sas_H_ListFormWorkflow", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "Sas_H_NotificationRule", columns: table => new @@ -3933,16 +3891,6 @@ namespace Sozsoft.Platform.Migrations table: "Sas_H_ListFormImportLog", column: "ImportId"); - migrationBuilder.CreateIndex( - name: "IX_Sas_H_ListFormWorkflowCriteria_ListFormCode_WorkflowItemId_NodeId", - table: "Sas_H_ListFormWorkflowCriteria", - columns: new[] { "ListFormCode", "WorkflowItemId", "NodeId" }); - - migrationBuilder.CreateIndex( - name: "IX_Sas_H_ListFormWorkflowCriteria_WorkflowItemId", - table: "Sas_H_ListFormWorkflowCriteria", - column: "WorkflowItemId"); - migrationBuilder.CreateIndex( name: "IX_Sas_H_Menu_Code", table: "Sas_H_Menu", @@ -4293,7 +4241,7 @@ namespace Sozsoft.Platform.Migrations name: "Sas_H_ListFormImportLog"); migrationBuilder.DropTable( - name: "Sas_H_ListFormWorkflowCriteria"); + name: "Sas_H_ListFormWorkflow"); migrationBuilder.DropTable( name: "Sas_H_LogEntry"); @@ -4400,9 +4348,6 @@ namespace Sozsoft.Platform.Migrations migrationBuilder.DropTable( name: "Sas_H_ListFormImport"); - migrationBuilder.DropTable( - name: "Sas_H_ListFormWorkflow"); - migrationBuilder.DropTable( name: "Sas_H_NotificationRule"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 43fa9b3..a86f3a8 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -3050,6 +3050,9 @@ namespace Sozsoft.Platform.Migrations b.Property("Width") .HasColumnType("int"); + b.Property("WorkflowJson") + .HasColumnType("text"); + b.Property("ZoomAndPanJson") .HasColumnType("text"); @@ -3407,86 +3410,6 @@ namespace Sozsoft.Platform.Migrations }); modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("Amount") - .HasPrecision(18, 2) - .HasColumnType("decimal(18,2)"); - - b.Property("AssignedApprover") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("CreationTime") - .HasColumnType("datetime2") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uniqueidentifier") - .HasColumnName("CreatorId"); - - b.Property("CurrentNodeId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("DeleterId") - .HasColumnType("uniqueidentifier") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("datetime2") - .HasColumnName("DeletionTime"); - - b.Property("HistoryJson") - .HasColumnType("text"); - - b.Property("InformedPerson") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - - b.Property("LastModificationTime") - .HasColumnType("datetime2") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uniqueidentifier") - .HasColumnName("LastModifierId"); - - b.Property("ListFormCode") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("nvarchar(64)"); - - b.Property("OrderNo") - .HasColumnType("int"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("nvarchar(150)"); - - b.HasKey("Id"); - - b.ToTable("Sas_H_ListFormWorkflow", (string)null); - }); - - modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflowCriteria", b => { b.Property("Id") .HasColumnType("uniqueidentifier"); @@ -3496,11 +3419,16 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(250) .HasColumnType("nvarchar(250)"); - b.Property("Column") + b.Property("CompareColumn") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("CompareOperator") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + b.Property("CompareOutcomesJson") .HasColumnType("text"); @@ -3508,46 +3436,11 @@ namespace Sozsoft.Platform.Migrations .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); - b.Property("CreationTime") - .HasColumnType("datetime2") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uniqueidentifier") - .HasColumnName("CreatorId"); - - b.Property("DeleterId") - .HasColumnType("uniqueidentifier") - .HasColumnName("DeleterId"); - - b.Property("DeletionTime") - .HasColumnType("datetime2") - .HasColumnName("DeletionTime"); - - b.Property("InformPerson") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("IsDeleted") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false) - .HasColumnName("IsDeleted"); - b.Property("Kind") .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("LastModificationTime") - .HasColumnType("datetime2") - .HasColumnName("LastModificationTime"); - - b.Property("LastModifierId") - .HasColumnType("uniqueidentifier") - .HasColumnName("LastModifierId"); - b.Property("ListFormCode") .IsRequired() .HasMaxLength(64) @@ -3578,16 +3471,6 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("NodeId") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Operator") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - b.Property("PositionX") .HasColumnType("int"); @@ -3599,16 +3482,9 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(250) .HasColumnType("nvarchar(250)"); - b.Property("WorkflowItemId") - .HasColumnType("uniqueidentifier"); - b.HasKey("Id"); - b.HasIndex("WorkflowItemId"); - - b.HasIndex("ListFormCode", "WorkflowItemId", "NodeId"); - - b.ToTable("Sas_H_ListFormWorkflowCriteria", (string)null); + b.ToTable("Sas_H_ListFormWorkflow", (string)null); }); modelBuilder.Entity("Sozsoft.Platform.Entities.LogEntry", b => @@ -8341,17 +8217,6 @@ namespace Sozsoft.Platform.Migrations .IsRequired(); }); - modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflowCriteria", b => - { - b.HasOne("Sozsoft.Platform.Entities.ListFormWorkflow", "WorkflowItem") - .WithMany("Criteria") - .HasForeignKey("WorkflowItemId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("WorkflowItem"); - }); - modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b => { b.HasOne("Sozsoft.Platform.Entities.Order", "Order") @@ -8787,11 +8652,6 @@ namespace Sozsoft.Platform.Migrations b.Navigation("Events"); }); - modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b => - { - b.Navigation("Criteria"); - }); - modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b => { b.Navigation("Items"); diff --git a/ui/src/services/workflow.service.ts b/ui/src/services/workflow.service.ts index 7362d4f..b7a1b2c 100644 --- a/ui/src/services/workflow.service.ts +++ b/ui/src/services/workflow.service.ts @@ -1,8 +1,8 @@ import apiService from './api.service' export interface WorkflowConditionDto { - column: string - operator: string + compareColumn: string + compareOperator: string compareValue: number } @@ -12,38 +12,16 @@ export interface CompareOutcomeDto { conditions: WorkflowConditionDto[] } -export interface WorkflowItemDto { - id: string - listFormCode: string - orderNo: number - title: string - sorumlu: string - tarih: string - status: string - durum: string - amount: number - currentNodeId: string - assignedApprover: string - informedPerson: string - history: Array<{ - time: string - action: string - note: string - }> -} - export interface WorkflowCriteriaDto { id: string listFormCode: string - workflowItemId: string nodeId: string kind: string title: string - column: string - operator: string + compareColumn: string + compareOperator: string compareValue: number approver: string - informPerson: string nextOnStart?: string | null nextOnTrue?: string | null nextOnFalse?: string | null @@ -55,20 +33,12 @@ export interface WorkflowCriteriaDto { } export interface WorkflowStateDto { - workflowItems: WorkflowItemDto[] criteria: WorkflowCriteriaDto[] } -export type CreateUpdateWorkflowInput = Partial & { - listFormCode?: string - sorumlu: string - amount: number - tarih?: string -} - export type SaveCriteriaInput = Omit, 'id'> & { id?: string | null - workflowItemId: string + listFormCode: string } const baseUrl = '/api/app/list-form-workflow' @@ -84,45 +54,6 @@ export const workflowService = { return response.data }, - async createWorkflow(payload: CreateUpdateWorkflowInput) { - const response = await apiService.fetchData({ - method: 'POST', - url: `${baseUrl}/workflows`, - data: payload, - }) - - return response.data - }, - - async updateWorkflow(id: string, payload: CreateUpdateWorkflowInput) { - const response = await apiService.fetchData({ - method: 'PUT', - url: `${baseUrl}/workflows/${id}`, - data: payload, - }) - - return response.data - }, - - async startWorkflow(id: string) { - const response = await apiService.fetchData({ - method: 'POST', - url: `${baseUrl}/workflows/${id}/start`, - }) - - return response.data - }, - - async decideWorkflow(id: string, payload: { approved: boolean; note?: string }) { - const response = await apiService.fetchData({ - method: 'POST', - url: `${baseUrl}/workflows/${id}/decision`, - data: payload, - }) - - return response.data - }, - async saveCriteria(payload: SaveCriteriaInput) { const response = await apiService.fetchData({ method: 'POST', diff --git a/ui/src/utils/workflow/workflowHelpers.ts b/ui/src/utils/workflow/workflowHelpers.ts index 52f2ab2..25a6536 100644 --- a/ui/src/utils/workflow/workflowHelpers.ts +++ b/ui/src/utils/workflow/workflowHelpers.ts @@ -1,75 +1,60 @@ -import { getNodeHeight, nodeSize } from "./workflowConstants"; +import { getNodeHeight, nodeSize } from './workflowConstants' import type { CompareOutcomeDto, SaveCriteriaInput, WorkflowConditionDto, WorkflowCriteriaDto, - WorkflowItemDto, -} from "@/services/workflow.service"; +} from '@/services/workflow.service' export type WorkflowCriteriaForm = Partial & { - id?: string | null; - workflowItemId: string; - compareOutcomes: CompareOutcomeDto[]; -}; + id?: string | null + listFormCode: string + compareOutcomes: CompareOutcomeDto[] +} export type WorkflowOutcome = { - field: string; - label: string; - targetId?: string | null; -}; + field: string + label: string + targetId?: string | null +} export type WorkflowLinkPort = { - field?: string; - index?: number; - count?: number; - sourceSlotIndex?: number; - sourceSlotCount?: number; - targetSlotIndex?: number; - targetSlotCount?: number; - routeIndex?: number; - routeCount?: number; -}; + field?: string + index?: number + count?: number + sourceSlotIndex?: number + sourceSlotCount?: number + targetSlotIndex?: number + targetSlotCount?: number + routeIndex?: number + routeCount?: number +} export type WorkflowLink = { - key: string; - source: WorkflowCriteriaDto; - target: WorkflowCriteriaDto; - label: string; - sourcePort: WorkflowLinkPort; -}; + key: string + source: WorkflowCriteriaDto + target: WorkflowCriteriaDto + label: string + sourcePort: WorkflowLinkPort +} type Endpoint = { - link: WorkflowLink; - role: "source" | "target"; -}; - -export function isPendingApproval( - item: WorkflowItemDto | undefined | null, - criteria: WorkflowCriteriaDto[], -) { - if (!item) return false; - - return criteria.some( - (candidate) => - candidate.workflowItemId === item.id && - candidate.id === item.currentNodeId && - candidate.kind === "Approval", - ); + link: WorkflowLink + role: 'source' | 'target' } export function buildFitLayout(criteria: WorkflowCriteriaDto[]) { - const links = collectLinks(criteria); - const rankById = buildTraversalRanks(criteria, links); - const groups = new Map(); + const links = collectLinks(criteria) + const rankById = buildTraversalRanks(criteria, links) + const groups = new Map() criteria.forEach((item) => { - const column = fitColumn(item); - if (!groups.has(column)) groups.set(column, []); - groups.get(column)?.push(item); - }); + const column = fitColumn(item) + if (!groups.has(column)) groups.set(column, []) + groups.get(column)?.push(item) + }) - const sortedColumns = [...groups.keys()].sort((a, b) => a - b); - const yGap = 74; + const sortedColumns = [...groups.keys()].sort((a, b) => a - b) + const yGap = 74 const maxGroupHeight = Math.max( 1, ...[...groups.values()].map( @@ -77,31 +62,29 @@ export function buildFitLayout(criteria: WorkflowCriteriaDto[]) { items.reduce((sum, item) => sum + getNodeHeight(item), 0) + Math.max(0, items.length - 1) * yGap, ), - ); - const top = 72; - const left = 72; - const xGap = 128; - const positions = new Map(); + ) + const top = 72 + const left = 72 + const xGap = 128 + const positions = new Map() sortedColumns.forEach((column, columnIndex) => { - const items = (groups.get(column) || []).sort((a, b) => - compareLayoutNodes(a, b, rankById), - ); + const items = (groups.get(column) || []).sort((a, b) => compareLayoutNodes(a, b, rankById)) const groupHeight = items.reduce((sum, item) => sum + getNodeHeight(item), 0) + - Math.max(0, items.length - 1) * yGap; - let y = top + Math.max(0, (maxGroupHeight - groupHeight) / 2); + Math.max(0, items.length - 1) * yGap + let y = top + Math.max(0, (maxGroupHeight - groupHeight) / 2) items.forEach((item) => { positions.set(item.id, { x: left + columnIndex * (nodeSize.width + xGap), y: Math.round(y), - }); - y += getNodeHeight(item) + yGap; - }); - }); + }) + y += getNodeHeight(item) + yGap + }) + }) - return positions; + return positions } function fitColumn(item: WorkflowCriteriaDto) { @@ -111,9 +94,9 @@ function fitColumn(item: WorkflowCriteriaDto) { Approval: 2, Inform: 3, End: 4, - }; + } - return priority[item.kind] ?? 2; + return priority[item.kind] ?? 2 } function compareLayoutNodes( @@ -123,201 +106,181 @@ function compareLayoutNodes( ) { return ( (rankById.get(a.id) ?? 999) - (rankById.get(b.id) ?? 999) || - a.title.localeCompare(b.title, "tr") - ); + a.title.localeCompare(b.title, 'tr') + ) } -function buildTraversalRanks( - criteria: WorkflowCriteriaDto[], - links: WorkflowLink[], -) { - const rankById = new Map(); - const outgoing = new Map( - criteria.map((item) => [item.id, []]), - ); +function buildTraversalRanks(criteria: WorkflowCriteriaDto[], links: WorkflowLink[]) { + const rankById = new Map() + const outgoing = new Map(criteria.map((item) => [item.id, []])) links.forEach((link) => { - outgoing.get(link.source.id)?.push(link.target.id); - }); + outgoing.get(link.source.id)?.push(link.target.id) + }) - const roots = criteria.filter((item) => item.kind === "Start"); - const queue = roots.length - ? roots.map((item) => item.id) - : criteria.map((item) => item.id); + const roots = criteria.filter((item) => item.kind === 'Start') + const queue = roots.length ? roots.map((item) => item.id) : criteria.map((item) => item.id) while (queue.length) { - const id = queue.shift(); - if (!id) continue; - if (rankById.has(id)) continue; + const id = queue.shift() + if (!id) continue + if (rankById.has(id)) continue - rankById.set(id, rankById.size); - (outgoing.get(id) || []).forEach((targetId) => { - if (targetId && !rankById.has(targetId)) queue.push(targetId); - }); + rankById.set(id, rankById.size) + ;(outgoing.get(id) || []).forEach((targetId) => { + if (targetId && !rankById.has(targetId)) queue.push(targetId) + }) } criteria.forEach((item) => { - if (!rankById.has(item.id)) rankById.set(item.id, rankById.size); - }); + if (!rankById.has(item.id)) rankById.set(item.id, rankById.size) + }) - return rankById; + return rankById } export function collectLinks(criteria: WorkflowCriteriaDto[]) { - const links: WorkflowLink[] = []; + const links: WorkflowLink[] = [] criteria.forEach((source) => { - if (source.kind === "Compare" && source.compareOutcomes?.length) { + if (source.kind === 'Compare' && source.compareOutcomes?.length) { source.compareOutcomes.forEach((outcome, index) => { - addLink( - links, - criteria, - source, - outcome.targetId, - outcome.label, - `compare-${index}`, - { - index, - count: source.compareOutcomes.length, - field: `compareOutcomes:${index}`, - }, - ); - }); - return; + addLink(links, criteria, source, outcome.targetId, outcome.label, `compare-${index}`, { + index, + count: source.compareOutcomes.length, + field: `compareOutcomes:${index}`, + }) + }) + return } - addLink(links, criteria, source, source.nextOnStart, "Sonraki", "next", { + addLink(links, criteria, source, source.nextOnStart, 'Sonraki', 'next', { index: 0, count: 1, - field: "nextOnStart", - }); - addLink(links, criteria, source, source.nextOnTrue, "Doğru", "true", { + field: 'nextOnStart', + }) + addLink(links, criteria, source, source.nextOnTrue, 'Doğru', 'true', { index: 0, count: 2, - field: "nextOnTrue", - }); - addLink(links, criteria, source, source.nextOnFalse, "Yanlış", "false", { + field: 'nextOnTrue', + }) + addLink(links, criteria, source, source.nextOnFalse, 'Yanlış', 'false', { index: 1, count: 2, - field: "nextOnFalse", - }); - addLink(links, criteria, source, source.nextOnApprove, "Onay", "approve", { + field: 'nextOnFalse', + }) + addLink(links, criteria, source, source.nextOnApprove, 'Onay', 'approve', { index: 0, count: 2, - field: "nextOnApprove", - }); - addLink(links, criteria, source, source.nextOnReject, "Red", "reject", { + field: 'nextOnApprove', + }) + addLink(links, criteria, source, source.nextOnReject, 'Red', 'reject', { index: 1, count: 2, - field: "nextOnReject", - }); - }); - return assignLinkSlots(links, criteria); + field: 'nextOnReject', + }) + }) + return assignLinkSlots(links, criteria) } -export function assignLinkSlots( - links: WorkflowLink[], - criteria: WorkflowCriteriaDto[], -) { - const endpointGroups = new Map(); +export function assignLinkSlots(links: WorkflowLink[], criteria: WorkflowCriteriaDto[]) { + const endpointGroups = new Map() const addEndpoint = (nodeId: string, side: string, endpoint: Endpoint) => { - const key = `${nodeId}:${side}`; - if (!endpointGroups.has(key)) endpointGroups.set(key, []); - endpointGroups.get(key)?.push(endpoint); - }; + const key = `${nodeId}:${side}` + if (!endpointGroups.has(key)) endpointGroups.set(key, []) + endpointGroups.get(key)?.push(endpoint) + } links.forEach((link) => { addEndpoint(link.source.id, sideToward(link.source, link.target), { link, - role: "source", - }); + role: 'source', + }) addEndpoint(link.target.id, sideToward(link.target, link.source), { link, - role: "target", - }); - }); + role: 'target', + }) + }) endpointGroups.forEach((endpoints) => { endpoints.forEach((endpoint, index) => { - if (endpoint.role === "source") { - endpoint.link.sourcePort.sourceSlotIndex = index; - endpoint.link.sourcePort.sourceSlotCount = endpoints.length; + if (endpoint.role === 'source') { + endpoint.link.sourcePort.sourceSlotIndex = index + endpoint.link.sourcePort.sourceSlotCount = endpoints.length } else { - endpoint.link.sourcePort.targetSlotIndex = index; - endpoint.link.sourcePort.targetSlotCount = endpoints.length; + endpoint.link.sourcePort.targetSlotIndex = index + endpoint.link.sourcePort.targetSlotCount = endpoints.length } - }); - }); + }) + }) links.forEach((link) => { - link.sourcePort.routeIndex = link.sourcePort.targetSlotIndex ?? 0; - link.sourcePort.routeCount = link.sourcePort.targetSlotCount ?? 1; - }); + link.sourcePort.routeIndex = link.sourcePort.targetSlotIndex ?? 0 + link.sourcePort.routeCount = link.sourcePort.targetSlotCount ?? 1 + }) - return links; + return links } function sideToward(from: WorkflowCriteriaDto, to: WorkflowCriteriaDto) { - const fromLeft = Number(from.positionX || 0); - const fromTop = Number(from.positionY || 0); + const fromLeft = Number(from.positionX || 0) + const fromTop = Number(from.positionY || 0) const fromCenter = { x: fromLeft + nodeSize.width / 2, y: fromTop + getNodeHeight(from) / 2, - }; + } const toCenter = { x: Number(to.positionX || 0) + nodeSize.width / 2, y: Number(to.positionY || 0) + getNodeHeight(to) / 2, - }; + } - const dx = toCenter.x - fromCenter.x; - const dy = toCenter.y - fromCenter.y; + const dx = toCenter.x - fromCenter.x + const dy = toCenter.y - fromCenter.y - const horizontalDistance = Math.abs(dx) / (nodeSize.width / 2); - const verticalDistance = Math.abs(dy) / (getNodeHeight(from) / 2); - if (horizontalDistance >= verticalDistance) return dx >= 0 ? "right" : "left"; - return dy >= 0 ? "bottom" : "top"; + const horizontalDistance = Math.abs(dx) / (nodeSize.width / 2) + const verticalDistance = Math.abs(dy) / (getNodeHeight(from) / 2) + if (horizontalDistance >= verticalDistance) return dx >= 0 ? 'right' : 'left' + return dy >= 0 ? 'bottom' : 'top' } export function getNodeOutcomes(item: WorkflowCriteriaDto): WorkflowOutcome[] { - if (item.kind === "Compare") { + if (item.kind === 'Compare') { const outcomes = item.compareOutcomes?.length ? item.compareOutcomes : [ - { label: "Doğru", targetId: item.nextOnTrue }, - { label: "Yanlış", targetId: item.nextOnFalse }, - ]; + { label: 'Doğru', targetId: item.nextOnTrue }, + { label: 'Yanlış', targetId: item.nextOnFalse }, + ] return outcomes.slice(0, 4).map((outcome, index) => ({ field: `compareOutcomes:${index}`, label: outcome.label || `Durum ${index + 1}`, targetId: outcome.targetId, - })); + })) } - if (item.kind === "Approval") { + if (item.kind === 'Approval') { return [ - { field: "nextOnApprove", label: "Onay", targetId: item.nextOnApprove }, - { field: "nextOnReject", label: "Red", targetId: item.nextOnReject }, - ]; + { field: 'nextOnApprove', label: 'Onay', targetId: item.nextOnApprove }, + { field: 'nextOnReject', label: 'Red', targetId: item.nextOnReject }, + ] } - if (item.kind === "End") return []; + if (item.kind === 'End') return [] - return [ - { field: "nextOnStart", label: "Sonraki", targetId: item.nextOnStart }, - ]; + return [{ field: 'nextOnStart', label: 'Sonraki', targetId: item.nextOnStart }] } export function outcomeLabel(field?: string) { - if (field?.startsWith("compareOutcomes:")) return "Karşılaştırma durumu"; + if (field?.startsWith('compareOutcomes:')) return 'Karşılaştırma durumu' const labels: Record = { - nextOnStart: "Sonraki", - nextOnTrue: "Doğru", - nextOnFalse: "Yanlış", - nextOnApprove: "Onay", - nextOnReject: "Red", - }; + nextOnStart: 'Sonraki', + nextOnTrue: 'Doğru', + nextOnFalse: 'Yanlış', + nextOnApprove: 'Onay', + nextOnReject: 'Red', + } - return field ? labels[field] : undefined; + return field ? labels[field] : undefined } export function addLink( @@ -329,8 +292,8 @@ export function addLink( type: string, sourcePort: WorkflowLinkPort = {}, ) { - if (!targetId) return; - const target = criteria.find((item) => item.id === targetId); + if (!targetId) return + const target = criteria.find((item) => item.id === targetId) if (target) { links.push({ key: `${source.id}-${target.id}-${type}`, @@ -338,125 +301,119 @@ export function addLink( target, label, sourcePort, - }); + }) } } -export function emptyCriteria( - kind = "Compare", - workflowItemId = "", -): WorkflowCriteriaForm { +export function emptyCriteria(kind = 'Compare', listFormCode = ''): WorkflowCriteriaForm { return { - id: "", - workflowItemId, + id: '', + listFormCode, kind, title: defaultTitle(kind), - column: "Tutar", - operator: ">", + compareColumn: 'Tutar', + compareOperator: '>', compareValue: 5000, - approver: "", - informPerson: "", - nextOnStart: "", - nextOnTrue: "", - nextOnFalse: "", - nextOnApprove: "", - nextOnReject: "", + approver: '', + nextOnStart: '', + nextOnTrue: '', + nextOnFalse: '', + nextOnApprove: '', + nextOnReject: '', compareOutcomes: - kind === "Compare" - ? [emptyCompareOutcome("Durum 1"), emptyCompareOutcome("Durum 2")] - : [], + kind === 'Compare' ? [emptyCompareOutcome('Durum 1'), emptyCompareOutcome('Durum 2')] : [], positionX: 32, positionY: 150, - }; + } } export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm { - const sharedPerson = item.approver || item.informPerson || ""; + const sharedPerson = item.approver || '' return { ...emptyCriteria(item.kind), ...item, approver: sharedPerson, - informPerson: sharedPerson, - nextOnStart: item.nextOnStart || "", - nextOnTrue: item.nextOnTrue || "", - nextOnFalse: item.nextOnFalse || "", - nextOnApprove: item.nextOnApprove || "", - nextOnReject: item.nextOnReject || "", + nextOnStart: item.nextOnStart || '', + nextOnTrue: item.nextOnTrue || '', + nextOnFalse: item.nextOnFalse || '', + nextOnApprove: item.nextOnApprove || '', + nextOnReject: item.nextOnReject || '', compareOutcomes: item.compareOutcomes?.length ? item.compareOutcomes.map(toCompareOutcomeForm) : emptyCriteria(item.kind).compareOutcomes, - }; + } } export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput { - const sharedPerson = item.approver || item.informPerson || ""; + const sharedPerson = item.approver || '' return { ...item, id: item.id || null, - workflowItemId: item.workflowItemId || "", + listFormCode: item.listFormCode || '', compareValue: Number(item.compareValue || 0), approver: sharedPerson, - informPerson: sharedPerson, positionX: Number(item.positionX || 32), positionY: Number(item.positionY || 150), compareOutcomes: (item.compareOutcomes || []) .slice(0, 4) .filter((outcome) => outcome.label?.trim()) .map(normalizeCompareOutcome), - }; + } } export function defaultTitle(kind: string) { - return { - Start: "İş Akışı Başlat", - Compare: "Tutar > 5000 TL", - Approval: "Onaylanacak Kişi", - Inform: "Bilgilendirme Yapılacak Personel", - End: "Akışı Bitir", - }[kind] ?? "İş Akışı Adımı"; + return ( + { + Start: 'İş Akışı Başlat', + Compare: 'Tutar > 5000 TL', + Approval: 'Onaylanacak Kişi', + Inform: 'Bilgilendirme Yapılacak Personel', + End: 'Akışı Bitir', + }[kind] ?? 'İş Akışı Adımı' + ) } -export function emptyCompareOutcome(label = "Durum"): CompareOutcomeDto { +export function emptyCompareOutcome(label = 'Durum'): CompareOutcomeDto { return { label, - targetId: "", - conditions: [{ column: "Tutar", operator: ">", compareValue: 5000 }], - }; + targetId: '', + conditions: [{ compareColumn: 'Tutar', compareOperator: '>', compareValue: 5000 }], + } } export function toCompareOutcomeForm( outcome: Partial & Partial & { - conditions?: Partial[]; + conditions?: Partial[] }, ): CompareOutcomeDto { const conditions = outcome.conditions?.length ? outcome.conditions : [ { - column: outcome.column || "Tutar", - operator: outcome.operator || ">", + compareColumn: outcome.compareColumn || 'Tutar', + compareOperator: outcome.compareOperator || '>', compareValue: outcome.compareValue || 0, }, - ]; + ] return { - label: outcome.label || "", - targetId: outcome.targetId || "", + label: outcome.label || '', + targetId: outcome.targetId || '', conditions: conditions.map((condition) => ({ - column: condition.column || "Tutar", - operator: condition.operator || ">", + compareColumn: condition.compareColumn || 'Tutar', + compareOperator: condition.compareOperator || '>', compareValue: condition.compareValue ?? 0, })), - }; + } } export function normalizeCompareOutcome( outcome: Partial & Partial & { - conditions?: Partial[]; + conditions?: Partial[] }, ): CompareOutcomeDto { const conditions = ( @@ -464,27 +421,24 @@ export function normalizeCompareOutcome( ? outcome.conditions : [ { - column: outcome.column || "Tutar", - operator: outcome.operator || ">", + compareColumn: outcome.compareColumn || 'Tutar', + compareOperator: outcome.compareOperator || '>', compareValue: outcome.compareValue || 0, }, ] ) - .filter( - (condition) => - condition.operator && String(condition.compareValue ?? "") !== "", - ) + .filter((condition) => condition.compareOperator && String(condition.compareValue ?? '') !== '') .map((condition) => ({ - column: condition.column || "Tutar", - operator: condition.operator || ">", + compareColumn: condition.compareColumn || 'Tutar', + compareOperator: condition.compareOperator || '>', compareValue: Number(condition.compareValue || 0), - })); + })) return { - label: (outcome.label || "").trim(), + label: (outcome.label || '').trim(), targetId: outcome.targetId || null, conditions, - }; + } } export function compareOutcomeRuleText( @@ -492,67 +446,61 @@ export function compareOutcomeRuleText( ) { const conditions = outcome.conditions?.length ? outcome.conditions - : outcome.operator + : outcome.compareOperator ? [ { - column: outcome.column || "Tutar", - operator: outcome.operator, + compareColumn: outcome.compareColumn, + compareOperator: outcome.compareOperator, compareValue: outcome.compareValue, }, ] - : []; + : [] return conditions.length ? conditions .map( (condition) => - `${condition.column} ${condition.operator} ${formatCompactValue(condition.compareValue)}`, + `${condition.compareColumn} ${condition.compareOperator} ${formatCompactValue(condition.compareValue)}`, ) - .join(" ve ") - : "Kural yok"; + .join(' ve ') + : 'Kural yok' } export function formatCompactValue(value: number | string | null | undefined) { - return new Intl.NumberFormat("tr-TR", { + return new Intl.NumberFormat('tr-TR', { maximumFractionDigits: 2, - }).format(Number(value || 0)); + }).format(Number(value || 0)) } export function criteriaSummary(item: WorkflowCriteriaDto) { - if (item.kind === "Compare") { + if (item.kind === 'Compare') { return ( (item.compareOutcomes || []) - .map( - (outcome) => `${outcome.label}: ${compareOutcomeRuleText(outcome)}`, - ) - .join(" / ") || "-" - ); + .map((outcome) => `${outcome.label}: ${compareOutcomeRuleText(outcome)}`) + .join(' / ') || '-' + ) } - if (item.kind === "Approval") - return item.approver || item.informPerson || "-"; - if (item.kind === "Inform") return item.approver || item.informPerson || "-"; - return item.title; + if (item.kind === 'Approval') return item.approver || '-' + if (item.kind === 'Inform') return item.approver || '-' + return item.title } -export function targetTitle( - criteria: WorkflowCriteriaDto[], - id?: string | null, -) { - if (!id) return "-"; - const item = criteria.find((candidate) => candidate.id === id); - return item ? `${item.id} - ${item.title}` : id; +export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) { + if (!id) return '-' + const item = criteria.find((candidate) => candidate.id === id) + return item ? `${item.id} - ${item.title}` : id } export function statusClass(status?: string) { - if (status === "Onay Bekliyor") return "pending"; - if (status === "Bitti") return "done"; - if (status === "Bilgilendirildi") return "info"; - return ""; + if (status === 'Onay Bekliyor') return 'pending' + if (status === 'Bitti') return 'done' + if (status === 'Bilgilendirildi') return 'info' + return '' } export function formatMoney(value?: number | string | null) { - return new Intl.NumberFormat("tr-TR", { - style: "currency", - currency: "TRY", - }).format(Number(value || 0)); + return new Intl.NumberFormat('tr-TR', { + style: 'currency', + currency: 'TRY', + }).format(Number(value || 0)) } diff --git a/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx b/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx index cac1194..c53aee6 100644 --- a/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabWorkflow.tsx @@ -1,464 +1,299 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import type { FormEvent } from "react"; -import dayjs from "dayjs"; -import "dayjs/locale/tr"; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import type { FormEvent } from 'react' import { buildFitLayout, emptyCriteria, - isPendingApproval, normalizeCriteria, toCriteriaForm, type WorkflowCriteriaForm, -} from "@/utils/workflow/workflowHelpers"; -import { - workflowService, - type WorkflowCriteriaDto, - type WorkflowItemDto, -} from "@/services/workflow.service"; -import { DashboardShell } from "../workflow/DashboardShell"; - -dayjs.locale("tr"); - -type WorkflowForm = { - sorumlu: string; - amount: number | string; -}; - -type WorkflowEditForm = WorkflowForm & { - tarih: string; -}; +} from '@/utils/workflow/workflowHelpers' +import { workflowService, type WorkflowCriteriaDto } from '@/services/workflow.service' +import { DashboardShell } from '../workflow/DashboardShell' type PendingLink = { - sourceId: string; - outcome: string; -} | null; + sourceId: string + outcome: string +} | null type DragPreview = { - id: string; - delta: { x: number; y: number }; -} | null; + id: string + delta: { x: number; y: number } +} | null type DragEndEvent = { - active: { id: string }; - delta: { x: number; y: number }; -}; + active: { id: string } + delta: { x: number; y: number } +} -export function FormTabWorkflow(props: { listFormCode: string }) { - const [workflowItems, setWorkflowItems] = useState([]); - const [criteria, setCriteria] = useState([]); - const [selectedWorkflowId, setSelectedWorkflowId] = useState( - null, - ); - const [selectedId, setSelectedId] = useState(""); - const [pendingLink, setPendingLink] = useState(null); - const [workflowForm, setWorkflowForm] = useState({ - sorumlu: "", - amount: 7200, - }); - const [editingWorkflowId, setEditingWorkflowId] = useState( - null, - ); - const [workflowEditForm, setWorkflowEditForm] = useState({ - sorumlu: "", - tarih: "", - amount: 0, - }); +export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) { + const [criteria, setCriteria] = useState([]) + const [selectedId, setSelectedId] = useState('') + const [pendingLink, setPendingLink] = useState(null) const [criteriaForm, setCriteriaForm] = useState( - emptyCriteria(), - ); - const [dragPreview, setDragPreview] = useState(null); - const [canvasZoom, setCanvasZoom] = useState(1); - const [designerTab, setDesignerTab] = useState("flow"); - const [busy, setBusy] = useState(false); - const [approvalDialogWorkflowId, setApprovalDialogWorkflowId] = - useState(null); - const canvasRef = useRef(null); + emptyCriteria('Start', listFormCode), + ) + const [dragPreview, setDragPreview] = useState(null) + const [canvasZoom, setCanvasZoom] = useState(1) + const [designerTab, setDesignerTab] = useState('flow') + const [busy, setBusy] = useState(false) + const canvasRef = useRef(null) - const currentCriteria = useMemo( - () => criteria.filter((item) => item.workflowItemId === selectedWorkflowId), - [criteria, selectedWorkflowId], - ); - - const selectedWorkflow = useMemo( - () => workflowItems.find((item) => item.id === selectedWorkflowId), - [selectedWorkflowId, workflowItems], - ); + const currentCriteria = useMemo(() => criteria, [criteria]) const selectedCriteria = useMemo( () => currentCriteria.find((item) => item.id === selectedId) ?? null, [currentCriteria, selectedId], - ); - - const pendingItems = useMemo( - () => workflowItems.filter((item) => isPendingApproval(item, criteria)), - [criteria, workflowItems], - ); - - const dialogPendingItems = useMemo( - () => pendingItems.filter((item) => item.id === approvalDialogWorkflowId), - [approvalDialogWorkflowId, pendingItems], - ); + ) const loadState = useCallback(async () => { - const data = await workflowService.getState(); - setWorkflowItems(data.workflowItems); - setCriteria(data.criteria); - setSelectedWorkflowId( - (current) => current || data.workflowItems[0]?.id || null, - ); - return data; - }, []); + const data = await workflowService.getState(listFormCode) + setCriteria(data.criteria) + return data + }, [listFormCode]) const runAction = useCallback( async (action: () => Promise) => { - setBusy(true); + setBusy(true) try { - await action(); - await loadState(); + await action() + await loadState() } finally { - setBusy(false); + setBusy(false) } }, [loadState], - ); + ) useEffect(() => { - loadState(); - }, [loadState]); + loadState() + }, [loadState]) useEffect(() => { if (selectedCriteria) { - setCriteriaForm(toCriteriaForm(selectedCriteria)); - } else if (selectedWorkflowId) { - setCriteriaForm(emptyCriteria("Start", selectedWorkflowId)); + setCriteriaForm(toCriteriaForm(selectedCriteria)) + } else { + setCriteriaForm(emptyCriteria('Start', listFormCode)) } - }, [selectedCriteria, selectedWorkflowId]); + }, [listFormCode, selectedCriteria]) useEffect(() => { - if (!selectedWorkflowId || !selectedId) return; + if (!selectedId) return - const selectedStillBelongs = currentCriteria.some( - (item) => item.id === selectedId, - ); - if (!selectedStillBelongs) { - setSelectedId(""); + const selectedStillExists = currentCriteria.some((item) => item.id === selectedId) + if (!selectedStillExists) { + setSelectedId('') } - }, [currentCriteria, selectedId, selectedWorkflowId]); - - useEffect(() => { - if (!approvalDialogWorkflowId) return; - - const stillPending = pendingItems.some( - (item) => item.id === approvalDialogWorkflowId, - ); - if (!stillPending) { - setApprovalDialogWorkflowId(null); - } - }, [approvalDialogWorkflowId, pendingItems]); - - const createWorkflow = (event: FormEvent) => { - event.preventDefault(); - runAction(async () => { - const created = await workflowService.createWorkflow({ - sorumlu: workflowForm.sorumlu, - amount: Number(workflowForm.amount), - }); - setWorkflowForm({ sorumlu: "", amount: 7200 }); - setSelectedWorkflowId(created.id); - setSelectedId(""); - setCriteriaForm(emptyCriteria("Start", created.id)); - }); - }; - - const beginWorkflowEdit = (item: WorkflowItemDto) => { - setSelectedWorkflowId(item.id); - setPendingLink(null); - setSelectedId(""); - setEditingWorkflowId(item.id); - setWorkflowEditForm({ - sorumlu: item.sorumlu, - tarih: dayjs(item.tarih).format("YYYY-MM-DD"), - amount: item.amount, - }); - }; - - const cancelWorkflowEdit = () => { - setEditingWorkflowId(null); - setWorkflowEditForm({ sorumlu: "", tarih: "", amount: 0 }); - }; - - const saveWorkflowEdit = (id: string) => { - runAction(async () => { - await workflowService.updateWorkflow(id, { - sorumlu: workflowEditForm.sorumlu, - tarih: workflowEditForm.tarih, - amount: Number(workflowEditForm.amount), - }); - cancelWorkflowEdit(); - }); - }; - - const startWorkflow = useCallback( - (id: string) => { - runAction(async () => { - await workflowService.startWorkflow(id); - const data = await loadState(); - const startedWorkflow = data.workflowItems.find( - (item) => item.id === id, - ); - - setApprovalDialogWorkflowId( - isPendingApproval(startedWorkflow, data.criteria) ? id : null, - ); - }); - }, - [loadState, runAction], - ); + }, [currentCriteria, selectedId]) const saveCriteria = (event: FormEvent) => { - event.preventDefault(); + event.preventDefault() runAction(async () => { - await workflowService.saveCriteria(normalizeCriteria(criteriaForm)); - setSelectedId(""); - }); - }; + await workflowService.saveCriteria({ + ...normalizeCriteria(criteriaForm), + listFormCode, + }) + setSelectedId('') + }) + } const addCriteria = (kind: string) => { - if (!selectedWorkflowId) return; - - setDesignerTab("flow"); + setDesignerTab('flow') runAction(async () => { const saved = await workflowService.saveCriteria({ - ...normalizeCriteria(emptyCriteria(kind, selectedWorkflowId)), + ...normalizeCriteria(emptyCriteria(kind, listFormCode)), + listFormCode, positionX: 80 + (currentCriteria.length % 5) * 230, positionY: 220 + Math.floor(currentCriteria.length / 5) * 140, - }); - setSelectedId(saved.id); - }); - }; + }) + setSelectedId(saved.id) + }) + } const deleteSelectedCriteria = useCallback( (criteriaId: string = selectedId) => { - if (!criteriaId || busy) return; + if (!criteriaId || busy) return runAction(async () => { - await workflowService.deleteCriteria(criteriaId); - setSelectedId(""); - }); + await workflowService.deleteCriteria(criteriaId) + setSelectedId('') + }) }, [busy, runAction, selectedId], - ); + ) const disconnectLink = useCallback( (sourceId: string, outcome: string) => { - if (!sourceId || !outcome || busy) return; + if (!sourceId || !outcome || busy) return - const source = currentCriteria.find((item) => item.id === sourceId); - if (!source) return; + const source = currentCriteria.find((item) => item.id === sourceId) + if (!source) return - const next: WorkflowCriteriaForm = toCriteriaForm(source); - if (outcome.startsWith("compareOutcomes:")) { - const outcomeIndex = Number(outcome.split(":")[1]); - next.compareOutcomes = [...(source.compareOutcomes || [])]; + const next: WorkflowCriteriaForm = toCriteriaForm(source) + if (outcome.startsWith('compareOutcomes:')) { + const outcomeIndex = Number(outcome.split(':')[1]) + next.compareOutcomes = [...(source.compareOutcomes || [])] if (next.compareOutcomes?.[outcomeIndex]) { next.compareOutcomes[outcomeIndex] = { ...next.compareOutcomes[outcomeIndex], targetId: null, - }; + } } - if (outcomeIndex === 0) next.nextOnTrue = null; - if (outcomeIndex === 1) next.nextOnFalse = null; + if (outcomeIndex === 0) next.nextOnTrue = null + if (outcomeIndex === 1) next.nextOnFalse = null } else { - (next as Record)[outcome] = null; + ;(next as Record)[outcome] = null } runAction(async () => { - await workflowService.saveCriteria(normalizeCriteria(next)); - setPendingLink(null); - setSelectedId(sourceId); - }); + await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode }) + setPendingLink(null) + setSelectedId(sourceId) + }) }, - [busy, currentCriteria, runAction], - ); + [busy, currentCriteria, listFormCode, runAction], + ) useEffect(() => { const deleteWithKeyboard = (event: globalThis.KeyboardEvent) => { - const activeTag = document.activeElement?.tagName?.toLowerCase(); + const activeTag = document.activeElement?.tagName?.toLowerCase() const isEditing = - Boolean(activeTag && ["input", "textarea", "select"].includes(activeTag)) || - (document.activeElement instanceof HTMLElement && - document.activeElement.isContentEditable); + Boolean(activeTag && ['input', 'textarea', 'select'].includes(activeTag)) || + (document.activeElement instanceof HTMLElement && document.activeElement.isContentEditable) - if (event.key !== "Delete" || isEditing) return; + if (event.key !== 'Delete' || isEditing) return - event.preventDefault(); + event.preventDefault() if (pendingLink) { - disconnectLink(pendingLink.sourceId, pendingLink.outcome); - return; + disconnectLink(pendingLink.sourceId, pendingLink.outcome) + return } - deleteSelectedCriteria(); - }; + deleteSelectedCriteria() + } - window.addEventListener("keydown", deleteWithKeyboard); - return () => window.removeEventListener("keydown", deleteWithKeyboard); - }, [deleteSelectedCriteria, disconnectLink, pendingLink]); + window.addEventListener('keydown', deleteWithKeyboard) + return () => window.removeEventListener('keydown', deleteWithKeyboard) + }, [deleteSelectedCriteria, disconnectLink, pendingLink]) const updateNodePosition = ({ active, delta }: DragEndEvent) => { - setDragPreview(null); + setDragPreview(null) - const item = currentCriteria.find( - (candidate) => candidate.id === active.id, - ); - if (!item) return; + const item = currentCriteria.find((candidate) => candidate.id === active.id) + if (!item) return - setSelectedId(item.id); - if (delta.x === 0 && delta.y === 0) return; + setSelectedId(item.id) + if (delta.x === 0 && delta.y === 0) return const next = { ...item, positionX: Math.max(12, Math.round(item.positionX + delta.x)), positionY: Math.max(12, Math.round(item.positionY + delta.y)), - }; + } runAction(async () => { - await workflowService.saveCriteria(next); - setSelectedId(next.id); - }); - }; + await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode }) + setSelectedId(next.id) + }) + } const connectNodes = (sourceId: string, outcome: string, targetId: string) => { - const source = currentCriteria.find((item) => item.id === sourceId); - if (!source || source.id === targetId) return; + const source = currentCriteria.find((item) => item.id === sourceId) + if (!source || source.id === targetId) return - const next: WorkflowCriteriaForm = toCriteriaForm(source); - if (outcome.startsWith("compareOutcomes:")) { - const outcomeIndex = Number(outcome.split(":")[1]); - next.compareOutcomes = [...(source.compareOutcomes || [])]; + const next: WorkflowCriteriaForm = toCriteriaForm(source) + if (outcome.startsWith('compareOutcomes:')) { + const outcomeIndex = Number(outcome.split(':')[1]) + next.compareOutcomes = [...(source.compareOutcomes || [])] next.compareOutcomes[outcomeIndex] = { ...next.compareOutcomes[outcomeIndex], targetId, - }; - if (outcomeIndex === 0) next.nextOnTrue = targetId; - if (outcomeIndex === 1) next.nextOnFalse = targetId; + } + if (outcomeIndex === 0) next.nextOnTrue = targetId + if (outcomeIndex === 1) next.nextOnFalse = targetId } else { - (next as Record)[outcome] = targetId; + ;(next as Record)[outcome] = targetId } - setPendingLink(null); + setPendingLink(null) runAction(async () => { - await workflowService.saveCriteria(normalizeCriteria(next)); - setSelectedId(""); - }); - }; + await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode }) + setSelectedId('') + }) + } const fitFlowLayout = () => { - if (!currentCriteria.length || busy) return; + if (!currentCriteria.length || busy) return - const nextPositions = buildFitLayout(currentCriteria); - setDesignerTab("flow"); - setCanvasZoom(1); + const nextPositions = buildFitLayout(currentCriteria) + setDesignerTab('flow') + setCanvasZoom(1) runAction(async () => { for (const item of currentCriteria) { - const position = nextPositions.get(item.id); - if (!position) continue; + const position = nextPositions.get(item.id) + if (!position) continue await workflowService.saveCriteria({ ...normalizeCriteria(item), + listFormCode, positionX: position.x, positionY: position.y, - }); + }) } requestAnimationFrame(() => { - canvasRef.current?.scrollTo({ left: 0, top: 0, behavior: "smooth" }); - }); - }); - }; - - const selectWorkflow = (item: WorkflowItemDto) => { - setSelectedWorkflowId(item.id); - setPendingLink(null); - setSelectedId(""); - }; + canvasRef.current?.scrollTo({ left: 0, top: 0, behavior: 'smooth' }) + }) + }) + } const openCriteriaDetails = (id: string) => { - setSelectedId(id); - setPendingLink(null); - setDesignerTab("criteria"); - }; + setSelectedId(id) + setPendingLink(null) + setDesignerTab('criteria') + } const clearCanvasSelection = () => { - setPendingLink(null); - setSelectedId(""); - }; + setPendingLink(null) + setSelectedId('') + } const beginLink = (sourceId: string, outcome: string) => { - setPendingLink({ sourceId, outcome }); - setSelectedId(sourceId); - }; + setPendingLink({ sourceId, outcome }) + setSelectedId(sourceId) + } return ( setApprovalDialogWorkflowId(null)} onConnectNodes={connectNodes} - onCreateWorkflow={createWorkflow} - onDecision={(id: string, approved: boolean, note: string) => - runAction(() => workflowService.decideWorkflow(id, { approved, note })) - } onDeleteSelectedCriteria={deleteSelectedCriteria} onDisconnectLink={disconnectLink} onDragMove={(event: DragEndEvent | null) => - setDragPreview( - event ? { id: event.active.id, delta: event.delta } : null, - ) + setDragPreview(event ? { id: event.active.id, delta: event.delta } : null) } onFitFlowLayout={fitFlowLayout} onOpenCriteriaDetails={openCriteriaDetails} - onResetDemo={() => runAction(workflowService.resetDemo)} + onResetDemo={() => runAction(() => workflowService.resetDemo(listFormCode))} onSaveCriteria={saveCriteria} - onSaveWorkflowEdit={saveWorkflowEdit} onSelectCriteria={setSelectedId} - onSelectWorkflow={selectWorkflow} onSetDesignerTab={setDesignerTab} - onStartWorkflow={startWorkflow} onUpdateNodePosition={updateNodePosition} - onWorkflowEditFormChange={setWorkflowEditForm} - onWorkflowFormChange={setWorkflowForm} - onZoomIn={() => - setCanvasZoom((current) => - Math.min(1.5, Number((current + 0.1).toFixed(2))), - ) - } + onZoomIn={() => setCanvasZoom((current) => Math.min(1.5, Number((current + 0.1).toFixed(2))))} onZoomOut={() => - setCanvasZoom((current) => - Math.max(0.6, Number((current - 0.1).toFixed(2))), - ) + setCanvasZoom((current) => Math.max(0.6, Number((current - 0.1).toFixed(2)))) } /> - ); + ) } diff --git a/ui/src/views/admin/listForm/workflow/ApprovalDialog.tsx b/ui/src/views/admin/listForm/workflow/ApprovalDialog.tsx deleted file mode 100644 index 32a3dc8..0000000 --- a/ui/src/views/admin/listForm/workflow/ApprovalDialog.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { formatMoney } from "@/utils/workflow/workflowHelpers"; -import { useState } from "react"; -import { FiCheck, FiSlash, FiX } from "react-icons/fi"; -import type { - WorkflowCriteriaDto, - WorkflowItemDto, -} from "@/services/workflow.service"; - -const CloseIcon = FiX as any; -const CheckIcon = FiCheck as any; -const SlashIcon = FiSlash as any; - -type ApprovalDialogProps = { - busy: boolean; - criteria: WorkflowCriteriaDto[]; - items: WorkflowItemDto[]; - onClose: () => void; - onDecision: (id: string, approved: boolean, note: string) => void; -}; - -export function ApprovalDialog({ - busy, - criteria, - items, - onClose, - onDecision, -}: ApprovalDialogProps) { - if (!items.length) return null; - - return ( -
-
-
-
-

- Bekleyen Onaylar -

-

- Workflow onay adiminda bekliyor. -

-
- -
- -
-
- ); -} - -function PendingApprovals({ - items, - criteria, - busy, - onDecision, - showChrome = true, -}: Omit & { showChrome?: boolean }) { - const [notes, setNotes] = useState>({}); - - const content = ( - <> - {showChrome && ( -
-

Bekleyen Onaylar

- - {items.length} bekleyen - -
- )} -
- {items.length === 0 && ( -

Bekleyen onay yok.

- )} - {items.map((item) => { - const activeStep = criteria.find( - (candidate) => - candidate.workflowItemId === item.id && - candidate.id === item.currentNodeId, - ); - - return ( -
-
- - #{item.id} {item.sorumlu} - - {activeStep?.title && ( - - {activeStep.title} - - )} - - {formatMoney(item.amount)} - Onaylayacak kişi:{" "} - {item.assignedApprover} - -
-