/
afanasevn
/
MaxSystems
Обзор
Документация
Войти
/
afanasevn
/
MaxSystems
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/MaxSystems.Infrastructure/Services/ImportService.cs
338 строк
14 KB
IBS\NAfanasev
Integration Event Bus
22 июн 2026, 17:01
22 июн 2026, 17:01
83907da
Код
Авторство
О чём код?
using MaxSystems.Application.Abstractions; using MaxSystems.Application.Contracts.Import; using MaxSystems.Domain.Entities; using MaxSystems.Domain.Enums; using MaxSystems.Infrastructure.Audit; using MaxSystems.Infrastructure.Data; using Microsoft.EntityFrameworkCore; namespace MaxSystems.Infrastructure.Services; /// <summary>Импорт оборудования и складских позиций из CSV/XLSX.</summary> public sealed class ImportService(AppDbContext db, IAuditService audit) : IImportService { private static readonly Dictionary<string, EquipmentSystem> EquipmentSystemMap = new(StringComparer.OrdinalIgnoreCase) { ["ибп"] = EquipmentSystem.Ups, ["ups"] = EquipmentSystem.Ups, ["электропитание"] = EquipmentSystem.Ups, ["скуд"] = EquipmentSystem.Acs, ["acs"] = EquipmentSystem.Acs, ["видеонаблюдение"] = EquipmentSystem.Cctv, ["cctv"] = EquipmentSystem.Cctv, ["лвс"] = EquipmentSystem.Lan, ["скс"] = EquipmentSystem.Lan, ["lan"] = EquipmentSystem.Lan, ["кондиционирование"] = EquipmentSystem.Hvac, ["hvac"] = EquipmentSystem.Hvac, ["связь"] = EquipmentSystem.Telecom, ["телефон"] = EquipmentSystem.Telecom }; private static readonly Dictionary<string, EquipmentType> EquipmentTypeMap = new(StringComparer.OrdinalIgnoreCase) { ["ибп"] = EquipmentType.Ups, ["ups"] = EquipmentType.Ups, ["дверь"] = EquipmentType.AcsDoor, ["скуд"] = EquipmentType.AcsDoor, ["камера"] = EquipmentType.Camera, ["шкаф"] = EquipmentType.Cabinet, ["кондиционер"] = EquipmentType.Conditioner, ["коммутатор"] = EquipmentType.Switch }; private static readonly Dictionary<string, EquipmentStatus> StatusMap = new(StringComparer.OrdinalIgnoreCase) { ["исправно"] = EquipmentStatus.Ok, ["ok"] = EquipmentStatus.Ok, ["неисправно"] = EquipmentStatus.Faulty, ["faulty"] = EquipmentStatus.Faulty, ["ограниченно"] = EquipmentStatus.Limited, ["limited"] = EquipmentStatus.Limited, ["выведено"] = EquipmentStatus.Decommissioned }; private static readonly Dictionary<string, Criticality> CriticalityMap = new(StringComparer.OrdinalIgnoreCase) { ["высокая"] = Criticality.High, ["high"] = Criticality.High, ["средняя"] = Criticality.Medium, ["medium"] = Criticality.Medium, ["низкая"] = Criticality.Low, ["low"] = Criticality.Low }; private static readonly Dictionary<string, ItemCondition> ConditionMap = new(StringComparer.OrdinalIgnoreCase) { ["новое"] = ItemCondition.New, ["new"] = ItemCondition.New, ["бу исправное"] = ItemCondition.UsedOk, ["used_ok"] = ItemCondition.UsedOk, ["бу неисправное"] = ItemCondition.UsedFaulty, ["гарантийное"] = ItemCondition.Warranty }; /// <inheritdoc /> public async Task<ImportResultDto> ImportEquipmentAsync( IReadOnlyList<IDictionary<string, string>> rows, Guid? userId = null, CancellationToken ct = default) { var errors = new List<ImportErrorDto>(); var success = 0; for (var i = 0; i < rows.Count; i++) { var row = rows[i]; var rowNum = i + 2; try { row.TryGetValue("equipment_id", out var externalIdFromEquipment); row.TryGetValue("id", out var externalIdFromId); var externalId = externalIdFromEquipment ?? externalIdFromId; row.TryGetValue("name", out var name); row.TryGetValue("location", out var location); if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(location)) { errors.Add(new ImportErrorDto(rowNum, "Обязательные поля: name, location")); continue; } row.TryGetValue("system", out var systemKey); row.TryGetValue("type", out var typeKey); row.TryGetValue("status", out var statusKey); row.TryGetValue("criticality", out var criticalityKey); row.TryGetValue("model", out var model); row.TryGetValue("serial_number", out var serialNumber); row.TryGetValue("inventory_number", out var inventoryNumber); row.TryGetValue("last_maintenance", out var lastMaintenanceRaw); row.TryGetValue("next_maintenance", out var nextMaintenanceRaw); var system = EquipmentSystemMap.GetValueOrDefault(systemKey ?? string.Empty, EquipmentSystem.Other); var type = EquipmentTypeMap.GetValueOrDefault(typeKey ?? string.Empty, EquipmentType.Other); var status = StatusMap.GetValueOrDefault(statusKey ?? "исправно", EquipmentStatus.Ok); var criticality = CriticalityMap.GetValueOrDefault(criticalityKey ?? "средняя", Criticality.Medium); var data = new Equipment { Id = Guid.NewGuid(), ExternalId = string.IsNullOrWhiteSpace(externalId) ? null : externalId, Type = type, System = system, Name = name, Model = string.IsNullOrWhiteSpace(model) ? null : model, SerialNumber = string.IsNullOrWhiteSpace(serialNumber) ? null : serialNumber, InventoryNumber = string.IsNullOrWhiteSpace(inventoryNumber) ? null : inventoryNumber, Location = location, Status = status, Criticality = criticality, LastMaintenance = ParseDate(lastMaintenanceRaw), NextMaintenance = ParseDate(nextMaintenanceRaw), QrCode = string.IsNullOrWhiteSpace(externalId) ? null : $"QR-{externalId}", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; if (!string.IsNullOrWhiteSpace(externalId)) { var existing = await db.Equipment .FirstOrDefaultAsync(x => x.ExternalId == externalId, ct); if (existing is null) db.Equipment.Add(data); else { existing.Type = data.Type; existing.System = data.System; existing.Name = data.Name; existing.Model = data.Model; existing.SerialNumber = data.SerialNumber; existing.InventoryNumber = data.InventoryNumber; existing.Location = data.Location; existing.Status = data.Status; existing.Criticality = data.Criticality; existing.LastMaintenance = data.LastMaintenance; existing.NextMaintenance = data.NextMaintenance; existing.QrCode = data.QrCode; existing.UpdatedAt = DateTime.UtcNow; } } else { db.Equipment.Add(data); } success++; } catch (Exception ex) { errors.Add(new ImportErrorDto(rowNum, ex.Message)); } } var importLogId = Guid.NewGuid(); await db.ImportLogs.AddAsync(new ImportLog { Id = importLogId, UserId = userId, FileName = "equipment-import", DataType = "equipment", TotalRows = rows.Count, SuccessRows = success, ErrorRows = errors.Count, ErrorsJson = errors.Count > 0 ? System.Text.Json.JsonSerializer.Serialize(errors) : null, CreatedAt = DateTime.UtcNow }, ct); audit.Log( userId, "IMPORT", "Equipment", importLogId, AuditMessages.ImportCompleted("оборудование", rows.Count, success, errors.Count), new { rows.Count, success, errors = errors.Count, DataType = "equipment" }); await db.SaveChangesAsync(ct); return new ImportResultDto(success, errors); } /// <inheritdoc /> public async Task<ImportResultDto> ImportWarehouseAsync( IReadOnlyList<IDictionary<string, string>> rows, Guid? userId = null, CancellationToken ct = default) { var errors = new List<ImportErrorDto>(); var success = 0; for (var i = 0; i < rows.Count; i++) { var row = rows[i]; var rowNum = i + 2; try { row.TryGetValue("item_id", out var externalIdFromItem); row.TryGetValue("id", out var externalIdFromId); var externalId = externalIdFromItem ?? externalIdFromId; row.TryGetValue("name", out var name); if (string.IsNullOrWhiteSpace(name)) { errors.Add(new ImportErrorDto(rowNum, "Обязательное поле: name")); continue; } row.TryGetValue("category", out var category); row.TryGetValue("model", out var model); row.TryGetValue("unit", out var unit); row.TryGetValue("condition", out var conditionKey); row.TryGetValue("location", out var location); row.TryGetValue("quantity", out var quantityRaw); row.TryGetValue("min_quantity", out var minQuantityRaw); row.TryGetValue("reorder_point", out var reorderPointRaw); row.TryGetValue("emergency_reserve", out var emergencyReserveRaw); row.TryGetValue("criticality", out var criticalityKey); var data = new WarehouseItem { Id = Guid.NewGuid(), ExternalId = string.IsNullOrWhiteSpace(externalId) ? null : externalId, Name = name, Category = string.IsNullOrWhiteSpace(category) ? "Прочее" : category, Model = string.IsNullOrWhiteSpace(model) ? null : model, Unit = string.IsNullOrWhiteSpace(unit) ? "шт" : unit, Condition = ConditionMap.GetValueOrDefault(conditionKey ?? "новое", ItemCondition.New), StorageLocation = string.IsNullOrWhiteSpace(location) ? "Склад-1" : location, Quantity = double.TryParse(quantityRaw, out var qty) ? qty : 0, MinQuantity = double.TryParse(minQuantityRaw, out var minQty) ? minQty : 0, ReorderPoint = double.TryParse(reorderPointRaw, out var reorder) ? reorder : 0, EmergencyReserve = double.TryParse(emergencyReserveRaw, out var reserve) ? reserve : 0, Criticality = CriticalityMap.GetValueOrDefault(criticalityKey ?? "средняя", Criticality.Medium), CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; if (!string.IsNullOrWhiteSpace(externalId)) { var existing = await db.WarehouseItems .FirstOrDefaultAsync(x => x.ExternalId == externalId, ct); if (existing is null) db.WarehouseItems.Add(data); else { existing.Name = data.Name; existing.Category = data.Category; existing.Model = data.Model; existing.Unit = data.Unit; existing.Condition = data.Condition; existing.StorageLocation = data.StorageLocation; existing.Quantity = data.Quantity; existing.MinQuantity = data.MinQuantity; existing.ReorderPoint = data.ReorderPoint; existing.EmergencyReserve = data.EmergencyReserve; existing.Criticality = data.Criticality; existing.UpdatedAt = DateTime.UtcNow; } } else { db.WarehouseItems.Add(data); } success++; } catch (Exception ex) { errors.Add(new ImportErrorDto(rowNum, ex.Message)); } } var importLogId = Guid.NewGuid(); await db.ImportLogs.AddAsync(new ImportLog { Id = importLogId, UserId = userId, FileName = "warehouse-import", DataType = "warehouse", TotalRows = rows.Count, SuccessRows = success, ErrorRows = errors.Count, ErrorsJson = errors.Count > 0 ? System.Text.Json.JsonSerializer.Serialize(errors) : null, CreatedAt = DateTime.UtcNow }, ct); audit.Log( userId, "IMPORT", "WarehouseItem", importLogId, AuditMessages.ImportCompleted("склад", rows.Count, success, errors.Count), new { rows.Count, success, errors = errors.Count, DataType = "warehouse" }); await db.SaveChangesAsync(ct); return new ImportResultDto(success, errors); } /// <summary>Разбирает дату из ячейки импорта; пустое значение или «—» даёт null.</summary> private static DateTime? ParseDate(string? value) { if (string.IsNullOrWhiteSpace(value) || value is "—" or "-") return null; return DateTime.TryParse(value, out var date) ? date : null; } }