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>
public long WeekDefinitionId { get; init; }
/// <summary>
/// شناسه پکیج — اگر مقدار داشته باشد فقط همین پکیج محاسبه می‌شود.
/// </summary>
public long? PackageId { get; init; }
/// <summary>
/// آیا محاسبه مجدد انجام شود؟
/// </summary>
@@ -23,8 +23,17 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
}
// بررسی وجود استخر
var existingPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekDefinitionId == request.WeekDefinitionId, cancellationToken);
var existingPoolQuery = _context.WeeklyCommissionPools
.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)
{
@@ -40,9 +49,15 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
}
// بررسی وجود تعادل‌های هفتگی
var weeklyBalances = await _context.NetworkWeeklyBalances
.Where(x => x.WeekDefinitionId == request.WeekDefinitionId)
.ToListAsync(cancellationToken);
var weeklyBalancesQuery = _context.NetworkWeeklyBalances
.Where(x => x.WeekDefinitionId == request.WeekDefinitionId);
if (request.PackageId.HasValue)
{
weeklyBalancesQuery = weeklyBalancesQuery.Where(x => x.PackageId == request.PackageId.Value);
}
var weeklyBalances = await weeklyBalancesQuery.ToListAsync(cancellationToken);
if (!weeklyBalances.Any())
{
@@ -76,9 +91,15 @@ public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<Calcu
// حذف پرداخت‌های قبلی در صورت ForceRecalculate
if (request.ForceRecalculate)
{
var oldPayouts = await _context.UserCommissionPayouts
.Where(p => p.WeekDefinitionId == request.WeekDefinitionId)
.ToListAsync(cancellationToken);
var oldPayoutsQuery = _context.UserCommissionPayouts
.Where(p => p.WeekDefinitionId == request.WeekDefinitionId);
if (request.PackageId.HasValue)
{
oldPayoutsQuery = oldPayoutsQuery.Where(p => p.PackageId == request.PackageId.Value);
}
var oldPayouts = await oldPayoutsQuery.ToListAsync(cancellationToken);
if (oldPayouts.Any())
{
@@ -15,75 +15,49 @@ public class GetUserWeeklyBalancesQueryHandler : IRequestHandler<GetUserWeeklyBa
public async Task<GetUserWeeklyBalancesResponseDto> Handle(GetUserWeeklyBalancesQuery request, CancellationToken cancellationToken)
{
var query = _context.NetworkWeeklyBalances
.Include(x => x.WeekDefinition)
.Include(x => x.User)
.Include(x => x.Package)
.AsNoTracking()
.AsQueryable();
var pageSize = request.PaginationState?.PageSize > 0 ? request.PaginationState.PageSize : PaginationDefaults.PageSize;
var pageNumber = request.PaginationState?.PageNumber > 0 ? request.PaginationState.PageNumber : PaginationDefaults.PageNumber;
// UserId > 0 → filter by that user
// UserId == 0 or null → show ALL users (admin mode)
// Customer endpoints resolve UserId from JWT before calling this handler
long? userId = request.UserId;
// تشخیص جهت مرتب‌سازی از SortBy (پیش‌فرض: نزولی)
var sortBy = request.SortBy ?? "-WeekDefinitionId";
int sortDescending = sortBy.StartsWith("-") ? 1 : 0;
if (userId.HasValue && userId.Value > 0)
{
query = query.Where(x => x.UserId == userId.Value);
}
long? userId = request.UserId.HasValue && request.UserId.Value > 0 ? request.UserId : null;
long? weekDefinitionId = request.WeekDefinitionId.HasValue ? request.WeekDefinitionId : null;
long? packageId = request.PackageId.HasValue && request.PackageId.Value > 0 ? request.PackageId : null;
int onlyActive = request.OnlyActive.HasValue && request.OnlyActive.Value ? 1 : 0;
// فیلتر بر اساس WeekDefinitionId (روش ترجیحی)
if (request.WeekDefinitionId.HasValue)
{
query = query.Where(x => x.WeekDefinitionId == request.WeekDefinitionId.Value);
}
if (request.OnlyActive.HasValue && request.OnlyActive.Value)
{
query = query.Where(x => !x.IsExpired);
}
// فیلتر بر اساس پکیج
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 : ""
})
var rows = await _context.Database
.SqlQuery<GetUserWeeklyBalancesResponseModel>(
$"""
EXEC [CMS].[sp_GetUserWeeklyBalances]
@UserId = {userId},
@WeekDefinitionId = {weekDefinitionId},
@PackageId = {packageId},
@OnlyActive = {onlyActive},
@SortDescending = {sortDescending},
@PageNumber = {pageNumber},
@PageSize = {pageSize}
""")
.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
{
MetaData = meta,
Models = models
Models = rows
};
}
}
@@ -33,4 +33,7 @@ public class GetUserWeeklyBalancesResponseModel
// Package info
public long PackageId { get; set; }
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,
CancellationToken cancellationToken)
{
// Check idempotency: Skip if already calculated
var existingPool = await _context.WeeklyCommissionPools
.FirstOrDefaultAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken);
// Skip only when all pools for the week are already calculated.
var hasAnyPool = await _context.WeeklyCommissionPools
.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(
"⚠️ [{ExecutionId}] WeekDefinitionId={WeekDefinitionId} already calculated. Skipping.",
"⚠️ [{ExecutionId}] WeekDefinitionId={WeekDefinitionId} already fully calculated. Skipping.",
executionId, weekDefinitionId);
return;
}
@@ -56,5 +56,9 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
// Index برای IsExpired
builder.HasIndex(e => e.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")
.HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired");
b.HasIndex("PackageId");
b.HasIndex("PackageId")
.HasDatabaseName("IX_NetworkWeeklyBalance_PackageId");
b.HasIndex("WeekDefinitionId")
.HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId");
@@ -24,6 +24,7 @@ BEGIN
DECLARE @MaxBalancesPerLeg INT;
DECLARE @MaxNetworkLevel INT;
DECLARE @CalculatedAt DATETIME = GETDATE();
DECLARE @HasPackageActivityUntilWeek BIT = 0;
-- تعیین PackageId پیش‌فرض (پکیج پایه)
IF @PackageId IS NULL
@@ -66,20 +67,48 @@ BEGIN
FROM CMS.WeekDefinitions
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. بررسی محاسبه قبلی
-- =============================================
IF EXISTS (SELECT 1 FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId)
IF EXISTS (
SELECT 1
FROM CMS.NetworkWeeklyBalances
WHERE WeekDefinitionId = @WeekDefinitionId
AND PackageId = @PackageId
)
BEGIN
IF @ForceRecalculate = 0
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);
RETURN;
END
-- حذف محاسبات قبلی
DELETE FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId;
-- حذف محاسبات قبلی فقط برای همین پکیج
DELETE FROM CMS.NetworkWeeklyBalances
WHERE WeekDefinitionId = @WeekDefinitionId
AND PackageId = @PackageId;
END
-- =============================================
@@ -96,7 +125,7 @@ BEGIN
-- @MaxBalancesPerLeg و @MaxNetworkLevel قبلاً مقداردهی شده‌اند
-- =============================================
-- 5. ایجاد جدول موقت برای نتایج
-- 5. ایجاد جداول موقت برای نتایج
-- =============================================
CREATE TABLE #Balances (
UserId BIGINT PRIMARY KEY,
@@ -114,31 +143,18 @@ BEGIN
SubordinateBalances INT DEFAULT 0
);
-- =============================================
-- 6. دریافت کاربران فعال باشگاه
-- =============================================
INSERT INTO #Balances (UserId)
SELECT DISTINCT u.Id
FROM CMS.Users u
INNER JOIN CMS.ClubMemberships cm ON cm.UserId = u.Id AND cm.IsActive = 1
WHERE cm.LastPackageId = @PackageId;
CREATE TABLE #LeftNewMembers (
UserId BIGINT PRIMARY KEY,
NewMembersCount INT NOT NULL
);
CREATE TABLE #RightNewMembers (
UserId BIGINT PRIMARY KEY,
NewMembersCount INT NOT NULL
);
-- =============================================
-- 7. دریافت باقیمانده هفته قبل
-- =============================================
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 (بهینه!)
-- 6. محاسبه اعضای جدید هر پا با CTE (بهینه!)
-- =============================================
-- CTE برای شمارش اعضای جدید شاخه چپ
@@ -169,13 +185,15 @@ BEGIN
COUNT(DISTINCT cte.ChildId) AS NewMembersCount
FROM LeftLegCTE cte
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
)
UPDATE b
SET b.LeftLegNewMembers = ISNULL(lnm.NewMembersCount, 0)
FROM #Balances b
LEFT JOIN LeftNewMembers lnm ON lnm.UserId = b.UserId
INSERT INTO #LeftNewMembers (UserId, NewMembersCount)
SELECT UserId, NewMembersCount
FROM LeftNewMembers
OPTION (MAXRECURSION 100);
-- CTE برای شمارش اعضای جدید شاخه راست
@@ -206,14 +224,65 @@ BEGIN
COUNT(DISTINCT cte.ChildId) AS NewMembersCount
FROM RightLegCTE cte
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
)
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
SET b.RightLegNewMembers = ISNULL(rnm.NewMembersCount, 0)
FROM #Balances b
LEFT JOIN RightNewMembers rnm ON rnm.UserId = b.UserId
OPTION (MAXRECURSION 100);
LEFT JOIN #RightNewMembers rnm ON rnm.UserId = b.UserId;
-- =============================================
-- 9. محاسبه تعادل‌ها
@@ -346,11 +415,25 @@ BEGIN
'SP', -- CreatedBy
@CalculatedAt, -- LastModified
'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;
-- پاکسازی
DROP TABLE #RightNewMembers;
DROP TABLE #LeftNewMembers;
DROP TABLE #Balances;
COMMIT TRANSACTION;
@@ -361,6 +444,12 @@ BEGIN
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
DROP TABLE #Balances;
@@ -28,9 +28,9 @@ BEGIN
-- =============================================
-- 1. پیدا کردن Pool هفته (فیلتر بر اساس PackageId)
-- =============================================
SELECT @PoolId = Id, @TotalPoolAmount = TotalPoolAmount
SELECT @PoolId = MIN(Id), @TotalPoolAmount = ISNULL(SUM(TotalPoolAmount), 0)
FROM CMS.WeeklyCommissionPools
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId;
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
-- اگر Pool وجود ندارد (هیچ فعال‌سازی در این هفته نبوده)، یک رکورد خالی ایجاد می‌کنیم
IF @PoolId IS NULL
@@ -50,7 +50,15 @@ BEGIN
-- =============================================
-- 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
IF @ForceRecalculate = 0
BEGIN
@@ -68,43 +76,80 @@ BEGIN
UPDATE uw
SET uw.NetworkBalance = uw.NetworkBalance - ISNULL(
(SELECT SUM(wl.ChangeNerworkValue)
FROM CMS.UserWalletChangeLogs wl
FROM CMS.UserWalletHistories wl
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)
FROM CMS.UserWallets uw
WHERE uw.Id IN (
SELECT DISTINCT wl.WalletId
FROM CMS.UserWalletChangeLogs wl
WHERE wl.RefrenceId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId)
FROM CMS.UserWalletHistories wl
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
WHERE RefrenceId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeeklyPoolId = @PoolId);
DELETE FROM CMS.UserWalletHistories
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
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
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
UPDATE CMS.WeeklyCommissionPools
SET IsCalculated = 0
WHERE Id = @PoolId;
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
END
-- =============================================
-- 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
-- بالانسی محاسبه نشده — pool را به عنوان "محاسبه شده بدون پرداخت" علامت می‌زنیم
UPDATE CMS.WeeklyCommissionPools
SET IsCalculated = 1, CalculatedAt = @CalculatedAt
WHERE Id = @PoolId;
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
COMMIT TRANSACTION;
RETURN;
@@ -115,14 +160,14 @@ BEGIN
-- =============================================
SELECT @TotalBalances = SUM(TotalBalances)
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
BEGIN
-- هیچ تعادلی برای توزیع وجود ندارد — pool محاسبه شده اما پرداختی ندارد
UPDATE CMS.WeeklyCommissionPools
SET IsCalculated = 1, CalculatedAt = @CalculatedAt
WHERE Id = @PoolId;
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
COMMIT TRANSACTION;
RETURN;
@@ -140,6 +185,7 @@ BEGIN
UserId,
WeekDefinitionId,
WeeklyPoolId,
PackageId,
BalancesEarned,
ValuePerBalance,
TotalAmount,
@@ -155,6 +201,7 @@ BEGIN
nb.UserId,
@WeekDefinitionId,
@PoolId,
@PackageId,
nb.TotalBalances,
@ValuePerBalance,
nb.TotalBalances * @ValuePerBalance,
@@ -166,7 +213,7 @@ BEGIN
@CalculatedAt,
'SP'
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. ثبت تاریخچه پرداخت
@@ -205,7 +252,11 @@ BEGIN
@CalculatedAt,
'SP'
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. شارژ کیف پول کاربران
@@ -216,10 +267,14 @@ BEGIN
SET uw.NetworkBalance = uw.NetworkBalance + cp.TotalAmount
FROM CMS.UserWallets uw
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,
CurrentBalance,
ChangeValue,
@@ -252,7 +307,11 @@ BEGIN
'SP'
FROM CMS.UserCommissionPayouts cp
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
@@ -265,7 +324,7 @@ BEGIN
ValuePerBalance = @ValuePerBalance,
LastModified = @CalculatedAt,
LastModifiedBy = 'SP'
WHERE Id = @PoolId;
WHERE WeekDefinitionId = @WeekDefinitionId AND PackageId = @PackageId AND IsDeleted = 0;
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
.FirstOrDefaultAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken);
var existingPoolQuery = _context.WeeklyCommissionPools
.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)
{
@@ -239,9 +248,15 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
}
// بررسی وجود تعادل‌های هفتگی
var weeklyBalances = await _context.NetworkWeeklyBalances
.Where(x => x.WeekDefinitionId == weekDefinitionId)
.ToListAsync(cancellationToken);
var weeklyBalancesQuery = _context.NetworkWeeklyBalances
.Where(x => x.WeekDefinitionId == weekDefinitionId);
if (packageId.HasValue)
{
weeklyBalancesQuery = weeklyBalancesQuery.Where(x => x.PackageId == packageId.Value);
}
var weeklyBalances = await weeklyBalancesQuery.ToListAsync(cancellationToken);
if (!weeklyBalances.Any())
{
@@ -269,9 +284,15 @@ public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
// حذف پرداخت‌های قبلی در صورت ForceRecalculate
if (forceRecalculate)
{
var oldPayouts = await _context.UserCommissionPayouts
.Where(p => p.WeekDefinitionId == weekDefinitionId)
.ToListAsync(cancellationToken);
var oldPayoutsQuery = _context.UserCommissionPayouts
.Where(p => p.WeekDefinitionId == weekDefinitionId);
if (packageId.HasValue)
{
oldPayoutsQuery = oldPayoutsQuery.Where(p => p.PackageId == packageId.Value);
}
var oldPayouts = await oldPayoutsQuery.ToListAsync(cancellationToken);
if (oldPayouts.Any())
{
@@ -141,6 +141,7 @@ public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculati
// حلقه روی پکیج‌ها — هر پکیج Pool مستقل دارد
foreach (var package in activePackages)
{
using var command = connection.CreateCommand();
command.CommandText = "CMS.sp_CalculateWeeklyCommissionPool";
command.CommandType = System.Data.CommandType.StoredProcedure;