using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; 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 logger; private readonly ITransformService transformService; private readonly IUnitService unitService; private readonly IUnitFieldService unitFieldService; private readonly IUnitFieldValueService unitFieldValueService; public SyncerService( ILogger logger, ITransformService transformService, IUnitService unitService, IUnitFieldService unitFieldService, IUnitFieldValueService unitFieldValueService ) { this.logger = logger; this.transformService = transformService; this.unitService = unitService; this.unitFieldService = unitFieldService; this.unitFieldValueService = unitFieldValueService; } public async Task SyncAsync(string msg) { //logger.LogInformation("Запуск синхронизации данных из очереди сообщений в ПАРР."); var objFromQuery = transformService.GetModelFromJson(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; foreach (var kvp in objFromQuery.Properties.ToList()) objFromQuery.Properties[kvp.Key.Trim()] = kvp.Value?.Trim(); var listAihitData = new List>(); var propertisWithSimpleValues = objFromQuery.Properties //.Where(t => fieldsFromDB.Any(a => IsStringEqual(t.Key.Trim(), a.Name) && a.IsMultipleValue != true)) .Where(t => fieldsFromDB.Any(a => IsStringEqual(t.Key.Trim(), a.AihitName) && a.IsMultipleValue != true)) .Select(s => new KeyValuePair(s.Key.Trim(), s.Value?.Trim())).ToList(); listAihitData.AddRange(propertisWithSimpleValues); 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(s.Key.Trim(), s.Value?.Trim())) )); var unit = await GetUnit(objFromQuery.Name); var incomingPairs = listAihitData .Select(kv => new { kv.Key, kv.Value }) .ToList(); var unitValuesToRemove = unit.UnitValues .Where(uv => !incomingPairs.Any(ip => IsStringEqual(uv.Field!.AihitName, ip.Key) && IsStringEqual(uv.Value!.Value, ip.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> SplitValues(KeyValuePair multiValueProperty) { var splitedProperties = new List>(); if (multiValueProperty.Value == null) splitedProperties.Add(new KeyValuePair(multiValueProperty.Key, multiValueProperty.Value)); else { var values = multiValueProperty.Value.Split(',').Select(s => s.Trim()); foreach (var value in values) splitedProperties.Add(new KeyValuePair(multiValueProperty.Key, value)); } return splitedProperties; } private void ApplyChange(Unit unit, List fieldsNameWithId, List valuesWithId, KeyValuePair 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 { var f = new UnitInValue { UnitId = unit.Id, FieldId = Field.Id, ValueId = Value.Id, DateCreated = DateTimeOffset.UtcNow }; unit.UnitValues.Add(f); } } private async Task SyncValuesAsync(List values) { bool hasNull = values.Any(v => v is null); // все поля из АИХИТ var normalizedNoneNullValues = values .Where(v => v != null) .Select(v => v!.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); // Получаем существующие значения из БД var existingValuesInDb = await unitFieldValueService.Get() .Where(v => normalizedNoneNullValues.Contains(v.Value!) || v.Value == null) .Select(v => v.Value) .ToListAsync(); if (!hasNull) existingValuesInDb.RemoveAll(v => v is null); var newNonNullValues = normalizedNoneNullValues .Where(v => !existingValuesInDb.Any(ev => string.Equals(ev, v, StringComparison.OrdinalIgnoreCase))) .ToList(); var newValuesToInsert = new List(); 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 { Id = Guid.NewGuid(), Value = v // ← v — уже trim()-нутый non-null, или null }) .ToList(); 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 ?? "")); 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 ?? ""))); } } } private async Task SyncFieldsAsync(List fieldsFromAihit, List fieldsFromDB) { var newFields = fieldsFromAihit.Where(t => !fieldsFromDB.Any(f => f.AihitName == t)); if (newFields.Any()) { foreach (var item in newFields) { var field = new UnitField { Id = Guid.NewGuid(), AihitName = item, EsppName = null, }; if (!await unitFieldService.CreateAsync(field)) logger.LogError("Не удалось создать запись в таблице Fields:{item},{field}", item, field); else logger.LogInformation("Создана запись а таблице Fields: Fields:{item},{field}", item, field); } if (!await unitFieldService.CommitAsync()) logger.LogError($"Не удалось применить изменения по добавлению новых Fields в базе данных"); } } private bool IsStringEqual(string? value1, string? value2) { return string.Equals( value1?.Trim(), value2?.Trim(), StringComparison.OrdinalIgnoreCase); } private async Task GetUnit(string name) { var unit = await GetUnitByName(name.Trim()); if (unit == null) unit = await CreateUnitAsync(name); return unit; } private async Task CreateUnitAsync(string name) { var unit = new Unit { Name = name.Trim().ToUpperInvariant() }; if (!await unitService.CreateAsync(unit) || !await unitService.CommitAsync()) logger.LogError($"Не удалось создать Unit {unit.Name}"); else logger.LogInformation($"----- Создан Unit: {unit.Name} -----"); return unit; } public async Task GetUnitByName(string name) { var normalized = name.Trim().ToUpperInvariant(); return await GetUnitWithFieldsAndValues() .FirstOrDefaultAsync(u => u.Name == normalized); } private IQueryable GetUnitWithFieldsAndValues() { return unitService.Get() .Include(t => t.UnitValues) .ThenInclude(uv => uv.Field) .Include(t => t.UnitValues) .ThenInclude(uf => uf.Value); } } }