2025-05-30 09:50:47 +10:00
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Logging ;
using Newtonsoft.Json.Linq ;
2025-11-27 17:11:14 +10:00
using PARR.AIHITMainSyncer.Settings ;
2025-05-19 16:09:55 +10:00
using PARR.BLL.Domain.Mq ;
using PARR.BLL.Services.Interfaces ;
using PARR.DAL.Models.Unit ;
using PARR.DAL.Services.Interfaces.Unit ;
namespace PARR.AIHITMainSyncer.Services
{
internal class SyncerService : ISyncerService
{
private readonly ILogger < SyncerService > logger ;
private readonly ITransformService transformService ;
private readonly IUnitService unitService ;
private readonly IUnitFieldService unitFieldService ;
2025-05-21 11:53:07 +10:00
private readonly IUnitFieldValueService unitFieldValueService ;
2025-11-27 17:11:14 +10:00
private readonly FieldValueReplacementsSettings fieldValueReplacementsSettings ;
2025-05-19 16:09:55 +10:00
public SyncerService (
ILogger < SyncerService > logger ,
ITransformService transformService ,
IUnitService unitService ,
2025-05-21 11:53:07 +10:00
IUnitFieldService unitFieldService ,
2025-11-27 17:11:14 +10:00
IUnitFieldValueService unitFieldValueService ,
FieldValueReplacementsSettings fieldValueReplacementsSettings
2025-05-19 16:09:55 +10:00
)
{
this . logger = logger ;
this . transformService = transformService ;
this . unitService = unitService ;
this . unitFieldService = unitFieldService ;
2025-05-21 11:53:07 +10:00
this . unitFieldValueService = unitFieldValueService ;
2025-11-27 17:11:14 +10:00
this . fieldValueReplacementsSettings = fieldValueReplacementsSettings ;
2025-05-19 16:09:55 +10:00
}
public async Task SyncAsync ( string msg )
{
2025-08-26 12:04:26 +10:00
//logger.LogInformation("Запуск синхронизации данных из очереди сообщений в ПАРР.");
2025-05-19 16:09:55 +10:00
var objFromQuery = transformService . GetModelFromJson < AihitMainDataMq > ( msg ) ;
if ( objFromQuery = = null )
return ;
await SyncUnitAsync ( objFromQuery ) ;
}
private async Task SyncUnitAsync ( AihitMainDataMq objFromQuery )
{
2025-09-25 13:54:33 +10:00
//Получаем сразу словарь атрибутов, чтобы не ходить каждый раз в базу
2025-09-25 16:28:18 +10:00
var fieldsFromDB = await unitFieldService . Get ( ) . AsNoTracking ( ) /*.Select(t => new FieldDto { Id = t.Id, Name = t.AihitName, IsMultipleValue = t.IsMultipleValue })*/ . ToListAsync ( ) ;
2025-09-25 13:54:33 +10:00
if ( ! objFromQuery . Properties . Any ( ) )
return ;
2025-11-27 17:11:14 +10:00
// 1. Создаем копию и нормализуем свойства (удаляем лишние пробелы)
var normalizedProperties = new Dictionary < string , string? > ( ) ;
foreach ( var kvp in objFromQuery . Properties )
normalizedProperties [ kvp . Key . Trim ( ) ] = kvp . Value ? . Trim ( ) ;
// 2. Применяем замены значений к нормализованным свойствам
ApplyFieldValueReplacements ( normalizedProperties ) ;
2025-11-19 11:52:41 +10:00
2025-09-25 13:54:33 +10:00
var listAihitData = new List < KeyValuePair < string , string? > > ( ) ;
2025-11-27 17:11:14 +10:00
// 3. Обрабатываем простые значения из нормализованных свойств
var propertisWithSimpleValues = normalizedProperties
2025-09-25 16:28:18 +10:00
. Where ( t = > fieldsFromDB . Any ( a = > IsStringEqual ( t . Key . Trim ( ) , a . AihitName ) & & a . IsMultipleValue ! = true ) )
2025-09-25 13:54:33 +10:00
. Select ( s = > new KeyValuePair < string , string? > ( s . Key . Trim ( ) , s . Value ? . Trim ( ) ) ) . ToList ( ) ;
listAihitData . AddRange ( propertisWithSimpleValues ) ;
2025-11-27 17:11:14 +10:00
// 4. Обрабатываем множественные значения из нормализованных свойств
2025-09-25 16:28:18 +10:00
var propertiesWithMultipleValues = objFromQuery . Properties . Where ( t = > ! propertisWithSimpleValues . Any ( a = > t . Key . Trim ( ) = = a . Key ) ) . ToList ( ) ;
2025-09-25 13:54:33 +10:00
propertiesWithMultipleValues . ForEach ( p = > listAihitData . AddRange (
SplitValues ( p ) . Distinct ( ) . Select ( s = > new KeyValuePair < string , string? > ( s . Key . Trim ( ) , s . Value ? . Trim ( ) ) )
) ) ;
var unit = await GetUnit ( objFromQuery . Name ) ;
2025-11-19 11:52:41 +10:00
var incomingPairs = listAihitData
. Select ( kv = > new { kv . Key , kv . Value } )
. ToList ( ) ;
2025-09-25 13:54:33 +10:00
2025-11-27 17:11:14 +10:00
// Определяем, какие ПОЛЯ обновляются этим сообщением чтобы случайно не удалить не наши
2025-11-19 14:06:41 +10:00
var fieldsInMessage = listAihitData
. Select ( kv = > kv . Key )
. Distinct ( StringComparer . OrdinalIgnoreCase )
. ToHashSet ( ) ;
2025-11-19 11:52:41 +10:00
var unitValuesToRemove = unit . UnitValues
2025-11-19 14:06:41 +10:00
. Where ( uv = >
uv . Field ! = null & &
fieldsInMessage . Contains ( uv . Field . AihitName ) & &
! listAihitData . Any ( kv = >
IsStringEqual ( uv . Field . AihitName , kv . Key ) & &
IsStringEqual ( uv . Value ? . Value , kv . Value )
)
)
2025-11-19 11:52:41 +10:00
. ToList ( ) ;
unitValuesToRemove . ForEach ( uv = > unit . UnitValues . Remove ( uv ) ) ;
2025-09-25 13:54:33 +10:00
2025-11-19 11:52:41 +10:00
var unitValueMissing = listAihitData . Where ( t = >
2025-09-25 13:54:33 +10:00
! unit . UnitValues . Any ( a = >
IsStringEqual ( t . Key , a . Field ! . AihitName ) & &
IsStringEqual ( t . Value , a . Value ! . Value )
) ) . ToList ( ) ;
2025-11-19 11:52:41 +10:00
if ( unitValueMissing . Any ( ) )
2025-09-25 13:54:33 +10:00
{
2025-11-19 11:52:41 +10:00
var fieldsInChanges = unitValueMissing . Select ( s = > s . Key ) . Distinct ( ) . ToList ( ) ;
var valuesInChanges = unitValueMissing . Select ( s = > s . Value ) . Distinct ( ) . ToList ( ) ;
2025-09-25 13:54:33 +10:00
await SyncFieldsAsync ( fieldsInChanges ! , fieldsFromDB ) ;
await SyncValuesAsync ( valuesInChanges ) ;
2025-11-19 11:52:41 +10:00
var normalizedFieldNames = fieldsInChanges
. Select ( n = > n ? . Trim ( ) )
. Where ( n = > n ! = null )
. ToHashSet ( StringComparer . OrdinalIgnoreCase ) ;
2025-09-25 13:54:33 +10:00
2025-11-19 11:52:41 +10:00
var fieldsNameWithId = await unitFieldService . Get ( ) . AsNoTracking ( )
. Where ( f = > normalizedFieldNames . Contains ( f . AihitName ) )
. ToListAsync ( ) ;
var normalizedValues = valuesInChanges
. Where ( v = > v ! = null )
. Select ( v = > v ! . Trim ( ) )
. ToHashSet ( StringComparer . OrdinalIgnoreCase ) ;
2025-09-25 13:54:33 +10:00
2025-11-19 11:52:41 +10:00
var valuesWithId = await unitFieldValueService . Get ( ) . AsNoTracking ( )
. Where ( v = >
( v . Value = = null & & valuesInChanges . Contains ( null ) ) | |
( v . Value ! = null & & normalizedValues . Contains ( v . Value ) ) )
. ToListAsync ( ) ;
foreach ( var change in unitValueMissing )
2025-09-25 13:54:33 +10:00
ApplyChange ( unit , fieldsNameWithId , valuesWithId , change ) ;
if ( ! await unitService . CommitAsync ( ) )
logger . LogError ( $"Н е удалось применить изменения по актуализиции аттрибуто Unit {unit.Name} в базе данных" ) ;
}
2025-05-19 16:09:55 +10:00
2025-09-25 13:54:33 +10:00
return ;
}
2025-05-19 16:09:55 +10:00
2025-09-25 13:54:33 +10:00
private static List < KeyValuePair < string , string? > > SplitValues ( KeyValuePair < string , string? > multiValueProperty )
{
var splitedProperties = new List < KeyValuePair < string , string? > > ( ) ;
if ( multiValueProperty . Value = = null )
splitedProperties . Add ( new KeyValuePair < string , string? > ( multiValueProperty . Key , multiValueProperty . Value ) ) ;
else
{
var values = multiValueProperty . Value . Split ( ',' ) . Select ( s = > s . Trim ( ) ) ;
foreach ( var value in values )
splitedProperties . Add ( new KeyValuePair < string , string? > ( multiValueProperty . Key , value ) ) ;
}
return splitedProperties ;
}
2025-05-19 16:09:55 +10:00
2025-09-25 16:28:18 +10:00
2025-05-30 09:50:47 +10:00
private void ApplyChange ( Unit unit , List < UnitField > fieldsNameWithId , List < UnitFieldValue > valuesWithId , KeyValuePair < string , string? > change )
2025-05-19 16:09:55 +10:00
{
2025-09-25 13:54:33 +10:00
var Field = fieldsNameWithId . FirstOrDefault ( t = > IsStringEqual ( t . AihitName , change . Key ) ) ;
var Value = valuesWithId . FirstOrDefault ( t = > IsStringEqual ( t . Value , change . Value ) ) ;
2025-05-19 16:09:55 +10:00
2025-09-25 13:54:33 +10:00
if ( Field = = null | | Value = = null )
logger . LogError ( $"Ошибка присвоения значения({change.Value}) аттрибуту({change.Key}). FieldId - {Field}, ValueId - {Value}" ) ;
else
2025-05-30 09:50:47 +10:00
{
var f = new UnitInValue
{
UnitId = unit . Id ,
2025-09-25 13:54:33 +10:00
FieldId = Field . Id ,
ValueId = Value . Id ,
2025-05-30 09:50:47 +10:00
DateCreated = DateTimeOffset . UtcNow
} ;
2025-05-19 16:09:55 +10:00
2025-05-30 09:50:47 +10:00
unit . UnitValues . Add ( f ) ;
}
2025-09-25 16:28:18 +10:00
}
2025-05-19 16:09:55 +10:00
2025-05-21 11:53:07 +10:00
2025-09-25 13:54:33 +10:00
private async Task SyncValuesAsync ( List < string? > values )
2025-05-19 16:09:55 +10:00
{
2025-11-19 11:52:41 +10:00
bool hasNull = values . Any ( v = > v is null ) ;
2025-05-30 09:50:47 +10:00
// все поля из АИХИТ
2025-11-19 11:52:41 +10:00
var normalizedNoneNullValues = values
. Where ( v = > v ! = null )
. Select ( v = > v ! . Trim ( ) )
. Distinct ( StringComparer . OrdinalIgnoreCase )
. ToList ( ) ;
2025-05-30 09:50:47 +10:00
2025-11-19 11:52:41 +10:00
// Получаем существующие значения из БД
var existingValuesInDb = await unitFieldValueService . Get ( )
. Where ( v = > normalizedNoneNullValues . Contains ( v . Value ! ) | | v . Value = = null )
. Select ( v = > v . Value )
. ToListAsync ( ) ;
2025-05-30 09:50:47 +10:00
2025-11-19 11:52:41 +10:00
if ( ! hasNull )
existingValuesInDb . RemoveAll ( v = > v is null ) ;
2025-05-30 09:50:47 +10:00
2025-11-19 11:52:41 +10:00
var newNonNullValues = normalizedNoneNullValues
. Where ( v = > ! existingValuesInDb . Any ( ev = >
string . Equals ( ev , v , StringComparison . OrdinalIgnoreCase ) ) )
. ToList ( ) ;
2025-05-19 16:09:55 +10:00
2025-11-19 11:52:41 +10:00
var newValuesToInsert = new List < string? > ( ) ;
newValuesToInsert . AddRange ( newNonNullValues ) ;
2025-05-21 11:53:07 +10:00
2025-11-19 11:52:41 +10:00
if ( hasNull & & ! existingValuesInDb . Contains ( null ) )
newValuesToInsert . Add ( null ) ;
if ( newValuesToInsert . Count = = 0 )
return ;
// Создаём сущности — сохраняем оригинальный регистр из nonNullValues!
// (для null — просто null)
var newFieldValues = newValuesToInsert . Select ( v = >
new UnitFieldValue
2025-05-30 09:50:47 +10:00
{
2025-11-19 11:52:41 +10:00
Id = Guid . NewGuid ( ) ,
Value = v // ← v — уже trim()-нутый non-null, или null
} )
. ToList ( ) ;
2025-05-30 09:50:47 +10:00
2025-11-19 11:52:41 +10:00
if ( ! await unitFieldValueService . AddRangeAsync ( newFieldValues ) | | ! await unitFieldValueService . CommitAsync ( ) )
{
logger . LogError (
"Н е удалось сохранить {Count} новых значений FieldValues" ,
newValuesToInsert . Count ) ;
}
else
{
if ( newValuesToInsert . Count < = 10 )
{
var preview = string . Join ( ", " , newValuesToInsert . Select ( v = > v ? ? "<null>" ) ) ;
logger . LogInformation ( "Добавлено {Count} значений FieldValues: [{Values}]" , newValuesToInsert . Count , preview ) ;
}
else
{
logger . LogInformation ( "Добавлено {Count} значений FieldValues (первые 5: {Preview})" ,
newValuesToInsert . Count ,
string . Join ( ", " , newValuesToInsert . Take ( 5 ) . Select ( v = > v ? ? "<null>" ) ) ) ;
2025-05-30 09:50:47 +10:00
}
2025-05-21 11:53:07 +10:00
}
2025-05-30 09:50:47 +10:00
}
2025-11-19 11:52:41 +10:00
2025-09-25 16:28:18 +10:00
private async Task SyncFieldsAsync ( List < string > fieldsFromAihit , List < UnitField > fieldsFromDB )
2025-05-30 09:50:47 +10:00
{
2025-09-25 16:28:18 +10:00
var newFields = fieldsFromAihit . Where ( t = > ! fieldsFromDB . Any ( f = > f . AihitName = = t ) ) ;
2025-09-25 13:54:33 +10:00
2025-05-30 09:50:47 +10:00
if ( newFields . Any ( ) )
2025-05-21 11:53:07 +10:00
{
2025-05-30 09:50:47 +10:00
foreach ( var item in newFields )
2025-05-21 11:53:07 +10:00
{
2025-05-30 09:50:47 +10:00
var field = new UnitField
{
Id = Guid . NewGuid ( ) ,
2025-09-25 13:54:33 +10:00
AihitName = item ,
2025-05-30 09:50:47 +10:00
EsppName = null ,
} ;
if ( ! await unitFieldService . CreateAsync ( field ) )
2025-09-25 16:28:18 +10:00
logger . LogError ( "Н е удалось создать запись в таблице Fields:{item},{field}" , item , field ) ;
2025-05-30 09:50:47 +10:00
else
2025-09-25 16:28:18 +10:00
logger . LogInformation ( "Создана запись а таблице Fields: Fields:{item},{field}" , item , field ) ;
2025-05-21 11:53:07 +10:00
}
2025-05-30 09:50:47 +10:00
if ( ! await unitFieldService . CommitAsync ( ) )
logger . LogError ( $"Н е удалось применить изменения по добавлению новых Fields в базе данных" ) ;
2025-05-19 16:09:55 +10:00
}
}
2025-05-30 09:50:47 +10:00
private bool IsStringEqual ( string? value1 , string? value2 )
2025-05-19 16:09:55 +10:00
{
2025-11-19 11:52:41 +10:00
return string . Equals (
value1 ? . Trim ( ) ,
value2 ? . Trim ( ) ,
StringComparison . OrdinalIgnoreCase ) ;
2025-05-30 09:50:47 +10:00
}
2025-05-19 16:09:55 +10:00
2025-05-30 09:50:47 +10:00
private async Task < Unit > GetUnit ( string name )
{
var unit = await GetUnitByName ( name . Trim ( ) ) ;
2025-05-19 16:09:55 +10:00
2025-05-30 09:50:47 +10:00
if ( unit = = null )
unit = await CreateUnitAsync ( name ) ;
2025-05-19 16:09:55 +10:00
2025-05-30 09:50:47 +10:00
return unit ;
2025-05-21 11:53:07 +10:00
}
2025-09-25 16:28:18 +10:00
2025-05-30 09:50:47 +10:00
private async Task < Unit > CreateUnitAsync ( string name )
2025-05-21 11:53:07 +10:00
{
2025-11-19 11:52:41 +10:00
var unit = new Unit { Name = name . Trim ( ) . ToUpperInvariant ( ) } ;
2025-05-21 11:53:07 +10:00
2025-05-30 09:50:47 +10:00
if ( ! await unitService . CreateAsync ( unit ) | | ! await unitService . CommitAsync ( ) )
logger . LogError ( $"Н е удалось создать Unit {unit.Name}" ) ;
else
logger . LogInformation ( $"----- Создан Unit: {unit.Name} -----" ) ;
2025-05-21 11:53:07 +10:00
2025-05-30 09:50:47 +10:00
return unit ;
}
2025-05-21 11:53:07 +10:00
2025-05-30 09:50:47 +10:00
public async Task < Unit ? > GetUnitByName ( string name )
{
2025-11-19 11:52:41 +10:00
var normalized = name . Trim ( ) . ToUpperInvariant ( ) ;
2025-05-30 09:50:47 +10:00
return await GetUnitWithFieldsAndValues ( )
2025-11-19 11:52:41 +10:00
. FirstOrDefaultAsync ( u = > u . Name = = normalized ) ;
2025-05-30 09:50:47 +10:00
}
2025-05-21 11:53:07 +10:00
2025-05-30 09:50:47 +10:00
private IQueryable < Unit > GetUnitWithFieldsAndValues ( )
{
return unitService . Get ( )
. Include ( t = > t . UnitValues )
. ThenInclude ( uv = > uv . Field )
. Include ( t = > t . UnitValues )
. ThenInclude ( uf = > uf . Value ) ;
2025-05-19 16:09:55 +10:00
}
2025-11-27 17:11:14 +10:00
private void ApplyFieldValueReplacements ( Dictionary < string , string? > properties )
{
foreach ( var replacement in fieldValueReplacementsSettings . Replacements )
{
if ( properties . ContainsKey ( replacement . FieldName ) )
{
var currentValue = properties [ replacement . FieldName ] ;
// Проверяем, содержится ли текущее значение в списке OldValues (с учетом регистра)
if ( replacement . OldValues . Contains ( currentValue , StringComparer . OrdinalIgnoreCase ) )
{
logger . LogDebug ( "Заменяем значение поля '{FieldName}' с '{OldValue}' на '{NewValue}'" ,
replacement . FieldName , currentValue , replacement . NewValue ) ;
properties [ replacement . FieldName ] = replacement . NewValue ;
}
}
}
}
2025-05-19 16:09:55 +10:00
}
2025-05-30 09:50:47 +10:00
}