6 Commits

Author SHA1 Message Date
masoodafar-web be2b22c50a Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 10m25s
2026-05-04 23:55:16 +03:30
masoodafar-web b905c308c1 feat: enhance sp_CalculateWeeklyBalances to track package activity and optimize member calculations
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 10m21s
2026-05-04 23:54:13 +03:30
masoodafar-web d09db638cc feat: enhance sp_CalculateWeeklyCommissionPool to include IsDeleted filter and update commission calculations for package-specific pools
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m44s
2026-05-04 18:44:28 +03:30
masoodafar-web 41e7f29a0d feat: add PackageId to CalculateWeeklyCommissionPoolCommand and related logic for package-specific calculations
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m43s
2026-05-04 02:31:26 +03:30
masoodafar-web c4409491c1 feat: update stored procedure to use UserWalletHistories and include PackageId in balance checks
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m19s
2026-05-04 01:28:04 +03:30
masoodafar-web e4279f3d05 Add migration to rename index and create stored procedure for user weekly balances
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 9m55s
- Added migration to rename index from IX_NetworkWeeklyBalances_PackageId to IX_NetworkWeeklyBalance_PackageId in the NetworkWeeklyBalances table.
- Created stored procedure sp_GetUserWeeklyBalances to retrieve weekly balances with pagination and various filters.
2026-05-04 00:36:31 +03:30
14 changed files with 5612 additions and 142 deletions
@@ -10,6 +10,11 @@ public record CalculateWeeklyCommissionPoolCommand : IRequest<long>
/// </summary> /// </summary>
public long WeekDefinitionId { get; init; } public long WeekDefinitionId { get; init; }
/// <summary>
/// شناسه پکیج — اگر مقدار داشته باشد فقط همین پکیج محاسبه می‌شود.
/// </summary>
public long? PackageId { get; init; }
/// <summary> /// <summary>
/// آیا محاسبه مجدد انجام شود؟ /// آیا محاسبه مجدد انجام شود؟
/// </summary> /// </summary>
@@ -23,8 +23,17 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
} }
// بررسی وجود استخر // بررسی وجود استخر
var existingPool = await _context.WeeklyCommissionPools var existingPoolQuery = _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekDefinitionId == request.WeekDefinitionId, cancellationToken); .Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
if (request.PackageId.HasValue)
{
existingPoolQuery = existingPoolQuery.Where(x => x.PackageId == request.PackageId.Value);
}
var existingPool = await existingPoolQuery
.OrderBy(x => x.Id)
.FirstOrDefaultAsync(cancellationToken);
if (existingPool == null) if (existingPool == null)
{ {
@@ -40,9 +49,15 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
} }
// بررسی وجود تعادل‌های هفتگی // بررسی وجود تعادل‌های هفتگی
var weeklyBalances = await _context.NetworkWeeklyBalances var weeklyBalancesQuery = _context.NetworkWeeklyBalances
.Where(x => x.WeekDefinitionId == request.WeekDefinitionId) .Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
.ToListAsync(cancellationToken);
if (request.PackageId.HasValue)
{
weeklyBalancesQuery = weeklyBalancesQuery.Where(x => x.PackageId == request.PackageId.Value);
}
var weeklyBalances = await weeklyBalancesQuery.ToListAsync(cancellationToken);
if (!weeklyBalances.Any()) if (!weeklyBalances.Any())
{ {
@@ -76,9 +91,15 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
// حذف پرداخت‌های قبلی در صورت ForceRecalculate // حذف پرداخت‌های قبلی در صورت ForceRecalculate
if (request.ForceRecalculate) if (request.ForceRecalculate)
{ {
var oldPayouts = await _context.UserCommissionPayouts var oldPayoutsQuery = _context.UserCommissionPayouts
.Where(p => p.WeekDefinitionId == request.WeekDefinitionId) .Where(p => p.WeekDefinitionId == request.WeekDefinitionId);
.ToListAsync(cancellationToken);
if (request.PackageId.HasValue)
{
oldPayoutsQuery = oldPayoutsQuery.Where(p => p.PackageId == request.PackageId.Value);
}
var oldPayouts = await oldPayoutsQuery.ToListAsync(cancellationToken);
if (oldPayouts.Any()) if (oldPayouts.Any())
{ {
@@ -15,75 +15,49 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetUserWeeklyBalancesQuery request, CancellationToken cancellationToken) public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetUserWeeklyBalancesQuery request, CancellationToken cancellationToken)
{ {
var query = _context.NetworkWeeklyBalances var pageSize = request.PaginationState?.PageSize > 0 ? request.PaginationState.PageSize : PaginationDefaults.PageSize;
.Include(x => x.WeekDefinition) var pageNumber = request.PaginationState?.PageNumber > 0 ? request.PaginationState.PageNumber : PaginationDefaults.PageNumber;
.Include(x => x.User)
.Include(x => x.Package)
.AsNoTracking()
.AsQueryable();
// UserId > 0 → filter by that user // تشخیص جهت مرتب‌سازی از SortBy (پیش‌فرض: نزولی)
// UserId == 0 or null → show ALL users (admin mode) var sortBy = request.SortBy ?? "-WeekDefinitionId";
// Customer endpoints resolve UserId from JWT before calling this handler int sortDescending = sortBy.StartsWith("-") ? 1 : 0;
long? userId = request.UserId;
if (userId.HasValue && userId.Value > 0) long? userId = request.UserId.HasValue && request.UserId.Value > 0 ? request.UserId : null;
{ long? weekDefinitionId = request.WeekDefinitionId.HasValue ? request.WeekDefinitionId : null;
query = query.Where(x => x.UserId == userId.Value); long? packageId = request.PackageId.HasValue && request.PackageId.Value > 0 ? request.PackageId : null;
} int onlyActive = request.OnlyActive.HasValue && request.OnlyActive.Value ? 1 : 0;
// فیلتر بر اساس WeekDefinitionId (روش ترجیحی) var rows = await _context.Database
if (request.WeekDefinitionId.HasValue) .SqlQuery<GetUserWeeklyBalancesResponseModel>(
{ $"""
query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId.Value); EXEC [CMS].[sp_GetUserWeeklyBalances]
} @UserId = {userId},
@WeekDefinitionId = {weekDefinitionId},
@PackageId = {packageId},
if (request.OnlyActive.HasValue && request.OnlyActive.Value) @OnlyActive = {onlyActive},
{ @SortDescending = {sortDescending},
query = query.Where(x => !x.IsExpired); @PageNumber = {pageNumber},
} @PageSize = {pageSize}
""")
// فیلتر بر اساس پکیج
if (request.PackageId.HasValue && request.PackageId.Value > 0)
{
query = query.Where(x => x.PackageId == request.PackageId.Value);
}
// مرتب‌سازی بر اساس WeekDefinitionId (نزولی = جدیدترین اول)
query = query.ApplyOrder(sortBy: request.SortBy ?? "-WeekDefinitionId");
var meta = await query.GetMetaData(request.PaginationState, cancellationToken);
var models = await query
.PaginatedListAsync(paginationState: request.PaginationState)
.Select(x => new GetUserWeeklyBalancesResponseModel
{
Id = x.Id,
UserId = x.UserId,
UserFullName = x.User != null ? $"{x.User.FirstName} {x.User.LastName}".Trim() : "",
WeekDefinitionId = x.WeekDefinitionId,
WeekDisplayName = x.WeekDefinition != null ? x.WeekDefinition.DisplayName : "",
LeftLegNewMembers = x.LeftLegNewMembers,
LeftLegCarryover = x.LeftLegCarryover,
LeftLegTotal = x.LeftLegTotal,
RightLegNewMembers = x.RightLegNewMembers,
RightLegCarryover = x.RightLegCarryover,
RightLegTotal = x.RightLegTotal,
TotalBalances = x.TotalBalances,
WeeklyPoolContribution = x.WeeklyPoolContribution,
CalculatedAt = x.CalculatedAt,
IsExpired = x.IsExpired,
Created = x.Created,
PackageId = x.PackageId,
PackageTitle = x.Package != null ? x.Package.Title : ""
})
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
var totalCount = rows.Count > 0 ? rows[0].TotalCount : 0;
var totalPageCount = (int)Math.Ceiling(totalCount / (double)pageSize);
var meta = new MetaData
{
CurrentPage = pageNumber,
PageSize = pageSize,
TotalCount = totalCount,
TotalPage = totalPageCount,
HasNext = pageNumber < totalPageCount,
HasPrevious = pageNumber > 1
};
return new GetUserWeeklyBalancesResponseDto return new GetUserWeeklyBalancesResponseDto
{ {
MetaData = meta, MetaData = meta,
Models = models Models = rows
}; };
} }
} }
@@ -33,4 +33,7 @@ public class GetUserWeeklyBalancesResponseModel
// Package info // Package info
public long PackageId { get; set; } public long PackageId { get; set; }
public string PackageTitle { get; set; } = string.Empty; public string PackageTitle { get; set; } = string.Empty;
// نتیجه COUNT(*) OVER() از SP — فقط در خواندن از SP پر می‌شود
public int TotalCount { get; set; }
} }
@@ -159,14 +159,16 @@ public class WeeklyCommissionJob
long weekDefinitionId, long weekDefinitionId,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// Check idempotency: Skip if already calculated // Skip only when all pools for the week are already calculated.
var existingPool = await _context.WeeklyCommissionPools var hasAnyPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken); .AnyAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken);
var hasPendingPool = await _context.WeeklyCommissionPools
.AnyAsync(x => x.WeekDefinitionId == weekDefinitionId && !x.IsCalculated, cancellationToken);
if (existingPool != null && existingPool.IsCalculated) if (hasAnyPool && !hasPendingPool)
{ {
_logger.LogWarning( _logger.LogWarning(
"⚠️ [{ExecutionId}] WeekDefinitionId={WeekDefinitionId} already calculated. Skipping.", "⚠️ [{ExecutionId}] WeekDefinitionId={WeekDefinitionId} already fully calculated. Skipping.",
executionId, weekDefinitionId); executionId, weekDefinitionId);
return; return;
} }
@@ -56,5 +56,9 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
// Index برای IsExpired // Index برای IsExpired
builder.HasIndex(e => e.IsExpired) builder.HasIndex(e => e.IsExpired)
.HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired");
// Index برای PackageId — برای فیلتر در sp_GetUserWeeklyBalances
builder.HasIndex(e => e.PackageId)
.HasDatabaseName("IX_NetworkWeeklyBalance_PackageId");
} }
} }
@@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CMSMicroservice.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddNetworkWeeklyBalance_PackageIdIndex : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_NetworkWeeklyBalances_PackageId",
schema: "CMS",
table: "NetworkWeeklyBalances",
newName: "IX_NetworkWeeklyBalance_PackageId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameIndex(
name: "IX_NetworkWeeklyBalance_PackageId",
schema: "CMS",
table: "NetworkWeeklyBalances",
newName: "IX_NetworkWeeklyBalances_PackageId");
}
}
}
@@ -2478,7 +2478,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations
b.HasIndex("IsExpired") b.HasIndex("IsExpired")
.HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired");
b.HasIndex("PackageId"); b.HasIndex("PackageId")
.HasDatabaseName("IX_NetworkWeeklyBalance_PackageId");
b.HasIndex("WeekDefinitionId") b.HasIndex("WeekDefinitionId")
.HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId");
@@ -24,6 +24,7 @@ BEGIN
DECLARE @MaxBalancesPerLeg INT; DECLARE @MaxBalancesPerLeg INT;
DECLARE @MaxNetworkLevel INT; DECLARE @MaxNetworkLevel INT;
DECLARE @CalculatedAt DATETIME = GETDATE(); DECLARE @CalculatedAt DATETIME = GETDATE();
DECLARE @HasPackageActivityUntilWeek BIT = 0;
-- تعیین PackageId پیش‌فرض (پکیج پایه) -- تعیین PackageId پیش‌فرض (پکیج پایه)
IF @PackageId IS NULL IF @PackageId IS NULL
@@ -66,20 +67,48 @@ BEGIN
FROM CMS.WeekDefinitions FROM CMS.WeekDefinitions
WHERE Id = @WeekDefinitionId; WHERE Id = @WeekDefinitionId;
-- اگر تا پایان این هفته هنوز هیچ Cycle برای این پکیج وجود نداشته،
-- این پکیج نباید برای هفته‌های قبل از شروعش هیچ رکوردی بسازد.
IF EXISTS (
SELECT 1
FROM CMS.ClubMembershipCycles cc
WHERE cc.PackageId = @PackageId
AND cc.IsDeleted = 0
AND cc.PackagePurchasedAt <= @EndDate
)
BEGIN
SET @HasPackageActivityUntilWeek = 1;
END
IF @HasPackageActivityUntilWeek = 0
BEGIN
SET @RowCount = 0;
COMMIT TRANSACTION;
RETURN;
END
-- ============================================= -- =============================================
-- 2. بررسی محاسبه قبلی -- 2. بررسی محاسبه قبلی
-- ============================================= -- =============================================
IF EXISTS (SELECT 1 FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId) IF EXISTS (
SELECT 1
FROM CMS.NetworkWeeklyBalances
WHERE WeekDefinitionId = @WeekDefinitionId
AND PackageId = @PackageId
)
BEGIN BEGIN
IF @ForceRecalculate = 0 IF @ForceRecalculate = 0
BEGIN BEGIN
SET @ErrorMessage = N'تعادل‌های هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20)) + N' قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید'; SET @ErrorMessage = N'تعادل‌های هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20))
+ N' (پکیج ' + CAST(@PackageId AS NVARCHAR(20)) + N') قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید';
RAISERROR(@ErrorMessage, 16, 1); RAISERROR(@ErrorMessage, 16, 1);
RETURN; RETURN;
END END
-- حذف محاسبات قبلی -- حذف محاسبات قبلی فقط برای همین پکیج
DELETE FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId; DELETE FROM CMS.NetworkWeeklyBalances
WHERE WeekDefinitionId = @WeekDefinitionId
AND PackageId = @PackageId;
END END
-- ============================================= -- =============================================
@@ -96,7 +125,7 @@ BEGIN
-- @MaxBalancesPerLeg و @MaxNetworkLevel قبلاً مقداردهی شده‌اند -- @MaxBalancesPerLeg و @MaxNetworkLevel قبلاً مقداردهی شده‌اند
-- ============================================= -- =============================================
-- 5. ایجاد جدول موقت برای نتایج -- 5. ایجاد جداول موقت برای نتایج
-- ============================================= -- =============================================
CREATE TABLE #Balances ( CREATE TABLE #Balances (
UserId BIGINT PRIMARY KEY, UserId BIGINT PRIMARY KEY,
@@ -114,31 +143,18 @@ BEGIN
SubordinateBalances INT DEFAULT 0 SubordinateBalances INT DEFAULT 0
); );
-- ============================================= CREATE TABLE #LeftNewMembers (
-- 6. دریافت کاربران فعال باشگاه UserId BIGINT PRIMARY KEY,
-- ============================================= NewMembersCount INT NOT NULL
INSERT INTO #Balances (UserId) );
SELECT DISTINCT u.Id
FROM CMS.Users u CREATE TABLE #RightNewMembers (
INNER JOIN CMS.ClubMemberships cm ON cm.UserId = u.Id AND cm.IsActive = 1 UserId BIGINT PRIMARY KEY,
WHERE cm.LastPackageId = @PackageId; NewMembersCount INT NOT NULL
);
-- ============================================= -- =============================================
-- 7. دریافت باقیمانده هفته قبل -- 6. محاسبه اعضای جدید هر پا با CTE (بهینه!)
-- =============================================
IF @PreviousWeekDefinitionId IS NOT NULL
BEGIN
UPDATE b
SET b.LeftLegCarryover = ISNULL(nb.LeftLegRemainder, 0),
b.RightLegCarryover = ISNULL(nb.RightLegRemainder, 0)
FROM #Balances b
LEFT JOIN CMS.NetworkWeeklyBalances nb ON nb.UserId = b.UserId
AND nb.WeekDefinitionId = @PreviousWeekDefinitionId
AND nb.PackageId = @PackageId;
END
-- =============================================
-- 8. محاسبه اعضای جدید هر پا با CTE (بهینه!)
-- ============================================= -- =============================================
-- CTE برای شمارش اعضای جدید شاخه چپ -- CTE برای شمارش اعضای جدید شاخه چپ
@@ -169,13 +185,15 @@ BEGIN
COUNT(DISTINCT cte.ChildId) AS NewMembersCount COUNT(DISTINCT cte.ChildId) AS NewMembersCount
FROM LeftLegCTE cte FROM LeftLegCTE cte
INNER JOIN CMS.ClubMembershipCycles cc ON cc.UserId = cte.ChildId INNER JOIN CMS.ClubMembershipCycles cc ON cc.UserId = cte.ChildId
WHERE cc.PackagePurchasedAt >= @StartDate AND cc.PackagePurchasedAt <= @EndDate WHERE cc.PackagePurchasedAt >= @StartDate
AND cc.PackagePurchasedAt <= @EndDate
AND cc.PackageId = @PackageId
AND cc.IsDeleted = 0
GROUP BY cte.ParentId GROUP BY cte.ParentId
) )
UPDATE b INSERT INTO #LeftNewMembers (UserId, NewMembersCount)
SET b.LeftLegNewMembers = ISNULL(lnm.NewMembersCount, 0) SELECT UserId, NewMembersCount
FROM #Balances b FROM LeftNewMembers
LEFT JOIN LeftNewMembers lnm ON lnm.UserId = b.UserId
OPTION (MAXRECURSION 100); OPTION (MAXRECURSION 100);
-- CTE برای شمارش اعضای جدید شاخه راست -- CTE برای شمارش اعضای جدید شاخه راست
@@ -206,14 +224,65 @@ BEGIN
COUNT(DISTINCT cte.ChildId) AS NewMembersCount COUNT(DISTINCT cte.ChildId) AS NewMembersCount
FROM RightLegCTE cte FROM RightLegCTE cte
INNER JOIN CMS.ClubMembershipCycles cc ON cc.UserId = cte.ChildId INNER JOIN CMS.ClubMembershipCycles cc ON cc.UserId = cte.ChildId
WHERE cc.PackagePurchasedAt >= @StartDate AND cc.PackagePurchasedAt <= @EndDate WHERE cc.PackagePurchasedAt >= @StartDate
AND cc.PackagePurchasedAt <= @EndDate
AND cc.PackageId = @PackageId
AND cc.IsDeleted = 0
GROUP BY cte.ParentId GROUP BY cte.ParentId
) )
INSERT INTO #RightNewMembers (UserId, NewMembersCount)
SELECT UserId, NewMembersCount
FROM RightNewMembers
OPTION (MAXRECURSION 100);
-- =============================================
-- 7. انتخاب parentهایی که باید برای این پکیج/هفته row بگیرند
-- شرط ورود:
-- - عضو فعال باشگاه باشند
-- - یا از هفته قبل برای همین پکیج carryover داشته باشند
-- - یا این هفته recruit جدید همین پکیج در چپ/راست subtree داشته باشند
-- =============================================
INSERT INTO #Balances (UserId)
SELECT DISTINCT cm.UserId
FROM CMS.ClubMemberships cm
INNER JOIN CMS.Users u ON u.Id = cm.UserId
LEFT JOIN #LeftNewMembers lnm ON lnm.UserId = cm.UserId
LEFT JOIN #RightNewMembers rnm ON rnm.UserId = cm.UserId
LEFT JOIN CMS.NetworkWeeklyBalances prev ON prev.UserId = cm.UserId
AND prev.WeekDefinitionId = @PreviousWeekDefinitionId
AND prev.PackageId = @PackageId
WHERE cm.IsActive = 1
AND u.IsDeleted = 0
AND (
ISNULL(lnm.NewMembersCount, 0) > 0
OR ISNULL(rnm.NewMembersCount, 0) > 0
OR ISNULL(prev.LeftLegRemainder, 0) > 0
OR ISNULL(prev.RightLegRemainder, 0) > 0
);
-- =============================================
-- 8. دریافت باقیمانده هفته قبل + اعمال counts محاسبه‌شده
-- =============================================
IF @PreviousWeekDefinitionId IS NOT NULL
BEGIN
UPDATE b
SET b.LeftLegCarryover = ISNULL(nb.LeftLegRemainder, 0),
b.RightLegCarryover = ISNULL(nb.RightLegRemainder, 0)
FROM #Balances b
LEFT JOIN CMS.NetworkWeeklyBalances nb ON nb.UserId = b.UserId
AND nb.WeekDefinitionId = @PreviousWeekDefinitionId
AND nb.PackageId = @PackageId;
END
UPDATE b
SET b.LeftLegNewMembers = ISNULL(lnm.NewMembersCount, 0)
FROM #Balances b
LEFT JOIN #LeftNewMembers lnm ON lnm.UserId = b.UserId;
UPDATE b UPDATE b
SET b.RightLegNewMembers = ISNULL(rnm.NewMembersCount, 0) SET b.RightLegNewMembers = ISNULL(rnm.NewMembersCount, 0)
FROM #Balances b FROM #Balances b
LEFT JOIN RightNewMembers rnm ON rnm.UserId = b.UserId LEFT JOIN #RightNewMembers rnm ON rnm.UserId = b.UserId;
OPTION (MAXRECURSION 100);
-- ============================================= -- =============================================
-- 9. محاسبه تعادل‌ها -- 9. محاسبه تعادل‌ها
@@ -346,11 +415,25 @@ BEGIN
'SP', -- CreatedBy 'SP', -- CreatedBy
@CalculatedAt, -- LastModified @CalculatedAt, -- LastModified
'SP' -- LastModifiedBy 'SP' -- LastModifiedBy
FROM #Balances; FROM #Balances
WHERE LeftLegNewMembers <> 0
OR RightLegNewMembers <> 0
OR LeftLegCarryover <> 0
OR RightLegCarryover <> 0
OR LeftLegTotal <> 0
OR RightLegTotal <> 0
OR TotalBalances <> 0
OR LeftLegRemainder <> 0
OR RightLegRemainder <> 0
OR FlushedPerSide <> 0
OR TotalFlushed <> 0
OR SubordinateBalances <> 0;
SET @RowCount = @@ROWCOUNT; SET @RowCount = @@ROWCOUNT;
-- پاکسازی -- پاکسازی
DROP TABLE #RightNewMembers;
DROP TABLE #LeftNewMembers;
DROP TABLE #Balances; DROP TABLE #Balances;
COMMIT TRANSACTION; COMMIT TRANSACTION;
@@ -361,6 +444,12 @@ BEGIN
ROLLBACK TRANSACTION; ROLLBACK TRANSACTION;
-- پاکسازی در صورت خطا -- پاکسازی در صورت خطا
IF OBJECT_ID('tempdb..#RightNewMembers') IS NOT NULL
DROP TABLE #RightNewMembers;
IF OBJECT_ID('tempdb..#LeftNewMembers') IS NOT NULL
DROP TABLE #LeftNewMembers;
IF OBJECT_ID('tempdb..#Balances') IS NOT NULL IF OBJECT_ID('tempdb..#Balances') IS NOT NULL
DROP TABLE #Balances; DROP TABLE #Balances;
@@ -28,9 +28,9 @@ BEGIN
-- ============================================= -- =============================================
-- 1. پیدا کردن Pool هفته (فیلتر بر اساس PackageId) -- 1. پیدا کردن Pool هفته (فیلتر بر اساس PackageId)
-- ============================================= -- =============================================
SELECT @PoolId = Id, @TotalPoolAmount = TotalPoolAmount SELECT @PoolId = MIN(Id), @TotalPoolAmount = ISNULL(SUM(TotalPoolAmount), 0)
FROM CMS.WeeklyCommissionPools FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId; WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
-- اگر Pool وجود ندارد (هیچ فعال‌سازی در این هفته نبوده)، یک رکورد خالی ایجاد می‌کنیم -- اگر Pool وجود ندارد (هیچ فعال‌سازی در این هفته نبوده)، یک رکورد خالی ایجاد می‌کنیم
IF @PoolId IS NULL IF @PoolId IS NULL
@@ -50,7 +50,15 @@ BEGIN
-- ============================================= -- =============================================
-- 2. بررسی محاسبات قبلی (بر اساس همین Pool نه کل هفته) -- 2. بررسی محاسبات قبلی (بر اساس همین Pool نه کل هفته)
-- ============================================= -- =============================================
IF EXISTS (SELECT 1 FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId) IF EXISTS (
SELECT 1
FROM CMS.UserCommissionPayouts
WHERE WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
)
)
BEGIN BEGIN
IF @ForceRecalculate = 0 IF @ForceRecalculate = 0
BEGIN BEGIN
@@ -68,43 +76,80 @@ BEGIN
UPDATE uw UPDATE uw
SET uw.NetworkBalance = uw.NetworkBalance - ISNULL( SET uw.NetworkBalance = uw.NetworkBalance - ISNULL(
(SELECT SUM(wl.ChangeNerworkValue) (SELECT SUM(wl.ChangeNerworkValue)
FROM CMS.UserWalletChangeLogs wl FROM CMS.UserWalletHistories wl
WHERE wl.WalletId = uw.Id WHERE wl.WalletId = uw.Id
AND wl.RefrenceId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId) AND wl.RefrenceId IN (
SELECT Id
FROM CMS.UserCommissionPayouts
WHERE WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
)
)
), 0) ), 0)
FROM CMS.UserWallets uw FROM CMS.UserWallets uw
WHERE uw.Id IN ( WHERE uw.Id IN (
SELECT DISTINCT wl.WalletId SELECT DISTINCT wl.WalletId
FROM CMS.UserWalletChangeLogs wl FROM CMS.UserWalletHistories wl
WHERE wl.RefrenceId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId) WHERE wl.RefrenceId IN (
SELECT Id
FROM CMS.UserCommissionPayouts
WHERE WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
)
)
); );
-- حذف لاگ‌های تغییرات قبلی -- حذف لاگ‌های تغییرات قبلی
DELETE FROM CMS.UserWalletChangeLogs DELETE FROM CMS.UserWalletHistories
WHERE RefrenceId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId); WHERE RefrenceId IN (
SELECT Id
FROM CMS.UserCommissionPayouts
WHERE WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
)
);
-- حذف تاریخچه پرداخت -- حذف تاریخچه پرداخت
DELETE FROM CMS.CommissionPayoutHistories DELETE FROM CMS.CommissionPayoutHistories
WHERE UserCommissionPayoutId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId); WHERE UserCommissionPayoutId IN (
SELECT Id
FROM CMS.UserCommissionPayouts
WHERE WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
)
);
-- حذف پرداخت‌های قبلی این Pool -- حذف پرداخت‌های قبلی این Pool
DELETE FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId; DELETE FROM CMS.UserCommissionPayouts
WHERE WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
);
-- Reset وضعیت Pool -- Reset وضعیت Pool
UPDATE CMS.WeeklyCommissionPools UPDATE CMS.WeeklyCommissionPools
SET IsCalculated = 0 SET IsCalculated = 0
WHERE Id = @PoolId; WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
END END
-- ============================================= -- =============================================
-- 4. بررسی وجود تعادل‌های هفتگی -- 4. بررسی وجود تعادل‌های هفتگی
-- ============================================= -- =============================================
IF NOT EXISTS (SELECT 1 FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId) IF NOT EXISTS (SELECT 1 FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId)
BEGIN BEGIN
-- بالانسی محاسبه نشده — pool را به عنوان "محاسبه شده بدون پرداخت" علامت می‌زنیم -- بالانسی محاسبه نشده — pool را به عنوان "محاسبه شده بدون پرداخت" علامت می‌زنیم
UPDATE CMS.WeeklyCommissionPools UPDATE CMS.WeeklyCommissionPools
SET IsCalculated = 1, CalculatedAt = @CalculatedAt SET IsCalculated = 1, CalculatedAt = @CalculatedAt
WHERE Id = @PoolId; WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
COMMIT TRANSACTION; COMMIT TRANSACTION;
RETURN; RETURN;
@@ -115,14 +160,14 @@ BEGIN
-- ============================================= -- =============================================
SELECT @TotalBalances = SUM(TotalBalances) SELECT @TotalBalances = SUM(TotalBalances)
FROM CMS.NetworkWeeklyBalances FROM CMS.NetworkWeeklyBalances
WHERE WeekDefinitionId = @WeekDefinitionId AND TotalBalances > 0; WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND TotalBalances > 0;
IF @TotalBalances IS NULL OR @TotalBalances = 0 IF @TotalBalances IS NULL OR @TotalBalances = 0
BEGIN BEGIN
-- هیچ تعادلی برای توزیع وجود ندارد — pool محاسبه شده اما پرداختی ندارد -- هیچ تعادلی برای توزیع وجود ندارد — pool محاسبه شده اما پرداختی ندارد
UPDATE CMS.WeeklyCommissionPools UPDATE CMS.WeeklyCommissionPools
SET IsCalculated = 1, CalculatedAt = @CalculatedAt SET IsCalculated = 1, CalculatedAt = @CalculatedAt
WHERE Id = @PoolId; WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
COMMIT TRANSACTION; COMMIT TRANSACTION;
RETURN; RETURN;
@@ -140,6 +185,7 @@ BEGIN
UserId, UserId,
WeekDefinitionId, WeekDefinitionId,
WeeklyPoolId, WeeklyPoolId,
PackageId,
BalancesEarned, BalancesEarned,
ValuePerBalance, ValuePerBalance,
TotalAmount, TotalAmount,
@@ -155,6 +201,7 @@ BEGIN
nb.UserId, nb.UserId,
@WeekDefinitionId, @WeekDefinitionId,
@PoolId, @PoolId,
@PackageId,
nb.TotalBalances, nb.TotalBalances,
@ValuePerBalance, @ValuePerBalance,
nb.TotalBalances * @ValuePerBalance, nb.TotalBalances * @ValuePerBalance,
@@ -166,7 +213,7 @@ BEGIN
@CalculatedAt, @CalculatedAt,
'SP' 'SP'
FROM CMS.NetworkWeeklyBalances nb FROM CMS.NetworkWeeklyBalances nb
WHERE nb.WeekDefinitionId = @WeekDefinitionId AND nb.TotalBalances > 0; WHERE nb.WeekDefinitionId = @WeekDefinitionId AND nb.PackageId = @PackageId AND nb.TotalBalances > 0;
-- ============================================= -- =============================================
-- 8. ثبت تاریخچه پرداخت -- 8. ثبت تاریخچه پرداخت
@@ -205,7 +252,11 @@ BEGIN
@CalculatedAt, @CalculatedAt,
'SP' 'SP'
FROM CMS.UserCommissionPayouts cp FROM CMS.UserCommissionPayouts cp
WHERE cp.WeeklyPoolId = @PoolId; WHERE cp.WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
);
-- ============================================= -- =============================================
-- 9. شارژ کیف پول کاربران -- 9. شارژ کیف پول کاربران
@@ -216,10 +267,14 @@ BEGIN
SET uw.NetworkBalance = uw.NetworkBalance + cp.TotalAmount SET uw.NetworkBalance = uw.NetworkBalance + cp.TotalAmount
FROM CMS.UserWallets uw FROM CMS.UserWallets uw
INNER JOIN CMS.UserCommissionPayouts cp ON cp.UserId = uw.UserId INNER JOIN CMS.UserCommissionPayouts cp ON cp.UserId = uw.UserId
WHERE cp.WeeklyPoolId = @PoolId; WHERE cp.WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
);
-- ثبت لاگ تغییرات کیف پول -- ثبت لاگ تغییرات کیف پول
INSERT INTO CMS.UserWalletChangeLogs ( INSERT INTO CMS.UserWalletHistories (
WalletId, WalletId,
CurrentBalance, CurrentBalance,
ChangeValue, ChangeValue,
@@ -252,7 +307,11 @@ BEGIN
'SP' 'SP'
FROM CMS.UserCommissionPayouts cp FROM CMS.UserCommissionPayouts cp
INNER JOIN CMS.UserWallets uw ON uw.UserId = cp.UserId INNER JOIN CMS.UserWallets uw ON uw.UserId = cp.UserId
WHERE cp.WeeklyPoolId = @PoolId; WHERE cp.WeeklyPoolId IN (
SELECT Id
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0
);
-- ============================================= -- =============================================
-- 10. بروزرسانی Pool -- 10. بروزرسانی Pool
@@ -265,7 +324,7 @@ BEGIN
ValuePerBalance = @ValuePerBalance, ValuePerBalance = @ValuePerBalance,
LastModified = @CalculatedAt, LastModified = @CalculatedAt,
LastModifiedBy = 'SP' LastModifiedBy = 'SP'
WHERE Id = @PoolId; WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
COMMIT TRANSACTION; COMMIT TRANSACTION;
@@ -0,0 +1,56 @@
-- =============================================
-- Stored Procedure: sp_GetUserWeeklyBalances
-- Description: دریافت تعادل‌های هفتگی با صفحه‌بندی و فیلترهای مختلف
-- Author: System
-- Created: 2026-05-04
-- =============================================
CREATE OR ALTER PROCEDURE [CMS].[sp_GetUserWeeklyBalances]
@UserId BIGINT = NULL,
@WeekDefinitionId BIGINT = NULL,
@PackageId BIGINT = NULL,
@OnlyActive BIT = 0,
@SortDescending BIT = 1, -- 1 = نزولی (جدیدترین اول)، 0 = صعودی
@PageNumber INT = 1,
@PageSize INT = 10
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Skip INT = @PageSize * (@PageNumber - 1);
SELECT
n.Id,
n.UserId,
LTRIM(RTRIM(ISNULL(u.FirstName, '') + ' ' + ISNULL(u.LastName, ''))) AS UserFullName,
n.WeekDefinitionId,
ISNULL(wd.DisplayName, '') AS WeekDisplayName,
n.LeftLegNewMembers,
n.LeftLegCarryover,
n.LeftLegTotal,
n.RightLegNewMembers,
n.RightLegCarryover,
n.RightLegTotal,
n.TotalBalances,
n.WeeklyPoolContribution,
n.CalculatedAt,
n.IsExpired,
n.Created,
n.PackageId,
ISNULL(p.Title, '') AS PackageTitle,
COUNT(*) OVER() AS TotalCount
FROM [CMS].[NetworkWeeklyBalances] n
LEFT JOIN [CMS].[Users] u ON u.Id = n.UserId
LEFT JOIN [CMS].[WeekDefinitions] wd ON wd.Id = n.WeekDefinitionId
LEFT JOIN [CMS].[Packages] p ON p.Id = n.PackageId
WHERE n.IsDeleted = 0
AND (@UserId IS NULL OR n.UserId = @UserId)
AND (@WeekDefinitionId IS NULL OR n.WeekDefinitionId = @WeekDefinitionId)
AND (@PackageId IS NULL OR n.PackageId = @PackageId)
AND (@OnlyActive = 0 OR n.IsExpired = 0)
ORDER BY
CASE WHEN @SortDescending = 1 THEN n.WeekDefinitionId END DESC,
CASE WHEN @SortDescending = 0 THEN n.WeekDefinitionId END ASC
OFFSET @Skip ROWS
FETCH NEXT @PageSize ROWS ONLY;
END;
@@ -222,8 +222,17 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
} }
// بررسی وجود استخر // بررسی وجود استخر
var existingPool = await _context.WeeklyCommissionPools var existingPoolQuery = _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken); .Where(x => x.WeekDefinitionId == weekDefinitionId);
if (packageId.HasValue)
{
existingPoolQuery = existingPoolQuery.Where(x => x.PackageId == packageId.Value);
}
var existingPool = await existingPoolQuery
.OrderBy(x => x.Id)
.FirstOrDefaultAsync(cancellationToken);
if (existingPool == null) if (existingPool == null)
{ {
@@ -239,9 +248,15 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
} }
// بررسی وجود تعادل‌های هفتگی // بررسی وجود تعادل‌های هفتگی
var weeklyBalances = await _context.NetworkWeeklyBalances var weeklyBalancesQuery = _context.NetworkWeeklyBalances
.Where(x => x.WeekDefinitionId == weekDefinitionId) .Where(x => x.WeekDefinitionId == weekDefinitionId);
.ToListAsync(cancellationToken);
if (packageId.HasValue)
{
weeklyBalancesQuery = weeklyBalancesQuery.Where(x => x.PackageId == packageId.Value);
}
var weeklyBalances = await weeklyBalancesQuery.ToListAsync(cancellationToken);
if (!weeklyBalances.Any()) if (!weeklyBalances.Any())
{ {
@@ -269,9 +284,15 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
// حذف پرداخت‌های قبلی در صورت ForceRecalculate // حذف پرداخت‌های قبلی در صورت ForceRecalculate
if (forceRecalculate) if (forceRecalculate)
{ {
var oldPayouts = await _context.UserCommissionPayouts var oldPayoutsQuery = _context.UserCommissionPayouts
.Where(p => p.WeekDefinitionId == weekDefinitionId) .Where(p => p.WeekDefinitionId == weekDefinitionId);
.ToListAsync(cancellationToken);
if (packageId.HasValue)
{
oldPayoutsQuery = oldPayoutsQuery.Where(p => p.PackageId == packageId.Value);
}
var oldPayouts = await oldPayoutsQuery.ToListAsync(cancellationToken);
if (oldPayouts.Any()) if (oldPayouts.Any())
{ {
@@ -141,6 +141,7 @@ public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculati
// حلقه روی پکیج‌ها — هر پکیج Pool مستقل دارد // حلقه روی پکیج‌ها — هر پکیج Pool مستقل دارد
foreach (var package in activePackages) foreach (var package in activePackages)
{ {
using var command = connection.CreateCommand(); using var command = connection.CreateCommand();
command.CommandText = "CMS.sp_CalculateWeeklyCommissionPool"; command.CommandText = "CMS.sp_CalculateWeeklyCommissionPool";
command.CommandType = System.Data.CommandType.StoredProcedure; command.CommandType = System.Data.CommandType.StoredProcedure;