Files
OrpaonVision/OrpaonVision.ConfigApp/Infrastructure/Services/ProductPermissionService.cs
2026-04-06 22:04:05 +08:00

644 lines
28 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}