- Added FINAL-STATUS.md detailing project completion and key metrics - Created QUICK-REFERENCE.md for quick access to essential documents - Updated README.md with project overview and quick start guide - Established STRUCTURE.md outlining the final documentation structure - Organized and archived old files, ensuring a clean and efficient directory - Enhanced documentation quality with comprehensive metrics and checklists
28 KiB
🔴 تحلیل مغایرت Protobuf بین FrontOffice.BFF و CMS
تاریخ: ۱۴ آذر ۱۴۰۴
این سند تمام مغایرتهای موجود بین Handler های BFF و Protobuf های CMS را تحلیل میکند.
📊 خلاصه مشکلات
| ماژول | Handler های BFF | Proto های CMS | وضعیت | اولویت |
|---|---|---|---|---|
| ClubMembership | ✅ 2 Handler | ⚠️ 7 RPC | نیاز به اصلاح | 🔴 بالا |
| NetworkMembership | ✅ 2 Handler | ⚠️ 7 RPC | نیاز به اصلاح | 🔴 بالا |
| Commission | ✅ 2 Handler | ⚠️ 16 RPC | نیاز به اصلاح | 🔴 بالا |
| UserWallet | ⚠️ 5 Handler | ✅ CMS API | نیاز به Query جدید | 🟡 متوسط |
1️⃣ ClubMembership - مغایرتها
🟢 BFF Handlers (2 عدد - موجود)
✅ GetMyClubMembership (Query)
✅ ActivateMyClubMembership (Command)
📋 CMS Protobuf (clubmembership.proto)
service ClubMembershipContract {
// Commands
rpc ActivateClubMembership(ActivateClubMembershipRequest) returns (Empty);
rpc DeactivateClubMembership(DeactivateClubMembershipRequest) returns (Empty);
rpc AssignFeatureToMembership(AssignFeatureToMembershipRequest) returns (Empty);
// Queries
rpc GetClubMembership(GetClubMembershipRequest) returns (GetClubMembershipResponse);
rpc GetAllClubMemberships(GetAllClubMembershipsRequest) returns (GetAllClubMembershipsResponse);
rpc GetClubMembershipHistory(GetClubMembershipHistoryRequest) returns (GetClubMembershipHistoryResponse);
rpc GetClubStatistics(GetClubStatisticsRequest) returns (GetClubStatisticsResponse);
}
❌ مشکل 1: GetMyClubMembershipQueryHandler
فایل: FrontOffice.BFF/src/FrontOffice.BFF.Application/ClubMembershipCQ/Queries/GetMyClubMembership/GetMyClubMembershipQueryHandler.cs
کد فعلی:
var response = await _context.ClubMemberships.GetClubMembershipAsync(cmsRequest, cancellationToken: cancellationToken);
// استفاده از فیلدهای قدیمی:
var activationDate = response.ActivationDate?.ToDateTime(); // ❌ ActivationDate
var expirationDate = response.ExpirationDate?.ToDateTime(); // ❌ ExpirationDate
CMS Proto:
message GetClubMembershipResponse
{
int64 id = 1;
int64 user_id = 2;
int64 package_id = 3;
string package_name = 4;
string activation_code = 5;
google.protobuf.Timestamp activated_at = 6; // ✅ activated_at (نام جدید)
google.protobuf.Timestamp expires_at = 7; // ✅ expires_at (نام جدید)
bool is_active = 8;
google.protobuf.Timestamp created = 9;
repeated MembershipFeatureModel features = 10;
}
🔧 راه حل:
// تغییر نام فیلدها:
var activationDate = response.ActivatedAt?.ToDateTime(); // ✅ ActivatedAt
var expirationDate = response.ExpiresAt?.ToDateTime(); // ✅ ExpiresAt
⚠️ نکته مهم: CMS حالا یک لیست features نیز بر میگرداند که باید به Response DTO اضافه شود:
public class GetMyClubMembershipResponseDto
{
// ... فیلدهای موجود
public List<MembershipFeatureDto>? Features { get; set; } // ✅ جدید
}
public class MembershipFeatureDto
{
public long ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime? ExpiresAt { get; set; }
public bool IsActive { get; set; }
}
✅ ActivateMyClubMembershipCommandHandler
وضعیت: این Handler صحیح است، اما Response نیاز به بررسی دارد.
کد فعلی:
await _context.ClubMemberships.ActivateClubMembershipAsync(grpcRequest, cancellationToken: cancellationToken);
// ❌ Response Mock است:
return new ActivateMyClubMembershipResponseDto
{
Success = true,
Message = "عضویت باشگاه با موفقیت فعال شد",
ActivationDate = DateTime.UtcNow,
ExpirationDate = activationDate.AddMonths(request.DurationMonths),
AmountPaid = 56_000_000 // ❌ Hardcoded
};
مشکل: CMS فقط Empty بر میگرداند، اطلاعات واقعی باید از GetClubMembership گرفته شود.
🔧 راه حل:
// بعد از فعالسازی، GetClubMembership را صدا بزن:
await _context.ClubMemberships.ActivateClubMembershipAsync(grpcRequest, cancellationToken);
var membershipRequest = new GetClubMembershipRequest { UserId = userId };
var membership = await _context.ClubMemberships.GetClubMembershipAsync(membershipRequest, cancellationToken);
return new ActivateMyClubMembershipResponseDto
{
Success = true,
Message = "عضویت باشگاه با موفقیت فعال شد",
ActivationDate = membership.ActivatedAt?.ToDateTime(),
ExpirationDate = membership.ExpiresAt?.ToDateTime(),
AmountPaid = CalculatePackageCost(membership.PackageId, request.DurationMonths) // محاسبه واقعی
};
2️⃣ NetworkMembership - مغایرتها
🟢 BFF Handlers (2 عدد - موجود)
✅ GetMyNetworkTree (Query)
✅ GetMyNetworkStatistics (Query)
📋 CMS Protobuf (networkmembership.proto)
service NetworkMembershipContract {
// Commands
rpc JoinNetwork(JoinNetworkRequest) returns (Empty);
rpc ChangeNetworkParent(ChangeNetworkParentRequest) returns (Empty);
rpc RemoveFromNetwork(RemoveFromNetworkRequest) returns (Empty);
// Queries
rpc GetUserNetwork(GetUserNetworkRequest) returns (GetUserNetworkResponse);
rpc GetNetworkTree(GetNetworkTreeRequest) returns (GetNetworkTreeResponse);
rpc GetNetworkMembershipHistory(GetNetworkMembershipHistoryRequest) returns (GetNetworkMembershipHistoryResponse);
rpc GetNetworkStatistics(GetNetworkStatisticsRequest) returns (GetNetworkStatisticsResponse);
}
❌ مشکل 2: GetMyNetworkTreeQueryHandler
فایل: FrontOffice.BFF/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkTree/GetMyNetworkTreeQueryHandler.cs
کد فعلی:
var response = await _context.NetworkMemberships.GetNetworkTreeAsync(cmsRequest, cancellationToken);
// استفاده از فیلد RootNode:
return new GetMyNetworkTreeResponseDto
{
RootNode = MapToNetworkNode(response.RootNode, 0), // ❌ RootNode
TotalMembers = CountNodes(response.RootNode),
CurrentDepth = CalculateDepth(response.RootNode)
};
CMS Proto:
message GetNetworkTreeResponse
{
repeated NetworkTreeNodeModel nodes = 1; // ✅ Flat list (نه Tree)
}
message NetworkTreeNodeModel
{
int64 user_id = 1;
string user_name = 2;
google.protobuf.Int64Value parent_id = 3;
int32 network_leg = 4;
int32 network_level = 5;
bool is_active = 6;
google.protobuf.Timestamp joined_at = 7;
}
🚨 مشکل بزرگ: CMS حالا Flat List بر میگرداند نه Tree Structure!
🔧 راه حل: باید در BFF یک Tree Builder بسازیم:
public async Task<GetMyNetworkTreeResponseDto> Handle(GetMyNetworkTreeQuery request, CancellationToken cancellationToken)
{
var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException();
var cmsRequest = new GetNetworkTreeRequest
{
RootUserId = userId,
MaxDepth = Math.Clamp(request.MaxDepth, 1, 10)
};
var response = await _context.NetworkMemberships.GetNetworkTreeAsync(cmsRequest, cancellationToken);
// ✅ ساخت Tree از Flat List:
var rootNode = BuildTreeFromFlatList(response.Nodes, userId);
return new GetMyNetworkTreeResponseDto
{
RootNode = rootNode,
TotalMembers = response.Nodes.Count,
CurrentDepth = response.Nodes.Any() ? response.Nodes.Max(n => n.NetworkLevel) : 0
};
}
private NetworkNodeDto? BuildTreeFromFlatList(IEnumerable<NetworkTreeNodeModel> nodes, long rootUserId)
{
var nodeDict = nodes.ToDictionary(n => n.UserId);
if (!nodeDict.ContainsKey(rootUserId))
return null;
NetworkNodeDto BuildNode(long userId, int level)
{
var cmsNode = nodeDict[userId];
var node = new NetworkNodeDto
{
UserId = cmsNode.UserId,
FullName = cmsNode.UserName,
Mobile = string.Empty, // CMS ندارد
Avatar = null,
Position = cmsNode.NetworkLeg == 0 ? "Left" : "Right",
Level = level
};
// پیدا کردن children
var leftChild = nodes.FirstOrDefault(n => n.ParentId == userId && n.NetworkLeg == 0);
var rightChild = nodes.FirstOrDefault(n => n.ParentId == userId && n.NetworkLeg == 1);
if (leftChild != null)
node.LeftChild = BuildNode(leftChild.UserId, level + 1);
if (rightChild != null)
node.RightChild = BuildNode(rightChild.UserId, level + 1);
return node;
}
return BuildNode(rootUserId, 0);
}
❌ مشکل 3: GetMyNetworkStatisticsQueryHandler
فایل: FrontOffice.BFF/src/FrontOffice.BFF.Application/NetworkMembershipCQ/Queries/GetMyNetworkStatistics/GetMyNetworkStatisticsQueryHandler.cs
کد فعلی:
var cmsRequest = new GetNetworkStatisticsRequest
{
UserId = userId // ❌ GetNetworkStatisticsRequest فیلد UserId ندارد!
};
var response = await _context.NetworkMemberships.GetNetworkStatisticsAsync(cmsRequest, cancellationToken);
// استفاده از فیلدهای قدیمی:
return new GetMyNetworkStatisticsResponseDto
{
LeftLegCount = response.LeftLegCount,
RightLegCount = response.RightLegCount,
TotalMembers = response.TotalMembers,
TreeDepth = response.TreeDepth, // ❌ نام قدیمی
WeakerLeg = weakerLeg,
LastMember = response.LastMember != null ? new LastMemberDto { ... } // ❌ LastMember وجود ندارد!
};
CMS Proto:
message GetNetworkStatisticsRequest
{
// Empty - برای کل شبکه است نه یک کاربر خاص!
}
message GetNetworkStatisticsResponse
{
int32 total_members = 1;
int32 active_members = 2;
int32 left_leg_count = 3;
int32 right_leg_count = 4;
double left_percentage = 5;
double right_percentage = 6;
double average_depth = 7;
int32 max_depth = 8; // ✅ max_depth (نه tree_depth)
repeated LevelDistribution level_distribution = 9;
repeated MonthlyGrowth monthly_growth = 10;
repeated TopNetworkUser top_users = 11;
}
🚨 مشکل بزرگ:
- CMS دیگر
UserIdنمیگیرد - این Query برای کل شبکه است - فیلد
LastMemberوجود ندارد - Response خیلی جامعتر شده (LevelDistribution, MonthlyGrowth, TopUsers)
🔧 راه حل: باید یک Query جدید در CMS اضافه شود یا از GetUserNetwork استفاده کنیم:
گزینه A: استفاده از GetUserNetwork (سریعتر)
public async Task<GetMyNetworkStatisticsResponseDto> Handle(GetMyNetworkStatisticsQuery request, CancellationToken cancellationToken)
{
var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException();
// ✅ استفاده از GetUserNetwork:
var userNetworkRequest = new GetUserNetworkRequest { UserId = userId };
var userNetwork = await _context.NetworkMemberships.GetUserNetworkAsync(userNetworkRequest, cancellationToken);
// ✅ استفاده از GetNetworkTree برای شمارش:
var treeRequest = new GetNetworkTreeRequest
{
RootUserId = userId,
MaxDepth = 10 // Full tree
};
var tree = await _context.NetworkMemberships.GetNetworkTreeAsync(treeRequest, cancellationToken);
var leftCount = tree.Nodes.Count(n => n.ParentId == userId && n.NetworkLeg == 0);
var rightCount = tree.Nodes.Count(n => n.ParentId == userId && n.NetworkLeg == 1);
var lastMember = tree.Nodes
.Where(n => n.ParentId == userId)
.OrderByDescending(n => n.JoinedAt)
.FirstOrDefault();
return new GetMyNetworkStatisticsResponseDto
{
LeftLegCount = leftCount,
RightLegCount = rightCount,
TotalMembers = tree.Nodes.Count,
TreeDepth = tree.Nodes.Any() ? tree.Nodes.Max(n => n.NetworkLevel) : 0,
WeakerLeg = leftCount < rightCount ? "Left" : "Right",
LastMember = lastMember != null ? new LastMemberDto
{
UserId = lastMember.UserId,
FullName = lastMember.UserName,
Position = lastMember.NetworkLeg == 0 ? "Left" : "Right",
JoinedAt = lastMember.JoinedAt?.ToDateTime() ?? DateTime.UtcNow
} : null
};
}
گزینه B: اضافه کردن Query جدید به CMS (بهتر)
در networkmembership.proto اضافه کن:
rpc GetUserNetworkStatistics(GetUserNetworkStatisticsRequest) returns (GetUserNetworkStatisticsResponse);
message GetUserNetworkStatisticsRequest
{
int64 user_id = 1;
}
message GetUserNetworkStatisticsResponse
{
int32 left_leg_count = 1;
int32 right_leg_count = 2;
int32 total_children = 3;
int32 max_depth = 4;
string weaker_leg = 5; // "Left" | "Right"
google.protobuf.Int64Value last_member_id = 6;
google.protobuf.StringValue last_member_name = 7;
google.protobuf.Timestamp last_joined_at = 8;
}
3️⃣ Commission - مغایرتها
🟢 BFF Handlers (2 عدد - موجود)
✅ GetMyCommissionPayouts (Query)
✅ GetMyWeeklyBalances (Query)
📋 CMS Protobuf (commission.proto)
service CommissionContract {
// Commands
rpc CalculateWeeklyBalances(CalculateWeeklyBalancesRequest) returns (Empty);
rpc CalculateWeeklyCommissionPool(CalculateWeeklyCommissionPoolRequest) returns (Empty);
rpc ProcessUserPayouts(ProcessUserPayoutsRequest) returns (Empty);
rpc RequestWithdrawal(RequestWithdrawalRequest) returns (Empty);
rpc ProcessWithdrawal(ProcessWithdrawalRequest) returns (Empty);
rpc ApproveWithdrawal(ApproveWithdrawalRequest) returns (Empty);
rpc RejectWithdrawal(RejectWithdrawalRequest) returns (Empty);
// Queries
rpc GetWeeklyCommissionPool(GetWeeklyCommissionPoolRequest) returns (GetWeeklyCommissionPoolResponse);
rpc GetUserCommissionPayouts(GetUserCommissionPayoutsRequest) returns (GetUserCommissionPayoutsResponse);
rpc GetCommissionPayoutHistory(GetCommissionPayoutHistoryRequest) returns (GetCommissionPayoutHistoryResponse);
rpc GetUserWeeklyBalances(GetUserWeeklyBalancesRequest) returns (GetUserWeeklyBalancesResponse);
rpc GetAllWeeklyPools(GetAllWeeklyPoolsRequest) returns (GetAllWeeklyPoolsResponse);
rpc GetWithdrawalRequests(GetWithdrawalRequestsRequest) returns (GetWithdrawalRequestsResponse);
// Worker Control APIs
rpc TriggerWeeklyCalculation(TriggerWeeklyCalculationRequest) returns (TriggerWeeklyCalculationResponse);
rpc GetWorkerStatus(GetWorkerStatusRequest) returns (GetWorkerStatusResponse);
rpc GetWorkerExecutionLogs(GetWorkerExecutionLogsRequest) returns (GetWorkerExecutionLogsResponse);
}
❌ مشکل 4: GetMyCommissionPayoutsQueryHandler
فایل: FrontOffice.BFF/src/FrontOffice.BFF.Application/CommissionCQ/Queries/GetMyCommissionPayouts/GetMyCommissionPayoutsQueryHandler.cs
کد فعلی:
var cmsRequest = new GetUserCommissionPayoutsRequest
{
UserId = userId,
PageNumber = request.PageNumber, // ❌ نام اشتباه
PageSize = request.PageSize // ❌ نام اشتباه
};
if (request.WeekNumber.HasValue)
cmsRequest.WeekNumber = request.WeekNumber.Value; // ❌ نوع داده اشتباه
if (request.Status.HasValue)
cmsRequest.Status = request.Status.Value;
CMS Proto:
message GetUserCommissionPayoutsRequest
{
google.protobuf.Int64Value user_id = 1;
google.protobuf.Int32Value status = 2;
google.protobuf.StringValue week_number = 3; // ✅ string است (نه int)
int32 page_index = 4; // ✅ page_index (نه page_number)
int32 page_size = 5;
}
message UserCommissionPayoutModel
{
int64 id = 1;
int64 user_id = 2;
string user_name = 3;
string week_number = 4; // ✅ string است
int32 balances_earned = 5;
int64 value_per_balance = 6;
int64 total_amount = 7;
int32 status = 8;
google.protobuf.Int32Value withdrawal_method = 9;
string iban_number = 10;
google.protobuf.Timestamp created = 11;
google.protobuf.Timestamp last_modified = 12;
}
🔧 راه حل:
var cmsRequest = new GetUserCommissionPayoutsRequest
{
UserId = userId,
PageIndex = request.PageNumber, // ✅ PageIndex
PageSize = request.PageSize
};
if (!string.IsNullOrEmpty(request.WeekNumber))
cmsRequest.WeekNumber = request.WeekNumber; // ✅ string
if (request.Status.HasValue)
cmsRequest.Status = request.Status.Value;
// در DTO نیز باید تغییر کند:
var payouts = response.Models.Select(p => new CommissionPayoutDto
{
Id = p.Id,
WeekNumber = p.WeekNumber, // ✅ string
WeekLabel = $"هفته {p.WeekNumber}",
BalancesEarned = p.BalancesEarned,
ValuePerBalance = p.ValuePerBalance, // ✅ جدید
TotalAmount = p.TotalAmount,
AmountFormatted = FormatCurrency(p.TotalAmount),
Status = MapStatus(p.Status),
StatusBadgeColor = GetStatusColor(p.Status),
WithdrawalMethod = p.WithdrawalMethod?.ToString(), // ✅ جدید
IbanNumber = p.IbanNumber, // ✅ جدید
CalculatedDate = p.Created?.ToDateTime() ?? DateTime.UtcNow,
LastModified = p.LastModified?.ToDateTime(), // ✅ جدید
DatePersian = FormatPersianDate(p.Created?.ToDateTime())
}).ToList();
Query DTO نیز باید بروز شود:
public class GetMyCommissionPayoutsQuery : IRequest<GetMyCommissionPayoutsResponseDto>
{
public string? WeekNumber { get; set; } // ✅ string (نه int?)
public int? Status { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
}
❌ مشکل 5: GetMyWeeklyBalancesQueryHandler
این Handler احتمالا وجود دارد اما بررسی نشده. باید چک شود:
CMS Proto:
message GetUserWeeklyBalancesRequest
{
google.protobuf.Int64Value user_id = 1;
google.protobuf.StringValue week_number = 2; // ✅ string
bool only_active = 3;
int32 page_index = 4;
int32 page_size = 5;
}
message UserWeeklyBalanceModel
{
int64 id = 1;
int64 user_id = 2;
string week_number = 3; // ✅ string
int32 left_leg_balances = 4;
int32 right_leg_balances = 5;
int32 total_balances = 6;
int64 weekly_pool_contribution = 7;
google.protobuf.Timestamp calculated_at = 8;
bool is_expired = 9;
google.protobuf.Timestamp created = 10;
}
مشکل احتمالی: نام فیلدها و نوع week_number (string vs int)
4️⃣ UserWallet - TODO Queries
🟡 BFF Handlers (5 عدد - بعضی کامنت شده)
✅ GetUserWallet (Query) - موجود
⚠️ GetAllUserWalletChangeLog (Query) - TODO
⚠️ WithdrawBalance (Command) - TODO
⚠️ GetUserWithdrawals (Query) - TODO
⚠️ GetWithdrawalSettings (Query) - TODO
این ها در /FrontOffice/src/FrontOffice.Main/Utilities/WalletService.cs کامنت شدهاند.
CMS API ها موجود هستند در Commission proto:
rpc RequestWithdrawal(RequestWithdrawalRequest) returns (Empty);
rpc GetWithdrawalRequests(GetWithdrawalRequestsRequest) returns (GetWithdrawalRequestsResponse);
⚠️ نکته: WithdrawBalance در Commission است نه UserWallet!
📋 خلاصه اقدامات لازم
فاز 1: اصلاح Handler های موجود (اولویت بالا) ⚡
1.1. ClubMembershipCQ
فایل: GetMyClubMembershipQueryHandler.cs
// ❌ کد قدیمی:
var activationDate = response.ActivationDate?.ToDateTime();
var expirationDate = response.ExpirationDate?.ToDateTime();
// ✅ کد جدید:
var activationDate = response.ActivatedAt?.ToDateTime();
var expirationDate = response.ExpiresAt?.ToDateTime();
// ✅ اضافه کردن Features:
Features = response.Features.Select(f => new MembershipFeatureDto
{
ProductId = f.ProductId,
ProductName = f.ProductName,
Quantity = f.Quantity,
ExpiresAt = f.ExpiresAt?.ToDateTime(),
IsActive = f.IsActive
}).ToList()
فایل: ActivateMyClubMembershipCommandHandler.cs
// ✅ بعد از Activate، GetClubMembership را صدا بزن:
await _context.ClubMemberships.ActivateClubMembershipAsync(grpcRequest, cancellationToken);
var membershipRequest = new GetClubMembershipRequest { UserId = userId };
var membership = await _context.ClubMemberships.GetClubMembershipAsync(membershipRequest, cancellationToken);
return new ActivateMyClubMembershipResponseDto
{
Success = true,
Message = "عضویت باشگاه با موفقیت فعال شد",
ActivationDate = membership.ActivatedAt?.ToDateTime(),
ExpirationDate = membership.ExpiresAt?.ToDateTime(),
// AmountPaid باید از Package Service گرفته شود یا محاسبه شود
};
1.2. NetworkMembershipCQ
فایل: GetMyNetworkTreeQueryHandler.cs
// ✅ کامل بازنویسی با Tree Builder:
var response = await _context.NetworkMemberships.GetNetworkTreeAsync(cmsRequest, cancellationToken);
var rootNode = BuildTreeFromFlatList(response.Nodes, userId);
return new GetMyNetworkTreeResponseDto
{
RootNode = rootNode,
TotalMembers = response.Nodes.Count,
CurrentDepth = response.Nodes.Any() ? response.Nodes.Max(n => n.NetworkLevel) : 0
};
// اضافه کردن متد BuildTreeFromFlatList (کد کامل بالا)
فایل: GetMyNetworkStatisticsQueryHandler.cs
// ✅ استفاده از GetUserNetwork + GetNetworkTree:
var userNetworkRequest = new GetUserNetworkRequest { UserId = userId };
var userNetwork = await _context.NetworkMemberships.GetUserNetworkAsync(userNetworkRequest, cancellationToken);
var treeRequest = new GetNetworkTreeRequest { RootUserId = userId, MaxDepth = 10 };
var tree = await _context.NetworkMemberships.GetNetworkTreeAsync(treeRequest, cancellationToken);
// محاسبه آمار (کد کامل بالا)
1.3. CommissionCQ
فایل: GetMyCommissionPayoutsQueryHandler.cs
// ❌ کد قدیمی:
var cmsRequest = new GetUserCommissionPayoutsRequest
{
UserId = userId,
PageNumber = request.PageNumber,
PageSize = request.PageSize
};
if (request.WeekNumber.HasValue)
cmsRequest.WeekNumber = request.WeekNumber.Value;
// ✅ کد جدید:
var cmsRequest = new GetUserCommissionPayoutsRequest
{
UserId = userId,
PageIndex = request.PageNumber, // PageIndex
PageSize = request.PageSize
};
if (!string.IsNullOrEmpty(request.WeekNumber))
cmsRequest.WeekNumber = request.WeekNumber; // string
// ✅ Response Mapping:
var payouts = response.Models.Select(p => new CommissionPayoutDto
{
Id = p.Id,
WeekNumber = p.WeekNumber, // string
ValuePerBalance = p.ValuePerBalance, // جدید
WithdrawalMethod = p.WithdrawalMethod, // جدید
IbanNumber = p.IbanNumber, // جدید
LastModified = p.LastModified?.ToDateTime() // جدید
// ... بقیه فیلدها
}).ToList();
Query DTO:
public class GetMyCommissionPayoutsQuery
{
public string? WeekNumber { get; set; } // ✅ string
// ...
}
public class CommissionPayoutDto
{
public long ValuePerBalance { get; set; } // ✅ جدید
public string? WithdrawalMethod { get; set; } // ✅ جدید
public string? IbanNumber { get; set; } // ✅ جدید
public DateTime? LastModified { get; set; } // ✅ جدید
// ...
}
فاز 2: اضافه کردن Handler های جدید (اولویت متوسط) 🟡
2.1. UserWalletCQ - GetAllUserWalletChangeLog
// Query:
public class GetAllUserWalletChangeLogQuery : IRequest<GetAllUserWalletChangeLogResponseDto>
{
public long? ReferenceId { get; set; }
public bool? IsIncrease { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
// Handler:
public async Task<GetAllUserWalletChangeLogResponseDto> Handle(...)
{
var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException();
// TODO: باید در CMS یک Query اضافه شود
// فعلا از GetUserCommissionPayouts استفاده کنیم برای تاریخچه برداشت
var request = new GetWithdrawalRequestsRequest
{
UserId = userId,
PageIndex = request.PageNumber,
PageSize = request.PageSize
};
var response = await _context.Commission.GetWithdrawalRequestsAsync(request, cancellationToken);
// Mapping...
}
2.2. UserWalletCQ - WithdrawBalance Command
// Command:
public class WithdrawBalanceCommand : IRequest<WithdrawBalanceResponseDto>
{
public long PayoutId { get; set; }
public int WithdrawalMethod { get; set; } // 0=Cash, 1=Diamond
public string? IbanNumber { get; set; }
}
// Handler:
public async Task<WithdrawBalanceResponseDto> Handle(...)
{
var userId = _currentUserService.UserId ?? throw new UnauthorizedAccessException();
var request = new RequestWithdrawalRequest
{
PayoutId = command.PayoutId,
WithdrawalMethod = command.WithdrawalMethod,
IbanNumber = command.IbanNumber
};
await _context.Commission.RequestWithdrawalAsync(request, cancellationToken);
return new WithdrawBalanceResponseDto
{
Success = true,
Message = "درخواست برداشت ثبت شد"
};
}
⏱️ تخمین زمان اجرا
| مرحله | فایلها | زمان | اولویت |
|---|---|---|---|
| ClubMembership fix | 2 Handler | 2 ساعت | 🔴 بالا |
| NetworkMembership fix | 2 Handler | 3-4 ساعت | 🔴 بالا |
| Commission fix | 2 Handler | 2 ساعت | 🔴 بالا |
| UserWallet new Queries | 4 Handler | 3 ساعت | 🟡 متوسط |
| Testing & Build | - | 2 ساعت | 🟢 پایین |
| جمع کل | 10 Handler | 12-15 ساعت |
✅ Checklist اجرایی
مرحله 1: ClubMembershipCQ
- GetMyClubMembershipQueryHandler: تغییر ActivationDate → ActivatedAt
- GetMyClubMembershipQueryHandler: تغییر ExpirationDate → ExpiresAt
- GetMyClubMembershipResponseDto: اضافه کردن List
- ActivateMyClubMembershipCommandHandler: گرفتن داده واقعی از GetClubMembership
مرحله 2: NetworkMembershipCQ
- GetMyNetworkTreeQueryHandler: پیادهسازی BuildTreeFromFlatList
- GetMyNetworkTreeQueryHandler: حذف استفاده از response.RootNode
- GetMyNetworkStatisticsQueryHandler: حذف فیلد UserId از Request
- GetMyNetworkStatisticsQueryHandler: استفاده از GetUserNetwork + GetNetworkTree
- GetMyNetworkStatisticsResponseDto: نام TreeDepth → MaxDepth
مرحله 3: CommissionCQ
- GetMyCommissionPayoutsQuery: تغییر WeekNumber از int? به string?
- GetMyCommissionPayoutsQueryHandler: PageNumber → PageIndex
- CommissionPayoutDto: اضافه کردن ValuePerBalance, WithdrawalMethod, IbanNumber, LastModified
- GetMyWeeklyBalancesQueryHandler: بررسی و اصلاح (اگر لازم باشد)
مرحله 4: UserWalletCQ
- Query: GetAllUserWalletChangeLog ساخته شود
- Command: WithdrawBalance ساخته شود
- Query: GetUserWithdrawals ساخته شود (از GetWithdrawalRequests استفاده کند)
- Query: GetWithdrawalSettings ساخته شود
- WalletService.cs: uncomment کردن متدها
مرحله 5: Build & Test
- dotnet build FrontOffice.BFF.sln
- dotnet build FrontOffice/src/FrontOffice.sln
- تست هر Handler با Postman/Swagger
- تست UI با داده واقعی
📅 آخرین بروزرسانی: ۱۴ آذر ۱۴۰۴
👤 توسط: GitHub Copilot (Claude Sonnet 4.5)