@@ -7,23 +7,42 @@ using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces ;
using PARR.DAL.DomainServices.Shortcodes.Models ;
using PARR.DAL.Models ;
using PARR.DAL.Models.Unit ;
using PARR.DAL.Models.Job ;
using PARR.DAL.Services.Interfaces ;
using PARR.DAL.Services.Interfaces.Job ;
using PARR.DAL.Services.Interfaces.Unit ;
using System.Runtime.CompilerServices ;
using System.Text.RegularExpressions ;
namespace PARR.DAL.DomainServices.Shortcodes
{
/// <summary>
/// Сервис для подстановки шорткодов в строке на основе данных шаблона.
/// </summary>
internal class ShortcodesService : IShortcodesService
{
private const string shortcodePattern = "%[^%\\s]+%" ;
private const string maxShortcodePattern = @"%М А К С :([а -яА-Яa-zA-Z0-9_]+)%" ;
private const string lettersShortcodePattern = @"%БУКВЫ:([^%]+)% ";
private const string MissingJobWarningMsg =
"[{Caller}] Шаблон {TemplateId} не содержит Job в переданных данных. Данные будут догружены из БД. " +
"Рекомендуется обновить запрос шаблона с Include(j => j.Job). " ;
private static readonly HashSet < string > SupportedShortcodes = new ( StringComparer . OrdinalIgnoreCase )
private const string MissingIncludesWarningMsg =
"[{Caller}] Шаблон {TemplateId} содержит Job, но не хватает данных для шорткодов: {Shortcodes}. " +
"Отсутствуют Include: {MissingIncludes}. Данные будут догружены из БД. " +
"Рекомендуется обновить запрос шаблона." ;
private const string MissingUnitNameWarningMsg =
"[{Caller}] Шаблон {TemplateId} не содержит Unit.Name в переданных данных. Данные будут догружены из БД. " +
"Рекомендуется обновить запрос шаблона с Include(t => t.Unit)." ;
private const int MaxIterations = 3 ;
private const string NoContent = "Нет данных" ;
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 HashSet < string > SupportedStandardShortcodes = new ( StringComparer . OrdinalIgnoreCase )
{
"%ЭК%" , "%ГР У ППА _Р А БО Т %" , "%РАБОТА%" , "%Т Н К %" , "%СВЯЗИ%" , "%Т Н К -К Р А Т К О %" , "%СВЯЗИ-ПН%" , "%ИНДЕКС %"
"%ЭК%" , "%ГР У ППА _Р А БО Т %" , "%РАБОТА%" , "%Т Н К %" , "%Т Н К -К Р А Т К О %" , "%ТИКТАК %"
} ;
private readonly ILogger < ShortcodesService > logger ;
@@ -36,6 +55,43 @@ namespace PARR.DAL.DomainServices.Shortcodes
private readonly IRedisCacheService cacheService ;
private readonly IUnitFilterService unitFilterService ;
// Используем record struct вместо class
private record class TemplateForShortcodes (
Guid Id ,
int? Index ,
Guid JobId ,
Guid UnitId ,
JobForShortcodes ? Job ,
List < UnitInTemplateForShortcodes > UnitsInTemplate
) ;
private record class JobForShortcodes (
JobGroupForShortcodes ? Group ,
TnkForShortcodes ? Tnk ,
string WorkName ,
string Name
) ;
private record class JobGroupForShortcodes (
Guid Id ,
Guid ? GroupingUnitFieldId ,
JobGroupTypeForShortcodes ? GroupType ,
string GroupName
) ;
private record class JobGroupTypeForShortcodes (
JobGroupTypesEnum Code
) ;
private record class TnkForShortcodes (
string Name ,
string ShortName
) ;
private record class UnitInTemplateForShortcodes (
Guid UnitId
) ;
public ShortcodesService (
ILogger < ShortcodesService > logger ,
SettingsFromDb settingsFromDb ,
@@ -59,229 +115,380 @@ namespace PARR.DAL.DomainServices.Shortcodes
this . unitFilterService = unitFilterService ;
}
public Task < string > ApplyShortcodesAsync ( string str , Template template )
/// <summary>
/// Подставляет шорткоды в строке на основе данных шаблона.
/// </summary>
/// <param name="str">Строка с шорткодами</param>
/// <param name="template">Шаблон, из которого берутся данные</param>
/// <returns>Строка с подставленными значениями</returns>
public async Task < string > ApplyShortcodesAsync ( string str , Template template , [ CallerMemberName ] string? caller = null )
{
// Построим TemplateForShortcodes из уже загруженного template
var templateForShortcodes = new TemplateForShortcodes
{
Id = template . Id ,
Index = template . Index ,
JobId = template . JobId ,
UnitId = template . UnitId ,
Job = template . Job = = null ? null : new JobForShortcodes
{
Group = template . Job . Group = = null ? null : new JobGroupForShortcodes
{
Id = template . Job . GroupId ,
GroupingUnitFieldId = template . Job . Group . GroupingUnitFieldId ,
GroupType = template . Job . Group . GroupType = = null ? null : new JobGroupTypeForShortcodes
{
Code = template . Job . Group . GroupType . Code
} ,
GroupName = template . Job . Group . GroupName
} ,
Tnk = template . Job . Tnk = = null ? null : new TnkForShortcodes
{
Name = template . Job . Tnk . Name ,
ShortName = template . Job . Tnk . ShortName
} ,
WorkName = template . Job . WorkName ,
Name = template . Job . Name
} ,
UnitsInTemplate = template . UnitsInTemplate ? . Select ( uit = > new UnitInTemplateForShortcodes { UnitId = uit . UnitId } ) . ToList ( ) ? ? new List < UnitInTemplateForShortcodes > ( )
} ;
if ( string . IsNullOrEmpty ( str ) ) return str ;
if ( template = = null ) throw new ArgumentNullException ( nameof ( template ) ) ;
return ApplyShortcodesAsync ( str , templateForShortcodes ) ;
}
var callerName = caller ? ? "Unknown" ;
logger . LogDebug ( "[{Caller}] Начата подстановка шорткодов. Вход: '{Input}', templateId={TemplateId}" , callerName , str , template . Id ) ;
//todo: сделать private в перспективе
public async Task < string > ApplyShortcodesAsync ( string str , TemplateForShortcodes template )
{
if ( ! IsAnyShortcodes ( str ) )
{
logger . LogDebug ( "Строка не содержит шорткодов: {str}" , str ) ;
return str ;
}
logger . LogDebug ( "Начата подстановка шорткодов. Вход: '{Input}', templateId={TemplateId}, index={Index}" , str , template . Id , template . Index ) ;
var job = template . Job ;
if ( job = = null )
{
logger . LogError ( "Шаблон {TemplateId} не содержит Job. Подстановка прервана." , template . Id ) ;
return str ;
}
var unit = await unitService . Get ( ) . AsNoTracking ( ) . FirstOrDefaultAsync ( u = > u . Id = = template . UnitId ) ;
if ( 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 result = str ;
var iteration = 0 ;
while ( iteration < MaxStandard Iterations )
while ( iteration < MaxIterations )
{
var remainingS hortcodes = GetShortCodes ( resultName )
. Select ( m = > m . Value )
. Where ( s = > SupportedShortcodes . Contains ( s ) )
. ToList ( ) ;
var s hortcodes = GetShortCodes ( result ). Select ( m = > m . Value ) . ToList ( ) ;
if ( ! shortcodes . Any ( ) ) break ;
if ( ! remainingShortcodes . Any ( ) )
break ;
// Подготавливаем данные с догрузкой при необходимости
var data = await PrepareTemplateDataAsync ( template , shortcodes , callerName ) . ConfigureAwait ( false ) ;
var oldResult = result ;
// Подстановка всех типов шорткодов
result = await ApplyAllShortcodesOnceAsync ( result , data , shortcodes , callerName ) . ConfigureAwait ( false ) ;
var oldResult = resultName ;
resultName = ReplaceStandardShortcodes ( job , unit , resultName , template . Index ) ;
iteration + + ;
if ( resultName = = oldResult )
if ( result = = oldResult ) break ;
}
logger . LogDebug ( "[{Caller}] Подстановка завершена. Результат: '{Result}'" , callerName , result ) ;
return result ;
}
private async Task < TemplateForShortcodes > PrepareTemplateDataAsync ( Template template , List < string > shortcodes , string caller )
{
var data = new TemplateForShortcodes (
Id : template . Id ,
Index : template . Index ,
JobId : template . JobId ,
UnitId : template . UnitId ,
Job : null ,
UnitsInTemplate : template . UnitsInTemplate ? . Select ( uit = > new UnitInTemplateForShortcodes ( uit . UnitId ) ) . ToList ( ) ? ? new List < UnitInTemplateForShortcodes > ( )
) ;
// Загружаем Unit.Name
var unitName = template . Unit ? . Name ? ? await GetUnitNameAsyncWithWarning ( template , shortcodes , caller ) . ConfigureAwait ( false ) ;
// Проверяем, нужны ли данные Job
if ( ! NeedsJobForShortcodes ( shortcodes ) )
{
// Всё равно создаём пустой JobForShortcodes, чтобы не было null
data = data with { Job = new JobForShortcodes ( null , null , string . Empty , string . Empty ) } ;
return data ;
}
if ( template . Job = = null )
{
logger . LogWarning ( MissingJobWarningMsg , caller , template . Id ) ;
var jobData = await LoadJobForShortcodesAsync ( template . JobId , shortcodes , caller ) . ConfigureAwait ( false ) ;
return data with { Job = jobData } ;
}
else
{
var missingIncludes = GetMissingIncludesForShortcodes ( shortcodes , template . Job ) ;
if ( missingIncludes . Any ( ) )
{
logger . LogDebug ( "Замена стандартных шорткодов не изменила строку на итерации {Iteration}. Останов." , iteration ) ;
break ;
}
}
var problematicShortcodes = shortcodes . Where ( sc = >
( sc . Equals ( "%ГР У ППА _Р А БО Т %" , StringComparison . OrdinalIgnoreCase ) & & template . Job . Group = = null ) | |
( 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 . Equals ( "%Т Н К %" , StringComparison . OrdinalIgnoreCase ) | | sc . Equals ( "%Т Н К -К Р А Т К О %" , StringComparison . OrdinalIgnoreCase ) ) & & template . Job . Tnk = = null )
) . ToList ( ) ;
if ( iteration > = MaxStandardIterations )
{
logger . LogWarning (
"Достигнуто максимальное число итераций ({Max}) при замене стандартных шорткодов. Текущий результат: {Result}" ,
MaxStandardIterations , resultName ) ;
}
logger . LogWarning ( MissingIncludesWarningMsg , caller , template . Id , string . Join ( ", " , problematicShortcodes ) , string . Join ( " " , missingIncludes ) ) ;
// 2.5. %М А К С :FIELD% — только для групповых job (JobGroup.Type == Group)
if ( job . Group ! = null & & job . Group . GroupType ! = null & & j ob. Group . GroupType ! . Code = = J obGroupTypesEnum . Group )
{
var maxShortcodes = Regex . Matches ( resultName , maxShortcodePattern ) ;
if ( maxShortcodes . Count > 0 )
{
resultName = await ReplaceMaxShortcodesAsync ( job . Group . Id , template . UnitId , resultName , maxShortcodes ) ;
}
}
// 2.6. %БУКВЫ:FIELD% — извлекает только буквы из значения поля
var lettersShortcodes = Regex . Matches ( resultName , lettersShortcodePattern ) ;
if ( lettersShortcodes . Count > 0 )
{
resultName = await ReplaceLettersShortcodesAsync ( template . UnitId , resultName , lettersShortcodes ) ;
}
// 2.7. %ГР _ПО ЛЕ -ПН% — нумерованный список UnitsInTemplate с GroupingUnitFieldId, отсортированный по имени юнита
if ( shortcodesInMask . Any ( m = > string . Equals ( m . Value , "%ГР _ПО ЛЕ -ПН%" , StringComparison . OrdinalIgnoreCase ) ) )
{
var unitsInTemplate = template . UnitsInTemplate ;
if ( unitsInTemplate = = null | | ! unitsInTemplate . Any ( ) )
{
logger . LogDebug ( "Шаблон {TemplateId} не содержит UnitsInTemplate. %ГР _ПО ЛЕ -ПН% заменён на пустую строку." , template . Id ) ;
resultName = Regex . Replace ( resultName , "%ГР _ПО ЛЕ -ПН%" , "" , RegexOptions . IgnoreCase ) ;
var jobData = await LoadJobForShortcodesAsync ( template . JobId , shortcodes , caller ) . ConfigureAwait ( false ) ;
return data with { J ob = j obData } ;
}
else
{
var unitIds = unitsInTemplate . Select ( uit = > uit . UnitId ) . ToList ( ) ;
var jobData = MapJobForShortcodes ( template . Job ) ;
return data with { Job = jobData } ;
}
}
}
var units = await unitService . Get ( )
. AsNoTracking ( )
. Where ( u = > unitIds . Contains ( u . Id ) )
. ToListAsync ( ) ;
private static bool NeedsJobForShortcodes ( List < string > shortcodes )
{
return shortcodes . Any ( sc = >
sc ! = "%ЭК%" & & // %ЭК% не требует Job
( SupportedStandardShortcodes . Contains ( sc ) | |
sc . StartsWith ( "%М А К С :" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ГР _ПО ЛЕ -ПН%" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ИНДЕКС%" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%СВЯЗИ%" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%СВЯЗИ-ПН%" , StringComparison . OrdinalIgnoreCase ) ) ) ;
}
// Словарь: UnitId -> Unit (для быстрого поиска )
var unitDict = units . ToDictionary ( u = > u . Id , u = > u ) ;
private static List < string > GetMissingIncludesForShortcodes ( List < string > shortcodes , Job ? job )
{
var missing = new List < string > ( ) ;
var groupingFieldId = job . Group ? . GroupingUnitFieldId ;
Dictionary < Guid , s tring> valuesByUnit = new ( ) ;
if ( shortcodes . Any ( sc = >
sc . Equals ( "%ГР У ППА _Р А БО Т %" , S tringComparison . OrdinalIgnoreCase ) | |
sc . StartsWith ( "%М А К С :" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ГР _ПО ЛЕ -ПН%" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ИНДЕКС%" , StringComparison . OrdinalIgnoreCase ) )
& & job ? . Group = = null )
{
missing . Add ( ".Include(j => j.Group)" ) ;
}
if ( groupingFieldId . HasValue )
{
var fieldValues = await unitInValueService . Get ( )
. AsNoTracking ( )
. Include ( uv = > uv . Value )
. Where ( uv = >
uv . FieldId = = groupingFieldId . Value & &
unitIds . Contains ( uv . UnitId ) & &
uv . Value ! = null & &
! string . IsNullOrWhiteSpace ( uv . Value . Value ) )
. Select ( uv = > new { uv . UnitId , Value = uv . Value ! . Value } )
. ToListAsync ( ) ;
if ( shortcodes . Any ( sc = >
sc . StartsWith ( "%М А К С :" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ГР _ПО ЛЕ -ПН%" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ИНДЕКС%" , StringComparison . OrdinalIgnoreCase ) )
& & job ? . Group ? . GroupType = = null )
{
missing . Add ( ".ThenInclude(g => g.GroupType)" ) ;
}
valuesByUnit = fieldValues
. GroupBy ( x = > x . UnitId )
. ToDictionary (
g = > g . Key ,
g = > string . Join ( ", " , g . Select ( v = > v . Value ) . OrderBy ( v = > v ) )
) ;
}
if ( shortcodes . Any ( sc = >
sc . Equals ( "%Т Н К %" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%Т Н К -К Р А Т К О %" , StringComparison . OrdinalIgnoreCase ) )
& & job ? . Tnk = = null )
{
missing . Add ( ".Include(j => j.Tnk)" ) ;
}
// Сортируем UnitsInTemplate по имени юнита
var sortedUnitsInTemplate = unitsInTemplate
. OrderBy ( uit = > unitDict . TryGetValue ( uit . UnitId , out var unit ) ? unit . Name : $"(UnitId={uit.UnitId})" )
. ToList ( ) ;
return missing ;
}
var lines = sortedUnitsInTemplate
. Select ( ( uit , indexInList ) = >
{
var unitInList = unitDict . TryGetValue ( uit . UnitId , out var unit ) ? unit : null ;
var unitName = unitInList ? . Name ? ? $"(UnitId={uit.UnitId})" ;
var valuesStr = valuesByUnit . TryGetValue ( uit . UnitId , out var vals ) ? vals : "" ;
return $"{indexInList + 1}. {unitName} ({valuesStr})" ;
} )
. ToList ( ) ;
private async Task < string > GetUnitNameAsyncWithWarning ( Template template , List < string > shortcodes , string caller )
{
if ( shortcodes . Any ( sc = > sc . Equals ( "%ЭК%" , StringComparison . OrdinalIgnoreCase ) ) )
{
logger . LogWarning ( MissingUnitNameWarningMsg , caller , template . Id ) ;
}
var resultText = string . Jo in ( "\n" , lines ) ;
resultName = Regex . Replace ( resultName , "%ГР _ПО ЛЕ -ПН%" , resultText , RegexOptions . IgnoreCas e) ;
var unit = await unitService . Get ( ) . AsNoTrack ing ( ) . FirstOrDefaultAsync ( u = > u . Id = = template . UnitId ) . ConfigureAwait ( false ) ;
if ( unit = = null | | string . IsNullOrEmpty ( unit . Nam e) )
throw new InvalidOperationException ( $"Unit {template.UnitId} не найден или не содержит Name." ) ;
return unit . Name ;
}
private JobForShortcodes MapJobForShortcodes ( Job job )
{
return new JobForShortcodes (
Group : job . Group = = null ? null : new JobGroupForShortcodes (
Id : job . Group . Id ,
GroupingUnitFieldId : job . Group . GroupingUnitFieldId ,
GroupName : job . Group . GroupName ? ? string . Empty ,
GroupType : job . Group . GroupType = = null ? null : new JobGroupTypeForShortcodes ( job . Group . GroupType . Code )
) ,
Tnk : job . Tnk = = null ? null : new TnkForShortcodes (
Name : job . Tnk . Name ? ? string . Empty ,
ShortName : job . Tnk . ShortName ? ? string . Empty
) ,
WorkName : job . WorkName ? ? string . Empty ,
Name : job . Name ? ? string . Empty
) ;
}
private async Task < JobForShortcodes > LoadJobForShortcodesAsync ( Guid jobId , List < string > shortcodes , string caller )
{
var query = jobService . Get ( ) . AsNoTracking ( ) ;
// Всегда загружаем Group и GroupType, если нужны групповые шорткоды
if ( shortcodes . Any ( sc = >
sc . StartsWith ( "%М А К С :" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ГР _ПО ЛЕ -ПН%" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%ИНДЕКС%" , StringComparison . OrdinalIgnoreCase ) ) )
{
query = query
. Include ( j = > j . Group )
. ThenInclude ( g = > g ! . GroupType ) ;
}
// Для стандартных шорткодов тоже может понадобиться Group.GroupName
if ( shortcodes . Any ( sc = > sc . Equals ( "%ГР У ППА _Р А БО Т %" , StringComparison . OrdinalIgnoreCase ) ) )
{
query = query
. Include ( j = > j . Group ) ;
}
// Для Tnk
if ( shortcodes . Any ( sc = >
sc . Equals ( "%Т Н К %" , StringComparison . OrdinalIgnoreCase ) | |
sc . Equals ( "%Т Н К -К Р А Т К О %" , StringComparison . OrdinalIgnoreCase ) ) )
{
query = query
. Include ( j = > j . Tnk ) ;
}
var job = await query . FirstOrDefaultAsync ( j = > j . Id = = jobId ) . ConfigureAwait ( false ) ;
if ( job = = null )
throw new InvalidOperationException ( $"Job {jobId} не найден." ) ;
return MapJobForShortcodes ( job ) ;
}
private async Task < string > ApplyAllShortcodesOnceAsync ( string str , TemplateForShortcodes data , List < string > shortcodesInMask , string caller )
{
var result = str ;
// 1. Константы
var nameConstants = settingsFromDb . TemplateNameConstantPartsList ;
if ( shortcodesInMask . Any ( m = > nameConstants . Any ( c = > $"%{c.Name}%" . Equals ( m , StringComparison . OrdinalIgnoreCase ) ) ) )
{
result = ReplaceConstants ( nameConstants , result ) ;
}
// 2. Стандартные
var hasStandardShortcodes = shortcodesInMask . Any ( m = > SupportedStandardShortcodes . Contains ( m ) ) ;
if ( hasStandardShortcodes )
{
var unitName = await GetUnitNameForTemplateAsync ( data . UnitId ) . ConfigureAwait ( false ) ;
result = ReplaceStandardShortcodes ( data . Job , unitName , result ) ;
}
// 3. GroupJob: %ИНДЕКС%, %М А К С :..., %ГР _ПО ЛЕ -ПН%
if ( shortcodesInMask . Any ( m = > string . Equals ( m , "%ИНДЕКС%" , StringComparison . OrdinalIgnoreCase ) ) )
{
result = ReplaceSingleShortcode ( result , "%ИНДЕКС%" , data . Index ? . ToString ( ) ) ;
}
if ( data . Job ? . Group ? . GroupType ? . Code = = JobGroupTypesEnum . Group )
{
var maxShortcodes = MaxShortcodeRegex . Matches ( result ) ;
if ( maxShortcodes . Count > 0 )
{
result = await ReplaceMaxShortcodesAsync ( data . Job . Group . Id , data . UnitId , result , maxShortcodes , caller ) . ConfigureAwait ( false ) ;
}
}
// 3. %СВЯЗИ% или %СВЯЗИ-ПН% (если всё ещё зависят от jobId/unitId )
if ( shortcodesInMask . Any ( m = > string . Equals ( m , "%ГР _ПО ЛЕ -ПН%" , StringComparison . OrdinalIgnoreCase ) ) )
{
if ( data . Job ? . Group = = null )
{
logger . LogWarning ( "[{Caller}] Job не содержит Group, необходимый для %ГР _ПО ЛЕ -ПН%. Шорткод пропущен." , caller ) ;
}
else
{
List < Guid > unitIds ;
bool wasLoadedFromDb = false ;
if ( data . UnitsInTemplate . Count = = 0 )
{
logger . LogWarning (
"[{Caller}] Template {TemplateId} не содержит UnitsInTemplate. Данные будут догружены из БД. " +
"Рекомендуется обновить запрос с Include(t => t.UnitsInTemplate)." ,
caller , data . Id ) ;
var fullTemplate = await templateService . Get ( )
. AsNoTracking ( )
. Include ( t = > t . UnitsInTemplate )
. FirstOrDefaultAsync ( t = > t . Id = = data . Id )
. ConfigureAwait ( false ) ;
unitIds = fullTemplate ? . UnitsInTemplate ? . Select ( uit = > uit . UnitId ) . ToList ( ) ? ? new List < Guid > ( ) ;
wasLoadedFromDb = true ;
}
else
{
unitIds = data . UnitsInTemplate . Select ( uit = > uit . UnitId ) . ToList ( ) ;
}
if ( unitIds . Count = = 0 )
{
logger . LogDebug ( "[{Caller}] Нет UnitsInTemplate для шаблона {TemplateId}. %ГР _ПО ЛЕ -ПН% заменён на пустую строку." , caller , data . Id ) ;
result = Regex . Replace ( result , "%ГР _ПО ЛЕ -ПН%" , "" , RegexOptions . IgnoreCase ) ;
}
else
{
var units = await unitService . Get ( )
. AsNoTracking ( )
. Where ( u = > unitIds . Contains ( u . Id ) )
. ToDictionaryAsync ( u = > u . Id , u = > u ) . ConfigureAwait ( false ) ;
var groupingFieldId = data . Job . Group . GroupingUnitFieldId ;
Dictionary < Guid , string > valuesByUnit = new ( ) ;
if ( groupingFieldId . HasValue )
{
var fieldValues = await unitInValueService . Get ( )
. AsNoTracking ( )
. Include ( uv = > uv . Value )
. Where ( uv = >
uv . FieldId = = groupingFieldId . Value & &
unitIds . Contains ( uv . UnitId ) & &
uv . Value ! = null & &
! string . IsNullOrWhiteSpace ( uv . Value . Value ) )
. Select ( uv = > new { uv . UnitId , Value = uv . Value ! . Value } )
. ToListAsync ( ) . ConfigureAwait ( false ) ;
valuesByUnit = fieldValues
. GroupBy ( x = > x . UnitId )
. ToDictionary ( g = > g . Key , g = > string . Join ( ", " , g . OrderBy ( v = > v . Value ) . Select ( v = > v . Value ) ) ) ;
}
var actualUnitsInTemplate = unitIds . Select ( id = > new UnitInTemplateForShortcodes ( id ) ) . ToList ( ) ;
var sortedUnitsInTemplate = actualUnitsInTemplate
. OrderBy ( uit = > units . TryGetValue ( uit . UnitId , out var u ) ? u . Name : uit . UnitId . ToString ( ) )
. ToList ( ) ;
var lines = sortedUnitsInTemplate
. Select ( ( uit , i ) = >
{
var unitName = units . TryGetValue ( uit . UnitId , out var u ) ? u . Name : $"(UnitId={uit.UnitId})" ;
var valuesStr = valuesByUnit . TryGetValue ( uit . UnitId , out var vals ) ? vals : "" ;
return $"{i + 1}. {unitName} ({valuesStr})" ;
} ) ;
result = Regex . Replace ( result , "%ГР _ПО ЛЕ -ПН%" , string . Join ( "\n" , lines ) , RegexOptions . IgnoreCase ) ;
}
}
}
// 4. Transform: %БУКВЫ:...
var lettersShortcodes = LettersShortcodeRegex . Matches ( result ) ;
if ( lettersShortcodes . Count > 0 )
{
result = await ReplaceLettersShortcodesAsync ( data . UnitId , result , lettersShortcodes , caller ) . ConfigureAwait ( false ) ;
}
// 5. %СВЯЗИ% и %СВЯЗИ-ПН%
List < string > ? relatedUnitNames = null ;
if ( shortcodesInMask . Any ( m = > string . Equals ( m . Value , "%СВЯЗИ%" , StringComparison . OrdinalIgnoreCase ) ) )
if ( shortcodesInMask . Any ( m = > string . Equals ( m , "%СВЯЗИ%" , StringComparison . OrdinalIgnoreCase ) ) )
{
relatedUnitNames ? ? = await unitFilterService . GetRelatedUnitNamesAsync ( template . JobId , template . UnitId ) ;
var linksText = string . Join ( "\n" , relatedUnitNames ) ;
resul tName = Regex . Rep lace ( resultName , "%СВЯЗИ%" , linksText , RegexOptions . IgnoreCa se ) ;
if ( data . Job ! = null )
{
relatedUni tNames ? ? = await unitFilterService . Get RelatedUnitNamesAsync ( data . JobId , data . UnitId ) . ConfigureAwait ( fal se) ;
result = Regex . Replace ( result , "%СВЯЗИ%" , string . Join ( "\n" , relatedUnitNames ) , RegexOptions . IgnoreCase ) ;
}
}
if ( shortcodesInMask . Any ( m = > string . Equals ( m , "%СВЯЗИ-ПН%" , StringComparison . OrdinalIgnoreCase ) ) )
{
if ( data . Job ! = null )
{
relatedUnitNames ? ? = await unitFilterService . GetRelatedUnitNamesAsync ( data . JobId , data . UnitId ) . ConfigureAwait ( false ) ;
var numbered = relatedUnitNames . Select ( ( name , i ) = > $"{i + 1}. {name}" ) ;
result = Regex . Replace ( result , "%СВЯЗИ-ПН%" , string . Join ( "\n" , numbered ) , RegexOptions . IgnoreCase ) ;
}
}
if ( shortcodesInMask . Any ( m = > string . Equals ( m . Value , "%СВЯЗИ-ПН%" , StringComparison . OrdinalIgnoreCase ) ) )
{
relatedUnitNames ? ? = await unitFilterService . GetRelatedUnitNamesAsync ( template . JobId , template . UnitId ) ;
var linksText = string . Join ( "\n" , relatedUnitNam es. Select ( ( name , i ) = > $"{i + 1}. {name}" ) ) ;
resultName = Regex . Replace ( resultName , "%СВЯЗИ-ПН%" , linksText , RegexOptions . IgnoreCase ) ;
}
// 6. Поля
var fieldShortcodes = GetShortCodes ( result ) ;
if ( fieldShortcodes . Count > 0 )
result = await ReplaceFieldValu es( data . UnitId , result , fieldShortcodes ) . ConfigureAwait ( false ) ;
// 4. Поля (оставшиеся %FIELD_NAME%)
shortcodesInMask = GetShortCodes ( resultName ) ;
if ( shortcodesInMask . Count > 0 )
resultName = await ReplaceFieldValues ( template . UnitId , resultName , shortcodesInMask ) ;
logger . LogDebug ( "Подстановка завершена. Результат: '{Result}'" , resultName ) ;
return resultName ;
return result ;
}
private bool IsAnyShortcodes ( string str )
private async Task < string > GetUnitNameForTemplateAsync ( Guid unitId )
{
return Regex . IsMatch ( str , shortcodePattern ) | |
Regex . IsMatch ( str , maxShortcodePattern ) | |
Regex . IsMatch ( str , lettersShortcodePattern ) ;
var unit = await unitService . Get ( ) . AsNoTracking ( ) . FirstOrDefaultAsync ( u = > u . Id = = unitId ) . ConfigureAwait ( false ) ;
if ( unit = = null | | string . IsNullOrEmpty ( unit . Name ) )
throw new InvalidOperationException ( $"Unit {unitId} не найден или не содержит Name." ) ;
return unit . Name ;
}
private static List < Match > GetShortCodes ( string resultName )
{
var shortcodesInMask = Regex . Matches ( resultName , shortcodePattern ). ToList ( ) ;
var shortcodesInMask = GeneralShortcode Regex. Matches ( resultName ) . ToList ( ) ;
return shortcodesInMask ;
}
@@ -305,32 +512,48 @@ namespace PARR.DAL.DomainServices.Shortcodes
{
new ShortcodeInfoDto { Shortcode = "%ЭК%" ,
Description = "Наименование ЭК(Код поиска)" ,
Type = ShortcodeTypeEnum . Standart } ,
Type = ShortcodeTypeEnum . Standard } ,
new ShortcodeInfoDto { Shortcode = "%ГР У ППА _Р А БО Т %" ,
Description = "Наименование группы работ" ,
Type = ShortcodeTypeEnum . Standart } ,
Type = ShortcodeTypeEnum . Standard } ,
new ShortcodeInfoDto { Shortcode = "%РАБОТА%" ,
Description = "Наименование работы в А С У ЕСПП" ,
Type = ShortcodeTypeEnum . Standart } ,
Type = ShortcodeTypeEnum . Standard } ,
new ShortcodeInfoDto { Shortcode = "%Т Н К %" ,
Description = "Полное наименование Т Н К " ,
Type = ShortcodeTypeEnum . Standart } ,
Type = ShortcodeTypeEnum . Standard } ,
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
Type = ShortcodeTypeEnum . Standard } ,
new ShortcodeInfoDto { Shortcode = "%ТИКТАК %" ,
Description = "Текущее время в формате Unix timestamp (секунды с 1970-01-01 UTC) " ,
Type = ShortcodeTypeEnum . Standard
}
} ) ;
// 3. Связи
// 3. Групповые шорткоды
result . AddRange ( new [ ]
{
new ShortcodeInfoDto { Shortcode = "%ИНДЕКС%" ,
Description = "Порядковый индекс шаблона для групповых работ" ,
Type = ShortcodeTypeEnum . GroupValue } ,
new ShortcodeInfoDto { Shortcode = "%М А К С :ИМЯ АТРИБУТА%" ,
Description = "Используется только с групповым типом работ. Наиболее часто встречающееся значение поля в группе (игнорирует пустые). Пример: %М А К С :Р А БО ЧА Я_ГР _О Т В _З А _ЭК %" ,
Type = ShortcodeTypeEnum . GroupValue } ,
new ShortcodeInfoDto { Shortcode = "%ГР _ПО ЛЕ -ПН%" ,
Description = "Нумерованный список unit-ов из шаблона: 1. ЭК-123 (З на че ние 1, З на че ние 2). Использует GroupingUnitFieldId из JobGroup." ,
Type = ShortcodeTypeEnum . GroupValue } ,
} ) ;
// 4. Трансформирующие шорткоды
result . Add ( new ShortcodeInfoDto
{
Shortcode = "%БУКВЫ:ИМЯ АТРИБУТА%" ,
Description = "Извлекает только буквы из значения поля. Пример: %БУКВЫ:З О Н А _О Т В Е Т С Т В Е Н Н О С Т И% → ПРИВ" ,
Type = ShortcodeTypeEnum . Transform
} ) ;
// 5. Связи
result . AddRange ( new [ ]
{
new ShortcodeInfoDto { Shortcode = "%СВЯЗИ%" ,
@@ -339,13 +562,10 @@ namespace PARR.DAL.DomainServices.Shortcodes
new ShortcodeInfoDto { Shortcode = "%СВЯЗИ-ПН%" ,
Description = "Связанные ЭК с нумерацией (1. ..., 2. ...), выбираются только при настроенном фильтре по полям в связанных ЭК" ,
Type = ShortcodeTypeEnum . Relationship } ,
new ShortcodeInfoDto { Shortcode = "%ГР _ПО ЛЕ -ПН%" ,
Description = "Нумерованный список unit-ов из шаблона: 1. ЭК-123 (З на че ние 1, З на че ние 2). Использует GroupingUnitFieldId из JobGroup." ,
Type = ShortcodeTypeEnum . Relationship } ,
} ) ;
// 4 . В с е доступные поля из UnitField
var fieldNames = await unitFieldService . Get ( ) . AsNoTracking ( ) . Select ( t = > new { t . AihitName , t . DisplayName } ) . ToListAsync ( ) ;
// 6 . В с е доступные поля из UnitField
var fieldNames = await unitFieldService . Get ( ) . AsNoTracking ( ) . Select ( t = > new { t . AihitName , t . DisplayName } ) . ToListAsync ( ) . ConfigureAwait ( false ) ;
foreach ( var fieldName in fieldNames . OrderBy ( n = > n . AihitName ) )
{
result . Add ( new ShortcodeInfoDto
@@ -367,7 +587,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
if ( requiredFieldNames . Count = = 0 )
return resultName ;
var fieldValues = await unitInValueService . GetFieldValuesAsync ( unitId , requiredFieldNames ) ;
var fieldValues = await unitInValueService . GetFieldValuesAsync ( unitId , requiredFieldNames ) . ConfigureAwait ( false ) ;
var fieldValuesMap = fieldValues
. GroupBy ( x = > x . FieldName , StringComparer . OrdinalIgnoreCase )
@@ -395,15 +615,56 @@ namespace PARR.DAL.DomainServices.Shortcodes
return resultName ;
}
private static string ReplaceStandardShortcodes ( JobForShortcodes job , Unit unit , string input , int? index = null )
private static string ReplaceStandardShortcodes ( JobForShortcodes ? job , string unitName , 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 . OrdinalIgnoreCas e)
. Replace ( "%ИНДЕКС%" , index ? . ToString ( ) ? ? "" , StringComparison . OrdinalIgnoreCase ) ;
var result = input ;
// %ЭК% — заменяем, только если есть в строке
if ( result . Contains ( "%ЭК%" , StringComparison . OrdinalIgnoreCase ) )
{
result = ReplaceSingleShortcode ( result , "%ЭК% " , unitNam e) ;
}
// %ТИКТАК% — не зависит от Job или Unit
if ( result . Contains ( "%ТИКТАК%" , StringComparison . OrdinalIgnoreCase ) )
{
result = ReplaceSingleShortcode ( result , "%ТИКТАК%" , DateTimeOffset . UtcNow . ToUnixTimeSeconds ( ) . ToString ( ) ) ;
}
if ( job ! = null )
{
if ( result . Contains ( "%РАБОТА%" , StringComparison . OrdinalIgnoreCase ) )
{
result = ReplaceSingleShortcode ( result , "%РАБОТА%" , job . WorkName ) ;
}
if ( result . Contains ( "%ГР У ППА _Р А БО Т %" , StringComparison . OrdinalIgnoreCase ) )
{
result = ReplaceSingleShortcode ( result , "%ГР У ППА _Р А БО Т %" , job . Group ? . GroupName ) ;
}
if ( result . Contains ( "%Т Н К %" , StringComparison . OrdinalIgnoreCase ) )
{
result = ReplaceSingleShortcode ( result , "%Т Н К %" , job . Tnk ? . Name ) ;
}
if ( result . Contains ( "%Т Н К -К Р А Т К О %" , StringComparison . OrdinalIgnoreCase ) )
{
result = ReplaceSingleShortcode ( result , "%Т Н К -К Р А Т К О %" , job . Tnk ? . ShortName ) ;
}
}
return result ;
}
private static string ReplaceSingleShortcode ( string input , string shortcode , string? replacement )
{
if ( replacement = = null )
{
throw new InvalidOperationException ( $"Шорткод '{shortcode}' требует непустое значение, но подставляемое значение равно null." ) ;
}
return input . Replace ( shortcode , replacement , StringComparison . OrdinalIgnoreCase ) ;
}
private static string ReplaceConstants ( List < BLL . Domain . TemplateNameConstantPart > nameConstants , string resultName )
@@ -414,17 +675,17 @@ namespace PARR.DAL.DomainServices.Shortcodes
return resultName ;
}
private async Task < string > ReplaceMaxShortcodesAsync ( Guid jobGroupId , Guid unitId , string input , MatchCollection maxShortcodes )
private async Task < string > ReplaceMaxShortcodesAsync ( Guid jobGroupId , Guid unitId , string input , MatchCollection maxShortcodes , string caller )
{
if ( jobGroupId = = Guid . Empty )
{
logger . LogWarning ( "jobGroupId не задан. Пропускаем обработку %М А К С :...%" ) ;
logger . LogWarning ( "[{Caller}] jobGroupId не задан. Пропускаем обработку %М А К С :...%" , caller );
return input ;
}
if ( unitId = = Guid . Empty )
{
logger . LogWarning ( "unitId не задан. Пропускаем обработку %М А К С :...%" ) ;
logger . LogWarning ( "[{Caller}] unitId не задан. Пропускаем обработку %М А К С :...%" , caller );
return input ;
}
@@ -439,10 +700,10 @@ namespace PARR.DAL.DomainServices.Shortcodes
var matches = kvp . Value ;
var fieldName = fullShortcode . Trim ( '%' ) . Split ( ':' , 2 ) [ 1 ] . Trim ( ) ;
logger . LogDebug ( "Обработка {Shortcode} для JobGroup {JobGroupId}, Template.UnitId {UnitId}, fieldName {FieldName}" ,
fullShortcode , jobGroupId , unitId , fieldName ) ;
logger . LogDebug ( "[{Caller}] Обработка {Shortcode} для JobGroup {JobGroupId}, Template.UnitId {UnitId}, fieldName {FieldName}" ,
caller , fullShortcode, jobGroupId , unitId , fieldName ) ;
var mostFrequentValue = await GetMaxShortCodeFromCacheOrDbAsync ( jobGroupId , unitId , fullShortcode , fieldName ) ;
var mostFrequentValue = await GetMaxShortCodeFromCacheOrDbAsync ( jobGroupId , unitId , fullShortcode , fieldName , caller ) . ConfigureAwait ( false );
foreach ( var match in matches )
input = input . Replace ( match . Value , mostFrequentValue ) ;
@@ -451,30 +712,30 @@ namespace PARR.DAL.DomainServices.Shortcodes
return input ;
}
private async Task < string > GetMaxShortCodeFromCacheOrDbAsync ( Guid jobGroupId , Guid unitId , string fullShortcode , string fieldName )
private async Task < string > GetMaxShortCodeFromCacheOrDbAsync ( Guid jobGroupId , Guid unitId , string fullShortcode , string fieldName , string caller )
{
if ( jobGroupId = = Guid . Empty )
{
logger . LogWarning ( "jobGroupId не задан. Пропускаем обработку шорткода {Shortcode}" , fullShortcode ) ;
return "Нет данных" ;
logger . LogWarning ( "[{Caller}] jobGroupId не задан. Пропускаем обработку шорткода {Shortcode}" , caller , fullShortcode ) ;
return NoContent ;
}
if ( unitId = = Guid . Empty )
{
logger . LogWarning ( "unitId не задан. Пропускаем обработку шорткода {Shortcode}" , fullShortcode ) ;
return "Нет данных" ;
logger . LogWarning ( "[{Caller}] unitId не задан. Пропускаем обработку шорткода {Shortcode}" , caller , fullShortcode ) ;
return NoContent ;
}
var cacheKey = $"gr_shcd_{jobGroupId:N}_{unitId:N}_{ComputeHash(fullShortcode)}" ;
var cachedData = await cacheService . GetCachedDataAsync < CachedGroupedShortCode > ( cacheKey ) ;
var cachedData = await cacheService . GetCachedDataAsync < CachedGroupedShortCode > ( cacheKey ) . ConfigureAwait ( false ) ;
if ( cachedData ! = null )
{
logger . LogDebug ( "Кэш попал для GroupedShortCode '{Name}': {Value}" , fullShortcode , cachedData . Value ) ;
logger . LogDebug ( "[{Caller}] Кэш попал для GroupedShortCode '{Name}': {Value}" , caller , fullShortcode , cachedData . Value ) ;
return cachedData . Value ;
}
logger . LogDebug ( "Кэш промахнут для GroupedShortCode '{Name}'. Запрашиваем из БД." , fullShortcode ) ;
logger . LogDebug ( "[{Caller}] Кэш промахнут для GroupedShortCode '{Name}'. Запрашиваем из БД." , caller , fullShortcode ) ;
// Загружаем юниты из БД
var effectiveUnitIds = await jobService . Get ( )
@@ -490,14 +751,14 @@ namespace PARR.DAL.DomainServices.Shortcodes
. SelectMany ( template = > template . UnitsInTemplate )
. Select ( uit = > uit . UnitId )
. Distinct ( )
. ToListAsync ( ) ;
. ToListAsync ( ) . ConfigureAwait ( false ) ;
logger . LogDebug ( "EffectiveUnitIds: [{Ids}], Count: {Count}" , string . Join ( ", " , effectiveUnitIds ) , effectiveUnitIds . Count ) ;
logger . LogDebug ( "[{Caller}] EffectiveUnitIds: [{Ids}], Count: {Count}" , caller , string . Join ( ", " , effectiveUnitIds ) , effectiveUnitIds . Count ) ;
// Вызываем метод из сервиса
var mostFrequentValue = await unitInValueService . GetMostFrequentValueForFieldAsync ( effectiveUnitIds , fieldName ) ;
var mostFrequentValue = await unitInValueService . GetMostFrequentValueForFieldAsync ( effectiveUnitIds , fieldName ) . ConfigureAwait ( false ) ;
logger . LogDebug ( "Результат GetMostFrequentValueForFieldAsync: {Result}, для поля {FieldName}, unitIds: [{Ids}]" , mostFrequentValue , fieldName , string . Join ( ", " , effectiveUnitIds ) ) ;
logger . LogDebug ( "[{Caller}] Результат GetMostFrequentValueForFieldAsync: {Result}, для поля {FieldName}, unitIds: [{Ids}]" , caller , mostFrequentValue , fieldName , string . Join ( ", " , effectiveUnitIds ) ) ;
// Н е кэшируем пустые значения
if ( ! string . IsNullOrEmpty ( mostFrequentValue ) )
@@ -506,18 +767,18 @@ namespace PARR.DAL.DomainServices.Shortcodes
{
Value = mostFrequentValue ,
Timestamp = DateTimeOffset . UtcNow ,
Source = GetT ype( ) . Name ,
Source = t ypeof ( ShortcodesService ) . Name ,
Version = 1
} ;
await cacheService . SetCachedDataAsync ( cacheKey , toCache , TimeSpan . FromHours ( 1 ) ) ;
await cacheService . SetCachedDataAsync ( cacheKey , toCache , TimeSpan . FromHours ( 1 ) ) . ConfigureAwait ( false ) ;
return mostFrequentValue ;
}
return "Нет данных" ;
return NoContent ;
}
private async Task < string > ReplaceLettersShortcodesAsync ( Guid unitId , string input , MatchCollection lettersShortcodes )
private async Task < string > ReplaceLettersShortcodesAsync ( Guid unitId , string input , MatchCollection lettersShortcodes , string caller )
{
var shortcodeToMatches = lettersShortcodes
. Cast < Match > ( )
@@ -526,30 +787,29 @@ namespace PARR.DAL.DomainServices.Shortcodes
foreach ( var kvp in shortcodeToMatches )
{
var fullShortcode = kvp . Key ; // например, "%БУКВЫ:З О Н А _О Т В Е Т С Т В Е Н Н О С Т И%"
var fullShortcode = kvp . Key ;
var matches = kvp . Value ;
var fieldName = fullShortcode . Trim ( '%' ) . Split ( ':' , 2 ) [ 1 ] . Trim ( ) ; // "З О Н А _О Т В Е Т С Т В Е Н Н О С Т И"
var fieldName = fullShortcode . Trim ( '%' ) . Split ( ':' , 2 ) [ 1 ] . Trim ( ) ;
logger . LogDebug ( "Обработка {Shortcode} для unitId {UnitId}, fieldName {FieldName}" , fullShortcode , unitId , fieldName ) ;
logger . LogDebug ( "[{Caller}] Обработка {Shortcode} для unitId {UnitId}, fieldName {FieldName}" , caller , fullShortcode , unitId , fieldName ) ;
// Получаем значение поля через unitInValueService. GetFieldValuesAsync
var fieldValues = await unitInValueService . GetFieldValuesAsync ( unitId , new List < string > { fieldName } ) ;
var fieldValues = await unitInValueService. GetFieldValuesAsync( unitId , new List < string > { fieldName } ) . ConfigureAwait ( false ) ;
string extractedLetters = string . Empty ;
if ( fieldValues . Any ( ) )
{
var value = fieldValues . First ( ) . Value ; // Берём первое значение, если несколько
var value = fieldValues . First ( ) . Value ;
if ( value ! = null )
{
extractedLetters = ExtractLettersOnly ( value ) ;
logger . LogDebug ( "Извлечены буквы: '{Letters}' из значения '{Value}'" , extractedLetters , value ) ;
logger . LogDebug ( "[{Caller}] Извлечены буквы: '{Letters}' из значения '{Value}'" , caller , extractedLetters , value ) ;
}
}
if ( string . IsNullOrEmpty ( extractedLetters ) )
{
logger . LogDebug ( "Для шорткода {Shortcode} не найдено подходящее значение или из него нельзя извлечь буквы" , fullShortcode ) ;
logger . LogDebug ( "[{Caller}] Для шорткода {Shortcode} не найдено подходящее значение или из него нельзя извлечь буквы" , caller , fullShortcode ) ;
}
foreach ( var match in matches )