Files
parr_api/PARR.AIHITMainSyncer/Services/SyncerService.cs

354 lines
15 KiB
C#
Raw Normal View History

2025-05-30 09:50:47 +10:00
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using PARR.AIHITMainSyncer.Settings;
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;
private readonly IUnitFieldValueService unitFieldValueService;
private readonly FieldValueReplacementsSettings fieldValueReplacementsSettings;
public SyncerService(
ILogger<SyncerService> logger,
ITransformService transformService,
IUnitService unitService,
IUnitFieldService unitFieldService,
IUnitFieldValueService unitFieldValueService,
FieldValueReplacementsSettings fieldValueReplacementsSettings
)
{
this.logger = logger;
this.transformService = transformService;
this.unitService = unitService;
this.unitFieldService = unitFieldService;
this.unitFieldValueService = unitFieldValueService;
this.fieldValueReplacementsSettings = fieldValueReplacementsSettings;
}
public async Task SyncAsync(string msg)
{
//logger.LogInformation("Запуск синхронизации данных из очереди сообщений в ПАРР.");
var objFromQuery = transformService.GetModelFromJson<AihitMainDataMq>(msg);
if (objFromQuery == null)
return;
await SyncUnitAsync(objFromQuery);
}
private async Task SyncUnitAsync(AihitMainDataMq objFromQuery)
{
//Получаем сразу словарь атрибутов, чтобы не ходить каждый раз в базу
var fieldsFromDB = await unitFieldService.Get().AsNoTracking()/*.Select(t => new FieldDto { Id = t.Id, Name = t.AihitName, IsMultipleValue = t.IsMultipleValue })*/.ToListAsync();
if (!objFromQuery.Properties.Any())
return;
// 1. Создаем копию и нормализуем свойства (удаляем лишние пробелы)
var normalizedProperties = new Dictionary<string, string?>();
foreach (var kvp in objFromQuery.Properties)
normalizedProperties[kvp.Key.Trim()] = kvp.Value?.Trim();
// 2. Применяем замены значений к нормализованным свойствам
ApplyFieldValueReplacements(normalizedProperties);
var listAihitData = new List<KeyValuePair<string, string?>>();
// 3. Обрабатываем простые значения из нормализованных свойств
var propertisWithSimpleValues = normalizedProperties
.Where(t => fieldsFromDB.Any(a => IsStringEqual(t.Key.Trim(), a.AihitName) && a.IsMultipleValue != true))
.Select(s => new KeyValuePair<string, string?>(s.Key.Trim(), s.Value?.Trim())).ToList();
listAihitData.AddRange(propertisWithSimpleValues);
// 4. Обрабатываем множественные значения из нормализованных свойств
var propertiesWithMultipleValues = objFromQuery.Properties.Where(t => !propertisWithSimpleValues.Any(a => t.Key.Trim() == a.Key)).ToList();
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);
var incomingPairs = listAihitData
.Select(kv => new { kv.Key, kv.Value })
.ToList();
// Определяем, какие ПОЛЯ обновляются этим сообщением чтобы случайно не удалить не наши
var fieldsInMessage = listAihitData
.Select(kv => kv.Key)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToHashSet();
var unitValuesToRemove = unit.UnitValues
.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)
)
)
.ToList();
unitValuesToRemove.ForEach(uv => unit.UnitValues.Remove(uv));
var unitValueMissing = listAihitData.Where(t =>
!unit.UnitValues.Any(a =>
IsStringEqual(t.Key, a.Field!.AihitName) &&
IsStringEqual(t.Value, a.Value!.Value)
)).ToList();
if (unitValueMissing.Any())
{
var fieldsInChanges = unitValueMissing.Select(s => s.Key).Distinct().ToList();
var valuesInChanges = unitValueMissing.Select(s => s.Value).Distinct().ToList();
await SyncFieldsAsync(fieldsInChanges!, fieldsFromDB);
await SyncValuesAsync(valuesInChanges);
var normalizedFieldNames = fieldsInChanges
.Select(n => n?.Trim())
.Where(n => n != null)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
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);
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)
ApplyChange(unit, fieldsNameWithId, valuesWithId, change);
if (!await unitService.CommitAsync())
logger.LogError($"Не удалось применить изменения по актуализиции аттрибуто Unit {unit.Name} в базе данных");
}
return;
}
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-30 09:50:47 +10:00
private void ApplyChange(Unit unit, List<UnitField> fieldsNameWithId, List<UnitFieldValue> valuesWithId, KeyValuePair<string, string?> change)
{
var Field = fieldsNameWithId.FirstOrDefault(t => IsStringEqual(t.AihitName, change.Key));
var Value = valuesWithId.FirstOrDefault(t => IsStringEqual(t.Value, change.Value));
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,
FieldId = Field.Id,
ValueId = Value.Id,
2025-05-30 09:50:47 +10:00
DateCreated = DateTimeOffset.UtcNow
};
2025-05-30 09:50:47 +10:00
unit.UnitValues.Add(f);
}
}
private async Task SyncValuesAsync(List<string?> values)
{
bool hasNull = values.Any(v => v is null);
2025-05-30 09:50:47 +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
// Получаем существующие значения из БД
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
if (!hasNull)
existingValuesInDb.RemoveAll(v => v is null);
2025-05-30 09:50:47 +10:00
var newNonNullValues = normalizedNoneNullValues
.Where(v => !existingValuesInDb.Any(ev =>
string.Equals(ev, v, StringComparison.OrdinalIgnoreCase)))
.ToList();
var newValuesToInsert = new List<string?>();
newValuesToInsert.AddRange(newNonNullValues);
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
{
Id = Guid.NewGuid(),
Value = v // ← v — уже trim()-нутый non-null, или null
})
.ToList();
2025-05-30 09:50:47 +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-30 09:50:47 +10:00
}
private async Task SyncFieldsAsync(List<string> fieldsFromAihit, List<UnitField> fieldsFromDB)
2025-05-30 09:50:47 +10:00
{
var newFields = fieldsFromAihit.Where(t => !fieldsFromDB.Any(f => f.AihitName == t));
2025-05-30 09:50:47 +10:00
if (newFields.Any())
{
2025-05-30 09:50:47 +10:00
foreach (var item in newFields)
{
2025-05-30 09:50:47 +10:00
var field = new UnitField
{
Id = Guid.NewGuid(),
AihitName = item,
2025-05-30 09:50:47 +10:00
EsppName = null,
};
if (!await unitFieldService.CreateAsync(field))
logger.LogError("Не удалось создать запись в таблице Fields:{item},{field}", item, field);
2025-05-30 09:50:47 +10:00
else
logger.LogInformation("Создана запись а таблице Fields: Fields:{item},{field}", item, field);
}
2025-05-30 09:50:47 +10:00
if (!await unitFieldService.CommitAsync())
logger.LogError($"Не удалось применить изменения по добавлению новых Fields в базе данных");
}
}
2025-05-30 09:50:47 +10:00
private bool IsStringEqual(string? value1, string? value2)
{
return string.Equals(
value1?.Trim(),
value2?.Trim(),
StringComparison.OrdinalIgnoreCase);
2025-05-30 09:50:47 +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-30 09:50:47 +10:00
if (unit == null)
unit = await CreateUnitAsync(name);
2025-05-30 09:50:47 +10:00
return unit;
}
2025-05-30 09:50:47 +10:00
private async Task<Unit> CreateUnitAsync(string name)
{
var unit = new Unit { Name = name.Trim().ToUpperInvariant() };
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-30 09:50:47 +10:00
return unit;
}
2025-05-30 09:50:47 +10:00
public async Task<Unit?> GetUnitByName(string name)
{
var normalized = name.Trim().ToUpperInvariant();
2025-05-30 09:50:47 +10:00
return await GetUnitWithFieldsAndValues()
.FirstOrDefaultAsync(u => u.Name == normalized);
2025-05-30 09:50:47 +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);
}
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-30 09:50:47 +10:00
}