feat(core): Добавлены помощники по клонированию JobGroup и синхронизации Job в JobGroup по старшему MaxValueRelationships
This commit is contained in:
182
PARR.Core/Common/Helpers/JobGroupCloneHelper.cs
Normal file
182
PARR.Core/Common/Helpers/JobGroupCloneHelper.cs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.Core.Repositories.Interfaces.Job;
|
||||||
|
using PARR.Domain.Entities.Base.History;
|
||||||
|
using PARR.Domain.Entities.Job;
|
||||||
|
using PARR.Domain.Entities.Schedule;
|
||||||
|
|
||||||
|
namespace PARR.Core.Common.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сервис для полного клонирования JobGroup со всеми вложенными Job, фильтрами, AutoControl и EsppSchValues.
|
||||||
|
/// Шаблоны (Template) не клонируются. Идентификаторы генерируются стратегией EF Core.
|
||||||
|
/// </summary>
|
||||||
|
public class JobGroupCloneHelper
|
||||||
|
{
|
||||||
|
private readonly IJobGroupRepository groupRepository;
|
||||||
|
private readonly IJobRepository jobRepository;
|
||||||
|
private readonly ILogger<JobGroupCloneHelper> logger;
|
||||||
|
|
||||||
|
public JobGroupCloneHelper(
|
||||||
|
IJobGroupRepository groupRepository,
|
||||||
|
IJobRepository jobRepository,
|
||||||
|
ILogger<JobGroupCloneHelper> logger)
|
||||||
|
{
|
||||||
|
this.groupRepository = groupRepository;
|
||||||
|
this.jobRepository = jobRepository;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Guid> CloneAsync(
|
||||||
|
Guid sourceJobGroupId,
|
||||||
|
string? newGroupName,
|
||||||
|
HistoryInitiator initiator,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var sourceGroup = await groupRepository.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(g => g.Jobs)
|
||||||
|
.ThenInclude(j => j.UnitFilters)
|
||||||
|
.ThenInclude(uf => uf.FieldFilters)
|
||||||
|
.Include(g => g.Jobs)
|
||||||
|
.ThenInclude(j => j.UnitFilters)
|
||||||
|
.ThenInclude(uf => uf.RelationshipFilters)
|
||||||
|
.Include(g => g.Jobs)
|
||||||
|
.ThenInclude(j => j.AutoControl)
|
||||||
|
.Include(g => g.EsppSchValues)
|
||||||
|
.FirstOrDefaultAsync(g => g.Id == sourceJobGroupId, ct);
|
||||||
|
|
||||||
|
if (sourceGroup == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"JobGroup {sourceJobGroupId} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Начало клонирования JobGroup {JobGroupId}.", sourceJobGroupId);
|
||||||
|
|
||||||
|
var newGroup = MapJobGroup(sourceGroup, newGroupName);
|
||||||
|
|
||||||
|
foreach (var sourceJob in sourceGroup.Jobs ?? Enumerable.Empty<Job>())
|
||||||
|
{
|
||||||
|
var newJob = MapJob(sourceJob);
|
||||||
|
newGroup.Jobs.Add(newJob);
|
||||||
|
|
||||||
|
CloneJobFilters(sourceJob.UnitFilters, newJob);
|
||||||
|
|
||||||
|
if (sourceJob.AutoControl != null)
|
||||||
|
{
|
||||||
|
newJob.AutoControl = MapAutoControl(sourceJob.AutoControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Клонирование значений расписания
|
||||||
|
foreach (var sourceSchValue in sourceGroup.EsppSchValues ?? Enumerable.Empty<EsppSchValue>())
|
||||||
|
{
|
||||||
|
newGroup.EsppSchValues.Add(new EsppSchValue
|
||||||
|
{
|
||||||
|
TypeValueId = sourceSchValue.TypeValueId,
|
||||||
|
TypeConfigId = sourceSchValue.TypeConfigId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSuccess = await groupRepository.CreateAsync(newGroup);
|
||||||
|
if (!isSuccess)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Не удалось сохранить клон JobGroup в базу данных.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await groupRepository.CommitAsync(initiator);
|
||||||
|
|
||||||
|
logger.LogInformation("JobGroup успешно клонирована. Новый ID: {NewGroupId}.", newGroup.Id);
|
||||||
|
|
||||||
|
return newGroup.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobGroup MapJobGroup(JobGroup source, string? newName)
|
||||||
|
{
|
||||||
|
string finalName = string.IsNullOrWhiteSpace(newName)
|
||||||
|
? $"{source.GroupName?.TrimEnd()} КЛОН"
|
||||||
|
: newName.Trim();
|
||||||
|
|
||||||
|
return new JobGroup
|
||||||
|
{
|
||||||
|
GroupName = finalName,
|
||||||
|
ShortDescription = source.ShortDescription,
|
||||||
|
FullDescription = source.FullDescription,
|
||||||
|
Solution = source.Solution,
|
||||||
|
TemplateDuration = source.TemplateDuration,
|
||||||
|
ReferenceDate = source.ReferenceDate,
|
||||||
|
UserTimeZoneOffsetMinutes = source.UserTimeZoneOffsetMinutes,
|
||||||
|
IsResponseAreaTimezone = source.IsResponseAreaTimezone,
|
||||||
|
IsAutoDistributionEnabled = source.IsAutoDistributionEnabled,
|
||||||
|
IsAgent = source.IsAgent,
|
||||||
|
AgentName = source.AgentName,
|
||||||
|
AgentTimeOutSec = source.AgentTimeOutSec,
|
||||||
|
AgentScript = source.AgentScript,
|
||||||
|
GroupTypeId = source.GroupTypeId,
|
||||||
|
GroupingUnitFieldId = source.GroupingUnitFieldId,
|
||||||
|
IsGroupByResponsible = source.IsGroupByResponsible,
|
||||||
|
ScheduleExcludeTypeId = source.ScheduleExcludeTypeId,
|
||||||
|
ScheduleExcludeTypeCalendarId = source.ScheduleExcludeTypeCalendarId,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
Jobs = new HashSet<Job>(),
|
||||||
|
EsppSchValues = new HashSet<EsppSchValue>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Job MapJob(Job source)
|
||||||
|
{
|
||||||
|
return new Job
|
||||||
|
{
|
||||||
|
Name = source.Name,
|
||||||
|
WorkName = source.WorkName,
|
||||||
|
MinValueRelationships = source.MinValueRelationships,
|
||||||
|
MaxValueRelationships = source.MaxValueRelationships,
|
||||||
|
IsParentRelationships = source.IsParentRelationships,
|
||||||
|
TemplateNameMask = source.TemplateNameMask,
|
||||||
|
WorkGroupMask = source.WorkGroupMask,
|
||||||
|
ResponseAreaMask = source.ResponseAreaMask,
|
||||||
|
TnkId = source.TnkId,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
UnitFilters = new HashSet<JobUnitFilter>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobAutoControl MapAutoControl(JobAutoControl source)
|
||||||
|
{
|
||||||
|
return new JobAutoControl
|
||||||
|
{
|
||||||
|
InitUsedTemplateState = source.InitUsedTemplateState,
|
||||||
|
InitUsedScheduleState = source.InitUsedScheduleState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloneJobFilters(IEnumerable<JobUnitFilter>? sourceFilters, Job targetJob)
|
||||||
|
{
|
||||||
|
if (sourceFilters == null) return;
|
||||||
|
|
||||||
|
foreach (var sourceFilter in sourceFilters)
|
||||||
|
{
|
||||||
|
var newFilter = new JobUnitFilter
|
||||||
|
{
|
||||||
|
UnitFilter = sourceFilter.UnitFilter,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
FieldFilters = sourceFilter.FieldFilters?.Select(f => new JobFieldFilter
|
||||||
|
{
|
||||||
|
FieldId = f.FieldId,
|
||||||
|
ValueMask = f.ValueMask,
|
||||||
|
IsInverse = f.IsInverse,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow
|
||||||
|
}).ToList() ?? new List<JobFieldFilter>(),
|
||||||
|
RelationshipFilters = sourceFilter.RelationshipFilters?.Select(r => new JobRelationshipFilter
|
||||||
|
{
|
||||||
|
FieldId = r.FieldId,
|
||||||
|
ValueMask = r.ValueMask,
|
||||||
|
IsInverse = r.IsInverse,
|
||||||
|
IsParent = r.IsParent,
|
||||||
|
IsFullMatch = r.IsFullMatch
|
||||||
|
}).ToList() ?? new List<JobRelationshipFilter>()
|
||||||
|
};
|
||||||
|
targetJob.UnitFilters.Add(newFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
188
PARR.Core/Common/Helpers/JobGroupUnitFilterSynchronizeHelper.cs
Normal file
188
PARR.Core/Common/Helpers/JobGroupUnitFilterSynchronizeHelper.cs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.Core.Repositories.Interfaces.Job;
|
||||||
|
using PARR.Domain.Entities.Base.History;
|
||||||
|
using PARR.Domain.Entities.Job;
|
||||||
|
|
||||||
|
namespace PARR.Core.Common.Helpers;
|
||||||
|
|
||||||
|
public class JobGroupUnitFilterSynchronizeHelper
|
||||||
|
{
|
||||||
|
private readonly IJobRepository jobRepository;
|
||||||
|
private readonly ILogger<JobGroupUnitFilterSynchronizeHelper> logger;
|
||||||
|
|
||||||
|
public JobGroupUnitFilterSynchronizeHelper(IJobRepository jobRepository, ILogger<JobGroupUnitFilterSynchronizeHelper> logger)
|
||||||
|
{
|
||||||
|
this.jobRepository = jobRepository;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SynchronizeGroupFiltersAsync(Guid jobGroupId, HistoryInitiator initiator, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var jobs = await jobRepository.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(j => j.UnitFilters)
|
||||||
|
.ThenInclude(uf => uf.FieldFilters)
|
||||||
|
.Include(j => j.UnitFilters)
|
||||||
|
.ThenInclude(uf => uf.RelationshipFilters)
|
||||||
|
.Where(j => j.GroupId == jobGroupId)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
if (jobs == null || jobs.Count < 2)
|
||||||
|
{
|
||||||
|
logger.LogDebug("JobGroup {JobGroupId} содержит менее двух Job. Синхронизация фильтров не требуется.", jobGroupId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var referenceJob = jobs.OrderByDescending(j => j.MaxValueRelationships).FirstOrDefault();
|
||||||
|
if (referenceJob == null)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Не удалось определить эталонный Job для JobGroup {JobGroupId}.", jobGroupId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Начало синхронизации фильтров для JobGroup {JobGroupId}. Эталонный Job: {ReferenceJobId}.", jobGroupId, referenceJob.Id);
|
||||||
|
|
||||||
|
foreach (var job in jobs)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
if (job.Id == referenceJob.Id) continue;
|
||||||
|
|
||||||
|
if (!AreUnitFiltersIdentical(job.UnitFilters, referenceJob.UnitFilters))
|
||||||
|
{
|
||||||
|
logger.LogDebug("Фильтры Job {JobId} отличаются от эталона. Запуск обновления.", job.Id);
|
||||||
|
await ApplyFilterCorrectionAsync(job.Id, referenceJob.UnitFilters ?? Enumerable.Empty<JobUnitFilter>(), initiator, ct);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogDebug("Фильтры Job {JobId} идентичны эталону. Пропуск.", job.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Синхронизация фильтров для JobGroup {JobGroupId} завершена.", jobGroupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ApplyFilterCorrectionAsync(Guid jobId, IEnumerable<JobUnitFilter> referenceFilters, HistoryInitiator initiator, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var job = await jobRepository.Get()
|
||||||
|
.Include(j => j.UnitFilters)
|
||||||
|
.ThenInclude(uf => uf.FieldFilters)
|
||||||
|
.Include(j => j.UnitFilters)
|
||||||
|
.ThenInclude(uf => uf.RelationshipFilters)
|
||||||
|
.FirstOrDefaultAsync(j => j.Id == jobId, ct);
|
||||||
|
|
||||||
|
if (job == null) return;
|
||||||
|
|
||||||
|
if (job.UnitFilters == null)
|
||||||
|
{
|
||||||
|
job.UnitFilters = new List<JobUnitFilter>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
job.UnitFilters.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var clonedFilters = CloneFilters(referenceFilters, job.Id);
|
||||||
|
foreach (var filter in clonedFilters)
|
||||||
|
{
|
||||||
|
job.UnitFilters!.Add(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
await jobRepository.CommitAsync(initiator);
|
||||||
|
logger.LogInformation("Фильтры Job {JobId} успешно обновлены.", jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AreUnitFiltersIdentical(IEnumerable<JobUnitFilter>? current, IEnumerable<JobUnitFilter>? reference)
|
||||||
|
{
|
||||||
|
var currentList = (current ?? Enumerable.Empty<JobUnitFilter>()).ToList();
|
||||||
|
var refList = (reference ?? Enumerable.Empty<JobUnitFilter>()).ToList();
|
||||||
|
|
||||||
|
if (currentList.Count != refList.Count) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < currentList.Count; i++)
|
||||||
|
{
|
||||||
|
if (!AreFiltersEqual(currentList[i], refList[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AreFiltersEqual(JobUnitFilter a, JobUnitFilter b)
|
||||||
|
{
|
||||||
|
if (!AreFieldFiltersEqual(a.FieldFilters, b.FieldFilters)) return false;
|
||||||
|
if (!AreRelationshipFiltersEqual(a.RelationshipFilters, b.RelationshipFilters)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AreFieldFiltersEqual(IEnumerable<JobFieldFilter>? a, IEnumerable<JobFieldFilter>? b)
|
||||||
|
{
|
||||||
|
var listA = (a ?? Enumerable.Empty<JobFieldFilter>()).OrderBy(f => f.FieldId).ToList();
|
||||||
|
var listB = (b ?? Enumerable.Empty<JobFieldFilter>()).OrderBy(f => f.FieldId).ToList();
|
||||||
|
|
||||||
|
if (listA.Count != listB.Count) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < listA.Count; i++)
|
||||||
|
{
|
||||||
|
if (listA[i].FieldId != listB[i].FieldId ||
|
||||||
|
!string.Equals(listA[i].ValueMask, listB[i].ValueMask, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
listA[i].IsInverse != listB[i].IsInverse)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool AreRelationshipFiltersEqual(IEnumerable<JobRelationshipFilter>? a, IEnumerable<JobRelationshipFilter>? b)
|
||||||
|
{
|
||||||
|
var listA = (a ?? Enumerable.Empty<JobRelationshipFilter>()).OrderBy(f => f.FieldId).ToList();
|
||||||
|
var listB = (b ?? Enumerable.Empty<JobRelationshipFilter>()).OrderBy(f => f.FieldId).ToList();
|
||||||
|
|
||||||
|
if (listA.Count != listB.Count) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < listA.Count; i++)
|
||||||
|
{
|
||||||
|
if (listA[i].FieldId != listB[i].FieldId ||
|
||||||
|
!string.Equals(listA[i].ValueMask, listB[i].ValueMask, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
listA[i].IsInverse != listB[i].IsInverse ||
|
||||||
|
listA[i].IsParent != listB[i].IsParent ||
|
||||||
|
listA[i].IsFullMatch != listB[i].IsFullMatch)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<JobUnitFilter> CloneFilters(IEnumerable<JobUnitFilter> source, Guid targetJobId)
|
||||||
|
{
|
||||||
|
var result = new List<JobUnitFilter>();
|
||||||
|
foreach (var src in source ?? Enumerable.Empty<JobUnitFilter>())
|
||||||
|
{
|
||||||
|
var newFilter = new JobUnitFilter
|
||||||
|
{
|
||||||
|
JobId = targetJobId,
|
||||||
|
UnitFilter = src.UnitFilter!,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
FieldFilters = src.FieldFilters?.Select(f => new JobFieldFilter
|
||||||
|
{
|
||||||
|
FieldId = f.FieldId,
|
||||||
|
ValueMask = f.ValueMask,
|
||||||
|
IsInverse = f.IsInverse,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow
|
||||||
|
}).ToList() ?? new List<JobFieldFilter>(),
|
||||||
|
RelationshipFilters = src.RelationshipFilters?.Select(r => new JobRelationshipFilter
|
||||||
|
{
|
||||||
|
FieldId = r.FieldId,
|
||||||
|
ValueMask = r.ValueMask,
|
||||||
|
IsInverse = r.IsInverse,
|
||||||
|
IsParent = r.IsParent,
|
||||||
|
IsFullMatch = r.IsFullMatch
|
||||||
|
}).ToList() ?? new List<JobRelationshipFilter>()
|
||||||
|
};
|
||||||
|
result.Add(newFilter);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using PARR.Core.Common.Helpers;
|
||||||
using PARR.Core.Common.Implementations;
|
using PARR.Core.Common.Implementations;
|
||||||
using PARR.Core.Common.Interfaces;
|
using PARR.Core.Common.Interfaces;
|
||||||
using PARR.Core.Services.MatchingStatusService;
|
using PARR.Core.Services.MatchingStatusService;
|
||||||
@@ -49,6 +50,13 @@ namespace PARR.Core
|
|||||||
|
|
||||||
services.AddTransient<ITransformService, TransformService>();
|
services.AddTransient<ITransformService, TransformService>();
|
||||||
|
|
||||||
|
#region Helpers
|
||||||
|
|
||||||
|
services.AddScoped<JobGroupUnitFilterSynchronizeHelper>();
|
||||||
|
services.AddScoped<JobGroupCloneHelper>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Task
|
#region Task
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ namespace PARR.Core.Repositories.Base
|
|||||||
|
|
||||||
bool Delete(T obj);
|
bool Delete(T obj);
|
||||||
|
|
||||||
//Task<bool> CommitAsync();
|
|
||||||
Task<bool> CommitAsync(IHistoryInitiator? initiator = null);
|
Task<bool> CommitAsync(IHistoryInitiator? initiator = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace PARR.Core.Services.UnitFilterService;
|
|||||||
internal class UnitFilterService : IUnitFilterService
|
internal class UnitFilterService : IUnitFilterService
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
private readonly Guid debugTargetUnitId = Guid.Parse("f2017292-193c-48e9-b333-3a00e737c6fc");
|
private readonly Guid debugTargetUnitId = Guid.Parse("3494fbec-adb9-4167-8619-b2a8b6b6e36a");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private const int DebugMaxUnitsToLog = 10;
|
private const int DebugMaxUnitsToLog = 10;
|
||||||
|
|||||||
@@ -6,26 +6,18 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
namespace PARR.Domain.Entities.Schedule
|
namespace PARR.Domain.Entities.Schedule
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Расписание ЕСПП: значения заданий для ApplicationsInWorks
|
/// Расписание ЕСПП: значения заданий для JobGroup
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Table("EsppSchValues", Schema = DatabaseSchemas.Schedule)]
|
[Table("EsppSchValues", Schema = DatabaseSchemas.Schedule)]
|
||||||
//[Index(nameof(ApplicationsInWorkId), nameof(TypeValueId), nameof(TypeConfigId), IsUnique = true)]
|
|
||||||
//[PrimaryKey(nameof(ApplicationsInWorkId), nameof(TypeValueId), nameof(TypeConfigId))]
|
|
||||||
[PrimaryKey(nameof(JobGroupId), nameof(TypeValueId), nameof(TypeConfigId))]
|
[PrimaryKey(nameof(JobGroupId), nameof(TypeValueId), nameof(TypeConfigId))]
|
||||||
public class EsppSchValue
|
public class EsppSchValue
|
||||||
{
|
{
|
||||||
//public Guid ApplicationsInWorkId { get; set; }
|
|
||||||
|
|
||||||
public Guid TypeValueId { get; set; }
|
public Guid TypeValueId { get; set; }
|
||||||
|
|
||||||
public Guid TypeConfigId { get; set; }
|
public Guid TypeConfigId { get; set; }
|
||||||
|
|
||||||
public Guid JobGroupId { get; set; }
|
public Guid JobGroupId { get; set; }
|
||||||
|
|
||||||
|
|
||||||
//[ForeignKey(nameof(ApplicationsInWorkId))]
|
|
||||||
//public ApplicationsInWork? ApplicationsInWork { get; set; }
|
|
||||||
|
|
||||||
[ForeignKey(nameof(TypeValueId))]
|
[ForeignKey(nameof(TypeValueId))]
|
||||||
public EsppSchTypeValue? EsppSchTypeValue { get; set; }
|
public EsppSchTypeValue? EsppSchTypeValue { get; set; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user