Files
CMS/src/CMSMicroservice.Infrastructure/Persistence/StoredProcedures/sp_CalculateWeeklyBalances.sql
T

372 lines
14 KiB
Transact-SQL

-- =============================================
-- Stored Procedure: sp_CalculateWeeklyBalances
-- Description: محاسبه تعادل‌های هفتگی شبکه باینری با استفاده از CTE
-- Author: System
-- Created: 2025-12-19
-- =============================================
CREATE OR ALTER PROCEDURE [CMS].[sp_CalculateWeeklyBalances]
@WeekDefinitionId BIGINT,
@ForceRecalculate BIT = 0,
@PackageId BIGINT = NULL, -- پکیج خاص (اگر NULL از پکیج پایه استفاده می‌کند)
@InputMaxBalancesPerLeg INT = NULL, -- سقف تعادل هر پا (از Package entity)
@InputMaxNetworkLevel INT = NULL, -- حداکثر عمق شبکه (از Package entity)
@RowCount INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @StartDate DATETIME;
DECLARE @EndDate DATETIME;
DECLARE @PreviousWeekDefinitionId BIGINT;
DECLARE @MaxBalancesPerLeg INT;
DECLARE @MaxNetworkLevel INT;
DECLARE @CalculatedAt DATETIME = GETDATE();
-- تعیین PackageId پیش‌فرض (پکیج پایه)
IF @PackageId IS NULL
BEGIN
SELECT TOP 1 @PackageId = Id
FROM CMS.Packages
WHERE IsBasePackage = 1 AND IsDeleted = 0;
END
-- خواندن تنظیمات از Package entity اگر پارامتر داده نشده
IF @InputMaxBalancesPerLeg IS NOT NULL
SET @MaxBalancesPerLeg = @InputMaxBalancesPerLeg;
ELSE
SELECT @MaxBalancesPerLeg = ISNULL(MaxBalancesPerLeg, 300) FROM CMS.Packages WHERE Id = @PackageId;
IF @InputMaxNetworkLevel IS NOT NULL
SET @MaxNetworkLevel = @InputMaxNetworkLevel;
ELSE
SELECT @MaxNetworkLevel = ISNULL(MaxNetworkLevel, 15) FROM CMS.Packages WHERE Id = @PackageId;
-- fallbackهای امن
SET @MaxBalancesPerLeg = ISNULL(@MaxBalancesPerLeg, 300);
SET @MaxNetworkLevel = ISNULL(@MaxNetworkLevel, 15);
BEGIN TRY
BEGIN TRANSACTION;
-- =============================================
-- 1. اعتبارسنجی WeekDefinitionId
-- =============================================
IF NOT EXISTS (SELECT 1 FROM CMS.WeekDefinitions WHERE Id = @WeekDefinitionId)
BEGIN
SET @ErrorMessage = N'هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20)) + N' در سیستم تعریف نشده است';
RAISERROR(@ErrorMessage, 16, 1);
RETURN;
END
-- دریافت بازه تاریخی هفته
SELECT @StartDate = StartDate, @EndDate = EndDate
FROM CMS.WeekDefinitions
WHERE Id = @WeekDefinitionId;
-- =============================================
-- 2. بررسی محاسبه قبلی
-- =============================================
IF EXISTS (SELECT 1 FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId)
BEGIN
IF @ForceRecalculate = 0
BEGIN
SET @ErrorMessage = N'تعادل‌های هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20)) + N' قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید';
RAISERROR(@ErrorMessage, 16, 1);
RETURN;
END
-- حذف محاسبات قبلی
DELETE FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId;
END
-- =============================================
-- 3. پیدا کردن هفته قبل
-- =============================================
SELECT TOP 1 @PreviousWeekDefinitionId = Id
FROM CMS.WeekDefinitions
WHERE WeekOrder = (SELECT WeekOrder - 1 FROM CMS.WeekDefinitions WHERE Id = @WeekDefinitionId)
AND IsActive = 1;
-- =============================================
-- 4. تنظیمات پکیج (از پارامترها خوانده می‌شود)
-- =============================================
-- @MaxBalancesPerLeg و @MaxNetworkLevel قبلاً مقداردهی شده‌اند
-- =============================================
-- 5. ایجاد جدول موقت برای نتایج
-- =============================================
CREATE TABLE #Balances (
UserId BIGINT PRIMARY KEY,
LeftLegNewMembers INT DEFAULT 0,
RightLegNewMembers INT DEFAULT 0,
LeftLegCarryover INT DEFAULT 0,
RightLegCarryover INT DEFAULT 0,
LeftLegTotal INT DEFAULT 0,
RightLegTotal INT DEFAULT 0,
TotalBalances INT DEFAULT 0,
LeftLegRemainder INT DEFAULT 0,
RightLegRemainder INT DEFAULT 0,
FlushedPerSide INT DEFAULT 0,
TotalFlushed INT DEFAULT 0,
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;
-- =============================================
-- 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 (بهینه!)
-- =============================================
-- CTE برای شمارش اعضای جدید شاخه چپ
;WITH LeftLegCTE AS (
-- فرزند مستقیم چپ (لول 1)
SELECT
u.NetworkParentId AS ParentId,
u.Id AS ChildId,
1 AS Level
FROM CMS.Users u
WHERE u.LegPosition = 0 -- Left
AND u.NetworkParentId IS NOT NULL
UNION ALL
-- زیرمجموعه‌های بازگشتی
SELECT
cte.ParentId,
u.Id AS ChildId,
cte.Level + 1 AS Level
FROM CMS.Users u
INNER JOIN LeftLegCTE cte ON u.NetworkParentId = cte.ChildId
WHERE cte.Level < @MaxNetworkLevel
),
LeftNewMembers AS (
SELECT
cte.ParentId AS UserId,
COUNT(*) AS NewMembersCount
FROM LeftLegCTE cte
INNER JOIN CMS.ClubMemberships cm ON cm.UserId = cte.ChildId AND cm.IsActive = 1
WHERE cm.ActivatedAt >= @StartDate AND cm.ActivatedAt <= @EndDate
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
OPTION (MAXRECURSION 100);
-- CTE برای شمارش اعضای جدید شاخه راست
;WITH RightLegCTE AS (
-- فرزند مستقیم راست (لول 1)
SELECT
u.NetworkParentId AS ParentId,
u.Id AS ChildId,
1 AS Level
FROM CMS.Users u
WHERE u.LegPosition = 1 -- Right
AND u.NetworkParentId IS NOT NULL
UNION ALL
-- زیرمجموعه‌های بازگشتی
SELECT
cte.ParentId,
u.Id AS ChildId,
cte.Level + 1 AS Level
FROM CMS.Users u
INNER JOIN RightLegCTE cte ON u.NetworkParentId = cte.ChildId
WHERE cte.Level < @MaxNetworkLevel
),
RightNewMembers AS (
SELECT
cte.ParentId AS UserId,
COUNT(*) AS NewMembersCount
FROM RightLegCTE cte
INNER JOIN CMS.ClubMemberships cm ON cm.UserId = cte.ChildId AND cm.IsActive = 1
WHERE cm.ActivatedAt >= @StartDate AND cm.ActivatedAt <= @EndDate
GROUP BY cte.ParentId
)
UPDATE b
SET b.RightLegNewMembers = ISNULL(rnm.NewMembersCount, 0)
FROM #Balances b
LEFT JOIN RightNewMembers rnm ON rnm.UserId = b.UserId
OPTION (MAXRECURSION 100);
-- =============================================
-- 9. محاسبه تعادل‌ها
-- =============================================
UPDATE #Balances
SET
-- مجموع هر پا
LeftLegTotal = LeftLegNewMembers + LeftLegCarryover,
RightLegTotal = RightLegNewMembers + RightLegCarryover;
UPDATE #Balances
SET
-- تعادل = MIN(چپ، راست)
TotalBalances = CASE
WHEN LeftLegTotal <= RightLegTotal THEN LeftLegTotal
ELSE RightLegTotal
END;
UPDATE #Balances
SET
-- باقیمانده برای هفته بعد
LeftLegRemainder = LeftLegTotal - TotalBalances,
RightLegRemainder = RightLegTotal - TotalBalances,
-- فلش (اگر تعادل > سقف)
FlushedPerSide = CASE
WHEN TotalBalances > @MaxBalancesPerLeg THEN TotalBalances - @MaxBalancesPerLeg
ELSE 0
END;
UPDATE #Balances
SET
-- اعمال سقف
TotalBalances = CASE
WHEN TotalBalances > @MaxBalancesPerLeg THEN @MaxBalancesPerLeg
ELSE TotalBalances
END,
-- مجموع فلش از دو طرف
TotalFlushed = FlushedPerSide * 2;
-- =============================================
-- 10. محاسبه تعادل زیرمجموعه با CTE
-- =============================================
;WITH SubordinateCTE AS (
-- فرزندان مستقیم (لول 1)
SELECT
u.NetworkParentId AS ParentId,
u.Id AS ChildId,
1 AS Level
FROM CMS.Users u
WHERE u.NetworkParentId IS NOT NULL
UNION ALL
-- زیرمجموعه‌های بازگشتی
SELECT
cte.ParentId,
u.Id AS ChildId,
cte.Level + 1 AS Level
FROM CMS.Users u
INNER JOIN SubordinateCTE cte ON u.NetworkParentId = cte.ChildId
WHERE cte.Level < @MaxNetworkLevel
),
SubordinateBalances AS (
SELECT
cte.ParentId AS UserId,
SUM(b.TotalBalances) AS TotalSubordinateBalances
FROM SubordinateCTE cte
INNER JOIN #Balances b ON b.UserId = cte.ChildId
GROUP BY cte.ParentId
)
UPDATE b
SET b.SubordinateBalances = ISNULL(sb.TotalSubordinateBalances, 0)
FROM #Balances b
LEFT JOIN SubordinateBalances sb ON sb.UserId = b.UserId
OPTION (MAXRECURSION 100);
-- =============================================
-- 11. Insert نتایج در جدول اصلی
-- =============================================
INSERT INTO CMS.NetworkWeeklyBalances (
UserId,
WeekDefinitionId,
PackageId,
LeftLegNewMembers,
RightLegNewMembers,
LeftLegCarryover,
RightLegCarryover,
LeftLegTotal,
RightLegTotal,
TotalBalances,
LeftLegRemainder,
RightLegRemainder,
FlushedPerSide,
TotalFlushed,
SubordinateBalances,
LeftLegBalances,
RightLegBalances,
WeeklyPoolContribution,
CalculatedAt,
IsExpired,
IsDeleted,
Created,
CreatedBy,
LastModified,
LastModifiedBy
)
SELECT
UserId,
@WeekDefinitionId,
@PackageId,
LeftLegNewMembers,
RightLegNewMembers,
LeftLegCarryover,
RightLegCarryover,
LeftLegTotal,
RightLegTotal,
TotalBalances,
LeftLegRemainder,
RightLegRemainder,
FlushedPerSide,
TotalFlushed,
SubordinateBalances,
LeftLegTotal, -- LeftLegBalances (deprecated)
RightLegTotal, -- RightLegBalances (deprecated)
0, -- WeeklyPoolContribution
@CalculatedAt,
0, -- IsExpired
0, -- IsDeleted
@CalculatedAt, -- Created
'SP', -- CreatedBy
@CalculatedAt, -- LastModified
'SP' -- LastModifiedBy
FROM #Balances;
SET @RowCount = @@ROWCOUNT;
-- پاکسازی
DROP TABLE #Balances;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
-- پاکسازی در صورت خطا
IF OBJECT_ID('tempdb..#Balances') IS NOT NULL
DROP TABLE #Balances;
SET @ErrorMessage = ERROR_MESSAGE();
RAISERROR(@ErrorMessage, 16, 1);
END CATCH
END
GO