feat(dal): Добавлены 2 новых шорткода %ИД% и %СВЯЗЬ:ИМЯ АТРИБУТА%
This commit is contained in:
@@ -40,10 +40,11 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
private static readonly Regex GeneralShortcodeRegex = new(@"%[^%\s]+%", RegexOptions.Compiled);
|
||||
private static readonly Regex MaxShortcodeRegex = new(@"%МАКС:([а-яА-Яa-zA-Z0-9_]+)%", RegexOptions.Compiled);
|
||||
private static readonly Regex LettersShortcodeRegex = new(@"%БУКВЫ:([^%]+)%", RegexOptions.Compiled);
|
||||
private static readonly Regex RelShortcodeRegex = new(@"%СВЯЗЬ:([а-яА-Яa-zA-Z0-9_]+)%", RegexOptions.Compiled);
|
||||
|
||||
private static readonly HashSet<string> SupportedStandardShortcodes = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"%ЭК%", "%ГРУППА_РАБОТ%", "%РАБОТА%", "%ТНК%", "%ТНК-КРАТКО%", "%ТИКТАК%"
|
||||
"%ЭК%", "%ГРУППА_РАБОТ%", "%РАБОТА%", "%ТНК%", "%ТНК-КРАТКО%", "%ТИКТАК%", "%ИД%"
|
||||
};
|
||||
|
||||
private readonly ILogger<ShortcodesService> logger;
|
||||
@@ -194,6 +195,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
(sc.StartsWith("%МАКС:", StringComparison.OrdinalIgnoreCase) && template.Job.Group?.GroupType == null) ||
|
||||
(sc.Equals("%ГР_ПОЛЕ-ПН%", StringComparison.OrdinalIgnoreCase) && template.Job.Group?.GroupType == null) ||
|
||||
(sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase) && template.Job.Group?.GroupType == null) ||
|
||||
(sc.StartsWith("%СВЯЗЬ:", StringComparison.OrdinalIgnoreCase) && template.Job.Group?.GroupType == null) ||
|
||||
((sc.Equals("%ТНК%", StringComparison.OrdinalIgnoreCase) || sc.Equals("%ТНК-КРАТКО%", StringComparison.OrdinalIgnoreCase)) && template.Job.Tnk == null)
|
||||
).ToList();
|
||||
|
||||
@@ -216,6 +218,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
sc != "%ЭК%" && // %ЭК% не требует Job
|
||||
(SupportedStandardShortcodes.Contains(sc) ||
|
||||
sc.StartsWith("%МАКС:", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.StartsWith("%СВЯЗЬ:", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ГР_ПОЛЕ-ПН%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%СВЯЗИ%", StringComparison.OrdinalIgnoreCase) ||
|
||||
@@ -230,7 +233,8 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
sc.Equals("%ГРУППА_РАБОТ%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.StartsWith("%МАКС:", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ГР_ПОЛЕ-ПН%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase))
|
||||
sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.StartsWith("%СВЯЗЬ:", StringComparison.OrdinalIgnoreCase))
|
||||
&& job?.Group == null)
|
||||
{
|
||||
missing.Add(".Include(j => j.Group)");
|
||||
@@ -239,7 +243,8 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
if (shortcodes.Any(sc =>
|
||||
sc.StartsWith("%МАКС:", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ГР_ПОЛЕ-ПН%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase))
|
||||
sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.StartsWith("%СВЯЗЬ:", StringComparison.OrdinalIgnoreCase))
|
||||
&& job?.Group?.GroupType == null)
|
||||
{
|
||||
missing.Add(".ThenInclude(g => g.GroupType)");
|
||||
@@ -296,7 +301,8 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
if (shortcodes.Any(sc =>
|
||||
sc.StartsWith("%МАКС:", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ГР_ПОЛЕ-ПН%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase)))
|
||||
sc.Equals("%ИНДЕКС%", StringComparison.OrdinalIgnoreCase) ||
|
||||
sc.StartsWith("%СВЯЗЬ:", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
query = query
|
||||
.Include(j => j.Group)
|
||||
@@ -444,14 +450,24 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Transform: %БУКВЫ:...
|
||||
// 4. СВЯЗЬ:ИМЯ_АТРИБУТА (только для групповых работ)
|
||||
if (data.Job?.Group?.GroupType?.Code == JobGroupTypesEnum.Group)
|
||||
{
|
||||
var svyazShortcodes = RelShortcodeRegex.Matches(result);
|
||||
if (svyazShortcodes.Count > 0)
|
||||
{
|
||||
result = await ReplaceRelShortcodesAsync(data.Id, data.UnitId, data.UnitsInTemplate, result, svyazShortcodes, caller).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Transform: %БУКВЫ:...
|
||||
var lettersShortcodes = LettersShortcodeRegex.Matches(result);
|
||||
if (lettersShortcodes.Count > 0)
|
||||
{
|
||||
result = await ReplaceLettersShortcodesAsync(data.UnitId, result, lettersShortcodes, caller).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// 5. %СВЯЗИ% и %СВЯЗИ-ПН%
|
||||
// 6. %СВЯЗИ% и %СВЯЗИ-ПН%
|
||||
List<string>? relatedUnitNames = null;
|
||||
if (shortcodesInMask.Any(m => string.Equals(m, "%СВЯЗИ%", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
@@ -471,7 +487,14 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Поля
|
||||
// 7. ИД шаблона
|
||||
if (shortcodesInMask.Any(m => string.Equals(m, "%ИД%", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var templateIdWithoutDashes = data.Id.ToString("N");
|
||||
result = ReplaceSingleShortcode(result, "%ИД%", templateIdWithoutDashes);
|
||||
}
|
||||
|
||||
// 8. Поля
|
||||
var fieldShortcodes = GetShortCodes(result);
|
||||
if (fieldShortcodes.Count > 0)
|
||||
result = await ReplaceFieldValues(data.UnitId, result, fieldShortcodes).ConfigureAwait(false);
|
||||
@@ -528,8 +551,11 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
Type = ShortcodeTypeEnum.Standard },
|
||||
new ShortcodeInfoDto { Shortcode = "%ТИКТАК%",
|
||||
Description = "Текущее время в формате Unix timestamp (секунды с 1970-01-01 UTC)",
|
||||
Type = ShortcodeTypeEnum.Standard
|
||||
}
|
||||
Type = ShortcodeTypeEnum.Standard},
|
||||
new ShortcodeInfoDto {
|
||||
Shortcode = "%ИД%",
|
||||
Description = "Уникальный идентификатор шаблона (GUID без дефисов)",
|
||||
Type = ShortcodeTypeEnum.Standard}
|
||||
});
|
||||
|
||||
// 3. Групповые шорткоды
|
||||
@@ -542,8 +568,12 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
Description = "Используется только с групповым типом работ. Наиболее часто встречающееся значение поля в группе (игнорирует пустые). Пример: %МАКС:РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК%",
|
||||
Type = ShortcodeTypeEnum.GroupValue },
|
||||
new ShortcodeInfoDto { Shortcode = "%ГР_ПОЛЕ-ПН%",
|
||||
Description = "Нумерованный список unit-ов из шаблона: 1. ЭК-123 (Значение1, Значение2). Использует GroupingUnitFieldId из JobGroup.",
|
||||
Description = "Нумерованный список связанных ЭК с основноым ЭК шаблона: 1. ЭК-123 (Значение1, Значение2). Использует имя поля группировки для вывода значений",
|
||||
Type = ShortcodeTypeEnum.GroupValue },
|
||||
new ShortcodeInfoDto {
|
||||
Shortcode = "%СВЯЗЬ:ИМЯ АТРИБУТА%",
|
||||
Description = "Извлекает значения поля с именем ИМЯ АТРИБУТА из связанных ЭК, сортирует по алфавиту и берёт первое. Пример: %СВЯЗЬ:ЗОНА_ОТВЕТСТВЕННОСТИ%",
|
||||
Type = ShortcodeTypeEnum.GroupValue}
|
||||
});
|
||||
|
||||
// 4. Трансформирующие шорткоды
|
||||
@@ -819,6 +849,108 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
return effectiveUnitIds;
|
||||
}
|
||||
|
||||
private async Task<string> ReplaceRelShortcodesAsync(
|
||||
Guid templateId,
|
||||
Guid templateUnitId,
|
||||
List<UnitInTemplateForShortcodes> unitsInTemplate,
|
||||
string input,
|
||||
MatchCollection relShortcodes,
|
||||
string caller)
|
||||
{
|
||||
var shortcodeToMatches = relShortcodes
|
||||
.Cast<Match>()
|
||||
.GroupBy(m => m.Value, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(g => g.Key, g => g.ToList(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var kvp in shortcodeToMatches)
|
||||
{
|
||||
var fullShortcode = kvp.Key;
|
||||
var matches = kvp.Value;
|
||||
var fieldName = fullShortcode.Trim('%').Split(':', 2)[1].Trim();
|
||||
|
||||
logger.LogDebug("[{Caller}] Обработка {Shortcode} для шаблона с UnitId {UnitId}, fieldName {FieldName}", caller, templateUnitId, fieldName);
|
||||
|
||||
List<Guid> unitIds;
|
||||
bool wasLoadedFromDb = false;
|
||||
|
||||
if (unitsInTemplate.Count > 0)
|
||||
{
|
||||
unitIds = unitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning(
|
||||
"[{Caller}] Шаблон с UnitId {UnitId} не содержит UnitsInTemplate. Данные будут догружены из БД. " +
|
||||
"Рекомендуется обновить запрос с Include(t => t.UnitsInTemplate).",
|
||||
caller, templateUnitId);
|
||||
|
||||
var fullTemplate = await templateService.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.UnitsInTemplate)
|
||||
.FirstOrDefaultAsync(t => t.Id == templateId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
unitIds = fullTemplate?.UnitsInTemplate?.Select(uit => uit.UnitId).ToList() ?? new List<Guid>();
|
||||
wasLoadedFromDb = true;
|
||||
}
|
||||
|
||||
if (unitIds.Count == 0)
|
||||
{
|
||||
logger.LogDebug("[{Caller}] Нет UnitsInTemplate для шаблона {TemplateId}. Шорткод {Shortcode} пропущен.", caller, templateUnitId, fullShortcode);
|
||||
foreach (var match in matches)
|
||||
input = input.Replace(match.Value, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
var allFieldValues = new List<(string FieldName, string? Value)>();
|
||||
|
||||
foreach (var unitId in unitIds)
|
||||
{
|
||||
var values = await unitInValueService.GetFieldValuesAsync(unitId, new List<string> { fieldName }).ConfigureAwait(false);
|
||||
allFieldValues.AddRange(values.Select(v => (v.FieldName, v.Value)));
|
||||
}
|
||||
|
||||
if (allFieldValues == null || !allFieldValues.Any())
|
||||
{
|
||||
logger.LogDebug("[{Caller}] Для шорткода {Shortcode} не найдено значений поля {FieldName} среди UnitsInTemplate.", caller, fullShortcode, fieldName);
|
||||
foreach (var match in matches)
|
||||
input = input.Replace(match.Value, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
var distinctSortedValues = allFieldValues
|
||||
.Where(fv => !string.IsNullOrEmpty(fv.Value))
|
||||
.Select(fv => fv.Value!)
|
||||
.Distinct()
|
||||
.OrderBy(v => v, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
if (distinctSortedValues.Count == 0)
|
||||
{
|
||||
logger.LogDebug("[{Caller}] Для шорткода {Shortcode} все значения поля {FieldName} пустые.", caller, fullShortcode, fieldName);
|
||||
foreach (var match in matches)
|
||||
input = input.Replace(match.Value, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (distinctSortedValues.Count > 1)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"[{Caller}] Шорткод {Shortcode} нашёл {Count} различных значений поля {FieldName}: [{Values}]. Используется первое: '{FirstValue}'",
|
||||
caller, fullShortcode, distinctSortedValues.Count, fieldName,
|
||||
string.Join(", ", distinctSortedValues),
|
||||
distinctSortedValues[0]);
|
||||
}
|
||||
|
||||
var firstValue = distinctSortedValues[0];
|
||||
|
||||
foreach (var match in matches)
|
||||
input = input.Replace(match.Value, firstValue);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private async Task<string> ReplaceLettersShortcodesAsync(Guid unitId, string input, MatchCollection lettersShortcodes, string caller)
|
||||
{
|
||||
var shortcodeToMatches = lettersShortcodes
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.20">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PARR.Core\PARR.Core.csproj" />
|
||||
<ProjectReference Include="..\PARR.DAL\PARR.DAL.csproj" />
|
||||
<ProjectReference Include="..\PARR.Domain\PARR.Domain.csproj" />
|
||||
<ProjectReference Include="..\PARR.Infrastructure\PARR.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.DAL.DomainServices.Interfaces;
|
||||
using PARR.DAL.DomainServices.Shortcodes;
|
||||
using PARR.DAL.DomainServices.UnitFilterService;
|
||||
using PARR.DAL.Models;
|
||||
using PARR.DAL.Models.Job;
|
||||
@@ -38,9 +37,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||
private readonly IMatchingStatusService matchingStatusService;
|
||||
private readonly IShortcodesService shortcodesService;
|
||||
|
||||
//private readonly INextRunService nextRunService;
|
||||
|
||||
public GroupedTemplateSynchronizer(
|
||||
ILogger<GroupedTemplateSynchronizer> logger,
|
||||
@@ -57,9 +54,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
ITemplateDeactivator templateDeactivator,
|
||||
ITemplateNameNormalizer templateNameNormalizer,
|
||||
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||
IMatchingStatusService matchingStatusService,
|
||||
IShortcodesService shortcodesService
|
||||
//INextRunService nextRunService
|
||||
IMatchingStatusService matchingStatusService
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
@@ -77,8 +72,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
this.templateNameNormalizer = templateNameNormalizer;
|
||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||
this.matchingStatusService = matchingStatusService;
|
||||
this.shortcodesService = shortcodesService;
|
||||
//this.nextRunService = nextRunService;
|
||||
}
|
||||
|
||||
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
||||
@@ -248,7 +241,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
{
|
||||
// dto.Id - это ParentUnitId, связанные - ChildUnitIds (expectedUnitIds) -> dto.Id идет в UnitsInTemplate
|
||||
// relatedUnitIds - это ChildUnitIds, которые станут UnitId шаблона
|
||||
relatedUnitIds = dto.Children.Select(c => c.UnitId).ToList(); // <-- Исправлено
|
||||
relatedUnitIds = dto.Children.Select(c => c.UnitId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -319,6 +312,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
foreach (var innerGroup in innerGroupedUnitsInTemplate)
|
||||
{
|
||||
var groupingValueName = innerGroup.Key;
|
||||
|
||||
var unitsInTemplateInInnerGroup = innerGroup.ToList(); // Список юнитов (UnitId), связанных с potentialUnitId и имеющих одно и то же значение поля
|
||||
|
||||
logger.LogDebug("Обработка внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId} с {Count} юнитами.", groupingValueName, potentialUnitId, unitsInTemplateInInnerGroup.Count);
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
{
|
||||
internal class TemplateSettings
|
||||
{
|
||||
public string UnusedTemplateNameMask { get; set; } = "%П-1%_НЕАКТУАЛЬНЫЕ_%ЭК%_%ТИКТАК%";
|
||||
public string UnusedTemplateNameMask { get; set; } = "%П-1%_НЕАКТУАЛЬНЫЕ_%ИД%";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user