Workflow problemleri giderildi.

This commit is contained in:
Sedat ÖZTÜRK 2026-07-08 14:53:23 +03:00
parent 17e4716ee8
commit f4d2be65e5
22 changed files with 517 additions and 128 deletions

View file

@ -17,8 +17,8 @@ public class CustomTenantDto
public string Address1 { get; set; }
public string Address2 { get; set; }
public string PostalCode { get; set; }
public string MobileNumber { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public string FaxNumber { get; set; }
public string Email { get; set; }
public string Website { get; set; }

View file

@ -272,6 +272,7 @@ public class ListFormWizardAppService(
input.Workflow ??= new WorkflowDto();
input.Workflow.Criteria = input.WorkflowCriteria;
EnsureUniqueWorkflowCriteriaTitles(input.WorkflowCriteria);
NormalizeWorkflowCriteriaIds(listFormCode, input.WorkflowCriteria);
await repoListForm.InsertAsync(new ListForm
{
@ -469,6 +470,55 @@ public class ListFormWizardAppService(
return string.IsNullOrWhiteSpace(kind) ? "Step" : kind.Trim();
}
private static void NormalizeWorkflowCriteriaIds(string listFormCode, List<ListFormWorkflowCriteriaDto> criteria)
{
if (string.IsNullOrWhiteSpace(listFormCode) || criteria == null || criteria.Count == 0)
{
return;
}
foreach (var item in criteria)
{
item.Id = NormalizeWorkflowCriteriaId(listFormCode, item.Id);
item.ListFormCode = listFormCode;
item.NextOnStart = NormalizeWorkflowTargetId(listFormCode, item.NextOnStart);
item.NextOnTrue = NormalizeWorkflowTargetId(listFormCode, item.NextOnTrue);
item.NextOnFalse = NormalizeWorkflowTargetId(listFormCode, item.NextOnFalse);
item.NextOnApprove = NormalizeWorkflowTargetId(listFormCode, item.NextOnApprove);
item.NextOnReject = NormalizeWorkflowTargetId(listFormCode, item.NextOnReject);
foreach (var outcome in item.CompareOutcomes ?? [])
{
outcome.TargetId = NormalizeWorkflowTargetId(listFormCode, outcome.TargetId);
}
}
}
private static string NormalizeWorkflowTargetId(string listFormCode, string id)
{
return string.IsNullOrWhiteSpace(id)
? string.Empty
: NormalizeWorkflowCriteriaId(listFormCode, id);
}
private static string NormalizeWorkflowCriteriaId(string listFormCode, string id)
{
var trimmed = id?.Trim();
if (string.IsNullOrWhiteSpace(trimmed))
{
return string.Empty;
}
if (trimmed.StartsWith($"{listFormCode}-", StringComparison.OrdinalIgnoreCase))
{
return trimmed;
}
return trimmed.StartsWith("N", StringComparison.OrdinalIgnoreCase)
? $"{listFormCode}-{trimmed}"
: trimmed;
}
/// <summary>
/// Wizard konfigürasyonunu JSON dosyası olarak kaydeder.
/// Önce ContentRootPath'ten yukarı çıkarak Sozsoft.Platform.DbMigrator/Seeds/WizardData dizinini arar.

View file

@ -186,7 +186,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
var code = input.ListFormCode;
var isNew = input.Id.IsNullOrWhiteSpace();
var criteria = isNew
? new ListFormWorkflow(await GenerateNextCriteriaIdAsync())
? new ListFormWorkflow(await GenerateNextCriteriaIdAsync(code))
: await criteriaRepository.GetAsync(input.Id);
if (!isNew && criteria.ListFormCode != code)
@ -201,16 +201,16 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
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.NextOnStart = NormalizeCriteriaTargetId(code, input.NextOnStart);
criteria.NextOnTrue = NormalizeCriteriaTargetId(code, input.NextOnTrue);
criteria.NextOnFalse = NormalizeCriteriaTargetId(code, input.NextOnFalse);
criteria.NextOnApprove = NormalizeCriteriaTargetId(code, input.NextOnApprove);
criteria.NextOnReject = NormalizeCriteriaTargetId(code, input.NextOnReject);
criteria.PositionX = input.PositionX <= 0 ? 32 : input.PositionX;
criteria.PositionY = input.PositionY <= 0 ? 150 : input.PositionY;
criteria.CompareOutcomesJson = SerializeCompareOutcomes(input.CompareOutcomes);
var outcomes = NormalizeCompareOutcomeTargets(code, input.CompareOutcomes);
criteria.CompareOutcomesJson = SerializeCompareOutcomes(outcomes);
var outcomes = input.CompareOutcomes ?? [];
if (criteria.Kind == "Compare" && outcomes.Count > 0)
{
criteria.NextOnTrue = outcomes.ElementAtOrDefault(0)?.TargetId ?? criteria.NextOnTrue;
@ -1107,7 +1107,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
int positionY,
string approver = "")
{
var criteria = new ListFormWorkflow(await GenerateNextCriteriaIdAsync())
var criteria = new ListFormWorkflow(await GenerateNextCriteriaIdAsync(listFormCode))
{
ListFormCode = listFormCode,
Kind = kind,
@ -1130,41 +1130,95 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA
return criteria;
}
private async Task<string> GenerateNextCriteriaIdAsync()
private async Task<string> GenerateNextCriteriaIdAsync(string listFormCode)
{
var criteria = await criteriaRepository.GetListAsync();
var criteria = await criteriaRepository.GetListAsync(x => x.ListFormCode == listFormCode);
var maxNumber = criteria
.Select(x => TryParseCriteriaIdNumber(x.Id))
.Select(x => TryParseCriteriaIdNumber(listFormCode, x.Id))
.DefaultIfEmpty(0)
.Max();
var nextNumber = maxNumber + 1;
var nextId = FormatCriteriaId(nextNumber);
var existingIds = criteria.Select(x => x.Id).ToHashSet();
var nextId = FormatCriteriaId(listFormCode, nextNumber);
var existingIds = criteria.Select(x => x.Id).ToHashSet(StringComparer.OrdinalIgnoreCase);
while (existingIds.Contains(nextId))
{
nextNumber++;
nextId = FormatCriteriaId(nextNumber);
nextId = FormatCriteriaId(listFormCode, nextNumber);
}
return nextId;
}
private static int TryParseCriteriaIdNumber(string id)
private static int TryParseCriteriaIdNumber(string listFormCode, string id)
{
if (id.IsNullOrWhiteSpace() ||
!id.StartsWith(CriteriaIdPrefix, StringComparison.OrdinalIgnoreCase))
var localId = GetLocalCriteriaId(listFormCode, id);
if (localId.IsNullOrWhiteSpace() ||
!localId.StartsWith(CriteriaIdPrefix, StringComparison.OrdinalIgnoreCase))
{
return 0;
}
return int.TryParse(id[CriteriaIdPrefix.Length..], out var number) ? number : 0;
return int.TryParse(localId[CriteriaIdPrefix.Length..], out var number) ? number : 0;
}
private static string FormatCriteriaId(int number)
private static string FormatCriteriaId(string listFormCode, int number)
{
return $"{CriteriaIdPrefix}{number.ToString().PadLeft(CriteriaIdPadding, '0')}";
return $"{listFormCode}-{CriteriaIdPrefix}{number.ToString().PadLeft(CriteriaIdPadding, '0')}";
}
private static string NormalizeCriteriaTargetId(string listFormCode, string id)
{
return id.IsNullOrWhiteSpace()
? string.Empty
: NormalizeCriteriaId(listFormCode, id);
}
private static List<CompareOutcomeDto> NormalizeCompareOutcomeTargets(
string listFormCode,
List<CompareOutcomeDto> outcomes)
{
return (outcomes ?? [])
.Select(outcome => new CompareOutcomeDto
{
Label = outcome.Label,
TargetId = NormalizeCriteriaTargetId(listFormCode, outcome.TargetId),
Conditions = outcome.Conditions ?? []
})
.ToList();
}
private static string NormalizeCriteriaId(string listFormCode, string id)
{
var trimmed = id?.Trim();
if (trimmed.IsNullOrWhiteSpace())
{
return string.Empty;
}
if (trimmed.StartsWith($"{listFormCode}-", StringComparison.OrdinalIgnoreCase))
{
return trimmed;
}
return trimmed.StartsWith(CriteriaIdPrefix, StringComparison.OrdinalIgnoreCase)
? $"{listFormCode}-{trimmed}"
: trimmed;
}
private static string GetLocalCriteriaId(string listFormCode, string id)
{
var normalized = id?.Trim();
if (normalized.IsNullOrWhiteSpace())
{
return string.Empty;
}
var prefix = $"{listFormCode}-";
return normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
? normalized[prefix.Length..]
: normalized;
}
private static bool ClearDeletedTarget(ListFormWorkflow criteria, string deletedId)

View file

@ -8112,6 +8112,12 @@
"tr": "Referans",
"en": "Reference"
},
{
"resourceName": "Platform",
"key": "Public.payment.customer.website",
"tr": "Web Sitesi",
"en": "Website"
},
{
"resourceName": "Platform",
"key": "Public.payment.method.title",
@ -17118,6 +17124,30 @@
"en": "Approval or Rejection Comment",
"tr": "Onay veya red açıklaması"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ApprovalUserName",
"en": "Approval User Name",
"tr": "Onaylayan Kullanıcı Adı"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ApprovalStatus",
"en": "Approval Status",
"tr": "Onay Durumu"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ApprovalDate",
"en": "Approval Date",
"tr": "Onay Tarihi"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ApprovalDescription",
"en": "Approval Description",
"tr": "Onay Açıklaması"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.WorkflowDecisionMessage",

View file

@ -209,11 +209,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
HeaderFilterJson = DefaultHeaderFilterJson(),
SearchPanelJson = DefaultSearchPanelJson(),
GroupPanelJson = DefaultGroupPanelJson(),
SelectionJson = JsonSerializer.Serialize(new SelectionDto
{
Mode = GridOptions.SelectionAllModeAllPages,
AllowSelectAll = true
}),
SelectionJson = DefaultSelectionMultipleJson,
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(AbpIdentity.Permissions.Create, listFormName, AbpIdentity.Permissions.Update, AbpIdentity.Permissions.Delete, AbpIdentity.Permissions.Export, AbpIdentity.Permissions.Import, AbpIdentity.Permissions.Note),
PagerOptionJson = DefaultPagerOptionJson,

View file

@ -84,7 +84,7 @@ public static class ListFormSeeder_DefaultJsons
});
public static readonly string DefaultSelectionSingleJson = JsonSerializer.Serialize(new SelectionDto
{
Mode = GridOptions.SelectionModeNone,
Mode = GridOptions.SelectionModeSingle,
AllowSelectAll = false
});
public static readonly string DefaultSelectionMultipleJson = JsonSerializer.Serialize(new SelectionDto

View file

@ -26,6 +26,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
{
private readonly IRepository<ListForm, Guid> _listFormRepository;
private readonly IRepository<ListFormField, Guid> _listFormFieldRepository;
private readonly IRepository<ListFormWorkflow, string> _listFormWorkflowRepository;
private readonly IdentityUserManager _identityUserManager;
private readonly IdentityRoleManager _identityRoleManager;
private readonly IConfiguration _configuration;
@ -34,6 +35,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
public ListFormSeeder_Saas(
IRepository<ListForm, Guid> listFormRepository,
IRepository<ListFormField, Guid> listFormFieldRepository,
IRepository<ListFormWorkflow, string> listFormWorkflowRepository,
IdentityUserManager userManager,
IdentityRoleManager roleManager,
IConfiguration configuration,
@ -42,6 +44,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
{
_listFormRepository = listFormRepository;
_listFormFieldRepository = listFormFieldRepository;
_listFormWorkflowRepository = listFormWorkflowRepository;
_identityUserManager = userManager;
_identityRoleManager = roleManager;
_configuration = configuration;
@ -452,15 +455,15 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "MobileNumber",
CaptionName = "App.Listform.ListformField.MobileNumber",
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
Width = 0,
ListOrderNo = 16,
Visible = true,
IsActive = true,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
EditorOptions = EditorOptionValues.PhoneEditorOptions,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(TenantManagementPermissions.Tenants.Create, TenantManagementPermissions.Tenants.Default, TenantManagementPermissions.Tenants.Update, true, true, false),
PivotSettingsJson = DefaultPivotSettingsJson
@ -470,8 +473,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
FieldName = "MobileNumber",
CaptionName = "App.Listform.ListformField.MobileNumber",
Width = 0,
ListOrderNo = 17,
Visible = true,
@ -938,16 +941,16 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "MobileNumber",
CaptionName = "App.Listform.ListformField.MobileNumber",
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
Width = 0,
ListOrderNo = 15,
Visible = true,
IsActive = true,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
EditorOptions = EditorOptionValues.PhoneEditorOptions,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
@ -957,8 +960,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "PhoneNumber",
CaptionName = "Abp.Identity.User.UserInformation.PhoneNumber",
FieldName = "MobileNumber",
CaptionName = "App.Listform.ListformField.MobileNumber",
Width = 0,
ListOrderNo = 16,
Visible = true,
@ -6987,7 +6990,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
orderId = "@Id"
}),
IsVisible = true,
VisibleExpression = "(e) => !e.row?.data?.TenantId",
VisibleExpression = "(e) => !e.row?.data?.TenantId && e.row?.data?.ApprovalStatus === 'Approved'",
},
}),
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>
@ -7051,8 +7054,25 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
}
}
},
})
});
}),
WorkflowJson = JsonSerializer.Serialize(new WorkflowDto
{
ApprovalUserFieldName = "ApprovalUserName",
ApprovalStatusFieldName = "ApprovalStatus",
ApprovalDateFieldName = "ApprovalDate",
ApprovalDescriptionFieldName = "ApprovalDescription",
Criteria = []
}),
}, autoSave: true);
#region Sales Order Workflow
await _listFormWorkflowRepository.InsertManyAsync([
new ListFormWorkflow($"{listFormName}-N001") { ListFormCode = listFormName, Kind = "Start", Title = "İş Akışı Başlat1", CompareColumn="Price", CompareOperator=">", CompareValue=5000, Approver= "", NextOnStart = $"{listFormName}-N002", NextOnTrue = "", NextOnFalse = "", NextOnApprove= "", NextOnReject = "", PositionX = 47, PositionY = 76 },
new ListFormWorkflow($"{listFormName}-N002") { ListFormCode = listFormName, Kind = "Approval", Title = "Onay1", CompareColumn="Price", CompareOperator=">", CompareValue=5000, Approver = "system@sozsoft.com", NextOnStart = "", NextOnTrue = "", NextOnFalse = "", NextOnApprove = $"{listFormName}-N003", NextOnReject = $"{listFormName}-N004", PositionX = 391, PositionY = 76 },
new ListFormWorkflow($"{listFormName}-N003") { ListFormCode = listFormName, Kind = "End", Title = "Approved", CompareColumn="Price", CompareOperator=">", CompareValue=5000, Approver= "", NextOnStart = "", NextOnTrue = "", NextOnFalse = "", NextOnApprove = "", NextOnReject = "", PositionX = 843, PositionY = 61 },
new ListFormWorkflow($"{listFormName}-N004") { ListFormCode = listFormName, Kind = "End", Title = "Rejected", CompareColumn="Price", CompareOperator=">", CompareValue=5000, Approver= "", NextOnStart = "", NextOnTrue = "", NextOnFalse = "", NextOnApprove = "", NextOnReject = "", PositionX = 845, PositionY = 263 },
]);
#endregion
#region Sales Order Fields
await _listFormFieldRepository.InsertManyAsync([
@ -7091,7 +7111,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
LookupQuery = LookupQueryValues.TenantValues(),
}),
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
@ -7441,7 +7460,6 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
AllowSearch = true,
EditorOptions = EditorOptionValues.PhoneEditorOptions,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
@ -7616,6 +7634,8 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
AllowAdding = false,
AllowEditing = false,
},
new()
{
@ -7652,6 +7672,74 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "ApprovalUserName",
CaptionName = "App.Listform.ListformField.ApprovalUserName",
Width = 0,
ListOrderNo = 31,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "ApprovalStatus",
CaptionName = "App.Listform.ListformField.ApprovalStatus",
Width = 0,
ListOrderNo = 32,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.DateTime,
FieldName = "ApprovalDate",
CaptionName = "App.Listform.ListformField.ApprovalDate",
Width = 0,
ListOrderNo = 33,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "ApprovalDescription",
CaptionName = "App.Listform.ListformField.ApprovalDescription",
Width = 0,
ListOrderNo = 34,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
}
]);
#endregion
@ -7907,6 +7995,23 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
EditorOptions=EditorOptionValues.NumberStandartFormat(),
TotalSummaryJson = DefaultColumnTotalSummaryJson("TotalPrice")
},
new()
{
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "OrderId",
CaptionName = "App.Listform.ListformField.OrderId",
Width = 0,
ListOrderNo = 10,
Visible = false,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson,
}
], autoSave: true);
#endregion
}

View file

@ -148,6 +148,7 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode)
? code
: input.ListFormCode.Trim();
NormalizeWorkflowCriteriaIds(listFormCode, input.WorkflowCriteria);
var titleLangKey = $"{listFormCode}.Title";
var nameLangKey = code;
var descLangKey = $"{listFormCode}.Desc";
@ -559,6 +560,55 @@ public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
return string.IsNullOrWhiteSpace(kind) ? "Step" : kind.Trim();
}
private static void NormalizeWorkflowCriteriaIds(string listFormCode, List<ListFormWorkflowCriteriaDto> criteria)
{
if (string.IsNullOrWhiteSpace(listFormCode) || criteria == null || criteria.Count == 0)
{
return;
}
foreach (var item in criteria)
{
item.Id = NormalizeWorkflowCriteriaId(listFormCode, item.Id);
item.ListFormCode = listFormCode;
item.NextOnStart = NormalizeWorkflowTargetId(listFormCode, item.NextOnStart);
item.NextOnTrue = NormalizeWorkflowTargetId(listFormCode, item.NextOnTrue);
item.NextOnFalse = NormalizeWorkflowTargetId(listFormCode, item.NextOnFalse);
item.NextOnApprove = NormalizeWorkflowTargetId(listFormCode, item.NextOnApprove);
item.NextOnReject = NormalizeWorkflowTargetId(listFormCode, item.NextOnReject);
foreach (var outcome in item.CompareOutcomes ?? [])
{
outcome.TargetId = NormalizeWorkflowTargetId(listFormCode, outcome.TargetId);
}
}
}
private static string NormalizeWorkflowTargetId(string listFormCode, string id)
{
return string.IsNullOrWhiteSpace(id)
? string.Empty
: NormalizeWorkflowCriteriaId(listFormCode, id);
}
private static string NormalizeWorkflowCriteriaId(string listFormCode, string id)
{
var trimmed = id?.Trim();
if (string.IsNullOrWhiteSpace(trimmed))
{
return string.Empty;
}
if (trimmed.StartsWith($"{listFormCode}-", StringComparison.OrdinalIgnoreCase))
{
return trimmed;
}
return trimmed.StartsWith("N", StringComparison.OrdinalIgnoreCase)
? $"{listFormCode}-{trimmed}"
: trimmed;
}
private async Task CreateLangKeyAsync(string key, string textEn, string textTr)
{
if (string.IsNullOrWhiteSpace(key)) return;

View file

@ -42,6 +42,11 @@ public class Order : FullAuditedEntity<Guid>
public int? Installment { get; set; }
public string PaymentDataJson { get; set; } // JSON olarak saklanacak
public string ApprovalUserName { get; set; }
public string ApprovalStatus { get; set; }
public DateTime? ApprovalDate { get; set; }
public string ApprovalDescription { get; set; }
public List<OrderItem> Items { get; set; } = new();
public Order()

View file

@ -406,6 +406,7 @@ public class PlatformDbContext :
b.HasMany<ListFormField>().WithOne().HasForeignKey(x => x.ListFormCode).HasPrincipalKey(x => x.ListFormCode).IsRequired();
b.HasMany<ListFormCustomization>().WithOne().HasForeignKey(x => x.ListFormCode).HasPrincipalKey(x => x.ListFormCode).IsRequired();
b.HasMany<ListFormImport>().WithOne().HasForeignKey(x => x.ListFormCode).HasPrincipalKey(x => x.ListFormCode).IsRequired();
b.HasMany<ListFormWorkflow>().WithOne().HasForeignKey(x => x.ListFormCode).HasPrincipalKey(x => x.ListFormCode).IsRequired();
});
builder.Entity<ListFormField>(b =>
@ -498,7 +499,7 @@ public class PlatformDbContext :
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.ListFormWorkflow)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Id).HasMaxLength(50);
b.Property(x => x.Id).HasMaxLength(128);
b.Property(x => x.ListFormCode).IsRequired().HasMaxLength(64);
b.Property(x => x.Kind).IsRequired().HasMaxLength(50);
b.Property(x => x.Title).IsRequired().HasMaxLength(250);
@ -506,15 +507,14 @@ public class PlatformDbContext :
b.Property(x => x.CompareOperator).IsRequired().HasMaxLength(20);
b.Property(x => x.CompareValue).HasPrecision(18, 2);
b.Property(x => x.Approver).IsRequired().HasMaxLength(250);
b.Property(x => x.NextOnStart).IsRequired().HasMaxLength(50);
b.Property(x => x.NextOnTrue).IsRequired().HasMaxLength(50);
b.Property(x => x.NextOnFalse).IsRequired().HasMaxLength(50);
b.Property(x => x.NextOnApprove).IsRequired().HasMaxLength(50);
b.Property(x => x.NextOnReject).IsRequired().HasMaxLength(50);
b.Property(x => x.NextOnStart).IsRequired().HasMaxLength(128);
b.Property(x => x.NextOnTrue).IsRequired().HasMaxLength(128);
b.Property(x => x.NextOnFalse).IsRequired().HasMaxLength(128);
b.Property(x => x.NextOnApprove).IsRequired().HasMaxLength(128);
b.Property(x => x.NextOnReject).IsRequired().HasMaxLength(128);
b.Property(x => x.PositionX).IsRequired();
b.Property(x => x.PositionY).IsRequired();
b.Property(x => x.CompareOutcomesJson).HasColumnType("text");
b.HasIndex(x => new { x.ListFormCode, x.Title }).IsUnique();
});
builder.Entity<Note>(b =>
@ -1047,6 +1047,10 @@ public class PlatformDbContext :
b.Property(o => o.PaymentDataJson).HasMaxLength(4096);
b.Property(o => o.ApprovalUserName).HasMaxLength(256);
b.Property(o => o.ApprovalStatus).HasMaxLength(64);
b.Property(o => o.ApprovalDescription).HasMaxLength(256);
b.HasMany(o => o.Items)
.WithOne(i => i.Order)
.HasForeignKey(i => i.OrderId)

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20260707205022_Initial")]
[Migration("20260708113003_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -3652,8 +3652,8 @@ namespace Sozsoft.Platform.Migrations
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
{
b.Property<string>("Id")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Approver")
.IsRequired()
@ -3689,28 +3689,28 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("NextOnApprove")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnFalse")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnReject")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnStart")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnTrue")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<int>("PositionX")
.HasColumnType("int");
@ -3725,8 +3725,7 @@ namespace Sozsoft.Platform.Migrations
b.HasKey("Id");
b.HasIndex("ListFormCode", "Title")
.IsUnique();
b.HasIndex("ListFormCode");
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
});
@ -4088,6 +4087,21 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime?>("ApprovalDate")
.HasColumnType("datetime2");
b.Property<string>("ApprovalDescription")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ApprovalStatus")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("ApprovalUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("BillingCycle")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
@ -8728,6 +8742,16 @@ namespace Sozsoft.Platform.Migrations
.IsRequired();
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
{
b.HasOne("Sozsoft.Platform.Entities.ListForm", null)
.WithMany()
.HasForeignKey("ListFormCode")
.HasPrincipalKey("ListFormCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversationMessage", b =>
{
b.HasOne("Sozsoft.Platform.Entities.MessengerConversation", "Conversation")

View file

@ -1465,32 +1465,6 @@ namespace Sozsoft.Platform.Migrations
table.UniqueConstraint("AK_Sas_H_ListForm_ListFormCode", x => x.ListFormCode);
});
migrationBuilder.CreateTable(
name: "Sas_H_ListFormWorkflow",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
ListFormCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Kind = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Title = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
CompareColumn = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
CompareOperator = 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),
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)
},
constraints: table =>
{
table.PrimaryKey("PK_Sas_H_ListFormWorkflow", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sas_H_LogEntry",
columns: table => new
@ -1601,6 +1575,10 @@ namespace Sozsoft.Platform.Migrations
PaymentMethodId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Installment = table.Column<int>(type: "int", nullable: true),
PaymentDataJson = table.Column<string>(type: "nvarchar(max)", maxLength: 4096, nullable: true),
ApprovalUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
ApprovalStatus = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
ApprovalDate = table.Column<DateTime>(type: "datetime2", nullable: true),
ApprovalDescription = table.Column<string>(type: "nvarchar(256)", maxLength: 256, 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),
@ -2852,6 +2830,38 @@ namespace Sozsoft.Platform.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Sas_H_ListFormWorkflow",
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
ListFormCode = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Kind = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Title = table.Column<string>(type: "nvarchar(250)", maxLength: 250, nullable: false),
CompareColumn = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
CompareOperator = 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),
NextOnStart = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
NextOnTrue = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
NextOnFalse = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
NextOnApprove = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
NextOnReject = table.Column<string>(type: "nvarchar(128)", maxLength: 128, 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)
},
constraints: table =>
{
table.PrimaryKey("PK_Sas_H_ListFormWorkflow", x => x.Id);
table.ForeignKey(
name: "FK_Sas_H_ListFormWorkflow_Sas_H_ListForm_ListFormCode",
column: x => x.ListFormCode,
principalTable: "Sas_H_ListForm",
principalColumn: "ListFormCode",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Sas_H_NotificationRule",
columns: table => new
@ -4094,10 +4104,9 @@ namespace Sozsoft.Platform.Migrations
column: "ImportId");
migrationBuilder.CreateIndex(
name: "IX_Sas_H_ListFormWorkflow_ListFormCode_Title",
name: "IX_Sas_H_ListFormWorkflow_ListFormCode",
table: "Sas_H_ListFormWorkflow",
columns: new[] { "ListFormCode", "Title" },
unique: true);
column: "ListFormCode");
migrationBuilder.CreateIndex(
name: "IX_Sas_H_Menu_Code",

View file

@ -3649,8 +3649,8 @@ namespace Sozsoft.Platform.Migrations
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
{
b.Property<string>("Id")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("Approver")
.IsRequired()
@ -3686,28 +3686,28 @@ namespace Sozsoft.Platform.Migrations
b.Property<string>("NextOnApprove")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnFalse")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnReject")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnStart")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<string>("NextOnTrue")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<int>("PositionX")
.HasColumnType("int");
@ -3722,8 +3722,7 @@ namespace Sozsoft.Platform.Migrations
b.HasKey("Id");
b.HasIndex("ListFormCode", "Title")
.IsUnique();
b.HasIndex("ListFormCode");
b.ToTable("Sas_H_ListFormWorkflow", (string)null);
});
@ -4085,6 +4084,21 @@ namespace Sozsoft.Platform.Migrations
.HasMaxLength(512)
.HasColumnType("nvarchar(512)");
b.Property<DateTime?>("ApprovalDate")
.HasColumnType("datetime2");
b.Property<string>("ApprovalDescription")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ApprovalStatus")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("ApprovalUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("BillingCycle")
.HasMaxLength(32)
.HasColumnType("nvarchar(32)");
@ -8725,6 +8739,16 @@ namespace Sozsoft.Platform.Migrations
.IsRequired();
});
modelBuilder.Entity("Sozsoft.Platform.Entities.ListFormWorkflow", b =>
{
b.HasOne("Sozsoft.Platform.Entities.ListForm", null)
.WithMany()
.HasForeignKey("ListFormCode")
.HasPrincipalKey("ListFormCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Sozsoft.Platform.Entities.MessengerConversationMessage", b =>
{
b.HasOne("Sozsoft.Platform.Entities.MessengerConversation", "Conversation")

View file

@ -376,7 +376,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager
{
var orderQuery = await orderRepository.WithDetailsAsync(order => order.Items);
var activeOrders = await orderQuery
.Where(order => order.TenantId == tenantId && order.IsActive)
.Where(order => order.TenantId == tenantId && order.ApprovalStatus == "Approved" && order.IsActive)
.ToListAsync();
return activeOrders

View file

@ -26,7 +26,6 @@ export const BillingControls: React.FC<BillingControlsProps> = ({
cartItemsCount,
cartState,
}) => {
// Check if cart has items to prevent billing cycle changes
const hasCartItems = cartState.items.length > 0
const isScrolled = useScroll()
const { translate } = useLocalization()

View file

@ -50,9 +50,9 @@ export const OrderSuccess: React.FC<OrderSuccessProps> = ({ orderId, order }) =>
const billingCycleLabel =
order?.billingCycle === 'monthly'
? 'Aylık'
? translate('::Public.products.billingcycle.monthly')
: order?.billingCycle === 'yearly'
? 'Yıllık'
? translate('::Public.products.billingcycle.yearly')
: '-'
const getItemPrice = (item: BasketItem) =>

View file

@ -140,7 +140,12 @@ export const PaymentForm: React.FC<PaymentFormProps> = ({ onBack, onComplete, ca
year: 'numeric',
}).format(date)
const billingCycleLabel = billingCycle === 'monthly' ? 'Aylık' : 'Yıllık'
const billingCycleLabel =
billingCycle === 'monthly'
? translate('::Public.products.billingcycle.monthly')
: billingCycle === 'yearly'
? translate('::Public.products.billingcycle.yearly')
: '-'
return (
<div className="mx-auto px-4 lg:px-8 mt-5">

View file

@ -87,7 +87,6 @@ export const ProductCard: React.FC<ProductCardProps> = ({
return globalPeriod > 1 ? ` (${globalPeriod} ${periodText})` : ''
}
// Check if product has valid price for current billing cycle
const hasValidPrice = () => {
if (globalBillingCycle === 'monthly' && !product.monthlyPrice) return false
if (globalBillingCycle === 'yearly' && !product.yearlyPrice) return false

View file

@ -48,7 +48,6 @@ export const ProductCatalog: React.FC<ProductCatalogProps> = ({
setCartState((currentState) => addItemToCart(currentState, product, quantity, billingCycle))
}
// Filter products based on billing cycle - only show products that have pricing for the selected cycle
const availableProducts = useMemo(() => {
return products.filter((product) => {
// If monthly billing is selected, only show products with monthlyPrice

View file

@ -15,6 +15,7 @@ import {
FaUser,
FaUserPlus,
FaCheckCircle,
FaSitemap,
} from 'react-icons/fa'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
@ -1031,19 +1032,37 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.reference')}
</label>
<div className="relative">
<FaUserPlus className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder={translate('::Public.payment.customer.reference')}
value={formData.reference || ''}
onChange={(e) => handleInputChange('reference', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.website')}
</label>
<div className="relative">
<FaSitemap className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
required
placeholder={translate('::Public.payment.customer.website')}
value={formData.website || ''}
onChange={(e) => handleInputChange('website', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{translate('::Public.payment.customer.reference')}
</label>
<div className="relative">
<FaUserPlus className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder={translate('::Public.payment.customer.reference')}
value={formData.reference || ''}
onChange={(e) => handleInputChange('reference', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
</div>
</div>

View file

@ -302,6 +302,11 @@ export function FormTabWorkflow(
}
const beginLink = (sourceId: string, outcome: string) => {
if (pendingLink && pendingLink.sourceId !== sourceId) {
connectNodes(pendingLink.sourceId, pendingLink.outcome, sourceId)
return
}
setPendingLink({ sourceId, outcome })
setSelectedId(sourceId)
}

View file

@ -36,6 +36,7 @@ type FlowNodeProps = {
onOpenDetails: () => void
onDelete: () => void
onBeginLink: (sourceId: string, outcome: string) => void
onConnect: (sourceId: string, outcome: string, targetId: string) => void
}
export function WorkflowCanvas({
@ -241,6 +242,7 @@ export function WorkflowCanvas({
onOpenDetails={() => onOpenDetails(item.id)}
onDelete={() => onDelete(item.id)}
onBeginLink={onBeginLink}
onConnect={onConnect}
/>
))}
<svg
@ -274,6 +276,7 @@ function FlowNode({
onOpenDetails,
onDelete,
onBeginLink,
onConnect,
}: FlowNodeProps) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id: item.id,
@ -362,15 +365,24 @@ function FlowNode({
},
)}
onPointerDown={(event) => event.stopPropagation()}
onPointerUp={(event) => event.stopPropagation()}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
if (pendingLink && pendingLink.sourceId !== item.id) {
onConnect(pendingLink.sourceId, pendingLink.outcome, item.id)
return
}
onBeginLink(item.id, outcome.field)
}}
onKeyDown={(event) => {
if (event.key !== 'Enter' && event.key !== ' ') return
event.preventDefault()
event.stopPropagation()
if (pendingLink && pendingLink.sourceId !== item.id) {
onConnect(pendingLink.sourceId, pendingLink.outcome, item.id)
return
}
onBeginLink(item.id, outcome.field)
}}
>