fix(dal): исправлен UnitFilterService, фильтровались юниты у которых нет связей, но проверку фильтров они проходили.
This commit is contained in:
@@ -16,9 +16,12 @@ namespace PARR.DAL.DomainServices.UnitFilterService;
|
||||
internal class UnitFilterService : IUnitFilterService
|
||||
{
|
||||
#if DEBUG
|
||||
private readonly Guid targetUnitId = Guid.Parse("0e84812c-66ee-46fc-bb72-2afbecf354cb");
|
||||
private readonly Guid targetUnitId = Guid.Parse("9d88fff2-a861-487f-b73d-bce1f0218e9f");
|
||||
#endif
|
||||
|
||||
private const int DebugMaxUnitsToLog = 10;
|
||||
private const int DebugMaxRelationsToLog = 5;
|
||||
|
||||
private readonly int batchSize;
|
||||
|
||||
private readonly ILogger<UnitFilterService> logger;
|
||||
@@ -262,9 +265,6 @@ internal class UnitFilterService : IUnitFilterService
|
||||
/// <summary>
|
||||
/// Применяем фильтры аттрибутов
|
||||
/// </summary>
|
||||
/// <param name="unitIds"></param>
|
||||
/// <param name="fieldFilters"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<List<Guid>> ApplyFieldFiltersOnDbAsync(
|
||||
List<Guid> unitIds,
|
||||
IEnumerable<JobFieldFilter> fieldFilters,
|
||||
@@ -288,18 +288,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
if (string.IsNullOrEmpty(valueMask))
|
||||
continue;
|
||||
|
||||
// Обработка маски LIKE
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
string dbValueMask;
|
||||
if (isStartsWith && isEndsWith)
|
||||
dbValueMask = $"%{valueMask.Trim('%')}%";
|
||||
else if (isStartsWith)
|
||||
dbValueMask = $"{valueMask.TrimEnd('%')}%";
|
||||
else if (isEndsWith)
|
||||
dbValueMask = $"%{valueMask.TrimStart('%')}";
|
||||
else
|
||||
dbValueMask = valueMask;
|
||||
string dbValueMask = NormalizeLikeMask(valueMask);
|
||||
|
||||
var fieldName = fieldFilter.UnitField?.AihitName ?? $"FieldId={fieldFilter.FieldId}";
|
||||
logger.LogDebug(" FieldFilter #{Index}: Поле='{FieldName}', Маска='{Mask}', IsInverse={IsInverse}",
|
||||
@@ -317,6 +306,30 @@ internal class UnitFilterService : IUnitFilterService
|
||||
v.FieldId == fieldFilter.FieldId &&
|
||||
EF.Functions.ILike(v.Value.Value, dbValueMask)));
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (unitIds.Contains(targetUnitId))
|
||||
{
|
||||
var unitHasField = await unitInValueService.Get()
|
||||
.AsNoTracking()
|
||||
.AnyAsync(uiv => uiv.UnitId == targetUnitId && uiv.FieldId == fieldFilter.FieldId, cancellationToken);
|
||||
|
||||
var unitValue = await unitInValueService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(uiv => uiv.UnitId == targetUnitId && uiv.FieldId == fieldFilter.FieldId)
|
||||
.Select(uiv => uiv.Value.Value)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
var intermediateResult = await query.Select(u => u.Id).ToListAsync(cancellationToken);
|
||||
var passes = intermediateResult.Contains(targetUnitId);
|
||||
|
||||
logger.LogDebug(" DEBUG: Юнит {TargetUnitId}: Поле={FieldName}, Значение={Value}, Маска={Mask}, HasField={HasField}, Проходит={Passes}",
|
||||
targetUnitId, fieldName, unitValue ?? "null", dbValueMask, unitHasField, passes);
|
||||
|
||||
query = unitService.Get().AsNoTracking()
|
||||
.Where(u => intermediateResult.Contains(u.Id));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
var result = await query.Select(u => u.Id).ToListAsync(cancellationToken);
|
||||
@@ -329,9 +342,6 @@ internal class UnitFilterService : IUnitFilterService
|
||||
/// <summary>
|
||||
/// Применяем фильтры аттрибутов у связанных ЭК
|
||||
/// </summary>
|
||||
/// <param name="unitIds"></param>
|
||||
/// <param name="relationshipFilters"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<List<FilteredUnitContext>> ProcessRelationshipFiltersAsync(
|
||||
List<Guid> unitIds,
|
||||
IEnumerable<JobRelationshipFilter> relationshipFilters,
|
||||
@@ -347,12 +357,18 @@ internal class UnitFilterService : IUnitFilterService
|
||||
logger.LogDebug("ProcessRelationshipFiltersAsync: вход {UnitCount} юнитов, фильтров: {FilterCount}",
|
||||
unitIds.Count, relationshipFilters.Count());
|
||||
|
||||
#if DEBUG
|
||||
if (unitIds.Contains(targetUnitId))
|
||||
{
|
||||
logger.LogDebug("DEBUG: Юнит {TargetUnitId} найден во входных данных ProcessRelationshipFiltersAsync", targetUnitId);
|
||||
}
|
||||
#endif
|
||||
|
||||
var resultContexts = unitIds.ToDictionary(id => id, id => new FilteredUnitContext { UnitId = id });
|
||||
|
||||
var parentRelFilters = relationshipFilters.Where(rf => rf.IsParent).ToList();
|
||||
var childRelFilters = relationshipFilters.Where(rf => !rf.IsParent).ToList();
|
||||
|
||||
// Обработка родительских фильтров
|
||||
if (parentRelFilters.Any())
|
||||
{
|
||||
await ApplyRelationshipFiltersOnDbAsync(
|
||||
@@ -363,7 +379,6 @@ internal class UnitFilterService : IUnitFilterService
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
// Обработка дочерних фильтров
|
||||
if (childRelFilters.Any())
|
||||
{
|
||||
await ApplyRelationshipFiltersOnDbAsync(
|
||||
@@ -374,120 +389,137 @@ internal class UnitFilterService : IUnitFilterService
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
// Если фильтры были — оставляем только юниты со связями
|
||||
// Если фильтров не было — оставляем все юниты
|
||||
bool hasRelationshipFilters = parentRelFilters.Any() || childRelFilters.Any();
|
||||
#if DEBUG
|
||||
var unitsWithAnyConnections = resultContexts.Values.Count(c => c.ValidParentIds.Any() || c.ValidChildIds.Any());
|
||||
var unitsWithoutConnections = resultContexts.Values.Count(c => !c.ValidParentIds.Any() && !c.ValidChildIds.Any());
|
||||
logger.LogDebug("DEBUG: После ApplyRelationshipFiltersOnDbAsync: {WithConnections} юнитов со связями, {WithoutConnections} без связей",
|
||||
unitsWithAnyConnections, unitsWithoutConnections);
|
||||
|
||||
var unitsWithRelationships = hasRelationshipFilters
|
||||
? resultContexts.Values.Where(c => c.ValidParentIds.Any() || c.ValidChildIds.Any()).ToList()
|
||||
: resultContexts.Values.ToList();
|
||||
var targetContext = resultContexts.Values.FirstOrDefault(c => c.UnitId == targetUnitId);
|
||||
if (targetContext != null)
|
||||
{
|
||||
logger.LogDebug("DEBUG: Юнит {TargetUnitId}: Родителей={ParentCount}, Детей={ChildCount}, PassesFilter={Passes}",
|
||||
targetUnitId,
|
||||
targetContext.ValidParentIds.Count,
|
||||
targetContext.ValidChildIds.Count,
|
||||
targetContext.ValidParentIds.Any() || targetContext.ValidChildIds.Any());
|
||||
}
|
||||
#endif
|
||||
|
||||
var result = resultContexts.Values.ToList();
|
||||
|
||||
var result = unitsWithRelationships;
|
||||
logger.LogDebug("ProcessRelationshipFiltersAsync: выход {ContextCount} контекстов (из {InitialCount})",
|
||||
result.Count, resultContexts.Count);
|
||||
|
||||
#if DEBUG
|
||||
// Собираем все UnitId для пакетной загрузки (основные + родители + дети)
|
||||
var allUnitIds = result.Select(c => c.UnitId)
|
||||
.Concat(result.SelectMany(c => c.ValidParentIds))
|
||||
.Concat(result.SelectMany(c => c.ValidChildIds))
|
||||
.Distinct()
|
||||
.ToList(); // ← Убрали .Take(100)
|
||||
|
||||
// Пакетная загрузка всех юнитов
|
||||
var unitsMap = await unitService.Get().AsNoTracking()
|
||||
.Where(u => allUnitIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, cancellationToken);
|
||||
|
||||
// Получаем все FieldId из фильтров для отображения значений
|
||||
var parentFieldIds = relationshipFilters.Where(rf => rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||
var childFieldIds = relationshipFilters.Where(rf => !rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||
var allFieldIds = parentFieldIds.Concat(childFieldIds).Distinct().ToList();
|
||||
|
||||
// ПАКЕТНАЯ загрузка значений для ВСЕХ юнитов сразу
|
||||
var allValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitIds, allFieldIds);
|
||||
var valuesByUnit = allValues.GroupBy(v => v.UnitId)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
// ПАКЕТНАЯ загрузка имён полей
|
||||
var fieldsMap = await unitFieldService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(f => allFieldIds.Contains(f.Id))
|
||||
.ToDictionaryAsync(f => f.Id, f => f.AihitName, cancellationToken);
|
||||
|
||||
// Логируем только первые 10 юнитов (чтобы не засорять лог)
|
||||
foreach (var context in result.Take(10))
|
||||
if (unitIds.Contains(targetUnitId))
|
||||
{
|
||||
var u = unitsMap.GetValueOrDefault(context.UnitId);
|
||||
|
||||
if (u == null)
|
||||
continue;
|
||||
|
||||
logger.LogDebug("Найден ЭК {UnitName} (Id={UnitId})", u.Name, u.Id);
|
||||
|
||||
// === РОДИТЕЛИ ===
|
||||
if (context.ValidParentIds.Any())
|
||||
var targetContextInResult = result.FirstOrDefault(c => c.UnitId == targetUnitId);
|
||||
if (targetContextInResult != null)
|
||||
{
|
||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} родителей:", context.ValidParentIds.Count);
|
||||
logger.LogDebug("DEBUG: Юнит {TargetUnitId} в результатах ProcessRelationshipFiltersAsync. Родителей: {ParentCount}, Детей: {ChildCount}",
|
||||
targetUnitId, targetContextInResult.ValidParentIds.Count, targetContextInResult.ValidChildIds.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("DEBUG: Юнит {TargetUnitId} НЕ в результатах ProcessRelationshipFiltersAsync (удалён)", targetUnitId);
|
||||
}
|
||||
}
|
||||
|
||||
// Логируем только первые 5 родителей (чтобы не засорять лог)
|
||||
foreach (var parentId in context.ValidParentIds.Take(5))
|
||||
if (logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var allUnitIds = result.Select(c => c.UnitId)
|
||||
.Concat(result.SelectMany(c => c.ValidParentIds))
|
||||
.Concat(result.SelectMany(c => c.ValidChildIds))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var unitsMap = await unitService.Get().AsNoTracking()
|
||||
.Where(u => allUnitIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, cancellationToken);
|
||||
|
||||
var parentFieldIds = relationshipFilters.Where(rf => rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||
var childFieldIds = relationshipFilters.Where(rf => !rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||
var allFieldIds = parentFieldIds.Concat(childFieldIds).Distinct().ToList();
|
||||
|
||||
var allValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitIds, allFieldIds);
|
||||
var valuesByUnit = allValues.GroupBy(v => v.UnitId)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
var fieldsMap = await unitFieldService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(f => allFieldIds.Contains(f.Id))
|
||||
.ToDictionaryAsync(f => f.Id, f => f.AihitName, cancellationToken);
|
||||
|
||||
foreach (var context in result.Take(DebugMaxUnitsToLog))
|
||||
{
|
||||
var u = unitsMap.GetValueOrDefault(context.UnitId);
|
||||
if (u == null)
|
||||
continue;
|
||||
|
||||
logger.LogDebug("Найден ЭК {UnitName} (Id={UnitId})", u.Name, u.Id);
|
||||
|
||||
if (context.ValidParentIds.Any())
|
||||
{
|
||||
var p = unitsMap.GetValueOrDefault(parentId);
|
||||
logger.LogDebug("\t\t{ParentName} (Id={ParentId})", p?.Name ?? "null", parentId);
|
||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} родителей:", context.ValidParentIds.Count);
|
||||
|
||||
if (parentFieldIds.Any() && valuesByUnit.TryGetValue(parentId, out var parentValues))
|
||||
foreach (var parentId in context.ValidParentIds.Take(DebugMaxRelationsToLog))
|
||||
{
|
||||
foreach (var pv in parentValues.Where(v => parentFieldIds.Contains(v.FieldId)))
|
||||
var p = unitsMap.GetValueOrDefault(parentId);
|
||||
logger.LogDebug("\t\t{ParentName} (Id={ParentId})", p?.Name ?? "null", parentId);
|
||||
|
||||
if (parentFieldIds.Any() && valuesByUnit.TryGetValue(parentId, out var parentValues))
|
||||
{
|
||||
var fieldName = fieldsMap.GetValueOrDefault(pv.FieldId) ?? $"FieldId={pv.FieldId}";
|
||||
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
||||
fieldName,
|
||||
pv.Value?.Value ?? "null");
|
||||
foreach (var pv in parentValues.Where(v => parentFieldIds.Contains(v.FieldId)))
|
||||
{
|
||||
var fieldName = fieldsMap.GetValueOrDefault(pv.FieldId) ?? $"FieldId={pv.FieldId}";
|
||||
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
||||
fieldName,
|
||||
pv.Value?.Value ?? "null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ValidParentIds.Count > 5)
|
||||
{
|
||||
logger.LogDebug("\t\t... и ещё {Count} родителей", context.ValidParentIds.Count - 5);
|
||||
}
|
||||
}
|
||||
else if (parentRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug("\tРодительские фильтры заданы, но подходящих родителей не найдено");
|
||||
}
|
||||
|
||||
// === ДЕТИ ===
|
||||
if (context.ValidChildIds.Any())
|
||||
{
|
||||
logger.LogDebug("\tФильтрам соответствуют {ChildrenCount} детей:", context.ValidChildIds.Count);
|
||||
|
||||
foreach (var childId in context.ValidChildIds.Take(5))
|
||||
{
|
||||
var c = unitsMap.GetValueOrDefault(childId);
|
||||
logger.LogDebug("\t\t{ChildName} (Id={ChildId})", c?.Name ?? "null", childId);
|
||||
|
||||
if (childFieldIds.Any() && valuesByUnit.TryGetValue(childId, out var childValues))
|
||||
if (context.ValidParentIds.Count > DebugMaxRelationsToLog)
|
||||
{
|
||||
foreach (var cv in childValues.Where(v => childFieldIds.Contains(v.FieldId)))
|
||||
{
|
||||
var fieldName = fieldsMap.GetValueOrDefault(cv.FieldId) ?? $"FieldId={cv.FieldId}";
|
||||
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
||||
fieldName,
|
||||
cv.Value?.Value ?? "null");
|
||||
}
|
||||
logger.LogDebug("\t\t... и ещё {Count} родителей", context.ValidParentIds.Count - DebugMaxRelationsToLog);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ValidChildIds.Count > 5)
|
||||
else if (parentRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug("\t\t... и ещё {Count} детей", context.ValidChildIds.Count - 5);
|
||||
logger.LogDebug("\tРодительские фильтры заданы, но подходящих родителей не найдено");
|
||||
}
|
||||
|
||||
if (context.ValidChildIds.Any())
|
||||
{
|
||||
logger.LogDebug("\tФильтрам соответствуют {ChildrenCount} детей:", context.ValidChildIds.Count);
|
||||
|
||||
foreach (var childId in context.ValidChildIds.Take(DebugMaxRelationsToLog))
|
||||
{
|
||||
var c = unitsMap.GetValueOrDefault(childId);
|
||||
logger.LogDebug("\t\t{ChildName} (Id={ChildId})", c?.Name ?? "null", childId);
|
||||
|
||||
if (childFieldIds.Any() && valuesByUnit.TryGetValue(childId, out var childValues))
|
||||
{
|
||||
foreach (var cv in childValues.Where(v => childFieldIds.Contains(v.FieldId)))
|
||||
{
|
||||
var fieldName = fieldsMap.GetValueOrDefault(cv.FieldId) ?? $"FieldId={cv.FieldId}";
|
||||
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
||||
fieldName,
|
||||
cv.Value?.Value ?? "null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ValidChildIds.Count > DebugMaxRelationsToLog)
|
||||
{
|
||||
logger.LogDebug("\t\t... и ещё {Count} детей", context.ValidChildIds.Count - DebugMaxRelationsToLog);
|
||||
}
|
||||
}
|
||||
else if (childRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug("\tДочерние фильтры заданы, но подходящих детей не найдено");
|
||||
}
|
||||
}
|
||||
else if (childRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug("\tДочерние фильтры заданы, но подходящих детей не найдено");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -752,7 +784,6 @@ internal class UnitFilterService : IUnitFilterService
|
||||
#region вспомогательные методы
|
||||
/// <summary>
|
||||
/// Применяет фильтры к связям (родителям или детям)
|
||||
/// Связь должна пройти ВСЕ фильтры направления
|
||||
/// </summary>
|
||||
private async Task ApplyRelationshipFiltersOnDbAsync(
|
||||
List<Guid> unitIds,
|
||||
@@ -796,17 +827,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
if (string.IsNullOrWhiteSpace(valueMask))
|
||||
continue;
|
||||
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
string dbValueMask;
|
||||
if (isStartsWith && isEndsWith)
|
||||
dbValueMask = $"%{valueMask.Trim('%')}%";
|
||||
else if (isStartsWith)
|
||||
dbValueMask = $"{valueMask.TrimEnd('%')}%";
|
||||
else if (isEndsWith)
|
||||
dbValueMask = $"%{valueMask.TrimStart('%')}";
|
||||
else
|
||||
dbValueMask = valueMask;
|
||||
string dbValueMask = NormalizeLikeMask(valueMask);
|
||||
|
||||
var fieldName = relFilter.UnitField?.AihitName ?? $"FieldId={relFilter.FieldId}";
|
||||
logger.LogDebug(" RelationshipFilter ({Direction}): Поле='{FieldName}', Маска='{Mask}', IsInverse={IsInverse}, IsFullMatch={IsFullMatch}",
|
||||
@@ -938,6 +959,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task<List<Guid>> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var cacheKey = cacheService.GetKey(new[] { "uf_ids", filter.UnitFilter }, isUseHash: true);
|
||||
@@ -948,8 +970,10 @@ internal class UnitFilterService : IUnitFilterService
|
||||
return cachedData.Data.UnitIds;
|
||||
}
|
||||
|
||||
var dbValueMask = NormalizeLikeMask(filter.UnitFilter);
|
||||
|
||||
var initialUnitIds = await unitService.Get().AsNoTracking()
|
||||
.Where(unit => EF.Functions.ILike(unit.Name, filter.UnitFilter))
|
||||
.Where(unit => EF.Functions.ILike(unit.Name, dbValueMask))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
@@ -965,5 +989,29 @@ internal class UnitFilterService : IUnitFilterService
|
||||
return initialUnitIds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Обрабатывает маску LIKE для корректной работы с SQL
|
||||
/// </summary>
|
||||
private static string NormalizeLikeMask(string valueMask)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(valueMask))
|
||||
return valueMask;
|
||||
|
||||
valueMask = valueMask.Trim();
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
|
||||
if (isStartsWith && isEndsWith)
|
||||
return $"%{valueMask.Trim('%')}%";
|
||||
else if (isStartsWith)
|
||||
return $"{valueMask.TrimEnd('%')}%";
|
||||
else if (isEndsWith)
|
||||
return $"%{valueMask.TrimStart('%')}";
|
||||
else
|
||||
return valueMask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -53,6 +53,11 @@ namespace PARR.DAL.Services.Implementations
|
||||
// ставить статус Updating можно только если текущий статус == Ok
|
||||
if (configuration.TaskStatusCode != (int)TaskStatusEnum.Ok)
|
||||
{
|
||||
int taskStatusValue = configuration.TaskStatusCode;
|
||||
string taskStatusName = Enum.IsDefined(typeof(TaskStatusEnum), taskStatusValue)
|
||||
? ((TaskStatusEnum)taskStatusValue).ToString()
|
||||
: $"Unknown ({taskStatusValue})";
|
||||
|
||||
logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как текущий статус {currentStatus}",
|
||||
updatingStatus, configuration.Id, configuration.TemplateId, configuration.TaskStatusCode);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user