Files
parr_api/PARR.DAL/DomainServices/Implementations/ShortcodesService.cs

271 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.Models.Job;
using PARR.DAL.Models.Unit;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
using System.Text.RegularExpressions;
namespace PARR.DAL.DomainServices.Implementations
{
internal class ShortcodesService : IShortcodesService
{
private const string shortcodePattern = "%[^%\\s]+%";
private static readonly HashSet<string> SupportedShortcodes = new(StringComparer.OrdinalIgnoreCase)
{
"%ЭК%", "%ГРУППА_РАБОТ%", "%РАБОТА%", "%ТНК%", "%СВЯЗИ%", "%ТНК-КРАТКО%", "%СВЯЗИ-ПН%"
};
private readonly ILogger<ShortcodesService> logger;
private readonly SettingsFromDb settingsFromDb;
private readonly IJobService jobService;
private readonly IUnitService unitService;
private readonly IUnitInValueService unitInValueService;
private readonly IUnitFieldService unitFieldService;
private readonly IUnitFilterService unitFilterService;
public ShortcodesService(
ILogger<ShortcodesService> logger,
SettingsFromDb settingsFromDb,
IJobService jobService,
IUnitService unitService,
IUnitFilterService unitFilterService,
IUnitInValueService unitInValueService,
IUnitFieldService unitFieldService
)
{
this.logger = logger;
this.settingsFromDb = settingsFromDb;
this.jobService = jobService;
this.unitService = unitService;
this.unitInValueService = unitInValueService;
this.unitFieldService = unitFieldService;
this.unitFilterService = unitFilterService;
}
public async Task<string> ApplyShortcodesAsync(string str, Guid unitId, Guid jobId)
{
logger.LogDebug("Начата подстановка шорткодов. Вход: '{Input}', unitId={UnitId}, jobId={JobId}", str, unitId, jobId);
var job = await jobService
.Get().AsNoTracking()
.Include(j => j.Tnk)
.Include(j => j.Group)
.Include(j => j.UnitFilters)
.ThenInclude(uf => uf.RelationshipFilters)
.FirstOrDefaultAsync(j => j.Id == jobId);
var unit = await unitService.Get().AsNoTracking().FirstOrDefaultAsync(u => u.Id == unitId);
if (job == null || unit == null || string.IsNullOrEmpty(str))
{
logger.LogError("Переданы некорректные данные для подстановки динамических записей");
return str;
}
var resultName = str;
var shortcodesInMask = GetShortCodes(resultName);
// 1. Статические константы
var nameConstants = settingsFromDb.TemplateNameConstantPartsList;
if (shortcodesInMask.Any(m => nameConstants.Any(c => $"%{c.Name}%".Equals(m.Value, StringComparison.OrdinalIgnoreCase))))
{
resultName = ReplaceConstants(nameConstants, resultName);
}
// 2. Стандартные шорткоды — с поддержкой вложенных (%РАБОТА% → "Мониторинг | %ЭК%")
const int MaxStandardIterations = 3;
var iteration = 0;
while (iteration < MaxStandardIterations)
{
// Ищем ТОЛЬКО поддерживаемые шорткоды в текущей строке
var remainingShortcodes = GetShortCodes(resultName)
.Select(m => m.Value)
.Where(s => SupportedShortcodes.Contains(s))
.ToList();
if (!remainingShortcodes.Any())
break;
// Делаем замену
var oldResult = resultName;
resultName = ReplaceStandardShortcodes(job, unit, resultName);
iteration++;
// Защита от "бесполезных" итераций (строка не изменилась)
if (resultName == oldResult)
{
logger.LogWarning("Замена стандартных шорткодов не изменила строку на итерации {Iteration}. Останов.", iteration);
break;
}
}
if (iteration >= MaxStandardIterations)
{
logger.LogWarning(
"Достигнуто максимальное число итераций ({Max}) при замене стандартных шорткодов. Текущий результат: {Result}",
MaxStandardIterations, resultName);
}
// 3. %СВЯЗИ% или %СВЯЗИ-ПН%
List<string>? relatedUnitNames = null;
// 3.1. %СВЯЗИ%
if (shortcodesInMask.Any(m => string.Equals(m.Value, "%СВЯЗИ%", StringComparison.OrdinalIgnoreCase)))
{
relatedUnitNames ??= await unitFilterService.GetRelatedUnitNamesAsync(jobId, unitId);
var linksText = string.Join("\n", relatedUnitNames);
resultName = Regex.Replace(resultName, "%СВЯЗИ%", linksText, RegexOptions.IgnoreCase);
}
// 3.2. %СВЯЗИ-ПН%
if (shortcodesInMask.Any(m => string.Equals(m.Value, "%СВЯЗИ-ПН%", StringComparison.OrdinalIgnoreCase)))
{
relatedUnitNames ??= await unitFilterService.GetRelatedUnitNamesAsync(jobId, unitId);
var linksText = string.Join("\n", relatedUnitNames.Select((name, i) => $"{i + 1}. {name}"));
resultName = Regex.Replace(resultName, "%СВЯЗИ-ПН%", linksText, RegexOptions.IgnoreCase);
}
// 4. Поля (оставшиеся %FIELD_NAME%) - выполняем запрос ТОЛЬКО если после предыдущих замен остались необработанные шорткоды
shortcodesInMask = GetShortCodes(resultName);
if (shortcodesInMask.Count > 0)
resultName = await ReplaceFieldValues(unitId, resultName, shortcodesInMask);
logger.LogDebug("Подстановка завершена. Результат: '{Result}'", resultName);
return resultName;
}
public bool IsAnyShortcodes(string str)
{
return Regex.IsMatch(str, shortcodePattern);
}
private static List<Match> GetShortCodes(string resultName)
{
var shortcodesInMask = Regex.Matches(resultName, shortcodePattern).ToList();
return shortcodesInMask;
}
public async Task<List<ShortcodeInfoDto>> GetAvailableShortcodesAsync()
{
var result = new List<ShortcodeInfoDto>();
// 1. Статические константы — из settingsFromDb
foreach (var constant in settingsFromDb.TemplateNameConstantPartsList)
{
result.Add(new ShortcodeInfoDto
{
Shortcode = $"%{constant.Name}%",
Description = $"Константа: {constant.Value ?? "(пусто)"}",
Type = ShortcodeTypeEnum.Static
});
}
// 2. Стандартные шорткоды
result.AddRange(new[]
{
new ShortcodeInfoDto { Shortcode = "%ЭК%",
Description = "Наименование ЭК(Код поиска)",
Type = ShortcodeTypeEnum.Standart },
new ShortcodeInfoDto { Shortcode = "%ГРУППА_РАБОТ%",
Description = "Наименование группы работ",
Type = ShortcodeTypeEnum.Standart },
new ShortcodeInfoDto { Shortcode = "%РАБОТА%",
Description = "Наименование работы в АСУ ЕСПП",
Type = ShortcodeTypeEnum.Standart },
new ShortcodeInfoDto { Shortcode = "%ТНК%",
Description = "Полное наименование ТНК",
Type = ShortcodeTypeEnum.Standart },
new ShortcodeInfoDto { Shortcode = "%ТНК-КРАТКО%",
Description = "Краткое наименование ТНК",
Type = ShortcodeTypeEnum.Standart }
});
// 3. Связи
result.AddRange(new[]
{
new ShortcodeInfoDto { Shortcode = "%СВЯЗИ%",
Description = "Связанные ЭК (по одному на строку), выбираются только при настроенном фильтре по полям в связанных ЭК",
Type = ShortcodeTypeEnum.Relationship },
new ShortcodeInfoDto { Shortcode = "%СВЯЗИ-ПН%",
Description = "Связанные ЭК с нумерацией (1. ..., 2. ...), выбираются только при настроенном фильтре по полям в связанных ЭК",
Type = ShortcodeTypeEnum.Relationship }
});
// 4. Все доступные поля из UnitField
var fieldNames = await unitFieldService.Get().AsNoTracking().Select(t => new { t.AihitName, t.DisplayName }).ToListAsync();
foreach (var fieldName in fieldNames.OrderBy(n => n.AihitName))
{
result.Add(new ShortcodeInfoDto
{
Shortcode = $"%{fieldName.AihitName}%",
Description = $"Атрибут: {fieldName.DisplayName ?? fieldName.AihitName}",
Type = ShortcodeTypeEnum.FieldValue
});
}
return result;
}
private async Task<string> ReplaceFieldValues(Guid unitId, string resultName, List<Match> shortcodesInMask)
{
// 1. Извлекаем имена полей из шорткодов: %IP_АДРЕС% → "IP_АДРЕС"
var requiredFieldNames = shortcodesInMask
.Select(m => m.Value.Trim('%').ToUpper())
.ToList();
if (requiredFieldNames.Count == 0)
return resultName;
// 2. Получаем только нужные значения
var fieldValueMap = await unitInValueService.GetFieldValuesAsync(unitId, requiredFieldNames);
// 3. Подставляем значения
foreach (var match in shortcodesInMask)
{
var fieldName = match.Value.Trim('%').ToUpper();
if (fieldValueMap.TryGetValue(fieldName, out var fieldValue))
resultName = resultName.Replace(match.Value, fieldValue);
else
{
logger.LogWarning("Поле '{FieldName}' не найдено для unitId={UnitId} при подстановке шорткода '{Shortcode}'",
fieldName, unitId, match.Value);
}
}
return resultName;
}
private static string ReplaceStandardShortcodes(Job job, Unit unit, string input)
{
return input
.Replace("%ЭК%", unit.Name, StringComparison.OrdinalIgnoreCase)
.Replace("%ГРУППА_РАБОТ%", job.Group?.GroupName ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("%РАБОТА%", job.WorkName, StringComparison.OrdinalIgnoreCase)
.Replace("%ТНК%", job.Tnk?.Name ?? "", StringComparison.OrdinalIgnoreCase)
.Replace("%ТНК-КРАТКО%", job.Tnk?.ShortName ?? "", StringComparison.OrdinalIgnoreCase);
}
private static string ReplaceConstants(List<BLL.Domain.TemplateNameConstantPart> nameConstants, string resultName)
{
foreach (var item in nameConstants)
{
resultName = resultName.Replace($"%{item.Name}%", item.Value);
}
return resultName;
}
}
}