8518c7b3ec
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m9s
- Updated AcceptClubMembershipContractCommandHandler to set PackagePurchasedAt to the contract-signing time, ensuring accurate pool charging. - Modified ActivateClubMembershipCommand to allow specifying a PackageId for forced activation, defaulting to the base package if not provided. - Improved ActivateClubMembershipCommandHandler to handle new and existing memberships differently regarding pool charging. - Added validation logic in GetWeeklyCommissionPoolQueryHandler to compare TotalPoolAmount against actual activations. - Introduced validation fields in WeeklyCommissionPoolDto for tracking discrepancies and expected amounts. - Updated stored procedures to ensure accurate calculations based on package purchases and to handle empty pools. - Enhanced protobuf definitions to include validation fields for commission pools and network tree nodes. - Added mapping for new fields in CommissionProfile and NetworkMembershipProfile. - Created DiscountProductProfile for mapping discount product requests and responses.
198 lines
9.1 KiB
C#
198 lines
9.1 KiB
C#
using System.Data;
|
|
using System.Data.Common;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
|
|
|
|
public class GetNetworkTreeQueryHandler : IRequestHandler<GetNetworkTreeQuery, NetworkTreeDto?>
|
|
{
|
|
private readonly IApplicationDbContext _context;
|
|
private readonly ILogger<GetNetworkTreeQueryHandler> _logger;
|
|
|
|
public GetNetworkTreeQueryHandler(
|
|
IApplicationDbContext context,
|
|
ILogger<GetNetworkTreeQueryHandler> logger)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<NetworkTreeDto?> Handle(GetNetworkTreeQuery request, CancellationToken cancellationToken)
|
|
{
|
|
// UserId must be provided - customer endpoints resolve from JWT before calling
|
|
var userId = request.UserId;
|
|
|
|
if (userId == 0)
|
|
{
|
|
throw new ArgumentException("UserId is required for network tree query");
|
|
}
|
|
|
|
// Create a new request with the resolved userId
|
|
var resolvedRequest = request with { UserId = userId };
|
|
|
|
try
|
|
{
|
|
// دریافت نتایج flat از Stored Procedure
|
|
var flatNodes = await ExecuteStoredProcedureAsync(resolvedRequest, cancellationToken);
|
|
|
|
if (flatNodes == null || !flatNodes.Any())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// تبدیل نتایج flat به ساختار درختی
|
|
var tree = BuildTreeFromFlatNodes(flatNodes);
|
|
return tree;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error executing GetNetworkTree for UserId: {UserId}", request.UserId);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// اجرای Stored Procedure و دریافت نتایج
|
|
/// </summary>
|
|
private async Task<List<NetworkTreeNodeDto>> ExecuteStoredProcedureAsync(
|
|
GetNetworkTreeQuery request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var results = new List<NetworkTreeNodeDto>();
|
|
|
|
var connection = _context.Database.GetDbConnection();
|
|
|
|
try
|
|
{
|
|
if (connection.State != ConnectionState.Open)
|
|
{
|
|
await connection.OpenAsync(cancellationToken);
|
|
}
|
|
|
|
using var command = connection.CreateCommand();
|
|
command.CommandText = "[CMS].[GetNetworkTree]";
|
|
command.CommandType = CommandType.StoredProcedure;
|
|
command.CommandTimeout = 120; // 2 minutes timeout for large trees
|
|
|
|
// Parameters - use DbParameter for provider-agnostic code
|
|
var rootUserIdParam = command.CreateParameter();
|
|
rootUserIdParam.ParameterName = "@RootUserId";
|
|
rootUserIdParam.DbType = DbType.Int64;
|
|
rootUserIdParam.Value = request.UserId;
|
|
command.Parameters.Add(rootUserIdParam);
|
|
|
|
var maxDepthParam = command.CreateParameter();
|
|
maxDepthParam.ParameterName = "@MaxDepth";
|
|
maxDepthParam.DbType = DbType.Int32;
|
|
maxDepthParam.Value = request.MaxDepth > 0 ? request.MaxDepth : 100;
|
|
command.Parameters.Add(maxDepthParam);
|
|
|
|
var isClubActiveParam = command.CreateParameter();
|
|
isClubActiveParam.ParameterName = "@IsClubActive";
|
|
isClubActiveParam.DbType = DbType.Boolean;
|
|
isClubActiveParam.Value = request.IsClubActive.HasValue ? request.IsClubActive.Value : DBNull.Value;
|
|
command.Parameters.Add(isClubActiveParam);
|
|
|
|
var weekParam = command.CreateParameter();
|
|
weekParam.ParameterName = "@ActivationWeekDefinitionId";
|
|
weekParam.DbType = DbType.Int64;
|
|
weekParam.Value = request.ActivationWeekDefinitionId.HasValue ? request.ActivationWeekDefinitionId.Value : DBNull.Value;
|
|
command.Parameters.Add(weekParam);
|
|
|
|
using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
|
|
while (await reader.ReadAsync(cancellationToken))
|
|
{
|
|
var node = new NetworkTreeNodeDto
|
|
{
|
|
UserId = reader.GetInt64(reader.GetOrdinal("UserId")),
|
|
Mobile = reader.IsDBNull(reader.GetOrdinal("Mobile")) ? null : reader.GetString(reader.GetOrdinal("Mobile")),
|
|
FirstName = reader.IsDBNull(reader.GetOrdinal("FirstName")) ? null : reader.GetString(reader.GetOrdinal("FirstName")),
|
|
LastName = reader.IsDBNull(reader.GetOrdinal("LastName")) ? null : reader.GetString(reader.GetOrdinal("LastName")),
|
|
ReferralCode = reader.IsDBNull(reader.GetOrdinal("ReferralCode")) ? null : reader.GetString(reader.GetOrdinal("ReferralCode")),
|
|
LegPosition = reader.IsDBNull(reader.GetOrdinal("LegPosition")) ? null : reader.GetInt32(reader.GetOrdinal("LegPosition")),
|
|
ParentId = reader.IsDBNull(reader.GetOrdinal("ParentId")) ? null : reader.GetInt64(reader.GetOrdinal("ParentId")),
|
|
NetworkLevel = reader.GetInt32(reader.GetOrdinal("NetworkLevel")),
|
|
ClubActivatedAt = reader.IsDBNull(reader.GetOrdinal("ClubActivatedAt")) ? null : reader.GetDateTime(reader.GetOrdinal("ClubActivatedAt")),
|
|
IsClubActive = Convert.ToBoolean(reader.GetValue(reader.GetOrdinal("IsClubActive"))),
|
|
ActivationWeekDefinitionId = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDefinitionId")) ? null : reader.GetInt64(reader.GetOrdinal("ActivationWeekDefinitionId")),
|
|
ActivationWeekDisplayName = reader.IsDBNull(reader.GetOrdinal("ActivationWeekDisplayName")) ? null : reader.GetString(reader.GetOrdinal("ActivationWeekDisplayName")),
|
|
IsActivatedInTargetWeek = Convert.ToBoolean(reader.GetValue(reader.GetOrdinal("IsActivatedInTargetWeek"))),
|
|
UserCreated = new DateTimeOffset(reader.GetDateTime(reader.GetOrdinal("UserCreated"))),
|
|
IsNewActivation = reader.IsDBNull(reader.GetOrdinal("IsNewActivation")) ? null : (bool?)(reader.GetInt32(reader.GetOrdinal("IsNewActivation")) == 1),
|
|
PackageName = reader.IsDBNull(reader.GetOrdinal("PackageName")) ? null : reader.GetString(reader.GetOrdinal("PackageName")),
|
|
PackageId = reader.IsDBNull(reader.GetOrdinal("PackageId")) ? null : reader.GetInt64(reader.GetOrdinal("PackageId"))
|
|
};
|
|
|
|
results.Add(node);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// Connection is managed by DbContext, don't close it here
|
|
}
|
|
|
|
_logger.LogInformation("GetNetworkTree SP returned {Count} nodes for UserId: {UserId}", results.Count, request.UserId);
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// تبدیل لیست flat به ساختار درختی
|
|
/// </summary>
|
|
private NetworkTreeDto? BuildTreeFromFlatNodes(List<NetworkTreeNodeDto> flatNodes)
|
|
{
|
|
if (!flatNodes.Any()) return null;
|
|
|
|
// Dictionary برای دسترسی سریع به نودها
|
|
var nodeDict = new Dictionary<long, NetworkTreeDto>();
|
|
|
|
// ایجاد همه نودها
|
|
foreach (var flatNode in flatNodes)
|
|
{
|
|
nodeDict[flatNode.UserId] = new NetworkTreeDto
|
|
{
|
|
UserId = flatNode.UserId,
|
|
Mobile = flatNode.Mobile,
|
|
FirstName = flatNode.FirstName,
|
|
LastName = flatNode.LastName,
|
|
ReferralCode = flatNode.ReferralCode,
|
|
LegPosition = flatNode.LegPosition.HasValue ? (NetworkLeg)flatNode.LegPosition.Value : null,
|
|
CurrentDepth = flatNode.NetworkLevel,
|
|
ClubActivatedAt = flatNode.ClubActivatedAt,
|
|
IsClubActive = flatNode.IsClubActive,
|
|
ActivationWeekDefinitionId = flatNode.ActivationWeekDefinitionId,
|
|
ActivationWeekDisplayName = flatNode.ActivationWeekDisplayName,
|
|
IsActivatedInTargetWeek = flatNode.IsActivatedInTargetWeek,
|
|
UserCreated = flatNode.UserCreated,
|
|
IsNewActivation = flatNode.IsNewActivation,
|
|
PackageName = flatNode.PackageName,
|
|
PackageId = flatNode.PackageId
|
|
};
|
|
}
|
|
|
|
// برقراری ارتباط Parent-Child
|
|
foreach (var flatNode in flatNodes)
|
|
{
|
|
if (flatNode.ParentId.HasValue && nodeDict.ContainsKey(flatNode.ParentId.Value))
|
|
{
|
|
var parent = nodeDict[flatNode.ParentId.Value];
|
|
var child = nodeDict[flatNode.UserId];
|
|
|
|
// تعیین موقعیت چپ یا راست
|
|
if (flatNode.LegPosition == (int)NetworkLeg.Left)
|
|
{
|
|
parent.LeftChild = child;
|
|
}
|
|
else if (flatNode.LegPosition == (int)NetworkLeg.Right)
|
|
{
|
|
parent.RightChild = child;
|
|
}
|
|
}
|
|
}
|
|
|
|
// پیدا کردن ریشه (اولین نود با Level=0)
|
|
var rootNode = flatNodes.FirstOrDefault(n => n.NetworkLevel == 0);
|
|
return rootNode != null ? nodeDict[rootNode.UserId] : null;
|
|
}
|
|
}
|