351 lines
13 KiB
Transact-SQL
351 lines
13 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,
|
|
@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 = 300;
|
|
DECLARE @MaxNetworkLevel INT = 15;
|
|
DECLARE @CalculatedAt DATETIME = GETDATE();
|
|
|
|
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. خواندن Configuration ها
|
|
-- =============================================
|
|
SELECT @MaxBalancesPerLeg = CAST(Value AS INT)
|
|
FROM CMS.SystemConfigurations
|
|
WHERE [Key] = 'Commission.MaxWeeklyBalancesPerLeg' AND IsActive = 1;
|
|
|
|
SELECT @MaxNetworkLevel = CAST(Value AS INT)
|
|
FROM CMS.SystemConfigurations
|
|
WHERE [Key] = 'Commission.MaxNetworkLevel' AND IsActive = 1;
|
|
|
|
-- مقادیر پیشفرض
|
|
SET @MaxBalancesPerLeg = ISNULL(@MaxBalancesPerLeg, 300);
|
|
SET @MaxNetworkLevel = ISNULL(@MaxNetworkLevel, 15);
|
|
|
|
-- =============================================
|
|
-- 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;
|
|
|
|
-- =============================================
|
|
-- 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;
|
|
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,
|
|
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,
|
|
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
|