LisformWorkflow düzenlemesi
This commit is contained in:
parent
49d82d6123
commit
7b0f4acced
34 changed files with 1495 additions and 3213 deletions
2
.github/instructions/ai.instructions.md
vendored
2
.github/instructions/ai.instructions.md
vendored
|
|
@ -102,7 +102,7 @@ Driven by:
|
||||||
- ListFormFields
|
- ListFormFields
|
||||||
- ListFormCustomization (UserUiFilter, GridState, ServerJoin, ServerWhere)
|
- ListFormCustomization (UserUiFilter, GridState, ServerJoin, ServerWhere)
|
||||||
- ListFormImport and ListFormImportLog
|
- ListFormImport and ListFormImportLog
|
||||||
- ListFormWorkflow and ListFormWorkflowCriteria
|
- ListFormWorkflow
|
||||||
- ListFormJsonRow operations
|
- ListFormJsonRow operations
|
||||||
|
|
||||||
Capabilities:
|
Capabilities:
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,19 @@ public class GridOptionsDto : AuditedEntityDto<Guid>
|
||||||
}
|
}
|
||||||
set { SubFormsJson = JsonSerializer.Serialize(value); }
|
set { SubFormsJson = JsonSerializer.Serialize(value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string WorkflowJson { get; set; }
|
||||||
|
public WorkflowDto WorkflowDto
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(WorkflowJson))
|
||||||
|
return JsonSerializer.Deserialize<WorkflowDto>(WorkflowJson);
|
||||||
|
return new WorkflowDto();
|
||||||
|
}
|
||||||
|
set { WorkflowJson = JsonSerializer.Serialize(value); }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ExtraFilterJson { get; set; } // Cagrilacak Extra Filters
|
public string ExtraFilterJson { get; set; } // Cagrilacak Extra Filters
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Sozsoft.Platform.ListForms;
|
||||||
|
|
||||||
|
public class WorkflowDto
|
||||||
|
{
|
||||||
|
public string ApprovalFieldName { get; set; }
|
||||||
|
public DateTime ApprovalDateFieldName { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,7 @@ public class ListFormEditTabs
|
||||||
public const string StateForm = "state";
|
public const string StateForm = "state";
|
||||||
public const string SubFormJsonRow = "subForm";
|
public const string SubFormJsonRow = "subForm";
|
||||||
public const string WidgetForm = "widget";
|
public const string WidgetForm = "widget";
|
||||||
|
public const string WorkflowForm = "workflow";
|
||||||
public const string Fields = "fields";
|
public const string Fields = "fields";
|
||||||
public const string Customization = "customization";
|
public const string Customization = "customization";
|
||||||
public const string ExtraFilterForm = "extraFilter";
|
public const string ExtraFilterForm = "extraFilter";
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,12 @@ public class CreateUpdateListFormWorkflowCriteriaDto
|
||||||
{
|
{
|
||||||
public Guid? Id { get; set; }
|
public Guid? Id { get; set; }
|
||||||
public string ListFormCode { get; set; }
|
public string ListFormCode { get; set; }
|
||||||
public Guid WorkflowItemId { get; set; }
|
|
||||||
public string NodeId { get; set; }
|
|
||||||
public string Kind { get; set; }
|
public string Kind { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string Column { get; set; }
|
public string CompareColumn { get; set; }
|
||||||
public string Operator { get; set; }
|
public string CompareOperator { get; set; }
|
||||||
public decimal CompareValue { get; set; }
|
public decimal CompareValue { get; set; }
|
||||||
public string Approver { get; set; }
|
public string Approver { get; set; }
|
||||||
public string InformPerson { get; set; }
|
|
||||||
public string NextOnStart { get; set; }
|
public string NextOnStart { get; set; }
|
||||||
public string NextOnTrue { get; set; }
|
public string NextOnTrue { get; set; }
|
||||||
public string NextOnFalse { get; set; }
|
public string NextOnFalse { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -7,10 +7,6 @@ namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
public interface IListFormWorkflowAppService : IApplicationService
|
public interface IListFormWorkflowAppService : IApplicationService
|
||||||
{
|
{
|
||||||
Task<ListFormWorkflowStateDto> GetStateAsync(string listFormCode = null);
|
Task<ListFormWorkflowStateDto> GetStateAsync(string listFormCode = null);
|
||||||
Task<ListFormWorkflowDto> CreateWorkflowAsync(CreateUpdateListFormWorkflowDto input);
|
|
||||||
Task<ListFormWorkflowDto> UpdateWorkflowAsync(Guid id, CreateUpdateListFormWorkflowDto input);
|
|
||||||
Task<ListFormWorkflowDto> StartWorkflowAsync(Guid id);
|
|
||||||
Task<ListFormWorkflowDto> DecideWorkflowAsync(Guid id, DecisionWorkflowDto input);
|
|
||||||
Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input);
|
Task<ListFormWorkflowCriteriaDto> SaveCriteriaAsync(CreateUpdateListFormWorkflowCriteriaDto input);
|
||||||
Task DeleteCriteriaAsync(Guid id);
|
Task DeleteCriteriaAsync(Guid id);
|
||||||
Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null);
|
Task<ListFormWorkflowStateDto> ResetDemoAsync(string listFormCode = null);
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,12 @@ namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
public class ListFormWorkflowCriteriaDto : AuditedEntityDto<Guid>
|
public class ListFormWorkflowCriteriaDto : AuditedEntityDto<Guid>
|
||||||
{
|
{
|
||||||
public string ListFormCode { get; set; }
|
public string ListFormCode { get; set; }
|
||||||
public Guid WorkflowItemId { get; set; }
|
|
||||||
public string NodeId { get; set; }
|
|
||||||
public string Kind { get; set; }
|
public string Kind { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string Column { get; set; }
|
public string CompareColumn { get; set; }
|
||||||
public string Operator { get; set; }
|
public string CompareOperator { get; set; }
|
||||||
public decimal CompareValue { get; set; }
|
public decimal CompareValue { get; set; }
|
||||||
public string Approver { get; set; }
|
public string Approver { get; set; }
|
||||||
public string InformPerson { get; set; }
|
|
||||||
public string NextOnStart { get; set; }
|
public string NextOnStart { get; set; }
|
||||||
public string NextOnTrue { get; set; }
|
public string NextOnTrue { get; set; }
|
||||||
public string NextOnFalse { get; set; }
|
public string NextOnFalse { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ListForms.Workflow;
|
|
||||||
|
|
||||||
public class ListFormWorkflowDto : AuditedEntityDto<Guid>
|
|
||||||
{
|
|
||||||
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<WorkflowHistoryDto> History { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -4,7 +4,6 @@ namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
|
|
||||||
public class ListFormWorkflowStateDto
|
public class ListFormWorkflowStateDto
|
||||||
{
|
{
|
||||||
public List<ListFormWorkflowDto> WorkflowItems { get; set; } = [];
|
|
||||||
public List<ListFormWorkflowCriteriaDto> Criteria { get; set; } = [];
|
public List<ListFormWorkflowCriteriaDto> Criteria { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ namespace Sozsoft.Platform.ListForms.Workflow;
|
||||||
|
|
||||||
public class WorkflowConditionDto
|
public class WorkflowConditionDto
|
||||||
{
|
{
|
||||||
public string Column { get; set; }
|
public string CompareColumn { get; set; }
|
||||||
public string Operator { get; set; }
|
public string CompareOperator { get; set; }
|
||||||
public decimal CompareValue { get; set; }
|
public decimal CompareValue { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,10 @@ public class ListFormsAppService : CrudAppService<
|
||||||
{
|
{
|
||||||
item.SubFormsJson = JsonSerializer.Serialize(input.SubFormsDto);
|
item.SubFormsJson = JsonSerializer.Serialize(input.SubFormsDto);
|
||||||
}
|
}
|
||||||
|
else if (input.EditType == ListFormEditTabs.WorkflowForm)
|
||||||
|
{
|
||||||
|
item.WorkflowJson = JsonSerializer.Serialize(input.WorkflowDto);
|
||||||
|
}
|
||||||
|
|
||||||
/*Chart*/
|
/*Chart*/
|
||||||
else if (input.EditType == ListFormEditTabs.ChartCommonForm)
|
else if (input.EditType == ListFormEditTabs.ChartCommonForm)
|
||||||
|
|
|
||||||
|
|
@ -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<ListFormWorkflow, Guid> criteriaRepository;
|
||||||
|
|
||||||
|
public ListFormWorkflowAppService(IRepository<ListFormWorkflow, Guid> criteriaRepository)
|
||||||
|
{
|
||||||
|
this.criteriaRepository = criteriaRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("state")]
|
||||||
|
public async Task<ListFormWorkflowStateDto> 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<ListFormWorkflowCriteriaDto> 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<ListFormWorkflowStateDto> 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<ListFormWorkflow> 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<CompareOutcomeDto> outcomes)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(outcomes ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<CompareOutcomeDto> DeserializeCompareOutcomes(string json)
|
||||||
|
{
|
||||||
|
if (json.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<List<CompareOutcomeDto>>(json) ?? [];
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<ListFormWorkflow, Guid> workflowRepository;
|
|
||||||
private readonly IRepository<ListFormWorkflowCriteria, Guid> criteriaRepository;
|
|
||||||
|
|
||||||
public ListFormWorkflowAppService(
|
|
||||||
IRepository<ListFormWorkflow, Guid> workflowRepository,
|
|
||||||
IRepository<ListFormWorkflowCriteria, Guid> criteriaRepository)
|
|
||||||
{
|
|
||||||
this.workflowRepository = workflowRepository;
|
|
||||||
this.criteriaRepository = criteriaRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("state")]
|
|
||||||
public async Task<ListFormWorkflowStateDto> 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<ListFormWorkflowDto> 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<ListFormWorkflowDto> 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<ListFormWorkflowDto> 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<ListFormWorkflowDto> 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<ListFormWorkflowCriteriaDto> 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<ListFormWorkflowStateDto> 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<ListFormWorkflowCriteria> CreateDefaultStartCriteriaAsync(ListFormWorkflow workflow)
|
|
||||||
{
|
|
||||||
return await CreateCriteriaAsync(workflow, "Start", "İş Akışı Başlat", 32, 150);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ListFormWorkflowCriteria> 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<List<ListFormWorkflowCriteria>> 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<ListFormWorkflowCriteria> criteria,
|
|
||||||
ListFormWorkflowCriteria current)
|
|
||||||
{
|
|
||||||
var visited = new HashSet<Guid>();
|
|
||||||
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<ListFormWorkflowCriteria> 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<CompareOutcomeDto> outcomes)
|
|
||||||
{
|
|
||||||
return JsonSerializer.Serialize(outcomes ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<CompareOutcomeDto> DeserializeCompareOutcomes(string json)
|
|
||||||
{
|
|
||||||
if (json.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return JsonSerializer.Deserialize<List<CompareOutcomeDto>>(json) ?? [];
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string SerializeHistory(List<WorkflowHistoryDto> history)
|
|
||||||
{
|
|
||||||
return JsonSerializer.Serialize(history ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<WorkflowHistoryDto> DeserializeHistory(string json)
|
|
||||||
{
|
|
||||||
if (json.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return JsonSerializer.Deserialize<List<WorkflowHistoryDto>>(json) ?? [];
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -59,6 +59,8 @@ public class ListFormSeeder_Utils
|
||||||
{
|
{
|
||||||
ListFormCode = newListFormCode,
|
ListFormCode = newListFormCode,
|
||||||
SubFormsJson = listForm.SubFormsJson,
|
SubFormsJson = listForm.SubFormsJson,
|
||||||
|
WidgetsJson = listForm.WidgetsJson,
|
||||||
|
WorkflowJson = listForm.WorkflowJson,
|
||||||
ListFormType = listForm.ListFormType,
|
ListFormType = listForm.ListFormType,
|
||||||
IsSubForm = listForm.IsSubForm,
|
IsSubForm = listForm.IsSubForm,
|
||||||
ShowNote = listForm.ShowNote,
|
ShowNote = listForm.ShowNote,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ public enum TableNameEnum
|
||||||
ListFormImport,
|
ListFormImport,
|
||||||
ListFormImportLog,
|
ListFormImportLog,
|
||||||
ListFormWorkflow,
|
ListFormWorkflow,
|
||||||
ListFormWorkflowCriteria,
|
|
||||||
Note,
|
Note,
|
||||||
ForumCategory,
|
ForumCategory,
|
||||||
ForumTopic,
|
ForumTopic,
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ public static class TableNameResolver
|
||||||
{ nameof(TableNameEnum.ListFormImport), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.ListFormImport), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.ListFormImportLog), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.ListFormImportLog), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.ListFormWorkflow), (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.Notification), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.NotificationRule), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.NotificationRule), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
||||||
{ nameof(TableNameEnum.NotificationType), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
{ nameof(TableNameEnum.NotificationType), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou
|
||||||
{
|
{
|
||||||
using var uow = LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>().Begin(requiresNew: true, isTransactional: false);
|
using var uow = LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>().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 LogPrefix = $"{Clock.Now:s}_{Worker.Name}: {{0}}";
|
||||||
var DistributedLockName = Worker.Name;
|
var DistributedLockName = Worker.Name;
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,13 @@ public class ListForm : Entity<Guid>
|
||||||
/// <summary>bu listform'un üstünde yer alan widgetların listesidir</summary>
|
/// <summary>bu listform'un üstünde yer alan widgetların listesidir</summary>
|
||||||
public string WidgetsJson { get; set; }
|
public string WidgetsJson { get; set; }
|
||||||
|
|
||||||
/// <summary>bu listform'un üstünde yer alan widgetların listesidir</summary>
|
/// <summary>bu listform'un üstünde yer alan workflowların listesidir</summary>
|
||||||
|
public string WorkflowJson { get; set; }
|
||||||
|
|
||||||
|
/// <summary>bu listform'un üstünde yer alan extra filterların listesidir</summary>
|
||||||
public string ExtraFilterJson { get; set; }
|
public string ExtraFilterJson { get; set; }
|
||||||
|
|
||||||
/// <summary>bu listform'un üstünde yer alan widgetların listesidir</summary>
|
/// <summary>bu listform'un üstünde yer alan layoutların listesidir</summary>
|
||||||
public string LayoutJson { get; set; }
|
public string LayoutJson { get; set; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using Volo.Abp.Domain.Entities;
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Entities;
|
namespace Sozsoft.Platform.Entities;
|
||||||
|
|
||||||
public class ListFormWorkflow : FullAuditedEntity<Guid>
|
public class ListFormWorkflow : Entity<Guid>
|
||||||
{
|
{
|
||||||
protected ListFormWorkflow()
|
protected ListFormWorkflow()
|
||||||
{
|
{
|
||||||
|
|
@ -15,14 +14,18 @@ public class ListFormWorkflow : FullAuditedEntity<Guid>
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ListFormCode { get; set; }
|
public string ListFormCode { get; set; }
|
||||||
public int OrderNo { get; set; }
|
public string Kind { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string Status { get; set; }
|
public string CompareColumn { get; set; }
|
||||||
public decimal Amount { get; set; }
|
public string CompareOperator { get; set; }
|
||||||
public string CurrentNodeId { get; set; }
|
public decimal CompareValue { get; set; }
|
||||||
public string AssignedApprover { get; set; }
|
public string Approver { get; set; }
|
||||||
public string InformedPerson { get; set; }
|
public string NextOnStart { get; set; }
|
||||||
public string HistoryJson { get; set; }
|
public string NextOnTrue { get; set; }
|
||||||
|
public string NextOnFalse { get; set; }
|
||||||
public ICollection<ListFormWorkflowCriteria> Criteria { 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; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
using System;
|
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Entities;
|
|
||||||
|
|
||||||
public class ListFormWorkflowCriteria : FullAuditedEntity<Guid>
|
|
||||||
{
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
@ -55,8 +55,7 @@ public class PlatformDbContext :
|
||||||
public DbSet<ListFormCustomization> ListFormCustomization { get; set; }
|
public DbSet<ListFormCustomization> ListFormCustomization { get; set; }
|
||||||
public DbSet<ListFormImport> ListFormImports { get; set; }
|
public DbSet<ListFormImport> ListFormImports { get; set; }
|
||||||
public DbSet<ListFormImportLog> ListFormImportLogs { get; set; }
|
public DbSet<ListFormImportLog> ListFormImportLogs { get; set; }
|
||||||
public DbSet<ListFormWorkflow> ListFormWorkflows { get; set; }
|
public DbSet<ListFormWorkflow> ListFormWorkflow { get; set; }
|
||||||
public DbSet<ListFormWorkflowCriteria> ListFormWorkflowCriteria { get; set; }
|
|
||||||
public DbSet<BackgroundWorker> BackgroundWorkers { get; set; }
|
public DbSet<BackgroundWorker> BackgroundWorkers { get; set; }
|
||||||
public DbSet<ForumCategory> ForumCategories { get; set; }
|
public DbSet<ForumCategory> ForumCategories { get; set; }
|
||||||
public DbSet<ForumTopic> ForumTopics { get; set; }
|
public DbSet<ForumTopic> ForumTopics { get; set; }
|
||||||
|
|
@ -365,6 +364,7 @@ public class PlatformDbContext :
|
||||||
b.Property(a => a.FormFieldsDefaultValueJson).HasColumnType("text");
|
b.Property(a => a.FormFieldsDefaultValueJson).HasColumnType("text");
|
||||||
b.Property(a => a.SubFormsJson).HasColumnType("text");
|
b.Property(a => a.SubFormsJson).HasColumnType("text");
|
||||||
b.Property(a => a.WidgetsJson).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.ExtraFilterJson).HasColumnType("text");
|
||||||
b.Property(a => a.LayoutJson).HasColumnType("text");
|
b.Property(a => a.LayoutJson).HasColumnType("text");
|
||||||
b.Property(a => a.CommonJson).HasColumnType("text");
|
b.Property(a => a.CommonJson).HasColumnType("text");
|
||||||
|
|
@ -488,36 +488,12 @@ public class PlatformDbContext :
|
||||||
b.ConfigureByConvention();
|
b.ConfigureByConvention();
|
||||||
|
|
||||||
b.Property(x => x.ListFormCode).IsRequired().HasMaxLength(64);
|
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<ListFormWorkflowCriteria>(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.Kind).IsRequired().HasMaxLength(50);
|
||||||
b.Property(x => x.Title).IsRequired().HasMaxLength(250);
|
b.Property(x => x.Title).IsRequired().HasMaxLength(250);
|
||||||
b.Property(x => x.Column).IsRequired().HasMaxLength(100);
|
b.Property(x => x.CompareColumn).IsRequired().HasMaxLength(100);
|
||||||
b.Property(x => x.Operator).IsRequired().HasMaxLength(20);
|
b.Property(x => x.CompareOperator).IsRequired().HasMaxLength(20);
|
||||||
b.Property(x => x.CompareValue).HasPrecision(18, 2);
|
b.Property(x => x.CompareValue).HasPrecision(18, 2);
|
||||||
b.Property(x => x.Approver).IsRequired().HasMaxLength(250);
|
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.NextOnStart).IsRequired().HasMaxLength(50);
|
||||||
b.Property(x => x.NextOnTrue).IsRequired().HasMaxLength(50);
|
b.Property(x => x.NextOnTrue).IsRequired().HasMaxLength(50);
|
||||||
b.Property(x => x.NextOnFalse).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.PositionX).IsRequired();
|
||||||
b.Property(x => x.PositionY).IsRequired();
|
b.Property(x => x.PositionY).IsRequired();
|
||||||
b.Property(x => x.CompareOutcomesJson).HasColumnType("text");
|
b.Property(x => x.CompareOutcomesJson).HasColumnType("text");
|
||||||
|
|
||||||
b.HasIndex(x => new
|
|
||||||
{
|
|
||||||
x.ListFormCode,
|
|
||||||
x.WorkflowItemId,
|
|
||||||
x.NodeId
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Entity<Note>(b =>
|
builder.Entity<Note>(b =>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260522085648_Initial")]
|
[Migration("20260522200739_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -3053,6 +3053,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Property<int?>("Width")
|
b.Property<int?>("Width")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("WorkflowJson")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("ZoomAndPanJson")
|
b.Property<string>("ZoomAndPanJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
|
@ -3410,86 +3413,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<decimal>("Amount")
|
|
||||||
.HasPrecision(18, 2)
|
|
||||||
.HasColumnType("decimal(18,2)");
|
|
||||||
|
|
||||||
b.Property<string>("AssignedApprover")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(250)
|
|
||||||
.HasColumnType("nvarchar(250)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("CreationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("CreatorId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("CreatorId");
|
|
||||||
|
|
||||||
b.Property<string>("CurrentNodeId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("nvarchar(50)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("DeleterId");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DeletionTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("DeletionTime");
|
|
||||||
|
|
||||||
b.Property<string>("HistoryJson")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InformedPerson")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(250)
|
|
||||||
.HasColumnType("nvarchar(250)");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDeleted")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bit")
|
|
||||||
.HasDefaultValue(false)
|
|
||||||
.HasColumnName("IsDeleted");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("LastModificationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastModifierId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("LastModifierId");
|
|
||||||
|
|
||||||
b.Property<string>("ListFormCode")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<int>("OrderNo")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Status")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("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<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
@ -3499,11 +3422,16 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(250)
|
.HasMaxLength(250)
|
||||||
.HasColumnType("nvarchar(250)");
|
.HasColumnType("nvarchar(250)");
|
||||||
|
|
||||||
b.Property<string>("Column")
|
b.Property<string>("CompareColumn")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("CompareOperator")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
b.Property<string>("CompareOutcomesJson")
|
b.Property<string>("CompareOutcomesJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
|
@ -3511,46 +3439,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasPrecision(18, 2)
|
.HasPrecision(18, 2)
|
||||||
.HasColumnType("decimal(18,2)");
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
b.Property<DateTime>("CreationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("CreationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("CreatorId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("CreatorId");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("DeleterId");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DeletionTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("DeletionTime");
|
|
||||||
|
|
||||||
b.Property<string>("InformPerson")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(250)
|
|
||||||
.HasColumnType("nvarchar(250)");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDeleted")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bit")
|
|
||||||
.HasDefaultValue(false)
|
|
||||||
.HasColumnName("IsDeleted");
|
|
||||||
|
|
||||||
b.Property<string>("Kind")
|
b.Property<string>("Kind")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("nvarchar(50)");
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("LastModificationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastModifierId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("LastModifierId");
|
|
||||||
|
|
||||||
b.Property<string>("ListFormCode")
|
b.Property<string>("ListFormCode")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
|
|
@ -3581,16 +3474,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("nvarchar(50)");
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<string>("NodeId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("nvarchar(50)");
|
|
||||||
|
|
||||||
b.Property<string>("Operator")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("nvarchar(20)");
|
|
||||||
|
|
||||||
b.Property<int>("PositionX")
|
b.Property<int>("PositionX")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|
@ -3602,16 +3485,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(250)
|
.HasMaxLength(250)
|
||||||
.HasColumnType("nvarchar(250)");
|
.HasColumnType("nvarchar(250)");
|
||||||
|
|
||||||
b.Property<Guid>("WorkflowItemId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("WorkflowItemId");
|
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
|
||||||
|
|
||||||
b.HasIndex("ListFormCode", "WorkflowItemId", "NodeId");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_ListFormWorkflowCriteria", (string)null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.LogEntry", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.LogEntry", b =>
|
||||||
|
|
@ -8344,17 +8220,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.IsRequired();
|
.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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
||||||
|
|
@ -8790,11 +8655,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Events");
|
b.Navigation("Events");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Criteria");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
@ -1355,6 +1355,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
ShowNote = table.Column<bool>(type: "bit", nullable: false),
|
ShowNote = table.Column<bool>(type: "bit", nullable: false),
|
||||||
SubFormsJson = table.Column<string>(type: "text", nullable: true),
|
SubFormsJson = table.Column<string>(type: "text", nullable: true),
|
||||||
WidgetsJson = table.Column<string>(type: "text", nullable: true),
|
WidgetsJson = table.Column<string>(type: "text", nullable: true),
|
||||||
|
WorkflowJson = table.Column<string>(type: "text", nullable: true),
|
||||||
ExtraFilterJson = table.Column<string>(type: "text", nullable: true),
|
ExtraFilterJson = table.Column<string>(type: "text", nullable: true),
|
||||||
LayoutJson = table.Column<string>(type: "text", nullable: true),
|
LayoutJson = table.Column<string>(type: "text", nullable: true),
|
||||||
UserId = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
UserId = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||||
|
|
@ -1394,21 +1395,20 @@ namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
ListFormCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
ListFormCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||||
OrderNo = table.Column<int>(type: "int", nullable: false),
|
Kind = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
Title = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: false),
|
Title = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
||||||
Status = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
CompareColumn = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||||
Amount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
CompareOperator = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
|
||||||
CurrentNodeId = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
CompareValue = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||||
AssignedApprover = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
Approver = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
||||||
InformedPerson = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
NextOnStart = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
HistoryJson = table.Column<string>(type: "text", nullable: true),
|
NextOnTrue = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
NextOnFalse = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
NextOnApprove = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
NextOnReject = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
PositionX = table.Column<int>(type: "int", nullable: false),
|
||||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
PositionY = table.Column<int>(type: "int", nullable: false),
|
||||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
CompareOutcomesJson = table.Column<string>(type: "text", nullable: true)
|
||||||
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true)
|
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
|
|
@ -2661,48 +2661,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Sas_H_ListFormWorkflowCriteria",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
ListFormCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
|
||||||
WorkflowItemId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
|
||||||
NodeId = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
||||||
Kind = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
||||||
Title = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
|
||||||
Column = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
|
||||||
Operator = table.Column<string>(type: "nvarchar(20)", maxLength: 20, nullable: false),
|
|
||||||
CompareValue = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
|
||||||
Approver = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
|
||||||
InformPerson = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
|
|
||||||
NextOnStart = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
||||||
NextOnTrue = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
||||||
NextOnFalse = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
||||||
NextOnApprove = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
||||||
NextOnReject = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
|
||||||
PositionX = table.Column<int>(type: "int", nullable: false),
|
|
||||||
PositionY = table.Column<int>(type: "int", nullable: false),
|
|
||||||
CompareOutcomesJson = table.Column<string>(type: "text", nullable: true),
|
|
||||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
|
||||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
|
||||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
|
||||||
LastModifierId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
|
||||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
|
||||||
DeleterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
|
||||||
DeletionTime = table.Column<DateTime>(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(
|
migrationBuilder.CreateTable(
|
||||||
name: "Sas_H_NotificationRule",
|
name: "Sas_H_NotificationRule",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
|
@ -3933,16 +3891,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
table: "Sas_H_ListFormImportLog",
|
table: "Sas_H_ListFormImportLog",
|
||||||
column: "ImportId");
|
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(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Sas_H_Menu_Code",
|
name: "IX_Sas_H_Menu_Code",
|
||||||
table: "Sas_H_Menu",
|
table: "Sas_H_Menu",
|
||||||
|
|
@ -4293,7 +4241,7 @@ namespace Sozsoft.Platform.Migrations
|
||||||
name: "Sas_H_ListFormImportLog");
|
name: "Sas_H_ListFormImportLog");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_ListFormWorkflowCriteria");
|
name: "Sas_H_ListFormWorkflow");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_LogEntry");
|
name: "Sas_H_LogEntry");
|
||||||
|
|
@ -4400,9 +4348,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_ListFormImport");
|
name: "Sas_H_ListFormImport");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Sas_H_ListFormWorkflow");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Sas_H_NotificationRule");
|
name: "Sas_H_NotificationRule");
|
||||||
|
|
||||||
|
|
@ -3050,6 +3050,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Property<int?>("Width")
|
b.Property<int?>("Width")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("WorkflowJson")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("ZoomAndPanJson")
|
b.Property<string>("ZoomAndPanJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
|
@ -3407,86 +3410,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
||||||
{
|
|
||||||
b.Property<Guid>("Id")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.Property<decimal>("Amount")
|
|
||||||
.HasPrecision(18, 2)
|
|
||||||
.HasColumnType("decimal(18,2)");
|
|
||||||
|
|
||||||
b.Property<string>("AssignedApprover")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(250)
|
|
||||||
.HasColumnType("nvarchar(250)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("CreationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("CreatorId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("CreatorId");
|
|
||||||
|
|
||||||
b.Property<string>("CurrentNodeId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("nvarchar(50)");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("DeleterId");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DeletionTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("DeletionTime");
|
|
||||||
|
|
||||||
b.Property<string>("HistoryJson")
|
|
||||||
.HasColumnType("text");
|
|
||||||
|
|
||||||
b.Property<string>("InformedPerson")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(250)
|
|
||||||
.HasColumnType("nvarchar(250)");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDeleted")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bit")
|
|
||||||
.HasDefaultValue(false)
|
|
||||||
.HasColumnName("IsDeleted");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("LastModificationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastModifierId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("LastModifierId");
|
|
||||||
|
|
||||||
b.Property<string>("ListFormCode")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<int>("OrderNo")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Status")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("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<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.HasColumnType("uniqueidentifier");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
@ -3496,11 +3419,16 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(250)
|
.HasMaxLength(250)
|
||||||
.HasColumnType("nvarchar(250)");
|
.HasColumnType("nvarchar(250)");
|
||||||
|
|
||||||
b.Property<string>("Column")
|
b.Property<string>("CompareColumn")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<string>("CompareOperator")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("nvarchar(20)");
|
||||||
|
|
||||||
b.Property<string>("CompareOutcomesJson")
|
b.Property<string>("CompareOutcomesJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
|
@ -3508,46 +3436,11 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasPrecision(18, 2)
|
.HasPrecision(18, 2)
|
||||||
.HasColumnType("decimal(18,2)");
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
b.Property<DateTime>("CreationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("CreationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("CreatorId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("CreatorId");
|
|
||||||
|
|
||||||
b.Property<Guid?>("DeleterId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("DeleterId");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DeletionTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("DeletionTime");
|
|
||||||
|
|
||||||
b.Property<string>("InformPerson")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(250)
|
|
||||||
.HasColumnType("nvarchar(250)");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDeleted")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bit")
|
|
||||||
.HasDefaultValue(false)
|
|
||||||
.HasColumnName("IsDeleted");
|
|
||||||
|
|
||||||
b.Property<string>("Kind")
|
b.Property<string>("Kind")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("nvarchar(50)");
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<DateTime?>("LastModificationTime")
|
|
||||||
.HasColumnType("datetime2")
|
|
||||||
.HasColumnName("LastModificationTime");
|
|
||||||
|
|
||||||
b.Property<Guid?>("LastModifierId")
|
|
||||||
.HasColumnType("uniqueidentifier")
|
|
||||||
.HasColumnName("LastModifierId");
|
|
||||||
|
|
||||||
b.Property<string>("ListFormCode")
|
b.Property<string>("ListFormCode")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
|
|
@ -3578,16 +3471,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("nvarchar(50)");
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.Property<string>("NodeId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("nvarchar(50)");
|
|
||||||
|
|
||||||
b.Property<string>("Operator")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(20)
|
|
||||||
.HasColumnType("nvarchar(20)");
|
|
||||||
|
|
||||||
b.Property<int>("PositionX")
|
b.Property<int>("PositionX")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|
@ -3599,16 +3482,9 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.HasMaxLength(250)
|
.HasMaxLength(250)
|
||||||
.HasColumnType("nvarchar(250)");
|
.HasColumnType("nvarchar(250)");
|
||||||
|
|
||||||
b.Property<Guid>("WorkflowItemId")
|
|
||||||
.HasColumnType("uniqueidentifier");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("WorkflowItemId");
|
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
|
||||||
|
|
||||||
b.HasIndex("ListFormCode", "WorkflowItemId", "NodeId");
|
|
||||||
|
|
||||||
b.ToTable("Sas_H_ListFormWorkflowCriteria", (string)null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.LogEntry", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.LogEntry", b =>
|
||||||
|
|
@ -8341,17 +8217,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
.IsRequired();
|
.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 =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.OrderItem", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
b.HasOne("Sozsoft.Platform.Entities.Order", "Order")
|
||||||
|
|
@ -8787,11 +8652,6 @@ namespace Sozsoft.Platform.Migrations
|
||||||
b.Navigation("Events");
|
b.Navigation("Events");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Criteria");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
modelBuilder.Entity("Sozsoft.Platform.Entities.Order", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import apiService from './api.service'
|
import apiService from './api.service'
|
||||||
|
|
||||||
export interface WorkflowConditionDto {
|
export interface WorkflowConditionDto {
|
||||||
column: string
|
compareColumn: string
|
||||||
operator: string
|
compareOperator: string
|
||||||
compareValue: number
|
compareValue: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12,38 +12,16 @@ export interface CompareOutcomeDto {
|
||||||
conditions: WorkflowConditionDto[]
|
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 {
|
export interface WorkflowCriteriaDto {
|
||||||
id: string
|
id: string
|
||||||
listFormCode: string
|
listFormCode: string
|
||||||
workflowItemId: string
|
|
||||||
nodeId: string
|
nodeId: string
|
||||||
kind: string
|
kind: string
|
||||||
title: string
|
title: string
|
||||||
column: string
|
compareColumn: string
|
||||||
operator: string
|
compareOperator: string
|
||||||
compareValue: number
|
compareValue: number
|
||||||
approver: string
|
approver: string
|
||||||
informPerson: string
|
|
||||||
nextOnStart?: string | null
|
nextOnStart?: string | null
|
||||||
nextOnTrue?: string | null
|
nextOnTrue?: string | null
|
||||||
nextOnFalse?: string | null
|
nextOnFalse?: string | null
|
||||||
|
|
@ -55,20 +33,12 @@ export interface WorkflowCriteriaDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowStateDto {
|
export interface WorkflowStateDto {
|
||||||
workflowItems: WorkflowItemDto[]
|
|
||||||
criteria: WorkflowCriteriaDto[]
|
criteria: WorkflowCriteriaDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateUpdateWorkflowInput = Partial<WorkflowItemDto> & {
|
|
||||||
listFormCode?: string
|
|
||||||
sorumlu: string
|
|
||||||
amount: number
|
|
||||||
tarih?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {
|
export type SaveCriteriaInput = Omit<Partial<WorkflowCriteriaDto>, 'id'> & {
|
||||||
id?: string | null
|
id?: string | null
|
||||||
workflowItemId: string
|
listFormCode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = '/api/app/list-form-workflow'
|
const baseUrl = '/api/app/list-form-workflow'
|
||||||
|
|
@ -84,45 +54,6 @@ export const workflowService = {
|
||||||
return response.data
|
return response.data
|
||||||
},
|
},
|
||||||
|
|
||||||
async createWorkflow(payload: CreateUpdateWorkflowInput) {
|
|
||||||
const response = await apiService.fetchData<WorkflowItemDto>({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${baseUrl}/workflows`,
|
|
||||||
data: payload,
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.data
|
|
||||||
},
|
|
||||||
|
|
||||||
async updateWorkflow(id: string, payload: CreateUpdateWorkflowInput) {
|
|
||||||
const response = await apiService.fetchData<WorkflowItemDto>({
|
|
||||||
method: 'PUT',
|
|
||||||
url: `${baseUrl}/workflows/${id}`,
|
|
||||||
data: payload,
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.data
|
|
||||||
},
|
|
||||||
|
|
||||||
async startWorkflow(id: string) {
|
|
||||||
const response = await apiService.fetchData<WorkflowItemDto>({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${baseUrl}/workflows/${id}/start`,
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.data
|
|
||||||
},
|
|
||||||
|
|
||||||
async decideWorkflow(id: string, payload: { approved: boolean; note?: string }) {
|
|
||||||
const response = await apiService.fetchData<WorkflowItemDto>({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${baseUrl}/workflows/${id}/decision`,
|
|
||||||
data: payload,
|
|
||||||
})
|
|
||||||
|
|
||||||
return response.data
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveCriteria(payload: SaveCriteriaInput) {
|
async saveCriteria(payload: SaveCriteriaInput) {
|
||||||
const response = await apiService.fetchData<WorkflowCriteriaDto>({
|
const response = await apiService.fetchData<WorkflowCriteriaDto>({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,60 @@
|
||||||
import { getNodeHeight, nodeSize } from "./workflowConstants";
|
import { getNodeHeight, nodeSize } from './workflowConstants'
|
||||||
import type {
|
import type {
|
||||||
CompareOutcomeDto,
|
CompareOutcomeDto,
|
||||||
SaveCriteriaInput,
|
SaveCriteriaInput,
|
||||||
WorkflowConditionDto,
|
WorkflowConditionDto,
|
||||||
WorkflowCriteriaDto,
|
WorkflowCriteriaDto,
|
||||||
WorkflowItemDto,
|
} from '@/services/workflow.service'
|
||||||
} from "@/services/workflow.service";
|
|
||||||
|
|
||||||
export type WorkflowCriteriaForm = Partial<WorkflowCriteriaDto> & {
|
export type WorkflowCriteriaForm = Partial<WorkflowCriteriaDto> & {
|
||||||
id?: string | null;
|
id?: string | null
|
||||||
workflowItemId: string;
|
listFormCode: string
|
||||||
compareOutcomes: CompareOutcomeDto[];
|
compareOutcomes: CompareOutcomeDto[]
|
||||||
};
|
}
|
||||||
|
|
||||||
export type WorkflowOutcome = {
|
export type WorkflowOutcome = {
|
||||||
field: string;
|
field: string
|
||||||
label: string;
|
label: string
|
||||||
targetId?: string | null;
|
targetId?: string | null
|
||||||
};
|
}
|
||||||
|
|
||||||
export type WorkflowLinkPort = {
|
export type WorkflowLinkPort = {
|
||||||
field?: string;
|
field?: string
|
||||||
index?: number;
|
index?: number
|
||||||
count?: number;
|
count?: number
|
||||||
sourceSlotIndex?: number;
|
sourceSlotIndex?: number
|
||||||
sourceSlotCount?: number;
|
sourceSlotCount?: number
|
||||||
targetSlotIndex?: number;
|
targetSlotIndex?: number
|
||||||
targetSlotCount?: number;
|
targetSlotCount?: number
|
||||||
routeIndex?: number;
|
routeIndex?: number
|
||||||
routeCount?: number;
|
routeCount?: number
|
||||||
};
|
}
|
||||||
|
|
||||||
export type WorkflowLink = {
|
export type WorkflowLink = {
|
||||||
key: string;
|
key: string
|
||||||
source: WorkflowCriteriaDto;
|
source: WorkflowCriteriaDto
|
||||||
target: WorkflowCriteriaDto;
|
target: WorkflowCriteriaDto
|
||||||
label: string;
|
label: string
|
||||||
sourcePort: WorkflowLinkPort;
|
sourcePort: WorkflowLinkPort
|
||||||
};
|
}
|
||||||
|
|
||||||
type Endpoint = {
|
type Endpoint = {
|
||||||
link: WorkflowLink;
|
link: WorkflowLink
|
||||||
role: "source" | "target";
|
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",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildFitLayout(criteria: WorkflowCriteriaDto[]) {
|
export function buildFitLayout(criteria: WorkflowCriteriaDto[]) {
|
||||||
const links = collectLinks(criteria);
|
const links = collectLinks(criteria)
|
||||||
const rankById = buildTraversalRanks(criteria, links);
|
const rankById = buildTraversalRanks(criteria, links)
|
||||||
const groups = new Map<number, WorkflowCriteriaDto[]>();
|
const groups = new Map<number, WorkflowCriteriaDto[]>()
|
||||||
criteria.forEach((item) => {
|
criteria.forEach((item) => {
|
||||||
const column = fitColumn(item);
|
const column = fitColumn(item)
|
||||||
if (!groups.has(column)) groups.set(column, []);
|
if (!groups.has(column)) groups.set(column, [])
|
||||||
groups.get(column)?.push(item);
|
groups.get(column)?.push(item)
|
||||||
});
|
})
|
||||||
|
|
||||||
const sortedColumns = [...groups.keys()].sort((a, b) => a - b);
|
const sortedColumns = [...groups.keys()].sort((a, b) => a - b)
|
||||||
const yGap = 74;
|
const yGap = 74
|
||||||
const maxGroupHeight = Math.max(
|
const maxGroupHeight = Math.max(
|
||||||
1,
|
1,
|
||||||
...[...groups.values()].map(
|
...[...groups.values()].map(
|
||||||
|
|
@ -77,31 +62,29 @@ export function buildFitLayout(criteria: WorkflowCriteriaDto[]) {
|
||||||
items.reduce((sum, item) => sum + getNodeHeight(item), 0) +
|
items.reduce((sum, item) => sum + getNodeHeight(item), 0) +
|
||||||
Math.max(0, items.length - 1) * yGap,
|
Math.max(0, items.length - 1) * yGap,
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
const top = 72;
|
const top = 72
|
||||||
const left = 72;
|
const left = 72
|
||||||
const xGap = 128;
|
const xGap = 128
|
||||||
const positions = new Map<string, { x: number; y: number }>();
|
const positions = new Map<string, { x: number; y: number }>()
|
||||||
|
|
||||||
sortedColumns.forEach((column, columnIndex) => {
|
sortedColumns.forEach((column, columnIndex) => {
|
||||||
const items = (groups.get(column) || []).sort((a, b) =>
|
const items = (groups.get(column) || []).sort((a, b) => compareLayoutNodes(a, b, rankById))
|
||||||
compareLayoutNodes(a, b, rankById),
|
|
||||||
);
|
|
||||||
const groupHeight =
|
const groupHeight =
|
||||||
items.reduce((sum, item) => sum + getNodeHeight(item), 0) +
|
items.reduce((sum, item) => sum + getNodeHeight(item), 0) +
|
||||||
Math.max(0, items.length - 1) * yGap;
|
Math.max(0, items.length - 1) * yGap
|
||||||
let y = top + Math.max(0, (maxGroupHeight - groupHeight) / 2);
|
let y = top + Math.max(0, (maxGroupHeight - groupHeight) / 2)
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
positions.set(item.id, {
|
positions.set(item.id, {
|
||||||
x: left + columnIndex * (nodeSize.width + xGap),
|
x: left + columnIndex * (nodeSize.width + xGap),
|
||||||
y: Math.round(y),
|
y: Math.round(y),
|
||||||
});
|
})
|
||||||
y += getNodeHeight(item) + yGap;
|
y += getNodeHeight(item) + yGap
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
return positions;
|
return positions
|
||||||
}
|
}
|
||||||
|
|
||||||
function fitColumn(item: WorkflowCriteriaDto) {
|
function fitColumn(item: WorkflowCriteriaDto) {
|
||||||
|
|
@ -111,9 +94,9 @@ function fitColumn(item: WorkflowCriteriaDto) {
|
||||||
Approval: 2,
|
Approval: 2,
|
||||||
Inform: 3,
|
Inform: 3,
|
||||||
End: 4,
|
End: 4,
|
||||||
};
|
}
|
||||||
|
|
||||||
return priority[item.kind] ?? 2;
|
return priority[item.kind] ?? 2
|
||||||
}
|
}
|
||||||
|
|
||||||
function compareLayoutNodes(
|
function compareLayoutNodes(
|
||||||
|
|
@ -123,201 +106,181 @@ function compareLayoutNodes(
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
(rankById.get(a.id) ?? 999) - (rankById.get(b.id) ?? 999) ||
|
(rankById.get(a.id) ?? 999) - (rankById.get(b.id) ?? 999) ||
|
||||||
a.title.localeCompare(b.title, "tr")
|
a.title.localeCompare(b.title, 'tr')
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTraversalRanks(
|
function buildTraversalRanks(criteria: WorkflowCriteriaDto[], links: WorkflowLink[]) {
|
||||||
criteria: WorkflowCriteriaDto[],
|
const rankById = new Map<string, number>()
|
||||||
links: WorkflowLink[],
|
const outgoing = new Map<string, string[]>(criteria.map((item) => [item.id, []]))
|
||||||
) {
|
|
||||||
const rankById = new Map<string, number>();
|
|
||||||
const outgoing = new Map<string, string[]>(
|
|
||||||
criteria.map((item) => [item.id, []]),
|
|
||||||
);
|
|
||||||
links.forEach((link) => {
|
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 roots = criteria.filter((item) => item.kind === 'Start')
|
||||||
const queue = roots.length
|
const queue = roots.length ? roots.map((item) => item.id) : criteria.map((item) => item.id)
|
||||||
? roots.map((item) => item.id)
|
|
||||||
: criteria.map((item) => item.id);
|
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const id = queue.shift();
|
const id = queue.shift()
|
||||||
if (!id) continue;
|
if (!id) continue
|
||||||
if (rankById.has(id)) continue;
|
if (rankById.has(id)) continue
|
||||||
|
|
||||||
rankById.set(id, rankById.size);
|
rankById.set(id, rankById.size)
|
||||||
(outgoing.get(id) || []).forEach((targetId) => {
|
;(outgoing.get(id) || []).forEach((targetId) => {
|
||||||
if (targetId && !rankById.has(targetId)) queue.push(targetId);
|
if (targetId && !rankById.has(targetId)) queue.push(targetId)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
criteria.forEach((item) => {
|
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[]) {
|
export function collectLinks(criteria: WorkflowCriteriaDto[]) {
|
||||||
const links: WorkflowLink[] = [];
|
const links: WorkflowLink[] = []
|
||||||
criteria.forEach((source) => {
|
criteria.forEach((source) => {
|
||||||
if (source.kind === "Compare" && source.compareOutcomes?.length) {
|
if (source.kind === 'Compare' && source.compareOutcomes?.length) {
|
||||||
source.compareOutcomes.forEach((outcome, index) => {
|
source.compareOutcomes.forEach((outcome, index) => {
|
||||||
addLink(
|
addLink(links, criteria, source, outcome.targetId, outcome.label, `compare-${index}`, {
|
||||||
links,
|
index,
|
||||||
criteria,
|
count: source.compareOutcomes.length,
|
||||||
source,
|
field: `compareOutcomes:${index}`,
|
||||||
outcome.targetId,
|
})
|
||||||
outcome.label,
|
})
|
||||||
`compare-${index}`,
|
return
|
||||||
{
|
|
||||||
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,
|
index: 0,
|
||||||
count: 1,
|
count: 1,
|
||||||
field: "nextOnStart",
|
field: 'nextOnStart',
|
||||||
});
|
})
|
||||||
addLink(links, criteria, source, source.nextOnTrue, "Doğru", "true", {
|
addLink(links, criteria, source, source.nextOnTrue, 'Doğru', 'true', {
|
||||||
index: 0,
|
index: 0,
|
||||||
count: 2,
|
count: 2,
|
||||||
field: "nextOnTrue",
|
field: 'nextOnTrue',
|
||||||
});
|
})
|
||||||
addLink(links, criteria, source, source.nextOnFalse, "Yanlış", "false", {
|
addLink(links, criteria, source, source.nextOnFalse, 'Yanlış', 'false', {
|
||||||
index: 1,
|
index: 1,
|
||||||
count: 2,
|
count: 2,
|
||||||
field: "nextOnFalse",
|
field: 'nextOnFalse',
|
||||||
});
|
})
|
||||||
addLink(links, criteria, source, source.nextOnApprove, "Onay", "approve", {
|
addLink(links, criteria, source, source.nextOnApprove, 'Onay', 'approve', {
|
||||||
index: 0,
|
index: 0,
|
||||||
count: 2,
|
count: 2,
|
||||||
field: "nextOnApprove",
|
field: 'nextOnApprove',
|
||||||
});
|
})
|
||||||
addLink(links, criteria, source, source.nextOnReject, "Red", "reject", {
|
addLink(links, criteria, source, source.nextOnReject, 'Red', 'reject', {
|
||||||
index: 1,
|
index: 1,
|
||||||
count: 2,
|
count: 2,
|
||||||
field: "nextOnReject",
|
field: 'nextOnReject',
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
return assignLinkSlots(links, criteria);
|
return assignLinkSlots(links, criteria)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assignLinkSlots(
|
export function assignLinkSlots(links: WorkflowLink[], criteria: WorkflowCriteriaDto[]) {
|
||||||
links: WorkflowLink[],
|
const endpointGroups = new Map<string, Endpoint[]>()
|
||||||
criteria: WorkflowCriteriaDto[],
|
|
||||||
) {
|
|
||||||
const endpointGroups = new Map<string, Endpoint[]>();
|
|
||||||
const addEndpoint = (nodeId: string, side: string, endpoint: Endpoint) => {
|
const addEndpoint = (nodeId: string, side: string, endpoint: Endpoint) => {
|
||||||
const key = `${nodeId}:${side}`;
|
const key = `${nodeId}:${side}`
|
||||||
if (!endpointGroups.has(key)) endpointGroups.set(key, []);
|
if (!endpointGroups.has(key)) endpointGroups.set(key, [])
|
||||||
endpointGroups.get(key)?.push(endpoint);
|
endpointGroups.get(key)?.push(endpoint)
|
||||||
};
|
}
|
||||||
|
|
||||||
links.forEach((link) => {
|
links.forEach((link) => {
|
||||||
addEndpoint(link.source.id, sideToward(link.source, link.target), {
|
addEndpoint(link.source.id, sideToward(link.source, link.target), {
|
||||||
link,
|
link,
|
||||||
role: "source",
|
role: 'source',
|
||||||
});
|
})
|
||||||
addEndpoint(link.target.id, sideToward(link.target, link.source), {
|
addEndpoint(link.target.id, sideToward(link.target, link.source), {
|
||||||
link,
|
link,
|
||||||
role: "target",
|
role: 'target',
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
endpointGroups.forEach((endpoints) => {
|
endpointGroups.forEach((endpoints) => {
|
||||||
endpoints.forEach((endpoint, index) => {
|
endpoints.forEach((endpoint, index) => {
|
||||||
if (endpoint.role === "source") {
|
if (endpoint.role === 'source') {
|
||||||
endpoint.link.sourcePort.sourceSlotIndex = index;
|
endpoint.link.sourcePort.sourceSlotIndex = index
|
||||||
endpoint.link.sourcePort.sourceSlotCount = endpoints.length;
|
endpoint.link.sourcePort.sourceSlotCount = endpoints.length
|
||||||
} else {
|
} else {
|
||||||
endpoint.link.sourcePort.targetSlotIndex = index;
|
endpoint.link.sourcePort.targetSlotIndex = index
|
||||||
endpoint.link.sourcePort.targetSlotCount = endpoints.length;
|
endpoint.link.sourcePort.targetSlotCount = endpoints.length
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
links.forEach((link) => {
|
links.forEach((link) => {
|
||||||
link.sourcePort.routeIndex = link.sourcePort.targetSlotIndex ?? 0;
|
link.sourcePort.routeIndex = link.sourcePort.targetSlotIndex ?? 0
|
||||||
link.sourcePort.routeCount = link.sourcePort.targetSlotCount ?? 1;
|
link.sourcePort.routeCount = link.sourcePort.targetSlotCount ?? 1
|
||||||
});
|
})
|
||||||
|
|
||||||
return links;
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
function sideToward(from: WorkflowCriteriaDto, to: WorkflowCriteriaDto) {
|
function sideToward(from: WorkflowCriteriaDto, to: WorkflowCriteriaDto) {
|
||||||
const fromLeft = Number(from.positionX || 0);
|
const fromLeft = Number(from.positionX || 0)
|
||||||
const fromTop = Number(from.positionY || 0);
|
const fromTop = Number(from.positionY || 0)
|
||||||
const fromCenter = {
|
const fromCenter = {
|
||||||
x: fromLeft + nodeSize.width / 2,
|
x: fromLeft + nodeSize.width / 2,
|
||||||
y: fromTop + getNodeHeight(from) / 2,
|
y: fromTop + getNodeHeight(from) / 2,
|
||||||
};
|
}
|
||||||
const toCenter = {
|
const toCenter = {
|
||||||
x: Number(to.positionX || 0) + nodeSize.width / 2,
|
x: Number(to.positionX || 0) + nodeSize.width / 2,
|
||||||
y: Number(to.positionY || 0) + getNodeHeight(to) / 2,
|
y: Number(to.positionY || 0) + getNodeHeight(to) / 2,
|
||||||
};
|
}
|
||||||
|
|
||||||
const dx = toCenter.x - fromCenter.x;
|
const dx = toCenter.x - fromCenter.x
|
||||||
const dy = toCenter.y - fromCenter.y;
|
const dy = toCenter.y - fromCenter.y
|
||||||
|
|
||||||
const horizontalDistance = Math.abs(dx) / (nodeSize.width / 2);
|
const horizontalDistance = Math.abs(dx) / (nodeSize.width / 2)
|
||||||
const verticalDistance = Math.abs(dy) / (getNodeHeight(from) / 2);
|
const verticalDistance = Math.abs(dy) / (getNodeHeight(from) / 2)
|
||||||
if (horizontalDistance >= verticalDistance) return dx >= 0 ? "right" : "left";
|
if (horizontalDistance >= verticalDistance) return dx >= 0 ? 'right' : 'left'
|
||||||
return dy >= 0 ? "bottom" : "top";
|
return dy >= 0 ? 'bottom' : 'top'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeOutcomes(item: WorkflowCriteriaDto): WorkflowOutcome[] {
|
export function getNodeOutcomes(item: WorkflowCriteriaDto): WorkflowOutcome[] {
|
||||||
if (item.kind === "Compare") {
|
if (item.kind === 'Compare') {
|
||||||
const outcomes = item.compareOutcomes?.length
|
const outcomes = item.compareOutcomes?.length
|
||||||
? item.compareOutcomes
|
? item.compareOutcomes
|
||||||
: [
|
: [
|
||||||
{ label: "Doğru", targetId: item.nextOnTrue },
|
{ label: 'Doğru', targetId: item.nextOnTrue },
|
||||||
{ label: "Yanlış", targetId: item.nextOnFalse },
|
{ label: 'Yanlış', targetId: item.nextOnFalse },
|
||||||
];
|
]
|
||||||
|
|
||||||
return outcomes.slice(0, 4).map((outcome, index) => ({
|
return outcomes.slice(0, 4).map((outcome, index) => ({
|
||||||
field: `compareOutcomes:${index}`,
|
field: `compareOutcomes:${index}`,
|
||||||
label: outcome.label || `Durum ${index + 1}`,
|
label: outcome.label || `Durum ${index + 1}`,
|
||||||
targetId: outcome.targetId,
|
targetId: outcome.targetId,
|
||||||
}));
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.kind === "Approval") {
|
if (item.kind === 'Approval') {
|
||||||
return [
|
return [
|
||||||
{ field: "nextOnApprove", label: "Onay", targetId: item.nextOnApprove },
|
{ field: 'nextOnApprove', label: 'Onay', targetId: item.nextOnApprove },
|
||||||
{ field: "nextOnReject", label: "Red", targetId: item.nextOnReject },
|
{ field: 'nextOnReject', label: 'Red', targetId: item.nextOnReject },
|
||||||
];
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.kind === "End") return [];
|
if (item.kind === 'End') return []
|
||||||
|
|
||||||
return [
|
return [{ field: 'nextOnStart', label: 'Sonraki', targetId: item.nextOnStart }]
|
||||||
{ field: "nextOnStart", label: "Sonraki", targetId: item.nextOnStart },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function outcomeLabel(field?: string) {
|
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<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
nextOnStart: "Sonraki",
|
nextOnStart: 'Sonraki',
|
||||||
nextOnTrue: "Doğru",
|
nextOnTrue: 'Doğru',
|
||||||
nextOnFalse: "Yanlış",
|
nextOnFalse: 'Yanlış',
|
||||||
nextOnApprove: "Onay",
|
nextOnApprove: 'Onay',
|
||||||
nextOnReject: "Red",
|
nextOnReject: 'Red',
|
||||||
};
|
}
|
||||||
|
|
||||||
return field ? labels[field] : undefined;
|
return field ? labels[field] : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addLink(
|
export function addLink(
|
||||||
|
|
@ -329,8 +292,8 @@ export function addLink(
|
||||||
type: string,
|
type: string,
|
||||||
sourcePort: WorkflowLinkPort = {},
|
sourcePort: WorkflowLinkPort = {},
|
||||||
) {
|
) {
|
||||||
if (!targetId) return;
|
if (!targetId) return
|
||||||
const target = criteria.find((item) => item.id === targetId);
|
const target = criteria.find((item) => item.id === targetId)
|
||||||
if (target) {
|
if (target) {
|
||||||
links.push({
|
links.push({
|
||||||
key: `${source.id}-${target.id}-${type}`,
|
key: `${source.id}-${target.id}-${type}`,
|
||||||
|
|
@ -338,125 +301,119 @@ export function addLink(
|
||||||
target,
|
target,
|
||||||
label,
|
label,
|
||||||
sourcePort,
|
sourcePort,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyCriteria(
|
export function emptyCriteria(kind = 'Compare', listFormCode = ''): WorkflowCriteriaForm {
|
||||||
kind = "Compare",
|
|
||||||
workflowItemId = "",
|
|
||||||
): WorkflowCriteriaForm {
|
|
||||||
return {
|
return {
|
||||||
id: "",
|
id: '',
|
||||||
workflowItemId,
|
listFormCode,
|
||||||
kind,
|
kind,
|
||||||
title: defaultTitle(kind),
|
title: defaultTitle(kind),
|
||||||
column: "Tutar",
|
compareColumn: 'Tutar',
|
||||||
operator: ">",
|
compareOperator: '>',
|
||||||
compareValue: 5000,
|
compareValue: 5000,
|
||||||
approver: "",
|
approver: '',
|
||||||
informPerson: "",
|
nextOnStart: '',
|
||||||
nextOnStart: "",
|
nextOnTrue: '',
|
||||||
nextOnTrue: "",
|
nextOnFalse: '',
|
||||||
nextOnFalse: "",
|
nextOnApprove: '',
|
||||||
nextOnApprove: "",
|
nextOnReject: '',
|
||||||
nextOnReject: "",
|
|
||||||
compareOutcomes:
|
compareOutcomes:
|
||||||
kind === "Compare"
|
kind === 'Compare' ? [emptyCompareOutcome('Durum 1'), emptyCompareOutcome('Durum 2')] : [],
|
||||||
? [emptyCompareOutcome("Durum 1"), emptyCompareOutcome("Durum 2")]
|
|
||||||
: [],
|
|
||||||
positionX: 32,
|
positionX: 32,
|
||||||
positionY: 150,
|
positionY: 150,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm {
|
export function toCriteriaForm(item: WorkflowCriteriaDto): WorkflowCriteriaForm {
|
||||||
const sharedPerson = item.approver || item.informPerson || "";
|
const sharedPerson = item.approver || ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...emptyCriteria(item.kind),
|
...emptyCriteria(item.kind),
|
||||||
...item,
|
...item,
|
||||||
approver: sharedPerson,
|
approver: sharedPerson,
|
||||||
informPerson: sharedPerson,
|
nextOnStart: item.nextOnStart || '',
|
||||||
nextOnStart: item.nextOnStart || "",
|
nextOnTrue: item.nextOnTrue || '',
|
||||||
nextOnTrue: item.nextOnTrue || "",
|
nextOnFalse: item.nextOnFalse || '',
|
||||||
nextOnFalse: item.nextOnFalse || "",
|
nextOnApprove: item.nextOnApprove || '',
|
||||||
nextOnApprove: item.nextOnApprove || "",
|
nextOnReject: item.nextOnReject || '',
|
||||||
nextOnReject: item.nextOnReject || "",
|
|
||||||
compareOutcomes: item.compareOutcomes?.length
|
compareOutcomes: item.compareOutcomes?.length
|
||||||
? item.compareOutcomes.map(toCompareOutcomeForm)
|
? item.compareOutcomes.map(toCompareOutcomeForm)
|
||||||
: emptyCriteria(item.kind).compareOutcomes,
|
: emptyCriteria(item.kind).compareOutcomes,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
|
export function normalizeCriteria(item: WorkflowCriteriaForm): SaveCriteriaInput {
|
||||||
const sharedPerson = item.approver || item.informPerson || "";
|
const sharedPerson = item.approver || ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
id: item.id || null,
|
id: item.id || null,
|
||||||
workflowItemId: item.workflowItemId || "",
|
listFormCode: item.listFormCode || '',
|
||||||
compareValue: Number(item.compareValue || 0),
|
compareValue: Number(item.compareValue || 0),
|
||||||
approver: sharedPerson,
|
approver: sharedPerson,
|
||||||
informPerson: sharedPerson,
|
|
||||||
positionX: Number(item.positionX || 32),
|
positionX: Number(item.positionX || 32),
|
||||||
positionY: Number(item.positionY || 150),
|
positionY: Number(item.positionY || 150),
|
||||||
compareOutcomes: (item.compareOutcomes || [])
|
compareOutcomes: (item.compareOutcomes || [])
|
||||||
.slice(0, 4)
|
.slice(0, 4)
|
||||||
.filter((outcome) => outcome.label?.trim())
|
.filter((outcome) => outcome.label?.trim())
|
||||||
.map(normalizeCompareOutcome),
|
.map(normalizeCompareOutcome),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultTitle(kind: string) {
|
export function defaultTitle(kind: string) {
|
||||||
return {
|
return (
|
||||||
Start: "İş Akışı Başlat",
|
{
|
||||||
Compare: "Tutar > 5000 TL",
|
Start: 'İş Akışı Başlat',
|
||||||
Approval: "Onaylanacak Kişi",
|
Compare: 'Tutar > 5000 TL',
|
||||||
Inform: "Bilgilendirme Yapılacak Personel",
|
Approval: 'Onaylanacak Kişi',
|
||||||
End: "Akışı Bitir",
|
Inform: 'Bilgilendirme Yapılacak Personel',
|
||||||
}[kind] ?? "İş Akışı Adımı";
|
End: 'Akışı Bitir',
|
||||||
|
}[kind] ?? 'İş Akışı Adımı'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyCompareOutcome(label = "Durum"): CompareOutcomeDto {
|
export function emptyCompareOutcome(label = 'Durum'): CompareOutcomeDto {
|
||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
targetId: "",
|
targetId: '',
|
||||||
conditions: [{ column: "Tutar", operator: ">", compareValue: 5000 }],
|
conditions: [{ compareColumn: 'Tutar', compareOperator: '>', compareValue: 5000 }],
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toCompareOutcomeForm(
|
export function toCompareOutcomeForm(
|
||||||
outcome: Partial<CompareOutcomeDto> &
|
outcome: Partial<CompareOutcomeDto> &
|
||||||
Partial<WorkflowConditionDto> & {
|
Partial<WorkflowConditionDto> & {
|
||||||
conditions?: Partial<WorkflowConditionDto>[];
|
conditions?: Partial<WorkflowConditionDto>[]
|
||||||
},
|
},
|
||||||
): CompareOutcomeDto {
|
): CompareOutcomeDto {
|
||||||
const conditions = outcome.conditions?.length
|
const conditions = outcome.conditions?.length
|
||||||
? outcome.conditions
|
? outcome.conditions
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
column: outcome.column || "Tutar",
|
compareColumn: outcome.compareColumn || 'Tutar',
|
||||||
operator: outcome.operator || ">",
|
compareOperator: outcome.compareOperator || '>',
|
||||||
compareValue: outcome.compareValue || 0,
|
compareValue: outcome.compareValue || 0,
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: outcome.label || "",
|
label: outcome.label || '',
|
||||||
targetId: outcome.targetId || "",
|
targetId: outcome.targetId || '',
|
||||||
conditions: conditions.map((condition) => ({
|
conditions: conditions.map((condition) => ({
|
||||||
column: condition.column || "Tutar",
|
compareColumn: condition.compareColumn || 'Tutar',
|
||||||
operator: condition.operator || ">",
|
compareOperator: condition.compareOperator || '>',
|
||||||
compareValue: condition.compareValue ?? 0,
|
compareValue: condition.compareValue ?? 0,
|
||||||
})),
|
})),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeCompareOutcome(
|
export function normalizeCompareOutcome(
|
||||||
outcome: Partial<CompareOutcomeDto> &
|
outcome: Partial<CompareOutcomeDto> &
|
||||||
Partial<WorkflowConditionDto> & {
|
Partial<WorkflowConditionDto> & {
|
||||||
conditions?: Partial<WorkflowConditionDto>[];
|
conditions?: Partial<WorkflowConditionDto>[]
|
||||||
},
|
},
|
||||||
): CompareOutcomeDto {
|
): CompareOutcomeDto {
|
||||||
const conditions = (
|
const conditions = (
|
||||||
|
|
@ -464,27 +421,24 @@ export function normalizeCompareOutcome(
|
||||||
? outcome.conditions
|
? outcome.conditions
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
column: outcome.column || "Tutar",
|
compareColumn: outcome.compareColumn || 'Tutar',
|
||||||
operator: outcome.operator || ">",
|
compareOperator: outcome.compareOperator || '>',
|
||||||
compareValue: outcome.compareValue || 0,
|
compareValue: outcome.compareValue || 0,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.filter(
|
.filter((condition) => condition.compareOperator && String(condition.compareValue ?? '') !== '')
|
||||||
(condition) =>
|
|
||||||
condition.operator && String(condition.compareValue ?? "") !== "",
|
|
||||||
)
|
|
||||||
.map((condition) => ({
|
.map((condition) => ({
|
||||||
column: condition.column || "Tutar",
|
compareColumn: condition.compareColumn || 'Tutar',
|
||||||
operator: condition.operator || ">",
|
compareOperator: condition.compareOperator || '>',
|
||||||
compareValue: Number(condition.compareValue || 0),
|
compareValue: Number(condition.compareValue || 0),
|
||||||
}));
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: (outcome.label || "").trim(),
|
label: (outcome.label || '').trim(),
|
||||||
targetId: outcome.targetId || null,
|
targetId: outcome.targetId || null,
|
||||||
conditions,
|
conditions,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compareOutcomeRuleText(
|
export function compareOutcomeRuleText(
|
||||||
|
|
@ -492,67 +446,61 @@ export function compareOutcomeRuleText(
|
||||||
) {
|
) {
|
||||||
const conditions = outcome.conditions?.length
|
const conditions = outcome.conditions?.length
|
||||||
? outcome.conditions
|
? outcome.conditions
|
||||||
: outcome.operator
|
: outcome.compareOperator
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
column: outcome.column || "Tutar",
|
compareColumn: outcome.compareColumn,
|
||||||
operator: outcome.operator,
|
compareOperator: outcome.compareOperator,
|
||||||
compareValue: outcome.compareValue,
|
compareValue: outcome.compareValue,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: []
|
||||||
|
|
||||||
return conditions.length
|
return conditions.length
|
||||||
? conditions
|
? conditions
|
||||||
.map(
|
.map(
|
||||||
(condition) =>
|
(condition) =>
|
||||||
`${condition.column} ${condition.operator} ${formatCompactValue(condition.compareValue)}`,
|
`${condition.compareColumn} ${condition.compareOperator} ${formatCompactValue(condition.compareValue)}`,
|
||||||
)
|
)
|
||||||
.join(" ve ")
|
.join(' ve ')
|
||||||
: "Kural yok";
|
: 'Kural yok'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatCompactValue(value: number | string | null | undefined) {
|
export function formatCompactValue(value: number | string | null | undefined) {
|
||||||
return new Intl.NumberFormat("tr-TR", {
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
}).format(Number(value || 0));
|
}).format(Number(value || 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function criteriaSummary(item: WorkflowCriteriaDto) {
|
export function criteriaSummary(item: WorkflowCriteriaDto) {
|
||||||
if (item.kind === "Compare") {
|
if (item.kind === 'Compare') {
|
||||||
return (
|
return (
|
||||||
(item.compareOutcomes || [])
|
(item.compareOutcomes || [])
|
||||||
.map(
|
.map((outcome) => `${outcome.label}: ${compareOutcomeRuleText(outcome)}`)
|
||||||
(outcome) => `${outcome.label}: ${compareOutcomeRuleText(outcome)}`,
|
.join(' / ') || '-'
|
||||||
)
|
)
|
||||||
.join(" / ") || "-"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (item.kind === "Approval")
|
if (item.kind === 'Approval') return item.approver || '-'
|
||||||
return item.approver || item.informPerson || "-";
|
if (item.kind === 'Inform') return item.approver || '-'
|
||||||
if (item.kind === "Inform") return item.approver || item.informPerson || "-";
|
return item.title
|
||||||
return item.title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function targetTitle(
|
export function targetTitle(criteria: WorkflowCriteriaDto[], id?: string | null) {
|
||||||
criteria: WorkflowCriteriaDto[],
|
if (!id) return '-'
|
||||||
id?: string | null,
|
const item = criteria.find((candidate) => candidate.id === id)
|
||||||
) {
|
return item ? `${item.id} - ${item.title}` : id
|
||||||
if (!id) return "-";
|
|
||||||
const item = criteria.find((candidate) => candidate.id === id);
|
|
||||||
return item ? `${item.id} - ${item.title}` : id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function statusClass(status?: string) {
|
export function statusClass(status?: string) {
|
||||||
if (status === "Onay Bekliyor") return "pending";
|
if (status === 'Onay Bekliyor') return 'pending'
|
||||||
if (status === "Bitti") return "done";
|
if (status === 'Bitti') return 'done'
|
||||||
if (status === "Bilgilendirildi") return "info";
|
if (status === 'Bilgilendirildi') return 'info'
|
||||||
return "";
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMoney(value?: number | string | null) {
|
export function formatMoney(value?: number | string | null) {
|
||||||
return new Intl.NumberFormat("tr-TR", {
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
style: "currency",
|
style: 'currency',
|
||||||
currency: "TRY",
|
currency: 'TRY',
|
||||||
}).format(Number(value || 0));
|
}).format(Number(value || 0))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,464 +1,299 @@
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import type { FormEvent } from "react";
|
import type { FormEvent } from 'react'
|
||||||
import dayjs from "dayjs";
|
|
||||||
import "dayjs/locale/tr";
|
|
||||||
import {
|
import {
|
||||||
buildFitLayout,
|
buildFitLayout,
|
||||||
emptyCriteria,
|
emptyCriteria,
|
||||||
isPendingApproval,
|
|
||||||
normalizeCriteria,
|
normalizeCriteria,
|
||||||
toCriteriaForm,
|
toCriteriaForm,
|
||||||
type WorkflowCriteriaForm,
|
type WorkflowCriteriaForm,
|
||||||
} from "@/utils/workflow/workflowHelpers";
|
} from '@/utils/workflow/workflowHelpers'
|
||||||
import {
|
import { workflowService, type WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
workflowService,
|
import { DashboardShell } from '../workflow/DashboardShell'
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PendingLink = {
|
type PendingLink = {
|
||||||
sourceId: string;
|
sourceId: string
|
||||||
outcome: string;
|
outcome: string
|
||||||
} | null;
|
} | null
|
||||||
|
|
||||||
type DragPreview = {
|
type DragPreview = {
|
||||||
id: string;
|
id: string
|
||||||
delta: { x: number; y: number };
|
delta: { x: number; y: number }
|
||||||
} | null;
|
} | null
|
||||||
|
|
||||||
type DragEndEvent = {
|
type DragEndEvent = {
|
||||||
active: { id: string };
|
active: { id: string }
|
||||||
delta: { x: number; y: number };
|
delta: { x: number; y: number }
|
||||||
};
|
}
|
||||||
|
|
||||||
export function FormTabWorkflow(props: { listFormCode: string }) {
|
export function FormTabWorkflow({ listFormCode }: { listFormCode: string }) {
|
||||||
const [workflowItems, setWorkflowItems] = useState<WorkflowItemDto[]>([]);
|
const [criteria, setCriteria] = useState<WorkflowCriteriaDto[]>([])
|
||||||
const [criteria, setCriteria] = useState<WorkflowCriteriaDto[]>([]);
|
const [selectedId, setSelectedId] = useState('')
|
||||||
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | null>(
|
const [pendingLink, setPendingLink] = useState<PendingLink>(null)
|
||||||
null,
|
|
||||||
);
|
|
||||||
const [selectedId, setSelectedId] = useState("");
|
|
||||||
const [pendingLink, setPendingLink] = useState<PendingLink>(null);
|
|
||||||
const [workflowForm, setWorkflowForm] = useState<WorkflowForm>({
|
|
||||||
sorumlu: "",
|
|
||||||
amount: 7200,
|
|
||||||
});
|
|
||||||
const [editingWorkflowId, setEditingWorkflowId] = useState<string | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
const [workflowEditForm, setWorkflowEditForm] = useState<WorkflowEditForm>({
|
|
||||||
sorumlu: "",
|
|
||||||
tarih: "",
|
|
||||||
amount: 0,
|
|
||||||
});
|
|
||||||
const [criteriaForm, setCriteriaForm] = useState<WorkflowCriteriaForm>(
|
const [criteriaForm, setCriteriaForm] = useState<WorkflowCriteriaForm>(
|
||||||
emptyCriteria(),
|
emptyCriteria('Start', listFormCode),
|
||||||
);
|
)
|
||||||
const [dragPreview, setDragPreview] = useState<DragPreview>(null);
|
const [dragPreview, setDragPreview] = useState<DragPreview>(null)
|
||||||
const [canvasZoom, setCanvasZoom] = useState(1);
|
const [canvasZoom, setCanvasZoom] = useState(1)
|
||||||
const [designerTab, setDesignerTab] = useState("flow");
|
const [designerTab, setDesignerTab] = useState('flow')
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false)
|
||||||
const [approvalDialogWorkflowId, setApprovalDialogWorkflowId] =
|
const canvasRef = useRef<HTMLDivElement | null>(null)
|
||||||
useState<string | null>(null);
|
|
||||||
const canvasRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
const currentCriteria = useMemo(
|
const currentCriteria = useMemo(() => criteria, [criteria])
|
||||||
() => criteria.filter((item) => item.workflowItemId === selectedWorkflowId),
|
|
||||||
[criteria, selectedWorkflowId],
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedWorkflow = useMemo(
|
|
||||||
() => workflowItems.find((item) => item.id === selectedWorkflowId),
|
|
||||||
[selectedWorkflowId, workflowItems],
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedCriteria = useMemo(
|
const selectedCriteria = useMemo(
|
||||||
() => currentCriteria.find((item) => item.id === selectedId) ?? null,
|
() => currentCriteria.find((item) => item.id === selectedId) ?? null,
|
||||||
[currentCriteria, selectedId],
|
[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 loadState = useCallback(async () => {
|
||||||
const data = await workflowService.getState();
|
const data = await workflowService.getState(listFormCode)
|
||||||
setWorkflowItems(data.workflowItems);
|
setCriteria(data.criteria)
|
||||||
setCriteria(data.criteria);
|
return data
|
||||||
setSelectedWorkflowId(
|
}, [listFormCode])
|
||||||
(current) => current || data.workflowItems[0]?.id || null,
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const runAction = useCallback(
|
const runAction = useCallback(
|
||||||
async (action: () => Promise<unknown>) => {
|
async (action: () => Promise<unknown>) => {
|
||||||
setBusy(true);
|
setBusy(true)
|
||||||
try {
|
try {
|
||||||
await action();
|
await action()
|
||||||
await loadState();
|
await loadState()
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[loadState],
|
[loadState],
|
||||||
);
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadState();
|
loadState()
|
||||||
}, [loadState]);
|
}, [loadState])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedCriteria) {
|
if (selectedCriteria) {
|
||||||
setCriteriaForm(toCriteriaForm(selectedCriteria));
|
setCriteriaForm(toCriteriaForm(selectedCriteria))
|
||||||
} else if (selectedWorkflowId) {
|
} else {
|
||||||
setCriteriaForm(emptyCriteria("Start", selectedWorkflowId));
|
setCriteriaForm(emptyCriteria('Start', listFormCode))
|
||||||
}
|
}
|
||||||
}, [selectedCriteria, selectedWorkflowId]);
|
}, [listFormCode, selectedCriteria])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedWorkflowId || !selectedId) return;
|
if (!selectedId) return
|
||||||
|
|
||||||
const selectedStillBelongs = currentCriteria.some(
|
const selectedStillExists = currentCriteria.some((item) => item.id === selectedId)
|
||||||
(item) => item.id === selectedId,
|
if (!selectedStillExists) {
|
||||||
);
|
setSelectedId('')
|
||||||
if (!selectedStillBelongs) {
|
|
||||||
setSelectedId("");
|
|
||||||
}
|
}
|
||||||
}, [currentCriteria, selectedId, selectedWorkflowId]);
|
}, [currentCriteria, selectedId])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!approvalDialogWorkflowId) return;
|
|
||||||
|
|
||||||
const stillPending = pendingItems.some(
|
|
||||||
(item) => item.id === approvalDialogWorkflowId,
|
|
||||||
);
|
|
||||||
if (!stillPending) {
|
|
||||||
setApprovalDialogWorkflowId(null);
|
|
||||||
}
|
|
||||||
}, [approvalDialogWorkflowId, pendingItems]);
|
|
||||||
|
|
||||||
const createWorkflow = (event: FormEvent<HTMLFormElement>) => {
|
|
||||||
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],
|
|
||||||
);
|
|
||||||
|
|
||||||
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
|
const saveCriteria = (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria(normalizeCriteria(criteriaForm));
|
await workflowService.saveCriteria({
|
||||||
setSelectedId("");
|
...normalizeCriteria(criteriaForm),
|
||||||
});
|
listFormCode,
|
||||||
};
|
})
|
||||||
|
setSelectedId('')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const addCriteria = (kind: string) => {
|
const addCriteria = (kind: string) => {
|
||||||
if (!selectedWorkflowId) return;
|
setDesignerTab('flow')
|
||||||
|
|
||||||
setDesignerTab("flow");
|
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
const saved = await workflowService.saveCriteria({
|
const saved = await workflowService.saveCriteria({
|
||||||
...normalizeCriteria(emptyCriteria(kind, selectedWorkflowId)),
|
...normalizeCriteria(emptyCriteria(kind, listFormCode)),
|
||||||
|
listFormCode,
|
||||||
positionX: 80 + (currentCriteria.length % 5) * 230,
|
positionX: 80 + (currentCriteria.length % 5) * 230,
|
||||||
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
positionY: 220 + Math.floor(currentCriteria.length / 5) * 140,
|
||||||
});
|
})
|
||||||
setSelectedId(saved.id);
|
setSelectedId(saved.id)
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const deleteSelectedCriteria = useCallback(
|
const deleteSelectedCriteria = useCallback(
|
||||||
(criteriaId: string = selectedId) => {
|
(criteriaId: string = selectedId) => {
|
||||||
if (!criteriaId || busy) return;
|
if (!criteriaId || busy) return
|
||||||
|
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.deleteCriteria(criteriaId);
|
await workflowService.deleteCriteria(criteriaId)
|
||||||
setSelectedId("");
|
setSelectedId('')
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
[busy, runAction, selectedId],
|
[busy, runAction, selectedId],
|
||||||
);
|
)
|
||||||
|
|
||||||
const disconnectLink = useCallback(
|
const disconnectLink = useCallback(
|
||||||
(sourceId: string, outcome: string) => {
|
(sourceId: string, outcome: string) => {
|
||||||
if (!sourceId || !outcome || busy) return;
|
if (!sourceId || !outcome || busy) return
|
||||||
|
|
||||||
const source = currentCriteria.find((item) => item.id === sourceId);
|
const source = currentCriteria.find((item) => item.id === sourceId)
|
||||||
if (!source) return;
|
if (!source) return
|
||||||
|
|
||||||
const next: WorkflowCriteriaForm = toCriteriaForm(source);
|
const next: WorkflowCriteriaForm = toCriteriaForm(source)
|
||||||
if (outcome.startsWith("compareOutcomes:")) {
|
if (outcome.startsWith('compareOutcomes:')) {
|
||||||
const outcomeIndex = Number(outcome.split(":")[1]);
|
const outcomeIndex = Number(outcome.split(':')[1])
|
||||||
next.compareOutcomes = [...(source.compareOutcomes || [])];
|
next.compareOutcomes = [...(source.compareOutcomes || [])]
|
||||||
if (next.compareOutcomes?.[outcomeIndex]) {
|
if (next.compareOutcomes?.[outcomeIndex]) {
|
||||||
next.compareOutcomes[outcomeIndex] = {
|
next.compareOutcomes[outcomeIndex] = {
|
||||||
...next.compareOutcomes[outcomeIndex],
|
...next.compareOutcomes[outcomeIndex],
|
||||||
targetId: null,
|
targetId: null,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
if (outcomeIndex === 0) next.nextOnTrue = null;
|
if (outcomeIndex === 0) next.nextOnTrue = null
|
||||||
if (outcomeIndex === 1) next.nextOnFalse = null;
|
if (outcomeIndex === 1) next.nextOnFalse = null
|
||||||
} else {
|
} else {
|
||||||
(next as Record<string, unknown>)[outcome] = null;
|
;(next as Record<string, unknown>)[outcome] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria(normalizeCriteria(next));
|
await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode })
|
||||||
setPendingLink(null);
|
setPendingLink(null)
|
||||||
setSelectedId(sourceId);
|
setSelectedId(sourceId)
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
[busy, currentCriteria, runAction],
|
[busy, currentCriteria, listFormCode, runAction],
|
||||||
);
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const deleteWithKeyboard = (event: globalThis.KeyboardEvent) => {
|
const deleteWithKeyboard = (event: globalThis.KeyboardEvent) => {
|
||||||
const activeTag = document.activeElement?.tagName?.toLowerCase();
|
const activeTag = document.activeElement?.tagName?.toLowerCase()
|
||||||
const isEditing =
|
const isEditing =
|
||||||
Boolean(activeTag && ["input", "textarea", "select"].includes(activeTag)) ||
|
Boolean(activeTag && ['input', 'textarea', 'select'].includes(activeTag)) ||
|
||||||
(document.activeElement instanceof HTMLElement &&
|
(document.activeElement instanceof HTMLElement && document.activeElement.isContentEditable)
|
||||||
document.activeElement.isContentEditable);
|
|
||||||
|
|
||||||
if (event.key !== "Delete" || isEditing) return;
|
if (event.key !== 'Delete' || isEditing) return
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault()
|
||||||
if (pendingLink) {
|
if (pendingLink) {
|
||||||
disconnectLink(pendingLink.sourceId, pendingLink.outcome);
|
disconnectLink(pendingLink.sourceId, pendingLink.outcome)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
deleteSelectedCriteria();
|
deleteSelectedCriteria()
|
||||||
};
|
}
|
||||||
|
|
||||||
window.addEventListener("keydown", deleteWithKeyboard);
|
window.addEventListener('keydown', deleteWithKeyboard)
|
||||||
return () => window.removeEventListener("keydown", deleteWithKeyboard);
|
return () => window.removeEventListener('keydown', deleteWithKeyboard)
|
||||||
}, [deleteSelectedCriteria, disconnectLink, pendingLink]);
|
}, [deleteSelectedCriteria, disconnectLink, pendingLink])
|
||||||
|
|
||||||
const updateNodePosition = ({ active, delta }: DragEndEvent) => {
|
const updateNodePosition = ({ active, delta }: DragEndEvent) => {
|
||||||
setDragPreview(null);
|
setDragPreview(null)
|
||||||
|
|
||||||
const item = currentCriteria.find(
|
const item = currentCriteria.find((candidate) => candidate.id === active.id)
|
||||||
(candidate) => candidate.id === active.id,
|
if (!item) return
|
||||||
);
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
setSelectedId(item.id);
|
setSelectedId(item.id)
|
||||||
if (delta.x === 0 && delta.y === 0) return;
|
if (delta.x === 0 && delta.y === 0) return
|
||||||
|
|
||||||
const next = {
|
const next = {
|
||||||
...item,
|
...item,
|
||||||
positionX: Math.max(12, Math.round(item.positionX + delta.x)),
|
positionX: Math.max(12, Math.round(item.positionX + delta.x)),
|
||||||
positionY: Math.max(12, Math.round(item.positionY + delta.y)),
|
positionY: Math.max(12, Math.round(item.positionY + delta.y)),
|
||||||
};
|
}
|
||||||
|
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria(next);
|
await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode })
|
||||||
setSelectedId(next.id);
|
setSelectedId(next.id)
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const connectNodes = (sourceId: string, outcome: string, targetId: string) => {
|
const connectNodes = (sourceId: string, outcome: string, targetId: string) => {
|
||||||
const source = currentCriteria.find((item) => item.id === sourceId);
|
const source = currentCriteria.find((item) => item.id === sourceId)
|
||||||
if (!source || source.id === targetId) return;
|
if (!source || source.id === targetId) return
|
||||||
|
|
||||||
const next: WorkflowCriteriaForm = toCriteriaForm(source);
|
const next: WorkflowCriteriaForm = toCriteriaForm(source)
|
||||||
if (outcome.startsWith("compareOutcomes:")) {
|
if (outcome.startsWith('compareOutcomes:')) {
|
||||||
const outcomeIndex = Number(outcome.split(":")[1]);
|
const outcomeIndex = Number(outcome.split(':')[1])
|
||||||
next.compareOutcomes = [...(source.compareOutcomes || [])];
|
next.compareOutcomes = [...(source.compareOutcomes || [])]
|
||||||
next.compareOutcomes[outcomeIndex] = {
|
next.compareOutcomes[outcomeIndex] = {
|
||||||
...next.compareOutcomes[outcomeIndex],
|
...next.compareOutcomes[outcomeIndex],
|
||||||
targetId,
|
targetId,
|
||||||
};
|
}
|
||||||
if (outcomeIndex === 0) next.nextOnTrue = targetId;
|
if (outcomeIndex === 0) next.nextOnTrue = targetId
|
||||||
if (outcomeIndex === 1) next.nextOnFalse = targetId;
|
if (outcomeIndex === 1) next.nextOnFalse = targetId
|
||||||
} else {
|
} else {
|
||||||
(next as Record<string, unknown>)[outcome] = targetId;
|
;(next as Record<string, unknown>)[outcome] = targetId
|
||||||
}
|
}
|
||||||
|
|
||||||
setPendingLink(null);
|
setPendingLink(null)
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
await workflowService.saveCriteria(normalizeCriteria(next));
|
await workflowService.saveCriteria({ ...normalizeCriteria(next), listFormCode })
|
||||||
setSelectedId("");
|
setSelectedId('')
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const fitFlowLayout = () => {
|
const fitFlowLayout = () => {
|
||||||
if (!currentCriteria.length || busy) return;
|
if (!currentCriteria.length || busy) return
|
||||||
|
|
||||||
const nextPositions = buildFitLayout(currentCriteria);
|
const nextPositions = buildFitLayout(currentCriteria)
|
||||||
setDesignerTab("flow");
|
setDesignerTab('flow')
|
||||||
setCanvasZoom(1);
|
setCanvasZoom(1)
|
||||||
|
|
||||||
runAction(async () => {
|
runAction(async () => {
|
||||||
for (const item of currentCriteria) {
|
for (const item of currentCriteria) {
|
||||||
const position = nextPositions.get(item.id);
|
const position = nextPositions.get(item.id)
|
||||||
if (!position) continue;
|
if (!position) continue
|
||||||
|
|
||||||
await workflowService.saveCriteria({
|
await workflowService.saveCriteria({
|
||||||
...normalizeCriteria(item),
|
...normalizeCriteria(item),
|
||||||
|
listFormCode,
|
||||||
positionX: position.x,
|
positionX: position.x,
|
||||||
positionY: position.y,
|
positionY: position.y,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
canvasRef.current?.scrollTo({ left: 0, top: 0, behavior: "smooth" });
|
canvasRef.current?.scrollTo({ left: 0, top: 0, behavior: 'smooth' })
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const selectWorkflow = (item: WorkflowItemDto) => {
|
|
||||||
setSelectedWorkflowId(item.id);
|
|
||||||
setPendingLink(null);
|
|
||||||
setSelectedId("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const openCriteriaDetails = (id: string) => {
|
const openCriteriaDetails = (id: string) => {
|
||||||
setSelectedId(id);
|
setSelectedId(id)
|
||||||
setPendingLink(null);
|
setPendingLink(null)
|
||||||
setDesignerTab("criteria");
|
setDesignerTab('criteria')
|
||||||
};
|
}
|
||||||
|
|
||||||
const clearCanvasSelection = () => {
|
const clearCanvasSelection = () => {
|
||||||
setPendingLink(null);
|
setPendingLink(null)
|
||||||
setSelectedId("");
|
setSelectedId('')
|
||||||
};
|
}
|
||||||
|
|
||||||
const beginLink = (sourceId: string, outcome: string) => {
|
const beginLink = (sourceId: string, outcome: string) => {
|
||||||
setPendingLink({ sourceId, outcome });
|
setPendingLink({ sourceId, outcome })
|
||||||
setSelectedId(sourceId);
|
setSelectedId(sourceId)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardShell
|
<DashboardShell
|
||||||
busy={busy}
|
busy={busy}
|
||||||
canvasRef={canvasRef}
|
canvasRef={canvasRef}
|
||||||
canvasZoom={canvasZoom}
|
canvasZoom={canvasZoom}
|
||||||
criteria={criteria}
|
|
||||||
criteriaForm={criteriaForm}
|
criteriaForm={criteriaForm}
|
||||||
currentCriteria={currentCriteria}
|
currentCriteria={currentCriteria}
|
||||||
designerTab={designerTab}
|
designerTab={designerTab}
|
||||||
dialogPendingItems={dialogPendingItems}
|
|
||||||
dragPreview={dragPreview}
|
dragPreview={dragPreview}
|
||||||
editingWorkflowId={editingWorkflowId}
|
|
||||||
pendingLink={pendingLink}
|
pendingLink={pendingLink}
|
||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
selectedWorkflow={selectedWorkflow}
|
|
||||||
selectedWorkflowId={selectedWorkflowId}
|
|
||||||
showApprovalDialog={Boolean(approvalDialogWorkflowId)}
|
|
||||||
workflowEditForm={workflowEditForm}
|
|
||||||
workflowForm={workflowForm}
|
|
||||||
workflowItems={workflowItems}
|
|
||||||
onAddCriteria={addCriteria}
|
onAddCriteria={addCriteria}
|
||||||
onBeginLink={beginLink}
|
onBeginLink={beginLink}
|
||||||
onBeginWorkflowEdit={beginWorkflowEdit}
|
|
||||||
onCancelWorkflowEdit={cancelWorkflowEdit}
|
|
||||||
onChangeCriteriaForm={setCriteriaForm}
|
onChangeCriteriaForm={setCriteriaForm}
|
||||||
onClearCanvasSelection={clearCanvasSelection}
|
onClearCanvasSelection={clearCanvasSelection}
|
||||||
onCloseApprovalDialog={() => setApprovalDialogWorkflowId(null)}
|
|
||||||
onConnectNodes={connectNodes}
|
onConnectNodes={connectNodes}
|
||||||
onCreateWorkflow={createWorkflow}
|
|
||||||
onDecision={(id: string, approved: boolean, note: string) =>
|
|
||||||
runAction(() => workflowService.decideWorkflow(id, { approved, note }))
|
|
||||||
}
|
|
||||||
onDeleteSelectedCriteria={deleteSelectedCriteria}
|
onDeleteSelectedCriteria={deleteSelectedCriteria}
|
||||||
onDisconnectLink={disconnectLink}
|
onDisconnectLink={disconnectLink}
|
||||||
onDragMove={(event: DragEndEvent | null) =>
|
onDragMove={(event: DragEndEvent | null) =>
|
||||||
setDragPreview(
|
setDragPreview(event ? { id: event.active.id, delta: event.delta } : null)
|
||||||
event ? { id: event.active.id, delta: event.delta } : null,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
onFitFlowLayout={fitFlowLayout}
|
onFitFlowLayout={fitFlowLayout}
|
||||||
onOpenCriteriaDetails={openCriteriaDetails}
|
onOpenCriteriaDetails={openCriteriaDetails}
|
||||||
onResetDemo={() => runAction(workflowService.resetDemo)}
|
onResetDemo={() => runAction(() => workflowService.resetDemo(listFormCode))}
|
||||||
onSaveCriteria={saveCriteria}
|
onSaveCriteria={saveCriteria}
|
||||||
onSaveWorkflowEdit={saveWorkflowEdit}
|
|
||||||
onSelectCriteria={setSelectedId}
|
onSelectCriteria={setSelectedId}
|
||||||
onSelectWorkflow={selectWorkflow}
|
|
||||||
onSetDesignerTab={setDesignerTab}
|
onSetDesignerTab={setDesignerTab}
|
||||||
onStartWorkflow={startWorkflow}
|
|
||||||
onUpdateNodePosition={updateNodePosition}
|
onUpdateNodePosition={updateNodePosition}
|
||||||
onWorkflowEditFormChange={setWorkflowEditForm}
|
onZoomIn={() => setCanvasZoom((current) => Math.min(1.5, Number((current + 0.1).toFixed(2))))}
|
||||||
onWorkflowFormChange={setWorkflowForm}
|
|
||||||
onZoomIn={() =>
|
|
||||||
setCanvasZoom((current) =>
|
|
||||||
Math.min(1.5, Number((current + 0.1).toFixed(2))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onZoomOut={() =>
|
onZoomOut={() =>
|
||||||
setCanvasZoom((current) =>
|
setCanvasZoom((current) => Math.max(0.6, Number((current - 0.1).toFixed(2))))
|
||||||
Math.max(0.6, Number((current - 0.1).toFixed(2))),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 z-50 grid place-items-center bg-slate-900/40 p-[18px]"
|
|
||||||
role="presentation"
|
|
||||||
>
|
|
||||||
<section
|
|
||||||
className="max-h-[calc(100vh-36px)] w-[min(560px,100%)] overflow-auto rounded-lg border border-app-line bg-app-surface p-4 shadow-dialog"
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
aria-labelledby="approval-dialog-title"
|
|
||||||
>
|
|
||||||
<div className="mb-3 flex items-start justify-between gap-4 max-[720px]:flex-col max-[720px]:items-stretch">
|
|
||||||
<div>
|
|
||||||
<h2
|
|
||||||
id="approval-dialog-title"
|
|
||||||
className="m-0 text-lg tracking-normal"
|
|
||||||
>
|
|
||||||
Bekleyen Onaylar
|
|
||||||
</h2>
|
|
||||||
<p className="mb-0 mt-1 text-app-muted">
|
|
||||||
Workflow onay adiminda bekliyor.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="w-[38px] justify-center border-app-primary bg-white p-0 text-app-primary"
|
|
||||||
title="Kapat"
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<PendingApprovals
|
|
||||||
items={items}
|
|
||||||
criteria={criteria}
|
|
||||||
busy={busy}
|
|
||||||
showChrome={false}
|
|
||||||
onDecision={onDecision}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PendingApprovals({
|
|
||||||
items,
|
|
||||||
criteria,
|
|
||||||
busy,
|
|
||||||
onDecision,
|
|
||||||
showChrome = true,
|
|
||||||
}: Omit<ApprovalDialogProps, "onClose"> & { showChrome?: boolean }) {
|
|
||||||
const [notes, setNotes] = useState<Record<string, string>>({});
|
|
||||||
|
|
||||||
const content = (
|
|
||||||
<>
|
|
||||||
{showChrome && (
|
|
||||||
<div className="mb-3 flex items-center justify-between gap-4 max-[720px]:flex-col max-[720px]:items-stretch">
|
|
||||||
<h2 className="m-0 text-lg tracking-normal">Bekleyen Onaylar</h2>
|
|
||||||
<span className="text-sm text-app-muted">
|
|
||||||
{items.length} bekleyen
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="grid gap-2.5">
|
|
||||||
{items.length === 0 && (
|
|
||||||
<p className="m-0 text-app-muted">Bekleyen onay yok.</p>
|
|
||||||
)}
|
|
||||||
{items.map((item) => {
|
|
||||||
const activeStep = criteria.find(
|
|
||||||
(candidate) =>
|
|
||||||
candidate.workflowItemId === item.id &&
|
|
||||||
candidate.id === item.currentNodeId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<article
|
|
||||||
key={item.id}
|
|
||||||
className="grid gap-2.5 rounded-lg border border-app-line p-3"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<strong>
|
|
||||||
#{item.id} {item.sorumlu}
|
|
||||||
</strong>
|
|
||||||
{activeStep?.title && (
|
|
||||||
<span className="mt-1 block text-sm font-bold text-app-text">
|
|
||||||
{activeStep.title}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="mt-1 block text-app-muted">
|
|
||||||
{formatMoney(item.amount)} - Onaylayacak kişi:{" "}
|
|
||||||
{item.assignedApprover}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
rows={2}
|
|
||||||
placeholder="Onay/red notu"
|
|
||||||
value={notes[item.id] || ""}
|
|
||||||
onChange={(event) =>
|
|
||||||
setNotes({ ...notes, [item.id]: event.target.value })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="border-app-green bg-app-green text-white"
|
|
||||||
disabled={busy}
|
|
||||||
onClick={() =>
|
|
||||||
onDecision(item.id, true, notes[item.id] || "Onay verildi.")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CheckIcon />
|
|
||||||
Onayla
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="border-app-red bg-app-red text-white"
|
|
||||||
disabled={busy}
|
|
||||||
onClick={() =>
|
|
||||||
onDecision(item.id, false, notes[item.id] || "Red edildi.")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SlashIcon />
|
|
||||||
Reddet
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!showChrome) return content;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="min-w-0 rounded-lg border border-app-line bg-app-surface p-4">
|
|
||||||
{content}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +1,95 @@
|
||||||
import React from "react";
|
import React from 'react'
|
||||||
import { FiSave, FiTrash2 } from "react-icons/fi";
|
import { FiSave, FiTrash2 } from 'react-icons/fi'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import {
|
import {
|
||||||
columnOptions,
|
columnOptions,
|
||||||
kindIcon,
|
kindIcon,
|
||||||
kindOptions,
|
kindOptions,
|
||||||
operatorOptions,
|
operatorOptions,
|
||||||
} from "@/utils/workflow/workflowConstants";
|
} from '@/utils/workflow/workflowConstants'
|
||||||
import {
|
import {
|
||||||
compareOutcomeRuleText,
|
compareOutcomeRuleText,
|
||||||
criteriaSummary,
|
criteriaSummary,
|
||||||
emptyCompareOutcome,
|
emptyCompareOutcome,
|
||||||
targetTitle,
|
targetTitle,
|
||||||
} from "@/utils/workflow/workflowHelpers";
|
} from '@/utils/workflow/workflowHelpers'
|
||||||
import type {
|
import type { CompareOutcomeDto, WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
CompareOutcomeDto,
|
|
||||||
WorkflowCriteriaDto,
|
|
||||||
WorkflowItemDto,
|
|
||||||
} from "@/services/workflow.service";
|
|
||||||
|
|
||||||
const SaveIcon = FiSave as any;
|
const SaveIcon = FiSave as any
|
||||||
const TrashIcon = FiTrash2 as any;
|
const TrashIcon = FiTrash2 as any
|
||||||
|
|
||||||
|
const tableButtonClass =
|
||||||
|
'inline-flex min-h-8 items-center justify-center gap-1.5 rounded-md border px-2.5 py-1 text-[13px] font-medium leading-none transition-colors disabled:cursor-not-allowed disabled:opacity-50'
|
||||||
|
|
||||||
type CriteriaTableProps = {
|
type CriteriaTableProps = {
|
||||||
criteria: WorkflowCriteriaDto[];
|
criteria: WorkflowCriteriaDto[]
|
||||||
selectedWorkflow?: WorkflowItemDto | null;
|
selectedId: string
|
||||||
selectedId: string;
|
form: any
|
||||||
activeNodeId?: string;
|
busy: boolean
|
||||||
form: any;
|
onSelect: (id: string) => void
|
||||||
busy: boolean;
|
onChange: (form: any) => void
|
||||||
onSelect: (id: string) => void;
|
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||||
onChange: (form: any) => void;
|
onDelete: (id: string) => void
|
||||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
|
}
|
||||||
onDelete: (id: string) => void;
|
|
||||||
onAddCriteria?: (kind: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function CriteriaTable({
|
export function CriteriaTable({
|
||||||
criteria,
|
criteria,
|
||||||
selectedWorkflow,
|
|
||||||
selectedId,
|
selectedId,
|
||||||
activeNodeId,
|
|
||||||
form,
|
form,
|
||||||
busy,
|
busy,
|
||||||
onSelect,
|
onSelect,
|
||||||
onChange,
|
onChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onDelete,
|
onDelete,
|
||||||
onAddCriteria,
|
|
||||||
}: CriteriaTableProps) {
|
}: CriteriaTableProps) {
|
||||||
const setField = (name: string, value: unknown) =>
|
const setField = (name: string, value: unknown) => onChange({ ...form, [name]: value })
|
||||||
onChange({ ...form, [name]: value });
|
|
||||||
const targetOptions = [
|
const targetOptions = [
|
||||||
{ value: "", label: "Bağlantı yok" },
|
{ value: '', label: 'Bağlantı yok' },
|
||||||
...criteria
|
...criteria
|
||||||
.filter((item) => item.id !== form.id)
|
.filter((item) => item.id !== form.id)
|
||||||
.map((item) => ({ value: item.id, label: `${item.id} - ${item.title}` })),
|
.map((item) => ({ value: item.id, label: `${item.id} - ${item.title}` })),
|
||||||
];
|
]
|
||||||
const updateCompareOutcome = (index: number, patch: Partial<CompareOutcomeDto>) => {
|
const updateCompareOutcome = (index: number, patch: Partial<CompareOutcomeDto>) => {
|
||||||
const next = [...(form.compareOutcomes || [])];
|
const next = [...(form.compareOutcomes || [])]
|
||||||
next[index] = { ...next[index], ...patch };
|
next[index] = { ...next[index], ...patch }
|
||||||
setField("compareOutcomes", next);
|
setField('compareOutcomes', next)
|
||||||
};
|
}
|
||||||
const updateCompareCondition = (
|
const updateCompareCondition = (
|
||||||
outcomeIndex: number,
|
outcomeIndex: number,
|
||||||
conditionIndex: number,
|
conditionIndex: number,
|
||||||
patch: Record<string, unknown>,
|
patch: Record<string, unknown>,
|
||||||
) => {
|
) => {
|
||||||
const next = [...(form.compareOutcomes || [])];
|
const next = [...(form.compareOutcomes || [])]
|
||||||
const conditions = [...(next[outcomeIndex]?.conditions || [])];
|
const conditions = [...(next[outcomeIndex]?.conditions || [])]
|
||||||
conditions[conditionIndex] = { ...conditions[conditionIndex], ...patch };
|
conditions[conditionIndex] = { ...conditions[conditionIndex], ...patch }
|
||||||
next[outcomeIndex] = { ...next[outcomeIndex], conditions };
|
next[outcomeIndex] = { ...next[outcomeIndex], conditions }
|
||||||
setField("compareOutcomes", next);
|
setField('compareOutcomes', next)
|
||||||
};
|
}
|
||||||
const addCompareCondition = (outcomeIndex: number) => {
|
const addCompareCondition = (outcomeIndex: number) => {
|
||||||
const next = [...(form.compareOutcomes || [])];
|
const next = [...(form.compareOutcomes || [])]
|
||||||
next[outcomeIndex] = {
|
next[outcomeIndex] = {
|
||||||
...next[outcomeIndex],
|
...next[outcomeIndex],
|
||||||
conditions: [
|
conditions: [
|
||||||
...(next[outcomeIndex]?.conditions || []),
|
...(next[outcomeIndex]?.conditions || []),
|
||||||
{ column: "Tutar", operator: ">", compareValue: 0 },
|
{ compareColumn: 'Tutar', compareOperator: '>', compareValue: 0 },
|
||||||
],
|
],
|
||||||
};
|
}
|
||||||
setField("compareOutcomes", next);
|
setField('compareOutcomes', next)
|
||||||
};
|
}
|
||||||
const removeCompareCondition = (outcomeIndex: number, conditionIndex: number) => {
|
const removeCompareCondition = (outcomeIndex: number, conditionIndex: number) => {
|
||||||
const next = [...(form.compareOutcomes || [])];
|
const next = [...(form.compareOutcomes || [])]
|
||||||
const conditions = (next[outcomeIndex]?.conditions || []).filter(
|
const conditions = (next[outcomeIndex]?.conditions || []).filter(
|
||||||
(_: unknown, index: number) => index !== conditionIndex,
|
(_: unknown, index: number) => index !== conditionIndex,
|
||||||
);
|
)
|
||||||
next[outcomeIndex] = { ...next[outcomeIndex], conditions };
|
next[outcomeIndex] = { ...next[outcomeIndex], conditions }
|
||||||
setField("compareOutcomes", next);
|
setField('compareOutcomes', next)
|
||||||
};
|
}
|
||||||
const removeCompareOutcome = (index: number) => {
|
const removeCompareOutcome = (index: number) => {
|
||||||
setField(
|
setField(
|
||||||
"compareOutcomes",
|
'compareOutcomes',
|
||||||
(form.compareOutcomes || []).filter(
|
(form.compareOutcomes || []).filter((_: unknown, itemIndex: number) => itemIndex !== index),
|
||||||
(_: unknown, itemIndex: number) => itemIndex !== index,
|
)
|
||||||
),
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
const targetSelect = (
|
const targetSelect = (
|
||||||
value: string | null | undefined,
|
value: string | null | undefined,
|
||||||
onSelectTarget: (value: string) => void,
|
onSelectTarget: (value: string) => void,
|
||||||
|
|
@ -107,7 +97,7 @@ export function CriteriaTable({
|
||||||
) => (
|
) => (
|
||||||
<select
|
<select
|
||||||
required={required}
|
required={required}
|
||||||
value={value || ""}
|
value={value || ''}
|
||||||
onChange={(event) => onSelectTarget(event.target.value)}
|
onChange={(event) => onSelectTarget(event.target.value)}
|
||||||
>
|
>
|
||||||
{targetOptions.map((option) => (
|
{targetOptions.map((option) => (
|
||||||
|
|
@ -116,360 +106,304 @@ export function CriteriaTable({
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
);
|
)
|
||||||
const toggleRow = (id: string) => onSelect(id === selectedId ? "" : id);
|
const toggleRow = (id: string) => onSelect(id === selectedId ? '' : id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="min-w-0 rounded-lg">
|
<section className="min-w-0 rounded-lg">
|
||||||
<form className="block" onSubmit={onSubmit}>
|
<form className="block" onSubmit={onSubmit}>
|
||||||
<div className="overflow-auto rounded-md border border-app-line">
|
<div className="overflow-auto rounded-md border border-gray-200 text-sm">
|
||||||
<table className="[&_button]:text-[13px] [&_input]:text-[13px] [&_select]:text-[13px] [&_td]:text-[13px] [&_th]:text-[13px]">
|
<div className="min-w-[920px]">
|
||||||
<thead>
|
<div className="grid grid-cols-[minmax(110px,0.8fr)_120px_minmax(220px,1.4fr)_minmax(220px,1.2fr)_110px] bg-slate-50 font-semibold text-slate-700">
|
||||||
<tr>
|
<div className="px-4 py-3">Id</div>
|
||||||
<th>Id</th>
|
<div className="px-4 py-3">Tip</div>
|
||||||
<th>Tip</th>
|
<div className="px-4 py-3">Başlık / Kural</div>
|
||||||
<th>Başlık / Kural</th>
|
<div className="px-4 py-3">Bağlantılar</div>
|
||||||
<th>Bağlantılar</th>
|
<div className="px-4 py-3">İşlem</div>
|
||||||
<th>İşlem</th>
|
</div>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{criteria.map((item) => {
|
|
||||||
const isSelected = item.id === selectedId;
|
|
||||||
const isActive = item.id === activeNodeId;
|
|
||||||
const connectionSummary = criteriaConnectionSummary(
|
|
||||||
item,
|
|
||||||
criteria,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
{criteria.length === 0 && (
|
||||||
<React.Fragment key={item.id}>
|
<div className="border-t border-gray-100 px-4 py-6 text-center text-slate-500">
|
||||||
<tr
|
Seçili iş akışı için açıklama kaydı yok.
|
||||||
className={classNames({
|
</div>
|
||||||
"[&>td]:bg-[#eef5ff]": isSelected,
|
)}
|
||||||
"[&>td]:bg-[#f0fdf4] [&>td]:shadow-[inset_0_1px_0_#bbf7d0,inset_0_-1px_0_#bbf7d0]":
|
|
||||||
isActive,
|
{criteria.map((item) => {
|
||||||
"[&>td]:bg-[#e8f7ff]": isSelected && isActive,
|
const isSelected = item.id === selectedId
|
||||||
})}
|
const connectionSummary = criteriaConnectionSummary(item, criteria)
|
||||||
onClick={() => toggleRow(item.id)}
|
|
||||||
>
|
return (
|
||||||
<td>
|
<React.Fragment key={item.id}>
|
||||||
<strong>{item.id}</strong>
|
<div
|
||||||
{isActive && (
|
className={classNames(
|
||||||
<span className="ml-2 inline-flex rounded-full bg-[#dcfce7] px-2 py-0.5 text-[11px] font-bold text-[#166534]">
|
'grid cursor-pointer grid-cols-[minmax(110px,0.8fr)_120px_minmax(220px,1.4fr)_minmax(220px,1.2fr)_110px] border-t border-gray-100',
|
||||||
Aktif
|
{
|
||||||
</span>
|
'bg-blue-50': isSelected,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onClick={() => toggleRow(item.id)}
|
||||||
|
>
|
||||||
|
<div className="min-w-0 px-4 py-3">
|
||||||
|
<strong className="break-words">{item.id}</strong>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 py-3">
|
||||||
|
{kindOptions.find((option) => option.value === item.kind)?.label}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 break-words px-4 py-3">
|
||||||
|
{criteriaSummaryContent(item)}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 break-words px-4 py-3">{connectionSummary}</div>
|
||||||
|
<div className="px-4 py-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames(
|
||||||
|
tableButtonClass,
|
||||||
|
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
||||||
)}
|
)}
|
||||||
</td>
|
onClick={(event) => {
|
||||||
<td>
|
event.stopPropagation()
|
||||||
{
|
toggleRow(item.id)
|
||||||
kindOptions.find(
|
}}
|
||||||
(option) => option.value === item.kind,
|
>
|
||||||
)?.label
|
{isSelected ? 'Kapat' : 'Düzenle'}
|
||||||
}
|
</button>
|
||||||
</td>
|
</div>
|
||||||
<td>{criteriaSummaryContent(item)}</td>
|
</div>
|
||||||
<td>{connectionSummary}</td>
|
{isSelected && (
|
||||||
<td>
|
<div className="grid gap-3.5 border-t border-gray-100 bg-slate-50 p-3.5">
|
||||||
<button
|
<div className="grid grid-cols-3 gap-2.5 max-[720px]:grid-cols-1">
|
||||||
type="button"
|
<Field label="Tip" required>
|
||||||
className="ml-1.5 min-h-8 border-[#cfd6e2] bg-white px-2.5 text-[#344054]"
|
<select
|
||||||
onClick={(event) => {
|
value={form.kind}
|
||||||
event.stopPropagation();
|
onChange={(event) => setField('kind', event.target.value)}
|
||||||
toggleRow(item.id);
|
>
|
||||||
}}
|
{kindOptions.map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
<Field label="Başlık" required>
|
||||||
|
<input
|
||||||
|
required
|
||||||
|
value={form.title}
|
||||||
|
onChange={(event) => setField('title', event.target.value)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
label="Onaylayacak Kişi"
|
||||||
|
required={form.kind === 'Approval' || form.kind === 'Inform'}
|
||||||
>
|
>
|
||||||
{isSelected ? "Kapat" : "Düzenle"}
|
<input
|
||||||
</button>
|
required={form.kind === 'Approval' || form.kind === 'Inform'}
|
||||||
</td>
|
value={form.approver}
|
||||||
</tr>
|
onChange={(event) => setField('approver', event.target.value)}
|
||||||
{isSelected && (
|
/>
|
||||||
<tr className="[&>td]:bg-slate-50 [&>td]:p-3.5">
|
</Field>
|
||||||
<td colSpan={5}>
|
|
||||||
<div className="grid grid-cols-3 gap-2.5 max-[720px]:grid-cols-1">
|
{(form.kind === 'Start' || form.kind === 'Inform') && (
|
||||||
<Field label="Tip" required>
|
<Field label="Sonraki adım" required>
|
||||||
<select
|
{targetSelect(
|
||||||
value={form.kind}
|
form.nextOnStart,
|
||||||
onChange={(event) =>
|
(value) => setField('nextOnStart', value),
|
||||||
setField("kind", event.target.value)
|
true,
|
||||||
}
|
)}
|
||||||
>
|
</Field>
|
||||||
{kindOptions.map((option) => (
|
)}
|
||||||
<option
|
|
||||||
key={option.value}
|
{form.kind === 'Approval' && (
|
||||||
value={option.value}
|
<>
|
||||||
>
|
<Field label="Onay adımı" required>
|
||||||
{option.label}
|
{targetSelect(
|
||||||
</option>
|
form.nextOnApprove,
|
||||||
))}
|
(value) => setField('nextOnApprove', value),
|
||||||
</select>
|
true,
|
||||||
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Başlık" required>
|
<Field label="Red adımı" required>
|
||||||
<input
|
{targetSelect(
|
||||||
required
|
form.nextOnReject,
|
||||||
value={form.title}
|
(value) => setField('nextOnReject', value),
|
||||||
onChange={(event) =>
|
true,
|
||||||
setField("title", event.target.value)
|
)}
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Field>
|
</Field>
|
||||||
<Field
|
</>
|
||||||
label="Onaylayacak Kişi"
|
)}
|
||||||
required={
|
</div>
|
||||||
form.kind === "Approval" ||
|
|
||||||
form.kind === "Inform"
|
{form.kind === 'Compare' && (
|
||||||
|
<div className="grid gap-2.5 rounded-lg border border-gray-200 bg-slate-50 p-2.5">
|
||||||
|
<div className="flex items-center justify-between gap-2 text-[13px] font-bold text-slate-700">
|
||||||
|
<span>Karşılaştırma durumları</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames(
|
||||||
|
tableButtonClass,
|
||||||
|
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
||||||
|
)}
|
||||||
|
disabled={(form.compareOutcomes || []).length >= 4}
|
||||||
|
onClick={() =>
|
||||||
|
setField('compareOutcomes', [
|
||||||
|
...(form.compareOutcomes || []),
|
||||||
|
emptyCompareOutcome(
|
||||||
|
`Durum ${(form.compareOutcomes || []).length + 1}`,
|
||||||
|
),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<input
|
Ekle
|
||||||
required={
|
</button>
|
||||||
form.kind === "Approval" ||
|
|
||||||
form.kind === "Inform"
|
|
||||||
}
|
|
||||||
value={form.approver}
|
|
||||||
onChange={(event) =>
|
|
||||||
onChange({
|
|
||||||
...form,
|
|
||||||
approver: event.target.value,
|
|
||||||
informPerson: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
{false && (
|
|
||||||
<Field label="Bilgilendirme personeli">
|
|
||||||
<input
|
|
||||||
value={form.informPerson}
|
|
||||||
onChange={(event) =>
|
|
||||||
onChange({
|
|
||||||
...form,
|
|
||||||
approver: event.target.value,
|
|
||||||
informPerson: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(form.kind === "Start" ||
|
|
||||||
form.kind === "Inform") && (
|
|
||||||
<Field label="Sonraki adım" required>
|
|
||||||
{targetSelect(
|
|
||||||
form.nextOnStart,
|
|
||||||
(value) => setField("nextOnStart", value),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{form.kind === "Approval" && (
|
|
||||||
<>
|
|
||||||
<Field label="Onay adımı" required>
|
|
||||||
{targetSelect(
|
|
||||||
form.nextOnApprove,
|
|
||||||
(value) => setField("nextOnApprove", value),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field label="Red adımı" required>
|
|
||||||
{targetSelect(
|
|
||||||
form.nextOnReject,
|
|
||||||
(value) => setField("nextOnReject", value),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{(form.compareOutcomes || []).map(
|
||||||
{form.kind === "Compare" && (
|
(outcome: CompareOutcomeDto, index: number) => (
|
||||||
<div className="grid gap-2.5 rounded-lg border border-app-line bg-slate-50 p-2.5">
|
<div
|
||||||
<div className="flex items-center justify-between gap-2 text-[13px] font-bold text-[#344054]">
|
key={index}
|
||||||
<span>Karşılaştırma durumları</span>
|
className="grid gap-2 border-t border-gray-200 pt-2 first:border-t-0 first:pt-0"
|
||||||
<button
|
>
|
||||||
type="button"
|
<div className="grid grid-cols-[minmax(130px,0.8fr)_minmax(200px,1.4fr)_auto] items-center gap-2 max-[720px]:grid-cols-1">
|
||||||
className="ml-1.5 min-h-8 border-[#cfd6e2] bg-white px-2.5 text-[#344054]"
|
<input
|
||||||
disabled={
|
required
|
||||||
(form.compareOutcomes || []).length >= 4
|
value={outcome.label}
|
||||||
}
|
aria-label="Durum adı zorunlu"
|
||||||
onClick={() =>
|
onChange={(event) =>
|
||||||
setField("compareOutcomes", [
|
updateCompareOutcome(index, {
|
||||||
...(form.compareOutcomes || []),
|
label: event.target.value,
|
||||||
emptyCompareOutcome(
|
})
|
||||||
`Durum ${(form.compareOutcomes || []).length + 1}`,
|
}
|
||||||
),
|
/>
|
||||||
])
|
{targetSelect(
|
||||||
}
|
outcome.targetId,
|
||||||
>
|
(targetId) =>
|
||||||
Ekle
|
updateCompareOutcome(index, {
|
||||||
</button>
|
targetId,
|
||||||
</div>
|
}),
|
||||||
{(form.compareOutcomes || []).map(
|
true,
|
||||||
(outcome: CompareOutcomeDto, index: number) => (
|
)}
|
||||||
<div
|
<button
|
||||||
key={index}
|
type="button"
|
||||||
className="grid gap-2 border-t border-[#e4e7ec] pt-2 first:border-t-0 first:pt-0"
|
className={classNames(
|
||||||
|
tableButtonClass,
|
||||||
|
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
||||||
|
)}
|
||||||
|
disabled={(form.compareOutcomes || []).length <= 2}
|
||||||
|
onClick={() => removeCompareOutcome(index)}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-[minmax(130px,0.8fr)_minmax(200px,1.4fr)_auto] items-center gap-2 max-[720px]:grid-cols-1">
|
Sil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2.5">
|
||||||
|
{(outcome.conditions || []).map((condition, conditionIndex) => (
|
||||||
|
<div
|
||||||
|
key={conditionIndex}
|
||||||
|
className="grid grid-cols-[minmax(100px,0.7fr)_82px_minmax(110px,0.8fr)_auto] items-center gap-1.5 max-[720px]:grid-cols-1"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
value={condition.compareColumn}
|
||||||
|
onChange={(event) =>
|
||||||
|
updateCompareCondition(index, conditionIndex, {
|
||||||
|
column: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{columnOptions.map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
value={condition.compareOperator}
|
||||||
|
onChange={(event) =>
|
||||||
|
updateCompareCondition(index, conditionIndex, {
|
||||||
|
operator: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{operatorOptions.map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
value={outcome.label}
|
type="number"
|
||||||
aria-label="Durum adı zorunlu"
|
step="0.01"
|
||||||
|
value={condition.compareValue}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
updateCompareOutcome(index, {
|
updateCompareCondition(index, conditionIndex, {
|
||||||
label: event.target.value,
|
compareValue: event.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{targetSelect(
|
|
||||||
outcome.targetId,
|
|
||||||
(targetId) =>
|
|
||||||
updateCompareOutcome(index, {
|
|
||||||
targetId,
|
|
||||||
}),
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="ml-1.5 min-h-8 border-[#cfd6e2] bg-white px-2.5 text-[#344054]"
|
className={classNames(
|
||||||
disabled={
|
tableButtonClass,
|
||||||
(form.compareOutcomes || []).length <=
|
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
||||||
2
|
)}
|
||||||
}
|
disabled={(outcome.conditions || []).length <= 1}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
removeCompareOutcome(index)
|
removeCompareCondition(index, conditionIndex)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Sil
|
Koşulu sil
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2.5">
|
))}
|
||||||
{(outcome.conditions || []).map(
|
<button
|
||||||
(condition, conditionIndex) => (
|
type="button"
|
||||||
<div
|
className={classNames(
|
||||||
key={conditionIndex}
|
tableButtonClass,
|
||||||
className="grid grid-cols-[minmax(100px,0.7fr)_82px_minmax(110px,0.8fr)_auto] items-center gap-1.5 max-[720px]:grid-cols-1"
|
'ml-1.5 border-gray-300 bg-white text-slate-700',
|
||||||
>
|
)}
|
||||||
<select
|
onClick={() => addCompareCondition(index)}
|
||||||
value={condition.column}
|
>
|
||||||
onChange={(event) =>
|
Koşul ekle
|
||||||
updateCompareCondition(
|
</button>
|
||||||
index,
|
</div>
|
||||||
conditionIndex,
|
</div>
|
||||||
{
|
),
|
||||||
column: event.target.value,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{columnOptions.map((option) => (
|
|
||||||
<option
|
|
||||||
key={option.value}
|
|
||||||
value={option.value}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<select
|
|
||||||
value={condition.operator}
|
|
||||||
onChange={(event) =>
|
|
||||||
updateCompareCondition(
|
|
||||||
index,
|
|
||||||
conditionIndex,
|
|
||||||
{
|
|
||||||
operator:
|
|
||||||
event.target.value,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{operatorOptions.map((option) => (
|
|
||||||
<option
|
|
||||||
key={option.value}
|
|
||||||
value={option.value}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
value={condition.compareValue}
|
|
||||||
onChange={(event) =>
|
|
||||||
updateCompareCondition(
|
|
||||||
index,
|
|
||||||
conditionIndex,
|
|
||||||
{
|
|
||||||
compareValue:
|
|
||||||
event.target.value,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="ml-1.5 min-h-8 border-[#cfd6e2] bg-white px-2.5 text-[#344054]"
|
|
||||||
disabled={
|
|
||||||
(outcome.conditions || [])
|
|
||||||
.length <= 1
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
removeCompareCondition(
|
|
||||||
index,
|
|
||||||
conditionIndex,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Koşulu sil
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="ml-1.5 min-h-8 border-[#cfd6e2] bg-white px-2.5 text-[#344054]"
|
|
||||||
onClick={() =>
|
|
||||||
addCompareCondition(index)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Koşul ekle
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<button type="submit" disabled={busy}>
|
<button
|
||||||
<SaveIcon />
|
type="submit"
|
||||||
Kaydet
|
className={classNames(
|
||||||
</button>
|
tableButtonClass,
|
||||||
<button
|
'border-blue-600 bg-blue-600 text-white',
|
||||||
type="button"
|
)}
|
||||||
className="border-app-red bg-app-red text-white"
|
disabled={busy}
|
||||||
disabled={busy || !form.id}
|
>
|
||||||
onClick={() => onDelete(form.id)}
|
<SaveIcon />
|
||||||
>
|
Kaydet
|
||||||
<TrashIcon />
|
</button>
|
||||||
Sil
|
<button
|
||||||
</button>
|
type="button"
|
||||||
</div>
|
className={classNames(
|
||||||
</td>
|
tableButtonClass,
|
||||||
</tr>
|
'border-red-600 bg-red-600 text-white',
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
disabled={busy || !form.id}
|
||||||
);
|
onClick={() => onDelete(form.id)}
|
||||||
})}
|
>
|
||||||
</tbody>
|
<TrashIcon />
|
||||||
</table>
|
Sil
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Field({
|
function Field({
|
||||||
|
|
@ -477,64 +411,61 @@ function Field({
|
||||||
children,
|
children,
|
||||||
required = false,
|
required = false,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string
|
||||||
children: React.ReactNode;
|
children: React.ReactNode
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<label className="grid gap-1.5 text-[12px] text-[#344054]">
|
<label className="grid gap-1.5 text-[12px] text-slate-700">
|
||||||
<span>
|
<span>
|
||||||
{label}
|
{label}
|
||||||
{required && <span className="font-bold text-app-red"> *</span>}
|
{required && <span className="font-bold text-red-600"> *</span>}
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</label>
|
</label>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function criteriaSummaryContent(item: WorkflowCriteriaDto) {
|
function criteriaSummaryContent(item: WorkflowCriteriaDto) {
|
||||||
if (item.kind === "Compare") {
|
if (item.kind === 'Compare') {
|
||||||
const outcomes = item.compareOutcomes || [];
|
const outcomes = item.compareOutcomes || []
|
||||||
if (!outcomes.length) return "-";
|
if (!outcomes.length) return '-'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="m-0 grid gap-1 pl-[18px] [&_li]:pl-0.5">
|
<ul className="m-0 grid gap-1">
|
||||||
{outcomes.map((outcome, index: number) => (
|
{outcomes.map((outcome, index: number) => (
|
||||||
<li key={`${outcome.label || "outcome"}-${index}`}>
|
<li key={`${outcome.label || 'outcome'}-${index}`}>
|
||||||
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{" "}
|
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
||||||
{compareOutcomeRuleText(outcome)}
|
{compareOutcomeRuleText(outcome)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return criteriaSummary(item);
|
return criteriaSummary(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
function criteriaConnectionSummary(
|
function criteriaConnectionSummary(item: WorkflowCriteriaDto, criteria: WorkflowCriteriaDto[]) {
|
||||||
item: WorkflowCriteriaDto,
|
if (item.kind === 'Compare') {
|
||||||
criteria: WorkflowCriteriaDto[],
|
const outcomes = item.compareOutcomes || []
|
||||||
) {
|
if (!outcomes.length) return '-'
|
||||||
if (item.kind === "Compare") {
|
|
||||||
const outcomes = item.compareOutcomes || [];
|
|
||||||
if (!outcomes.length) return "-";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="m-0 grid gap-1 pl-[18px] [&_li]:pl-0.5">
|
<ul className="m-0 grid gap-1">
|
||||||
{outcomes.map((outcome, index: number) => (
|
{outcomes.map((outcome, index: number) => (
|
||||||
<li key={`${outcome.label || "target"}-${index}`}>
|
<li key={`${outcome.label || 'target'}-${index}`}>
|
||||||
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{" "}
|
<strong>{outcome.label || `Durum ${index + 1}`}:</strong>{' '}
|
||||||
{targetTitle(criteria, outcome.targetId)}
|
{targetTitle(criteria, outcome.targetId)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.kind === "Approval") {
|
if (item.kind === 'Approval') {
|
||||||
return (
|
return (
|
||||||
<ul className="m-0 grid gap-1 pl-[18px] [&_li]:pl-0.5">
|
<ul className="m-0 grid gap-1">
|
||||||
<li>
|
<li>
|
||||||
<strong>Onay:</strong> {targetTitle(criteria, item.nextOnApprove)}
|
<strong>Onay:</strong> {targetTitle(criteria, item.nextOnApprove)}
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -542,12 +473,12 @@ function criteriaConnectionSummary(
|
||||||
<strong>Red:</strong> {targetTitle(criteria, item.nextOnReject)}
|
<strong>Red:</strong> {targetTitle(criteria, item.nextOnReject)}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.kind === "Start" || item.kind === "Inform") {
|
if (item.kind === 'Start' || item.kind === 'Inform') {
|
||||||
return targetTitle(criteria, item.nextOnStart);
|
return targetTitle(criteria, item.nextOnStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "-";
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,89 +1,53 @@
|
||||||
import { ApprovalDialog } from "./ApprovalDialog";
|
import { WorkflowDesigner } from './WorkflowDesigner'
|
||||||
import { WorkflowDesigner } from "./WorkflowDesigner";
|
import { useEffect, useState, type FormEvent, type RefObject } from 'react'
|
||||||
import { WorkflowTable } from "./WorkflowTable";
|
import type { WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
import type { FormEvent, RefObject } from "react";
|
import { DatabaseColumnDto } from '@/proxy/sql-query-manager/models'
|
||||||
import type {
|
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
|
||||||
WorkflowCriteriaDto,
|
|
||||||
WorkflowItemDto,
|
|
||||||
} from "@/services/workflow.service";
|
|
||||||
|
|
||||||
type DashboardShellProps = {
|
type DashboardShellProps = {
|
||||||
busy: boolean;
|
busy: boolean
|
||||||
canvasRef: RefObject<HTMLDivElement>;
|
canvasRef: RefObject<HTMLDivElement>
|
||||||
canvasZoom: number;
|
canvasZoom: number
|
||||||
criteria: WorkflowCriteriaDto[];
|
criteriaForm: any
|
||||||
criteriaForm: any;
|
currentCriteria: WorkflowCriteriaDto[]
|
||||||
currentCriteria: WorkflowCriteriaDto[];
|
designerTab: string
|
||||||
designerTab: string;
|
dragPreview: any
|
||||||
dialogPendingItems: WorkflowItemDto[];
|
pendingLink: any
|
||||||
dragPreview: any;
|
selectedId: string
|
||||||
editingWorkflowId: string | null;
|
onAddCriteria: (kind: string) => void
|
||||||
pendingLink: any;
|
onBeginLink: (sourceId: string, outcome: string) => void
|
||||||
selectedId: string;
|
onChangeCriteriaForm: (form: any) => void
|
||||||
selectedWorkflow?: WorkflowItemDto | null;
|
onClearCanvasSelection: () => void
|
||||||
selectedWorkflowId: string | null;
|
onConnectNodes: (sourceId: string, outcome: string, targetId: string) => void
|
||||||
showApprovalDialog: boolean;
|
onDeleteSelectedCriteria: (criteriaId?: string) => void
|
||||||
workflowEditForm: any;
|
onDisconnectLink: (sourceId: string, outcome: string) => void
|
||||||
workflowForm: any;
|
onDragMove: (event: any) => void
|
||||||
workflowItems: WorkflowItemDto[];
|
onFitFlowLayout: () => void
|
||||||
onAddCriteria: (kind: string) => void;
|
onOpenCriteriaDetails: (id: string) => void
|
||||||
onBeginLink: (sourceId: string, outcome: string) => void;
|
onResetDemo: () => void
|
||||||
onBeginWorkflowEdit: (item: WorkflowItemDto) => void;
|
onSaveCriteria: (event: FormEvent<HTMLFormElement>) => void
|
||||||
onCancelWorkflowEdit: () => void;
|
onSelectCriteria: (id: string) => void
|
||||||
onChangeCriteriaForm: (form: any) => void;
|
onSetDesignerTab: (tab: string) => void
|
||||||
onClearCanvasSelection: () => void;
|
onUpdateNodePosition: (event: any) => void
|
||||||
onCloseApprovalDialog: () => void;
|
onZoomIn: () => void
|
||||||
onConnectNodes: (sourceId: string, outcome: string, targetId: string) => void;
|
onZoomOut: () => void
|
||||||
onCreateWorkflow: (event: FormEvent<HTMLFormElement>) => void;
|
}
|
||||||
onDecision: (id: string, approved: boolean, note: string) => void;
|
|
||||||
onDeleteSelectedCriteria: (criteriaId?: string) => void;
|
|
||||||
onDisconnectLink: (sourceId: string, outcome: string) => void;
|
|
||||||
onDragMove: (event: any) => void;
|
|
||||||
onFitFlowLayout: () => void;
|
|
||||||
onOpenCriteriaDetails: (id: string) => void;
|
|
||||||
onResetDemo: () => void;
|
|
||||||
onSaveCriteria: (event: FormEvent<HTMLFormElement>) => void;
|
|
||||||
onSaveWorkflowEdit: (id: string) => void;
|
|
||||||
onSelectCriteria: (id: string) => void;
|
|
||||||
onSelectWorkflow: (item: WorkflowItemDto) => void;
|
|
||||||
onSetDesignerTab: (tab: string) => void;
|
|
||||||
onStartWorkflow: (id: string) => void;
|
|
||||||
onUpdateNodePosition: (event: any) => void;
|
|
||||||
onWorkflowEditFormChange: (form: any) => void;
|
|
||||||
onWorkflowFormChange: (form: any) => void;
|
|
||||||
onZoomIn: () => void;
|
|
||||||
onZoomOut: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function DashboardShell({
|
export function DashboardShell({
|
||||||
busy,
|
busy,
|
||||||
canvasRef,
|
canvasRef,
|
||||||
canvasZoom,
|
canvasZoom,
|
||||||
criteria,
|
|
||||||
criteriaForm,
|
criteriaForm,
|
||||||
currentCriteria,
|
currentCriteria,
|
||||||
designerTab,
|
designerTab,
|
||||||
dialogPendingItems,
|
|
||||||
dragPreview,
|
dragPreview,
|
||||||
editingWorkflowId,
|
|
||||||
pendingLink,
|
pendingLink,
|
||||||
selectedId,
|
selectedId,
|
||||||
selectedWorkflow,
|
|
||||||
selectedWorkflowId,
|
|
||||||
showApprovalDialog,
|
|
||||||
workflowEditForm,
|
|
||||||
workflowForm,
|
|
||||||
workflowItems,
|
|
||||||
onAddCriteria,
|
onAddCriteria,
|
||||||
onBeginLink,
|
onBeginLink,
|
||||||
onBeginWorkflowEdit,
|
|
||||||
onCancelWorkflowEdit,
|
|
||||||
onChangeCriteriaForm,
|
onChangeCriteriaForm,
|
||||||
onClearCanvasSelection,
|
onClearCanvasSelection,
|
||||||
onCloseApprovalDialog,
|
|
||||||
onConnectNodes,
|
onConnectNodes,
|
||||||
onCreateWorkflow,
|
|
||||||
onDecision,
|
|
||||||
onDeleteSelectedCriteria,
|
onDeleteSelectedCriteria,
|
||||||
onDisconnectLink,
|
onDisconnectLink,
|
||||||
onDragMove,
|
onDragMove,
|
||||||
|
|
@ -91,41 +55,34 @@ export function DashboardShell({
|
||||||
onOpenCriteriaDetails,
|
onOpenCriteriaDetails,
|
||||||
onResetDemo,
|
onResetDemo,
|
||||||
onSaveCriteria,
|
onSaveCriteria,
|
||||||
onSaveWorkflowEdit,
|
|
||||||
onSelectCriteria,
|
onSelectCriteria,
|
||||||
onSelectWorkflow,
|
|
||||||
onSetDesignerTab,
|
onSetDesignerTab,
|
||||||
onStartWorkflow,
|
|
||||||
onUpdateNodePosition,
|
onUpdateNodePosition,
|
||||||
onWorkflowEditFormChange,
|
|
||||||
onWorkflowFormChange,
|
|
||||||
onZoomIn,
|
onZoomIn,
|
||||||
onZoomOut,
|
onZoomOut,
|
||||||
}: DashboardShellProps) {
|
}: DashboardShellProps) {
|
||||||
|
const [selectCommandColumns, setSelectCommandColumns] = useState<DatabaseColumnDto[]>([])
|
||||||
|
const [isLoadingColumns, setIsLoadingColumns] = useState(false)
|
||||||
|
|
||||||
|
const loadColumns = async (dsCode: string, schema: string, name: string) => {
|
||||||
|
if (!dsCode || !name) {
|
||||||
|
setSelectCommandColumns([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIsLoadingColumns(true)
|
||||||
|
try {
|
||||||
|
const res = await sqlObjectManagerService.getTableColumns(dsCode, schema, name)
|
||||||
|
setSelectCommandColumns(res.data ?? [])
|
||||||
|
} catch {
|
||||||
|
setSelectCommandColumns([])
|
||||||
|
} finally {
|
||||||
|
setIsLoadingColumns(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="min-h-screen">
|
||||||
<main className="grid gap-[18px] p-[18px]">
|
<main className="grid">
|
||||||
<section className="grid grid-cols-1 gap-[18px]">
|
|
||||||
<WorkflowTable
|
|
||||||
items={workflowItems}
|
|
||||||
criteria={criteria}
|
|
||||||
selectedWorkflowId={selectedWorkflowId}
|
|
||||||
form={workflowForm}
|
|
||||||
busy={busy}
|
|
||||||
onFormChange={onWorkflowFormChange}
|
|
||||||
onSubmit={onCreateWorkflow}
|
|
||||||
editingId={editingWorkflowId}
|
|
||||||
editForm={workflowEditForm}
|
|
||||||
onEditFormChange={onWorkflowEditFormChange}
|
|
||||||
onEdit={onBeginWorkflowEdit}
|
|
||||||
onCancelEdit={onCancelWorkflowEdit}
|
|
||||||
onSaveEdit={onSaveWorkflowEdit}
|
|
||||||
onSelect={onSelectWorkflow}
|
|
||||||
onStart={onStartWorkflow}
|
|
||||||
onResetDemo={onResetDemo}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<WorkflowDesigner
|
<WorkflowDesigner
|
||||||
busy={busy}
|
busy={busy}
|
||||||
canvasRef={canvasRef}
|
canvasRef={canvasRef}
|
||||||
|
|
@ -136,7 +93,6 @@ export function DashboardShell({
|
||||||
dragPreview={dragPreview}
|
dragPreview={dragPreview}
|
||||||
pendingLink={pendingLink}
|
pendingLink={pendingLink}
|
||||||
selectedCriteriaId={selectedId}
|
selectedCriteriaId={selectedId}
|
||||||
selectedWorkflow={selectedWorkflow}
|
|
||||||
onAddCriteria={onAddCriteria}
|
onAddCriteria={onAddCriteria}
|
||||||
onBeginLink={onBeginLink}
|
onBeginLink={onBeginLink}
|
||||||
onChangeCriteriaForm={onChangeCriteriaForm}
|
onChangeCriteriaForm={onChangeCriteriaForm}
|
||||||
|
|
@ -147,6 +103,7 @@ export function DashboardShell({
|
||||||
onDragMove={onDragMove}
|
onDragMove={onDragMove}
|
||||||
onFitLayout={onFitFlowLayout}
|
onFitLayout={onFitFlowLayout}
|
||||||
onOpenDetails={onOpenCriteriaDetails}
|
onOpenDetails={onOpenCriteriaDetails}
|
||||||
|
onResetDemo={onResetDemo}
|
||||||
onSaveCriteria={onSaveCriteria}
|
onSaveCriteria={onSaveCriteria}
|
||||||
onSelectCriteria={onSelectCriteria}
|
onSelectCriteria={onSelectCriteria}
|
||||||
onSetDesignerTab={onSetDesignerTab}
|
onSetDesignerTab={onSetDesignerTab}
|
||||||
|
|
@ -155,16 +112,6 @@ export function DashboardShell({
|
||||||
onZoomOut={onZoomOut}
|
onZoomOut={onZoomOut}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{showApprovalDialog && (
|
|
||||||
<ApprovalDialog
|
|
||||||
busy={busy}
|
|
||||||
criteria={criteria}
|
|
||||||
items={dialogPendingItems}
|
|
||||||
onClose={onCloseApprovalDialog}
|
|
||||||
onDecision={onDecision}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,19 +1,11 @@
|
||||||
import { DndContext } from '@dnd-kit/core'
|
import { DndContext } from '@dnd-kit/core'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import dayjs from 'dayjs'
|
import { FiMaximize2, FiRefreshCw, FiZoomIn, FiZoomOut } from 'react-icons/fi'
|
||||||
import { FiMaximize2, FiZoomIn, FiZoomOut } from 'react-icons/fi'
|
|
||||||
import { kindIcon, kindOptions } from '@/utils/workflow/workflowConstants'
|
import { kindIcon, kindOptions } from '@/utils/workflow/workflowConstants'
|
||||||
import { CriteriaTable } from './CriteriaTable'
|
import { CriteriaTable } from './CriteriaTable'
|
||||||
import { FlowCanvas } from './FlowCanvas'
|
import { FlowCanvas } from './FlowCanvas'
|
||||||
import type { FormEvent, RefObject } from 'react'
|
import type { FormEvent, RefObject } from 'react'
|
||||||
import type {
|
import type { WorkflowCriteriaDto } from '@/services/workflow.service'
|
||||||
WorkflowCriteriaDto,
|
|
||||||
WorkflowItemDto,
|
|
||||||
} from '@/services/workflow.service'
|
|
||||||
|
|
||||||
const MaximizeIcon = FiMaximize2 as any
|
|
||||||
const ZoomInIcon = FiZoomIn as any
|
|
||||||
const ZoomOutIcon = FiZoomOut as any
|
|
||||||
|
|
||||||
type WorkflowDesignerProps = {
|
type WorkflowDesignerProps = {
|
||||||
busy: boolean
|
busy: boolean
|
||||||
|
|
@ -25,7 +17,6 @@ type WorkflowDesignerProps = {
|
||||||
dragPreview: any
|
dragPreview: any
|
||||||
pendingLink: any
|
pendingLink: any
|
||||||
selectedCriteriaId: string
|
selectedCriteriaId: string
|
||||||
selectedWorkflow?: WorkflowItemDto | null
|
|
||||||
onAddCriteria: (kind: string) => void
|
onAddCriteria: (kind: string) => void
|
||||||
onBeginLink: (sourceId: string, outcome: string) => void
|
onBeginLink: (sourceId: string, outcome: string) => void
|
||||||
onChangeCriteriaForm: (form: any) => void
|
onChangeCriteriaForm: (form: any) => void
|
||||||
|
|
@ -36,6 +27,7 @@ type WorkflowDesignerProps = {
|
||||||
onDragMove: (event: any) => void
|
onDragMove: (event: any) => void
|
||||||
onFitLayout: () => void
|
onFitLayout: () => void
|
||||||
onOpenDetails: (id: string) => void
|
onOpenDetails: (id: string) => void
|
||||||
|
onResetDemo: () => void
|
||||||
onSaveCriteria: (event: FormEvent<HTMLFormElement>) => void
|
onSaveCriteria: (event: FormEvent<HTMLFormElement>) => void
|
||||||
onSelectCriteria: (id: string) => void
|
onSelectCriteria: (id: string) => void
|
||||||
onSetDesignerTab: (tab: string) => void
|
onSetDesignerTab: (tab: string) => void
|
||||||
|
|
@ -44,6 +36,14 @@ type WorkflowDesignerProps = {
|
||||||
onZoomOut: () => void
|
onZoomOut: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const designerButtonClass =
|
||||||
|
'inline-flex min-h-9 items-center justify-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium leading-none transition-colors disabled:cursor-not-allowed disabled:opacity-50'
|
||||||
|
|
||||||
|
const designerIconButtonClass =
|
||||||
|
'inline-flex min-h-9 w-9 items-center justify-center rounded-md border p-0 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50'
|
||||||
|
|
||||||
|
const designerTabClass = 'min-h-8 rounded-md border px-3 py-1.5 transition-colors'
|
||||||
|
|
||||||
export function WorkflowDesigner({
|
export function WorkflowDesigner({
|
||||||
busy,
|
busy,
|
||||||
canvasRef,
|
canvasRef,
|
||||||
|
|
@ -54,7 +54,6 @@ export function WorkflowDesigner({
|
||||||
dragPreview,
|
dragPreview,
|
||||||
pendingLink,
|
pendingLink,
|
||||||
selectedCriteriaId,
|
selectedCriteriaId,
|
||||||
selectedWorkflow,
|
|
||||||
onAddCriteria,
|
onAddCriteria,
|
||||||
onBeginLink,
|
onBeginLink,
|
||||||
onChangeCriteriaForm,
|
onChangeCriteriaForm,
|
||||||
|
|
@ -65,6 +64,7 @@ export function WorkflowDesigner({
|
||||||
onDragMove,
|
onDragMove,
|
||||||
onFitLayout,
|
onFitLayout,
|
||||||
onOpenDetails,
|
onOpenDetails,
|
||||||
|
onResetDemo,
|
||||||
onSaveCriteria,
|
onSaveCriteria,
|
||||||
onSelectCriteria,
|
onSelectCriteria,
|
||||||
onSetDesignerTab,
|
onSetDesignerTab,
|
||||||
|
|
@ -73,7 +73,7 @@ export function WorkflowDesigner({
|
||||||
onZoomOut,
|
onZoomOut,
|
||||||
}: WorkflowDesignerProps) {
|
}: WorkflowDesignerProps) {
|
||||||
return (
|
return (
|
||||||
<section className="relative min-w-0 rounded-lg border border-app-line bg-app-surface p-4 max-[1080px]:pr-4">
|
<section className="relative min-w-0 rounded-lg border border-gray-200 bg-white p-4 max-[1080px]:pr-4">
|
||||||
<div className="mb-3.5 flex items-center justify-between gap-4 max-[720px]:flex-col max-[720px]:items-stretch">
|
<div className="mb-3.5 flex items-center justify-between gap-4 max-[720px]:flex-col max-[720px]:items-stretch">
|
||||||
<DesignerTabs activeTab={designerTab} onChange={onSetDesignerTab} />
|
<DesignerTabs activeTab={designerTab} onChange={onSetDesignerTab} />
|
||||||
|
|
||||||
|
|
@ -84,6 +84,7 @@ export function WorkflowDesigner({
|
||||||
zoom={canvasZoom}
|
zoom={canvasZoom}
|
||||||
onAddCriteria={onAddCriteria}
|
onAddCriteria={onAddCriteria}
|
||||||
onFitLayout={onFitLayout}
|
onFitLayout={onFitLayout}
|
||||||
|
onResetDemo={onResetDemo}
|
||||||
onZoomIn={onZoomIn}
|
onZoomIn={onZoomIn}
|
||||||
onZoomOut={onZoomOut}
|
onZoomOut={onZoomOut}
|
||||||
/>
|
/>
|
||||||
|
|
@ -101,7 +102,6 @@ export function WorkflowDesigner({
|
||||||
currentCriteria={currentCriteria}
|
currentCriteria={currentCriteria}
|
||||||
dragPreview={dragPreview}
|
dragPreview={dragPreview}
|
||||||
zoom={canvasZoom}
|
zoom={canvasZoom}
|
||||||
activeNodeId={selectedWorkflow?.currentNodeId}
|
|
||||||
selectedId={selectedCriteriaId}
|
selectedId={selectedCriteriaId}
|
||||||
pendingLink={pendingLink}
|
pendingLink={pendingLink}
|
||||||
canvasRef={canvasRef}
|
canvasRef={canvasRef}
|
||||||
|
|
@ -120,20 +120,15 @@ export function WorkflowDesigner({
|
||||||
{designerTab === 'criteria' && (
|
{designerTab === 'criteria' && (
|
||||||
<CriteriaTable
|
<CriteriaTable
|
||||||
criteria={currentCriteria}
|
criteria={currentCriteria}
|
||||||
selectedWorkflow={selectedWorkflow}
|
|
||||||
selectedId={selectedCriteriaId}
|
selectedId={selectedCriteriaId}
|
||||||
activeNodeId={selectedWorkflow?.currentNodeId}
|
|
||||||
form={criteriaForm}
|
form={criteriaForm}
|
||||||
busy={busy}
|
busy={busy}
|
||||||
onSelect={onSelectCriteria}
|
onSelect={onSelectCriteria}
|
||||||
onChange={onChangeCriteriaForm}
|
onChange={onChangeCriteriaForm}
|
||||||
onSubmit={onSaveCriteria}
|
onSubmit={onSaveCriteria}
|
||||||
onDelete={onDeleteCriteria}
|
onDelete={onDeleteCriteria}
|
||||||
onAddCriteria={onAddCriteria}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{designerTab === 'history' && <ApprovalHistoryTable selectedWorkflow={selectedWorkflow} />}
|
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +139,7 @@ function DesignerToolbar({
|
||||||
zoom,
|
zoom,
|
||||||
onAddCriteria,
|
onAddCriteria,
|
||||||
onFitLayout,
|
onFitLayout,
|
||||||
|
onResetDemo,
|
||||||
onZoomIn,
|
onZoomIn,
|
||||||
onZoomOut,
|
onZoomOut,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -152,6 +148,7 @@ function DesignerToolbar({
|
||||||
zoom: number
|
zoom: number
|
||||||
onAddCriteria: (kind: string) => void
|
onAddCriteria: (kind: string) => void
|
||||||
onFitLayout: () => void
|
onFitLayout: () => void
|
||||||
|
onResetDemo: () => void
|
||||||
onZoomIn: () => void
|
onZoomIn: () => void
|
||||||
onZoomOut: () => void
|
onZoomOut: () => void
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -159,31 +156,41 @@ function DesignerToolbar({
|
||||||
<div className="flex flex-wrap justify-end gap-2">
|
<div className="flex flex-wrap justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="border-app-primary bg-white text-app-primary"
|
className={classNames(designerButtonClass, 'border-gray-300 bg-white text-slate-700')}
|
||||||
|
disabled={busy}
|
||||||
|
title="Demo akışı yükle"
|
||||||
|
onClick={onResetDemo}
|
||||||
|
>
|
||||||
|
<FiRefreshCw />
|
||||||
|
Demo
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames(designerButtonClass, 'border-blue-600 bg-white text-blue-600')}
|
||||||
disabled={busy || currentCriteria.length === 0}
|
disabled={busy || currentCriteria.length === 0}
|
||||||
title="Düğümleri okunabilir şekilde yerleştir"
|
title="Düğümleri okunabilir şekilde yerleştir"
|
||||||
onClick={onFitLayout}
|
onClick={onFitLayout}
|
||||||
>
|
>
|
||||||
<MaximizeIcon />
|
<FiMaximize2 />
|
||||||
Fit
|
Fit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-[38px] justify-center border-app-primary bg-white p-0 text-app-primary"
|
className={classNames(designerIconButtonClass, 'border-blue-600 bg-white text-blue-600')}
|
||||||
title="Yakınlaştır"
|
title="Yakınlaştır"
|
||||||
onClick={onZoomIn}
|
onClick={onZoomIn}
|
||||||
>
|
>
|
||||||
<ZoomInIcon />
|
<FiZoomIn />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-[38px] justify-center border-app-primary bg-white p-0 text-app-primary"
|
className={classNames(designerIconButtonClass, 'border-blue-600 bg-white text-blue-600')}
|
||||||
title="Uzaklaştır"
|
title="Uzaklaştır"
|
||||||
onClick={onZoomOut}
|
onClick={onZoomOut}
|
||||||
>
|
>
|
||||||
<ZoomOutIcon />
|
<FiZoomOut />
|
||||||
</button>
|
</button>
|
||||||
<span className="inline-flex min-w-12 items-center justify-center text-[13px] font-bold text-app-muted">
|
<span className="inline-flex min-w-12 items-center justify-center text-[13px] font-bold text-slate-500">
|
||||||
{Math.round(zoom * 100)}%
|
{Math.round(zoom * 100)}%
|
||||||
</span>
|
</span>
|
||||||
{kindOptions.map((option) => {
|
{kindOptions.map((option) => {
|
||||||
|
|
@ -192,7 +199,7 @@ function DesignerToolbar({
|
||||||
<button
|
<button
|
||||||
key={option.value}
|
key={option.value}
|
||||||
type="button"
|
type="button"
|
||||||
className="border-app-primary bg-white text-app-primary"
|
className={classNames(designerButtonClass, 'border-blue-600 bg-white text-blue-600')}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onClick={() => onAddCriteria(option.value)}
|
onClick={() => onAddCriteria(option.value)}
|
||||||
>
|
>
|
||||||
|
|
@ -218,10 +225,10 @@ function DesignerTabs({
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'min-h-8 rounded-md border px-3 py-1.5 bg-transparent text-[#475467] transition-colors',
|
designerTabClass,
|
||||||
{
|
activeTab === 'flow'
|
||||||
'border-[#1d4ed8] bg-[#1d4ed8] text-white shadow-sm': activeTab === 'flow',
|
? 'border-blue-700 bg-blue-700 text-white shadow-sm'
|
||||||
},
|
: 'border-gray-200 bg-white text-slate-600',
|
||||||
)}
|
)}
|
||||||
onClick={() => onChange('flow')}
|
onClick={() => onChange('flow')}
|
||||||
>
|
>
|
||||||
|
|
@ -231,66 +238,15 @@ function DesignerTabs({
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'min-h-8 rounded-md border px-3 py-1.5 bg-transparent text-[#475467] transition-colors',
|
designerTabClass,
|
||||||
{
|
activeTab === 'criteria'
|
||||||
'border-[#1d4ed8] bg-[#1d4ed8] text-white shadow-sm': activeTab === 'criteria',
|
? 'border-blue-700 bg-blue-700 text-white shadow-sm'
|
||||||
},
|
: 'border-gray-200 bg-white text-slate-600',
|
||||||
)}
|
)}
|
||||||
onClick={() => onChange('criteria')}
|
onClick={() => onChange('criteria')}
|
||||||
>
|
>
|
||||||
Adımlar
|
Adımlar
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
role="tab"
|
|
||||||
className={classNames(
|
|
||||||
'min-h-8 rounded-md border px-3 py-1.5 bg-transparent text-[#475467] transition-colors',
|
|
||||||
{
|
|
||||||
'border-[#1d4ed8] bg-[#1d4ed8] text-white shadow-sm': activeTab === 'history',
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
onClick={() => onChange('history')}
|
|
||||||
>
|
|
||||||
Akış Geçmişi
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ApprovalHistoryTable({
|
|
||||||
selectedWorkflow,
|
|
||||||
}: {
|
|
||||||
selectedWorkflow?: WorkflowItemDto | null
|
|
||||||
}) {
|
|
||||||
const history = selectedWorkflow?.history || []
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="min-w-0 rounded-lg">
|
|
||||||
<div className="overflow-auto rounded-md border border-app-line">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Tarih</th>
|
|
||||||
<th>İşlem</th>
|
|
||||||
<th>Açıklama</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{history.length === 0 && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={3}>Seçili iş akışı için açıklama kaydı yok.</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{history.map((item: WorkflowItemDto['history'][number], index: number) => (
|
|
||||||
<tr key={`${item.time}-${index}`}>
|
|
||||||
<td>{dayjs(item.time).format('DD MMM YYYY HH:mm')}</td>
|
|
||||||
<td>{item.action}</td>
|
|
||||||
<td>{item.note}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
||||||
import { FiCheck, FiEdit2, FiPlay, FiPlus, FiRefreshCw, FiX } from 'react-icons/fi'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { formatMoney, statusClass } from '@/utils/workflow/workflowHelpers'
|
|
||||||
import type { FormEvent } from 'react'
|
|
||||||
import type {
|
|
||||||
WorkflowCriteriaDto,
|
|
||||||
WorkflowItemDto,
|
|
||||||
} from '@/services/workflow.service'
|
|
||||||
|
|
||||||
const CheckIcon = FiCheck as any
|
|
||||||
const EditIcon = FiEdit2 as any
|
|
||||||
const PlayIcon = FiPlay as any
|
|
||||||
const PlusIcon = FiPlus as any
|
|
||||||
const RefreshIcon = FiRefreshCw as any
|
|
||||||
const CloseIcon = FiX as any
|
|
||||||
|
|
||||||
type WorkflowTableProps = {
|
|
||||||
items: WorkflowItemDto[]
|
|
||||||
criteria: WorkflowCriteriaDto[]
|
|
||||||
selectedWorkflowId: string | null
|
|
||||||
form: { sorumlu: string; amount: number | string }
|
|
||||||
busy: boolean
|
|
||||||
onFormChange: (form: { sorumlu: string; amount: number | string }) => void
|
|
||||||
onSubmit: (event: FormEvent<HTMLFormElement>) => void
|
|
||||||
editingId: string | null
|
|
||||||
editForm: { sorumlu: string; tarih: string; amount: number | string }
|
|
||||||
onEditFormChange: (form: { sorumlu: string; tarih: string; amount: number | string }) => void
|
|
||||||
onEdit: (item: WorkflowItemDto) => void
|
|
||||||
onCancelEdit: () => void
|
|
||||||
onSaveEdit: (id: string) => void
|
|
||||||
onSelect: (item: WorkflowItemDto) => void
|
|
||||||
onStart: (id: string) => void
|
|
||||||
onResetDemo: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WorkflowTable({
|
|
||||||
items,
|
|
||||||
criteria,
|
|
||||||
selectedWorkflowId,
|
|
||||||
form,
|
|
||||||
busy,
|
|
||||||
onFormChange,
|
|
||||||
onSubmit,
|
|
||||||
editingId,
|
|
||||||
editForm,
|
|
||||||
onEditFormChange,
|
|
||||||
onEdit,
|
|
||||||
onCancelEdit,
|
|
||||||
onSaveEdit,
|
|
||||||
onSelect,
|
|
||||||
onStart,
|
|
||||||
onResetDemo,
|
|
||||||
}: WorkflowTableProps) {
|
|
||||||
return (
|
|
||||||
<section className="min-w-0 rounded-lg border border-app-line bg-app-surface p-4">
|
|
||||||
<div className="mb-3 flex items-start justify-between gap-4 max-[720px]:flex-col max-[720px]:items-stretch">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<h2 className="m-0 text-lg tracking-normal">WorkflowItems Tablosu</h2>
|
|
||||||
|
|
||||||
<span className="rounded-full bg-blue-50 px-2.5 py-1 text-xs font-medium text-blue-700">
|
|
||||||
{items.length} kayıt
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="min-h-10 rounded-md border-[#c7d7f4] bg-[#f8fbff] px-3.5 text-sm font-bold text-app-primary shadow-[0_1px_2px_rgba(16,24,40,0.06)] transition hover:-translate-y-px hover:border-app-primary hover:bg-white hover:shadow-[0_8px_18px_rgba(37,99,235,0.14)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-app-primary/35 disabled:hover:translate-y-0 disabled:hover:border-[#c7d7f4] disabled:hover:bg-[#f8fbff] disabled:hover:shadow-[0_1px_2px_rgba(16,24,40,0.06)]"
|
|
||||||
disabled={busy}
|
|
||||||
onClick={onResetDemo}
|
|
||||||
title="Demo verisini yenile"
|
|
||||||
>
|
|
||||||
<RefreshIcon className={busy ? 'animate-spin' : undefined} />
|
|
||||||
<span>Demo Verisini Yenile</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form
|
|
||||||
className="mb-3 grid grid-cols-[minmax(420px,2fr)_minmax(160px,0.6fr)_max-content] items-end gap-2.5 max-[720px]:grid-cols-1"
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
>
|
|
||||||
<label>
|
|
||||||
Başlık
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
value={form.sorumlu}
|
|
||||||
placeholder="Örn. Üretim Süreci"
|
|
||||||
onChange={(event) => onFormChange({ ...form, sorumlu: event.target.value })}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Fiyat
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
type="number"
|
|
||||||
value={form.amount}
|
|
||||||
onChange={(event) => onFormChange({ ...form, amount: event.target.value })}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<button type="submit" disabled={busy}>
|
|
||||||
<PlusIcon />
|
|
||||||
Yeni Akışı Ekle
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div className="overflow-auto rounded-md border border-app-line">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Id</th>
|
|
||||||
<th>Başlık</th>
|
|
||||||
<th>Tarih</th>
|
|
||||||
<th>Fiyat</th>
|
|
||||||
<th>Durum</th>
|
|
||||||
<th>İşlem</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{items.map((item) => {
|
|
||||||
const currentStep = criteria.find(
|
|
||||||
(candidate) =>
|
|
||||||
candidate.workflowItemId === item.id && candidate.id === item.currentNodeId,
|
|
||||||
)
|
|
||||||
const statusTitle = currentStep?.title || item.durum
|
|
||||||
const canStart = currentStep?.kind === 'Start' || currentStep?.kind === 'Compare'
|
|
||||||
const isEditing = item.id === editingId
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr
|
|
||||||
key={item.id}
|
|
||||||
className={classNames({
|
|
||||||
'[&>td]:bg-[#eef5ff]': item.id === selectedWorkflowId,
|
|
||||||
})}
|
|
||||||
onClick={() => {
|
|
||||||
if (!isEditing) onSelect(item)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<td>{item.id}</td>
|
|
||||||
<td>
|
|
||||||
{isEditing ? (
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
value={editForm.sorumlu}
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onChange={(event) =>
|
|
||||||
onEditFormChange({
|
|
||||||
...editForm,
|
|
||||||
sorumlu: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
item.sorumlu
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{isEditing ? (
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
type="date"
|
|
||||||
value={editForm.tarih}
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onChange={(event) =>
|
|
||||||
onEditFormChange({
|
|
||||||
...editForm,
|
|
||||||
tarih: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
dayjs(item.tarih).format('DD MMM YYYY')
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{isEditing ? (
|
|
||||||
<input
|
|
||||||
required
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
type="number"
|
|
||||||
value={editForm.amount}
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
onChange={(event) =>
|
|
||||||
onEditFormChange({
|
|
||||||
...editForm,
|
|
||||||
amount: event.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
formatMoney(item.amount)
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<StatusPill status={statusTitle} />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{isEditing ? (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="border-app-green bg-app-green text-white"
|
|
||||||
disabled={busy || !editForm.sorumlu?.trim() || !editForm.tarih}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
onSaveEdit(item.id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CheckIcon />
|
|
||||||
Kaydet
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="border-app-primary bg-white text-app-primary"
|
|
||||||
disabled={busy}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
onCancelEdit()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
İptal
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="border-app-primary bg-white text-app-primary"
|
|
||||||
disabled={busy}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
onEdit(item)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EditIcon />
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="border-app-primary bg-white text-app-primary"
|
|
||||||
disabled={busy || !canStart}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
onStart(item.id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlayIcon />
|
|
||||||
Başlat
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatusPill({ status }: { status: string }) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={classNames(
|
|
||||||
'inline-flex min-h-3 items-center whitespace-nowrap rounded-full bg-[#eef2f7] px-1.5 py-0.5 text-[#344054]',
|
|
||||||
{
|
|
||||||
'bg-[#fff4df] text-app-amber': statusClass(status) === 'pending',
|
|
||||||
'bg-[#e8f5ee] text-app-green': statusClass(status) === 'done',
|
|
||||||
'bg-[#e7f7f5] text-app-teal': statusClass(status) === 'info',
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{status}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue