237 lines
9.7 KiB
C#
237 lines
9.7 KiB
C#
|
|
using Microsoft.EntityFrameworkCore;
|
|||
|
|
using Microsoft.Extensions.Logging;
|
|||
|
|
using PARR.AIHITRelationshipsSyncer.Models;
|
|||
|
|
using PARR.AIHITRelationshipsSyncer.Services.Interfaces;
|
|||
|
|
using PARR.AIHITRelationshipsSyncer.Settings;
|
|||
|
|
using PARR.DAL.Models.Unit;
|
|||
|
|
using PARR.DAL.Services.Interfaces.Unit;
|
|||
|
|
|
|||
|
|
namespace PARR.AIHITRelationshipsSyncer.Services.Implementations
|
|||
|
|
{
|
|||
|
|
internal class RelationshipsSyncService : IRelationshipsSyncService
|
|||
|
|
{
|
|||
|
|
private readonly ILogger<RelationshipsSyncService> logger;
|
|||
|
|
private readonly IUnitService unitService;
|
|||
|
|
private readonly WorkerSettings workerSettings;
|
|||
|
|
|
|||
|
|
public RelationshipsSyncService(
|
|||
|
|
ILogger<RelationshipsSyncService> logger,
|
|||
|
|
IUnitService unitService,
|
|||
|
|
WorkerSettings workerSettings
|
|||
|
|
)
|
|||
|
|
{
|
|||
|
|
this.logger = logger;
|
|||
|
|
this.unitService = unitService;
|
|||
|
|
this.workerSettings = workerSettings;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public async Task SyncAsync(List<AihitData> aihitdata)
|
|||
|
|
{
|
|||
|
|
var validPairs = ValidateInput(aihitdata);
|
|||
|
|
if (!validPairs.Any()) return;
|
|||
|
|
|
|||
|
|
var sourceUnitNames = ExtractUnitNames(validPairs);
|
|||
|
|
var existingUnits = await GetOrCreateUnitsAsync(sourceUnitNames);
|
|||
|
|
|
|||
|
|
var (toRemove, toAdd) = await GetDeltaAsync(validPairs);
|
|||
|
|
|
|||
|
|
await RemoveRelationshipsAsync(toRemove);
|
|||
|
|
await AddRelationshipsAsync(toAdd, existingUnits);
|
|||
|
|
|
|||
|
|
await FinalCommitAsync(toRemove.Count, toAdd.Count);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private List<(string Parent, string Child)> ValidateInput(List<AihitData> data)
|
|||
|
|
{
|
|||
|
|
if (data == null || data.Count == 0)
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("Получены пустые или null данные из АИХ ИТ. Пропускаю синхронизацию");
|
|||
|
|
return null!;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var validPairs = data
|
|||
|
|
.Where(t => !string.IsNullOrWhiteSpace(t.ParentName) && !string.IsNullOrWhiteSpace(t.ChildName))
|
|||
|
|
.Where(t => t.ParentName != t.ChildName)
|
|||
|
|
.Select(t => (
|
|||
|
|
Parent: t.ParentName!.Trim().ToUpperInvariant(),
|
|||
|
|
Child: t.ChildName!.Trim().ToUpperInvariant()
|
|||
|
|
))
|
|||
|
|
.Distinct()
|
|||
|
|
.ToList();
|
|||
|
|
if (validPairs.Count == 0)
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("После нормализации не осталось валидных связей. Пропускаю синхронизацию");
|
|||
|
|
return new();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logger.LogInformation("Получено {Count} валидных связей", validPairs.Count);
|
|||
|
|
return validPairs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private static List<string> ExtractUnitNames(List<(string Parent, string Child)> pairs)
|
|||
|
|
{
|
|||
|
|
return pairs
|
|||
|
|
.SelectMany(x => new[] { x.Parent, x.Child })
|
|||
|
|
.Distinct()
|
|||
|
|
.ToList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private async Task<Dictionary<string, Unit>> GetOrCreateUnitsAsync(List<string> names)
|
|||
|
|
{
|
|||
|
|
var existing = await unitService.Get()
|
|||
|
|
.AsNoTracking()
|
|||
|
|
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
|||
|
|
|
|||
|
|
var missing = names.Except(existing.Keys).ToList();
|
|||
|
|
if (!missing.Any()) return existing;
|
|||
|
|
|
|||
|
|
logger.LogInformation("Создание отсутствующих Unit:{Count}", missing.Count);
|
|||
|
|
foreach (var name in missing)
|
|||
|
|
{
|
|||
|
|
var unit = new Unit
|
|||
|
|
{
|
|||
|
|
Id = Guid.NewGuid(),
|
|||
|
|
Name = name,
|
|||
|
|
DateCreated = DateTimeOffset.UtcNow
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (await unitService.CreateAsync(unit))
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("Создан Unit: {Name}", name);
|
|||
|
|
existing[name] = unit;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
logger.LogError("Не удалось создать Unit: {Name}", name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await unitService.CommitAsync();
|
|||
|
|
return existing;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private async Task<(List<ExistingRelationship> ToRemove, List<(string, string)> ToAdd)> GetDeltaAsync(List<(string Parent, string Child)> sourcePairs)
|
|||
|
|
{
|
|||
|
|
var allExistings = await unitService.Get()
|
|||
|
|
.AsNoTracking()
|
|||
|
|
.Include(u => u.ChildUnits).ThenInclude(r => r.ChildUnit)
|
|||
|
|
.Where(u => u.ChildUnits.Any())
|
|||
|
|
.SelectMany(u => u.ChildUnits, (parent, relationship) => new ExistingRelationship
|
|||
|
|
{
|
|||
|
|
ParentName = parent.Name,
|
|||
|
|
ChildName = relationship.ChildUnit!.Name,
|
|||
|
|
Relationship = relationship
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
.ToListAsync();
|
|||
|
|
|
|||
|
|
var sourceSet = new HashSet<(string, string)>(sourcePairs);
|
|||
|
|
var existingSet = new HashSet<(string, string)>(allExistings.Select(x => (x.ParentName, x.ChildName)));
|
|||
|
|
|
|||
|
|
var toRemove = allExistings.Where(r => !sourceSet.Contains((r.ParentName, r.ChildName))).ToList();
|
|||
|
|
var toAdd = sourcePairs.Where(p => !existingSet.Contains(p)).ToList();
|
|||
|
|
|
|||
|
|
logger.LogInformation("Связей для удаления: {ToRemove}, для добавления: {ToAdd}", toRemove.Count, toAdd.Count);
|
|||
|
|
return (toRemove, toAdd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private async Task RemoveRelationshipsAsync(List<ExistingRelationship> toRemove)
|
|||
|
|
{
|
|||
|
|
if (!toRemove.Any()) return;
|
|||
|
|
|
|||
|
|
var parentNames = toRemove.Select(r => r.ParentName).Distinct().ToList();
|
|||
|
|
|
|||
|
|
var parents = await unitService.Get()
|
|||
|
|
.Include(u => u.ChildUnits)
|
|||
|
|
.Where(u => parentNames.Contains(u.Name))
|
|||
|
|
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
|||
|
|
|
|||
|
|
foreach (var rel in toRemove)
|
|||
|
|
{
|
|||
|
|
if (!parents.TryGetValue(rel.ParentName, out var parent))
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("Родитель '{Parent}' не найден при попытке удаления связи - '{Child}'", rel.ParentName, rel.ChildName);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var relationshipToRemove = parent.ChildUnits
|
|||
|
|
.FirstOrDefault(r => r.ChildUnitId == rel.Relationship.ChildUnitId);
|
|||
|
|
|
|||
|
|
if (relationshipToRemove != null)
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("Удаление связи: '{Parent}' - '{Child}'", rel.ParentName, rel.ChildName);
|
|||
|
|
parent.ChildUnits.Remove(relationshipToRemove);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("Связь '{Parent}'-'{Child}' не найдена в коллекции ChildUnits для удаления", rel.ParentName, rel.ChildName);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task AddRelationshipsAsync(List<(string Parent, string Child)> toAdd, Dictionary<string, Unit> allUnits)
|
|||
|
|
{
|
|||
|
|
if (!toAdd.Any()) return;
|
|||
|
|
|
|||
|
|
var parentNames = toAdd.Select(x => x.Parent).Distinct().ToList();
|
|||
|
|
var parents = await unitService.Get()
|
|||
|
|
.Include(u => u.ChildUnits)
|
|||
|
|
.Where(u => parentNames.Contains(u.Name))
|
|||
|
|
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
|||
|
|
|
|||
|
|
foreach (var (Parent, Child) in toAdd)
|
|||
|
|
{
|
|||
|
|
if (!parents.TryGetValue(Parent, out var parent) ||
|
|||
|
|
!allUnits.TryGetValue(Child, out var child))
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("Пропущена связь '{Parent}' - '{Child}': Unit не найден", Parent, Child);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (parent!.ChildUnits.Any(r => r.ChildUnitId == child.Id))
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("Связь уже существует: '{Parent}' - '{Child}'", Parent, Child);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
parent.ChildUnits.Add(new UnitInUnit
|
|||
|
|
{
|
|||
|
|
ParentUnitId = parent.Id,
|
|||
|
|
ChildUnitId = child.Id,
|
|||
|
|
DateCreated = DateTimeOffset.UtcNow
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.LogInformation("Добавлена связь: '{Parent}' - '{Child}'", Parent, Child);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private async Task FinalCommitAsync(int removedCount, int addedCount)
|
|||
|
|
{
|
|||
|
|
if (removedCount > 0 || addedCount > 0)
|
|||
|
|
{
|
|||
|
|
if (await unitService.CommitAsync())
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("Синхронизация завершена. Удалено: {Removed}, добавлено: {Added}", removedCount, addedCount);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
logger.LogError("Не удалось сохранить изменения в БД");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
logger.LogInformation("Изменений не обнаружено");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private sealed class ExistingRelationship
|
|||
|
|
{
|
|||
|
|
public string ParentName { get; init; }
|
|||
|
|
public string ChildName { get; init; }
|
|||
|
|
public UnitInUnit Relationship { get; init; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|