feat: implement commission calculation strategy with ORM and SP options
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m43s

This commit is contained in:
masoodafar-web
2025-12-20 03:15:28 +03:30
parent 9864302bab
commit 25476ba120
18 changed files with 1687 additions and 21 deletions
@@ -0,0 +1,350 @@
-- =============================================
-- 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