版本260406

This commit is contained in:
2026-04-06 22:04:05 +08:00
parent 7dc5e73af7
commit 0b150470be
216 changed files with 98993 additions and 33 deletions

View File

@@ -0,0 +1,643 @@
using Microsoft.Extensions.Logging;
using OrpaonVision.Core.Results;
using OrpaonVision.Core.Security;
using OrpaonVision.Model.Configuration;
using OrpaonVision.Model.Security;
using System.Collections.Concurrent;
namespace OrpaonVision.ConfigApp.Infrastructure.Services;
/// <summary>
/// 机种权限服务实现。
/// </summary>
public sealed class ProductPermissionService : IProductPermissionService
{
private readonly ILogger<ProductPermissionService> _logger;
private readonly ConcurrentDictionary<string, List<ProductPermissionModel>> _userPermissions;
private readonly ConcurrentDictionary<Guid, List<ProductLockRecordModel>> _productLocks;
private readonly ConcurrentDictionary<Guid, List<ProductSwitchRecordModel>> _switchHistory;
private readonly object _lockObject = new();
/// <summary>
/// 构造函数。
/// </summary>
public ProductPermissionService(ILogger<ProductPermissionService> logger)
{
_logger = logger;
_userPermissions = new ConcurrentDictionary<string, List<ProductPermissionModel>>();
_productLocks = new ConcurrentDictionary<Guid, List<ProductLockRecordModel>>();
_switchHistory = new ConcurrentDictionary<Guid, List<ProductSwitchRecordModel>>();
// 初始化默认权限数据
InitializeDefaultPermissions();
}
/// <inheritdoc />
public async Task<Result<bool>> CheckPermissionAsync(Guid userId, Guid productTypeId, ProductPermissionType permissionType, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("检查用户权限: {UserId}, {ProductTypeId}, {PermissionType}", userId, productTypeId, permissionType);
var userKey = userId.ToString();
if (!_userPermissions.TryGetValue(userKey, out var permissions))
{
_logger.LogWarning("用户 {UserId} 没有任何机种权限", userId);
return Result<bool>.Success(false);
}
var hasPermission = permissions.Any(p =>
p.ProductTypeId == productTypeId &&
p.PermissionType == permissionType &&
p.IsEnabled &&
(!p.ExpiresAtUtc.HasValue || p.ExpiresAtUtc.Value > DateTime.UtcNow));
_logger.LogDebug("用户 {UserId} 对机种 {ProductTypeId} 的权限 {PermissionType}: {HasPermission}",
userId, productTypeId, permissionType, hasPermission);
return Result<bool>.Success(hasPermission);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "检查用户权限失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CHECK_PERMISSION_FAILED", "检查用户权限失败。", traceId);
return Result<bool>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<bool>> CheckPermissionByCodeAsync(Guid userId, string productTypeCode, ProductPermissionType permissionType, CancellationToken cancellationToken = default)
{
try
{
// 这里需要根据机种编码查找机种ID暂时使用模拟数据
var productTypeId = await GetProductTypeIdByCodeAsync(productTypeCode, cancellationToken);
if (productTypeId == Guid.Empty)
{
_logger.LogWarning("机种编码 {ProductTypeCode} 不存在", productTypeCode);
return Result<bool>.Success(false);
}
return await CheckPermissionAsync(userId, productTypeId, permissionType, cancellationToken);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "根据机种编码检查权限失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CHECK_PERMISSION_BY_CODE_FAILED", "根据机种编码检查权限失败。", traceId);
return Result<bool>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<ProductPermissionModel>>> GetUserPermissionsAsync(Guid userId, Guid productTypeId, CancellationToken cancellationToken = default)
{
try
{
var userKey = userId.ToString();
if (!_userPermissions.TryGetValue(userKey, out var permissions))
{
return Result<IReadOnlyList<ProductPermissionModel>>.Success(new List<ProductPermissionModel>());
}
var userProductPermissions = permissions
.Where(p => p.ProductTypeId == productTypeId && p.IsEnabled)
.ToList();
return Result<IReadOnlyList<ProductPermissionModel>>.Success(userProductPermissions);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取用户机种权限失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_USER_PERMISSIONS_FAILED", "获取用户机种权限失败。", traceId);
return Result<IReadOnlyList<ProductPermissionModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<ProductPermissionModel>> GrantPermissionAsync(Guid userId, Guid productTypeId, ProductPermissionType permissionType, DateTime? expiresAtUtc = null, string? remark = null, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("授予用户机种权限: {UserId}, {ProductTypeId}, {PermissionType}", userId, productTypeId, permissionType);
lock (_lockObject)
{
var userKey = userId.ToString();
var permissions = _userPermissions.GetOrAdd(userKey, _ => new List<ProductPermissionModel>());
// 检查是否已存在相同权限
var existingPermission = permissions.FirstOrDefault(p =>
p.ProductTypeId == productTypeId &&
p.PermissionType == permissionType);
if (existingPermission != null)
{
// 更新现有权限
existingPermission.IsEnabled = true;
existingPermission.ExpiresAtUtc = expiresAtUtc;
existingPermission.Remark = remark;
existingPermission.UpdatedAtUtc = DateTime.UtcNow;
existingPermission.UpdatedBy = Environment.UserName;
_logger.LogInformation("更新现有权限: {PermissionId}", existingPermission.Id);
return Result<ProductPermissionModel>.Success(existingPermission);
}
// 创建新权限
var newPermission = new ProductPermissionModel
{
Id = Guid.NewGuid(),
ProductTypeId = productTypeId,
UserId = userId,
RoleId = Guid.NewGuid(), // 模拟角色ID
PermissionType = permissionType,
IsEnabled = true,
GrantedAtUtc = DateTime.UtcNow,
GrantedBy = Environment.UserName,
ExpiresAtUtc = expiresAtUtc,
Remark = remark,
CreatedAtUtc = DateTime.UtcNow,
CreatedBy = Environment.UserName,
UpdatedAtUtc = DateTime.UtcNow,
UpdatedBy = Environment.UserName
};
permissions.Add(newPermission);
_logger.LogInformation("成功授予权限: {PermissionId}", newPermission.Id);
return Result<ProductPermissionModel>.Success(newPermission);
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "授予用户机种权限失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GRANT_PERMISSION_FAILED", "授予用户机种权限失败。", traceId);
return Result<ProductPermissionModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result> RevokePermissionAsync(Guid permissionId, string reason, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("撤销用户机种权限: {PermissionId}, 原因: {Reason}", permissionId, reason);
lock (_lockObject)
{
foreach (var permissions in _userPermissions.Values)
{
var permission = permissions.FirstOrDefault(p => p.Id == permissionId);
if (permission != null)
{
permission.IsEnabled = false;
permission.UpdatedAtUtc = DateTime.UtcNow;
permission.UpdatedBy = Environment.UserName;
permission.Remark = $"权限已撤销: {reason}";
_logger.LogInformation("成功撤销权限: {PermissionId}", permissionId);
return Result.Success();
}
}
return Result.Fail("PERMISSION_NOT_FOUND", "指定的权限不存在");
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "撤销用户机种权限失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "REVOKE_PERMISSION_FAILED", "撤销用户机种权限失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<bool>> IsProductLockedAsync(Guid productTypeId, ProductLockType lockType, CancellationToken cancellationToken = default)
{
try
{
if (!_productLocks.TryGetValue(productTypeId, out var locks))
{
return Result<bool>.Success(false);
}
var activeLocks = locks.Where(l => !l.IsUnlocked &&
(!l.ExpectedUnlockAtUtc.HasValue || l.ExpectedUnlockAtUtc.Value > DateTime.UtcNow));
var isLocked = lockType == ProductLockType.Permission
? activeLocks.Any(l => l.LockType == ProductLockType.Permission || l.LockType == ProductLockType.Maintenance)
: activeLocks.Any(l => l.LockType == lockType);
return Result<bool>.Success(isLocked);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "检查机种锁定状态失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CHECK_LOCK_FAILED", "检查机种锁定状态失败。", traceId);
return Result<bool>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<ProductLockRecordModel>> LockProductAsync(Guid productTypeId, ProductLockType lockType, string lockReason, DateTime? expectedUnlockAtUtc = null, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("锁定机种: {ProductTypeId}, {LockType}, 原因: {LockReason}", productTypeId, lockType, lockReason);
lock (_lockObject)
{
var locks = _productLocks.GetOrAdd(productTypeId, _ => new List<ProductLockRecordModel>());
// 检查是否已存在相同类型的活跃锁定
var existingLock = locks.FirstOrDefault(l => !l.IsUnlocked && l.LockType == lockType);
if (existingLock != null)
{
return Result<ProductLockRecordModel>.Fail("ALREADY_LOCKED", $"机种已被相同类型的锁定锁定: {existingLock.LockReason}");
}
var lockRecord = new ProductLockRecordModel
{
Id = Guid.NewGuid(),
ProductTypeId = productTypeId,
LockType = lockType,
LockReason = lockReason,
LockedByUserId = Guid.NewGuid(), // 模拟用户ID
LockedByName = Environment.UserName,
LockedAtUtc = DateTime.UtcNow,
ExpectedUnlockAtUtc = expectedUnlockAtUtc,
IsUnlocked = false,
CreatedAtUtc = DateTime.UtcNow,
CreatedBy = Environment.UserName
};
locks.Add(lockRecord);
_logger.LogInformation("成功锁定机种: {LockId}", lockRecord.Id);
return Result<ProductLockRecordModel>.Success(lockRecord);
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "锁定机种失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "LOCK_PRODUCT_FAILED", "锁定机种失败。", traceId);
return Result<ProductLockRecordModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result> UnlockProductAsync(Guid lockRecordId, string unlockReason, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("解锁机种: {LockRecordId}, 原因: {UnlockReason}", lockRecordId, unlockReason);
lock (_lockObject)
{
foreach (var locks in _productLocks.Values)
{
var lockRecord = locks.FirstOrDefault(l => l.Id == lockRecordId);
if (lockRecord != null && !lockRecord.IsUnlocked)
{
lockRecord.IsUnlocked = true;
lockRecord.UnlockedAtUtc = DateTime.UtcNow;
lockRecord.UnlockedByUserId = Guid.NewGuid(); // 模拟用户ID
lockRecord.UnlockedByName = Environment.UserName;
lockRecord.UnlockReason = unlockReason;
_logger.LogInformation("成功解锁机种: {LockId}", lockRecordId);
return Result.Success();
}
}
return Result.Fail("LOCK_NOT_FOUND", "指定的锁定记录不存在或已解锁");
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "解锁机种失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "UNLOCK_PRODUCT_FAILED", "解锁机种失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<ProductSwitchValidationResult>> ValidateSwitchPermissionAsync(Guid userId, Guid? sourceProductTypeId, Guid targetProductTypeId, bool isForced = false, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("验证机种切换权限: {UserId}, {SourceProductTypeId} -> {TargetProductTypeId}, 强制: {IsForced}",
userId, sourceProductTypeId, targetProductTypeId, isForced);
var sourceProduct = sourceProductTypeId.HasValue ? await GetProductTypeAsync(sourceProductTypeId.Value) : null;
var targetProduct = await GetProductTypeAsync(targetProductTypeId);
// 检查目标机种是否存在
if (targetProduct == null)
{
var validationResult1 = new ProductSwitchValidationResult
{
SourceProduct = sourceProduct,
TargetProduct = targetProduct,
CanSwitch = false,
Message = "目标机种不存在"
};
return Result<ProductSwitchValidationResult>.Success(validationResult1);
}
// 检查目标机种状态
if (targetProduct.Status != ProductTypeStatus.Published)
{
var validationResult2 = new ProductSwitchValidationResult
{
SourceProduct = sourceProduct,
TargetProduct = targetProduct,
CanSwitch = false,
Message = "目标机种未发布,无法切换"
};
return Result<ProductSwitchValidationResult>.Success(validationResult2);
}
// 检查目标机种是否被锁定
var isLocked = await IsProductLockedAsync(targetProductTypeId, ProductLockType.Runtime, cancellationToken);
if (isLocked.Succeeded && isLocked.Data)
{
var validationResult3 = new ProductSwitchValidationResult
{
SourceProduct = sourceProduct,
TargetProduct = targetProduct,
CanSwitch = false,
Message = "目标机种已被锁定,无法切换"
};
return Result<ProductSwitchValidationResult>.Success(validationResult3);
}
// 获取用户权限
var userPermissions = await GetUserPermissionsAsync(userId, targetProductTypeId, cancellationToken);
var currentPermissions = userPermissions.Succeeded ? userPermissions.Data.Select(p => p.PermissionType).ToList() : new List<ProductPermissionType>();
// 确定所需权限
var requiredPermissions = new List<ProductPermissionType> { ProductPermissionType.Switch };
if (isForced)
{
requiredPermissions.Add(ProductPermissionType.ForceSwitch);
}
// 收集错误和警告
var errors = new List<string>();
var warnings = new List<string>();
var approvers = new List<string>();
var requiresApproval = false;
// 检查权限
if (!userPermissions.Succeeded)
{
errors.Add($"获取用户权限失败: {userPermissions.Message}");
}
else if (!userPermissions.Data.Any(p => requiredPermissions.Contains(p.PermissionType)))
{
errors.Add("用户没有足够的权限进行机种切换");
}
// 强制切换需要审批
if (isForced)
{
requiresApproval = true;
approvers.Add("班组长");
warnings.Add("强制切换需要班组长审批");
}
// 检查源机种(如果有)
if (sourceProductTypeId.HasValue)
{
var sourcePermissions = await GetUserPermissionsAsync(userId, sourceProductTypeId.Value, cancellationToken);
if (sourcePermissions.Succeeded && !sourcePermissions.Data.Any(p => p.PermissionType == ProductPermissionType.Switch))
{
warnings.Add("当前用户在源机种没有切换权限");
}
}
var canSwitch = !errors.Any();
var message = canSwitch
? "验证通过,可以切换机种"
: "验证失败,无法切换机种";
var validationResult = new ProductSwitchValidationResult
{
SourceProduct = sourceProduct,
TargetProduct = targetProduct,
CurrentPermissions = currentPermissions,
RequiredPermissions = requiredPermissions,
Errors = errors,
Warnings = warnings,
Approvers = approvers,
RequiresApproval = requiresApproval,
CanSwitch = canSwitch,
Message = message
};
return Result<ProductSwitchValidationResult>.Success(validationResult);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "验证机种切换权限失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "VALIDATE_SWITCH_FAILED", "验证机种切换权限失败。", traceId);
return Result<ProductSwitchValidationResult>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<ProductSwitchRecordModel>> RecordSwitchAsync(Guid userId, string userName, string userRole, Guid? sourceProductTypeId, Guid targetProductTypeId, ProductSwitchType switchType, string switchReason, bool isForced = false, string? forcedReason = null, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("记录机种切换: {UserId}, {SourceProductTypeId} -> {TargetProductTypeId}, {SwitchType}",
userId, sourceProductTypeId, targetProductTypeId, switchType);
lock (_lockObject)
{
var switchHistory = _switchHistory.GetOrAdd(targetProductTypeId, _ => new List<ProductSwitchRecordModel>());
var switchRecord = new ProductSwitchRecordModel
{
Id = Guid.NewGuid(),
SourceProductTypeId = sourceProductTypeId,
SourceProductTypeCode = sourceProductTypeId.HasValue ? GetProductTypeCodeAsync(sourceProductTypeId.Value).Result : null,
TargetProductTypeId = targetProductTypeId,
TargetProductTypeCode = GetProductTypeCodeAsync(targetProductTypeId).Result ?? string.Empty,
SwitchType = switchType,
SwitchReason = switchReason,
SwitchedByUserId = userId,
SwitchedByName = userName,
SwitchedByRole = userRole,
SwitchedAtUtc = DateTime.UtcNow,
IsForced = isForced,
ForcedReason = forcedReason,
CreatedAtUtc = DateTime.UtcNow,
CreatedBy = Environment.UserName
};
switchHistory.Add(switchRecord);
_logger.LogInformation("成功记录机种切换: {SwitchId}", switchRecord.Id);
return Result<ProductSwitchRecordModel>.Success(switchRecord);
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "记录机种切换失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "RECORD_SWITCH_FAILED", "记录机种切换失败。", traceId);
return Result<ProductSwitchRecordModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<ProductSwitchRecordModel>>> GetSwitchHistoryAsync(Guid? productTypeId = null, Guid? userId = null, DateTime? startTime = null, DateTime? endTime = null, CancellationToken cancellationToken = default)
{
try
{
var allRecords = new List<ProductSwitchRecordModel>();
if (productTypeId.HasValue)
{
if (_switchHistory.TryGetValue(productTypeId.Value, out var records))
{
allRecords.AddRange(records);
}
}
else
{
foreach (var records in _switchHistory.Values)
{
allRecords.AddRange(records);
}
}
// 过滤条件
if (userId.HasValue)
{
allRecords = allRecords.Where(r => r.SwitchedByUserId == userId.Value).ToList();
}
if (startTime.HasValue)
{
allRecords = allRecords.Where(r => r.SwitchedAtUtc >= startTime.Value).ToList();
}
if (endTime.HasValue)
{
allRecords = allRecords.Where(r => r.SwitchedAtUtc <= endTime.Value).ToList();
}
// 按时间倒序排列
allRecords = allRecords.OrderByDescending(r => r.SwitchedAtUtc).ToList();
return Result<IReadOnlyList<ProductSwitchRecordModel>>.Success(allRecords);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取机种切换历史失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_SWITCH_HISTORY_FAILED", "获取机种切换历史失败。", traceId);
return Result<IReadOnlyList<ProductSwitchRecordModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<ProductLockRecordModel>>> GetLockRecordsAsync(Guid productTypeId, bool includeUnlocked = false, CancellationToken cancellationToken = default)
{
try
{
if (!_productLocks.TryGetValue(productTypeId, out var locks))
{
return Result<IReadOnlyList<ProductLockRecordModel>>.Success(new List<ProductLockRecordModel>());
}
var filteredLocks = includeUnlocked
? locks.ToList()
: locks.Where(l => !l.IsUnlocked).ToList();
// 按时间倒序排列
filteredLocks = filteredLocks.OrderByDescending(l => l.LockedAtUtc).ToList();
return Result<IReadOnlyList<ProductLockRecordModel>>.Success(filteredLocks);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取机种锁定记录失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_LOCK_RECORDS_FAILED", "获取机种锁定记录失败。", traceId);
return Result<IReadOnlyList<ProductLockRecordModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
#region
/// <summary>
/// 初始化默认权限数据。
/// </summary>
private void InitializeDefaultPermissions()
{
// 这里可以初始化一些默认的权限数据用于测试
_logger.LogInformation("初始化默认机种权限数据");
}
/// <summary>
/// 根据机种编码获取机种ID。
/// </summary>
private async Task<Guid> GetProductTypeIdByCodeAsync(string productTypeCode, CancellationToken cancellationToken = default)
{
// 模拟实现,实际应该从数据库查询
await Task.Delay(10, cancellationToken);
return productTypeCode switch
{
"VF-A100" => Guid.Parse("11111111-1111-1111-1111-111111111111"),
"VF-B200" => Guid.Parse("22222222-2222-2222-2222-222222222222"),
"VF-C300" => Guid.Parse("33333333-3333-3333-3333-333333333333"),
_ => Guid.Empty
};
}
/// <summary>
/// 获取机种信息。
/// </summary>
private async Task<ProductTypeModel?> GetProductTypeAsync(Guid productTypeId, CancellationToken cancellationToken = default)
{
// 模拟实现,实际应该从数据库查询
await Task.Delay(10, cancellationToken);
return new ProductTypeModel
{
Id = productTypeId,
Code = "VF-A100",
Name = "变频器A100",
Status = ProductTypeStatus.Published,
TotalLayers = 3,
IsPublished = true,
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
CreatedBy = "System"
};
}
/// <summary>
/// 获取机种编码。
/// </summary>
private async Task<string?> GetProductTypeCodeAsync(Guid productTypeId, CancellationToken cancellationToken = default)
{
var productType = await GetProductTypeAsync(productTypeId, cancellationToken);
return productType?.Code;
}
#endregion
}