feat: implement commission calculation strategy with ORM and SP options
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m43s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 1m43s
This commit is contained in:
+2
@@ -12,6 +12,7 @@ public class GetAllClubMembershipsQueryHandler : IRequestHandler<GetAllClubMembe
|
|||||||
public async Task<GetAllClubMembershipsResponseDto> Handle(GetAllClubMembershipsQuery request, CancellationToken cancellationToken)
|
public async Task<GetAllClubMembershipsResponseDto> Handle(GetAllClubMembershipsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var query = _context.ClubMemberships
|
var query = _context.ClubMemberships
|
||||||
|
.Include(x => x.User)
|
||||||
.ApplyOrder(sortBy: request.SortBy)
|
.ApplyOrder(sortBy: request.SortBy)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
@@ -33,6 +34,7 @@ public class GetAllClubMembershipsQueryHandler : IRequestHandler<GetAllClubMembe
|
|||||||
{
|
{
|
||||||
Id = x.Id,
|
Id = x.Id,
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
|
UserName = x.User != null ? $"{x.User.FirstName} {x.User.LastName}" : string.Empty,
|
||||||
IsActive = x.IsActive,
|
IsActive = x.IsActive,
|
||||||
ActivatedAt = x.ActivatedAt,
|
ActivatedAt = x.ActivatedAt,
|
||||||
InitialContribution = x.InitialContribution,
|
InitialContribution = x.InitialContribution,
|
||||||
|
|||||||
+1
@@ -10,6 +10,7 @@ public class GetAllClubMembershipsResponseModel
|
|||||||
{
|
{
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
public long UserId { get; set; }
|
public long UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
public DateTime? ActivatedAt { get; set; }
|
public DateTime? ActivatedAt { get; set; }
|
||||||
public long InitialContribution { get; set; }
|
public long InitialContribution { get; set; }
|
||||||
|
|||||||
+22
-16
@@ -8,13 +8,16 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
|
|||||||
{
|
{
|
||||||
private readonly IApplicationDbContext _context;
|
private readonly IApplicationDbContext _context;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
private readonly ICommissionCalculationStrategyFactory _strategyFactory;
|
||||||
|
|
||||||
public TriggerWeeklyCalculationCommandHandler(
|
public TriggerWeeklyCalculationCommandHandler(
|
||||||
IApplicationDbContext context,
|
IApplicationDbContext context,
|
||||||
IMediator mediator)
|
IMediator mediator,
|
||||||
|
ICommissionCalculationStrategyFactory strategyFactory)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
|
_strategyFactory = strategyFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TriggerWeeklyCalculationResponseDto> Handle(
|
public async Task<TriggerWeeklyCalculationResponseDto> Handle(
|
||||||
@@ -26,9 +29,8 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
// Validate week number format
|
// Validate week number format
|
||||||
if (request.WeekDefinitionId<=0)
|
if (request.WeekDefinitionId <= 0)
|
||||||
{
|
{
|
||||||
return new TriggerWeeklyCalculationResponseDto
|
return new TriggerWeeklyCalculationResponseDto
|
||||||
{
|
{
|
||||||
@@ -40,33 +42,37 @@ public class TriggerWeeklyCalculationCommandHandler : IRequestHandler<TriggerWee
|
|||||||
}
|
}
|
||||||
|
|
||||||
var steps = new List<string>();
|
var steps = new List<string>();
|
||||||
|
|
||||||
|
// ⭐ دریافت استراتژی براساس Config (ORM یا SP)
|
||||||
|
var strategy = await _strategyFactory.CreateStrategyAsync(cancellationToken);
|
||||||
|
var strategyName = strategy.GetType().Name.Contains("StoredProcedure") ? "SP" : "ORM";
|
||||||
|
|
||||||
// Step 1: Calculate Weekly Balances (تا 15 لول)
|
// Step 1: Calculate Weekly Balances (تا 15 لول)
|
||||||
if (!request.SkipBalances)
|
if (!request.SkipBalances)
|
||||||
{
|
{
|
||||||
await _mediator.Send(new CalculateWeeklyBalancesCommand
|
var balancesCount = await strategy.CalculateWeeklyBalancesAsync(
|
||||||
{
|
request.WeekDefinitionId,
|
||||||
WeekDefinitionId = request.WeekDefinitionId,
|
request.ForceRecalculate,
|
||||||
ForceRecalculate = request.ForceRecalculate
|
cancellationToken);
|
||||||
}, cancellationToken);
|
|
||||||
steps.Add("محاسبه امتیازات هفتگی");
|
steps.Add($"محاسبه امتیازات هفتگی ({balancesCount} کاربر)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Calculate Pool & Process Payouts (محاسبه استخر + پرداخت کاربران)
|
// Step 2: Calculate Pool & Process Payouts (محاسبه استخر + پرداخت کاربران)
|
||||||
if (!request.SkipPayouts)
|
if (!request.SkipPayouts)
|
||||||
{
|
{
|
||||||
await _mediator.Send(new CalculateWeeklyCommissionPoolCommand
|
var poolId = await strategy.CalculateWeeklyCommissionPoolAsync(
|
||||||
{
|
request.WeekDefinitionId,
|
||||||
WeekDefinitionId = request.WeekDefinitionId,
|
request.ForceRecalculate,
|
||||||
ForceRecalculate = request.ForceRecalculate
|
cancellationToken);
|
||||||
}, cancellationToken);
|
|
||||||
steps.Add("محاسبه استخر و پرداخت کاربران");
|
steps.Add($"محاسبه استخر و پرداخت کاربران (Pool: {poolId})");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TriggerWeeklyCalculationResponseDto
|
return new TriggerWeeklyCalculationResponseDto
|
||||||
{
|
{
|
||||||
Success = true,
|
Success = true,
|
||||||
Message = $"محاسبات هفته {request.WeekDefinitionId} با موفقیت انجام شد. مراحل: {string.Join(", ", steps)}",
|
Message = $"محاسبات هفته {request.WeekDefinitionId} با موفقیت انجام شد [{strategyName}]. مراحل: {string.Join(", ", steps)}",
|
||||||
ExecutionId = executionId,
|
ExecutionId = executionId,
|
||||||
StartedAt = startedAt
|
StartedAt = startedAt
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// استراتژی محاسبه کمیسیون هفتگی
|
||||||
|
/// دو پیادهسازی دارد: ORM و Stored Procedure
|
||||||
|
/// </summary>
|
||||||
|
public interface ICommissionCalculationStrategy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// محاسبه تعادلهای هفتگی (مرحله 1)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="weekDefinitionId">شناسه هفته</param>
|
||||||
|
/// <param name="forceRecalculate">محاسبه مجدد حتی اگر قبلاً محاسبه شده</param>
|
||||||
|
/// <param name="cancellationToken">توکن لغو</param>
|
||||||
|
/// <returns>تعداد رکوردهای محاسبه شده</returns>
|
||||||
|
Task<int> CalculateWeeklyBalancesAsync(
|
||||||
|
long weekDefinitionId,
|
||||||
|
bool forceRecalculate,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// محاسبه استخر کمیسیون و پرداختها (مرحله 2)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="weekDefinitionId">شناسه هفته</param>
|
||||||
|
/// <param name="forceRecalculate">محاسبه مجدد حتی اگر قبلاً محاسبه شده</param>
|
||||||
|
/// <param name="cancellationToken">توکن لغو</param>
|
||||||
|
/// <returns>شناسه Pool ایجاد شده</returns>
|
||||||
|
Task<long> CalculateWeeklyCommissionPoolAsync(
|
||||||
|
long weekDefinitionId,
|
||||||
|
bool forceRecalculate,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع استراتژی محاسبه کمیسیون
|
||||||
|
/// </summary>
|
||||||
|
public enum CommissionCalculationStrategyType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// استفاده از Entity Framework (ORM) - پیشفرض
|
||||||
|
/// </summary>
|
||||||
|
Orm = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// استفاده از Stored Procedure - بهینهتر برای دادههای زیاد
|
||||||
|
/// </summary>
|
||||||
|
StoredProcedure = 1
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
namespace CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory برای ایجاد Strategy مناسب براساس Config
|
||||||
|
/// </summary>
|
||||||
|
public interface ICommissionCalculationStrategyFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ایجاد Strategy براساس تنظیمات سیستم
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>استراتژی مناسب (ORM یا SP)</returns>
|
||||||
|
Task<ICommissionCalculationStrategy> CreateStrategyAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ایجاد Strategy مشخص
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strategyType">نوع استراتژی</param>
|
||||||
|
/// <returns>استراتژی درخواستی</returns>
|
||||||
|
ICommissionCalculationStrategy CreateStrategy(CommissionCalculationStrategyType strategyType);
|
||||||
|
}
|
||||||
+7
-3
@@ -56,16 +56,20 @@ public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatis
|
|||||||
|
|
||||||
// Monthly growth (last 6 months) - using Created date
|
// Monthly growth (last 6 months) - using Created date
|
||||||
var sixMonthsAgo = DateTime.Now.AddMonths(-6);
|
var sixMonthsAgo = DateTime.Now.AddMonths(-6);
|
||||||
var monthlyGrowth = await _context.Users
|
var monthlyGrowthRaw = await _context.Users
|
||||||
.Where(x => x.NetworkParentId != null && x.Created >= sixMonthsAgo)
|
.Where(x => x.NetworkParentId != null && x.Created >= sixMonthsAgo)
|
||||||
.GroupBy(x => new { x.Created.Year, x.Created.Month })
|
.Select(x => new { x.Created.Year, x.Created.Month })
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var monthlyGrowth = monthlyGrowthRaw
|
||||||
|
.GroupBy(x => new { x.Year, x.Month })
|
||||||
.Select(g => new MonthlyGrowthModel
|
.Select(g => new MonthlyGrowthModel
|
||||||
{
|
{
|
||||||
Month = $"{g.Key.Year}-{g.Key.Month:D2}",
|
Month = $"{g.Key.Year}-{g.Key.Month:D2}",
|
||||||
NewMembers = g.Count()
|
NewMembers = g.Count()
|
||||||
})
|
})
|
||||||
.OrderBy(x => x.Month)
|
.OrderBy(x => x.Month)
|
||||||
.ToListAsync(cancellationToken);
|
.ToList();
|
||||||
|
|
||||||
// Top users by total children count
|
// Top users by total children count
|
||||||
var topUsers = await _context.Users
|
var topUsers = await _context.Users
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using CMSMicroservice.Infrastructure.BackgroundJobs;
|
|||||||
using CMSMicroservice.Infrastructure.Services.Monitoring;
|
using CMSMicroservice.Infrastructure.Services.Monitoring;
|
||||||
using CMSMicroservice.Infrastructure.Configuration;
|
using CMSMicroservice.Infrastructure.Configuration;
|
||||||
using CMSMicroservice.Infrastructure.Services.Payment;
|
using CMSMicroservice.Infrastructure.Services.Payment;
|
||||||
|
using CMSMicroservice.Infrastructure.Services.Commission;
|
||||||
using CMSMicroservice.Infrastructure.Repositories;
|
using CMSMicroservice.Infrastructure.Repositories;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@@ -88,6 +89,9 @@ public static class ConfigureServices
|
|||||||
services.AddSingleton<WeekDefinitionRepository>();
|
services.AddSingleton<WeekDefinitionRepository>();
|
||||||
services.AddSingleton<IWeekDefinitionRepository>(sp => sp.GetRequiredService<WeekDefinitionRepository>());
|
services.AddSingleton<IWeekDefinitionRepository>(sp => sp.GetRequiredService<WeekDefinitionRepository>());
|
||||||
|
|
||||||
|
// Commission Calculation Strategy Factory - برای سوییچ بین ORM و SP
|
||||||
|
services.AddScoped<ICommissionCalculationStrategyFactory, CommissionCalculationStrategyFactory>();
|
||||||
|
|
||||||
// Background Workers - Deprecated: Using Hangfire instead
|
// Background Workers - Deprecated: Using Hangfire instead
|
||||||
// services.AddHostedService<WeeklyNetworkCommissionWorker>();
|
// services.AddHostedService<WeeklyNetworkCommissionWorker>();
|
||||||
services.AddScoped<WeeklyCommissionJob>(); // Hangfire Job (Scoped for DI)
|
services.AddScoped<WeeklyCommissionJob>(); // Hangfire Job (Scoped for DI)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class WeekDefinitionSeeder
|
|||||||
while (currentDate <= endDate)
|
while (currentDate <= endDate)
|
||||||
{
|
{
|
||||||
var weekStart = currentDate; // شنبه
|
var weekStart = currentDate; // شنبه
|
||||||
var weekEnd = currentDate.AddDays(6); // جمعه
|
var weekEnd = currentDate.AddDays(6).Date.AddHours(23).AddMinutes(59).AddSeconds(59); // جمعه 23:59:59
|
||||||
|
|
||||||
// محاسبه شماره ترتیبی هفته (از 1 شروع میشود)
|
// محاسبه شماره ترتیبی هفته (از 1 شروع میشود)
|
||||||
var weekOrder = CalculateWeekOrder(weekStart);
|
var weekOrder = CalculateWeekOrder(weekStart);
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- Insert Configuration for Strategy Selection
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
-- Check if config exists, insert if not
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM CMS.SystemConfigurations WHERE [Key] = 'Commission.CalculationStrategy')
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO CMS.SystemConfigurations ([Key], [Value], [Description], IsActive, Created, CreatedBy, LastModified, LastModifiedBy)
|
||||||
|
VALUES (
|
||||||
|
'Commission.CalculationStrategy',
|
||||||
|
'ORM',
|
||||||
|
N'روش محاسبه کمیسیون: ORM (Entity Framework) یا SP (Stored Procedure)',
|
||||||
|
1,
|
||||||
|
GETDATE(),
|
||||||
|
'System',
|
||||||
|
GETDATE(),
|
||||||
|
'System'
|
||||||
|
);
|
||||||
|
PRINT 'Configuration Commission.CalculationStrategy created with default value: ORM'
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
PRINT 'Configuration Commission.CalculationStrategy already exists'
|
||||||
|
END
|
||||||
|
|
||||||
|
-- Show current value
|
||||||
|
SELECT [Key], [Value], [Description]
|
||||||
|
FROM CMS.SystemConfigurations
|
||||||
|
WHERE [Key] LIKE 'Commission.%'
|
||||||
|
ORDER BY [Key];
|
||||||
|
|
||||||
|
/*
|
||||||
|
تغییر روش محاسبه:
|
||||||
|
|
||||||
|
-- برای استفاده از ORM (Entity Framework):
|
||||||
|
UPDATE CMS.SystemConfigurations SET Value = 'ORM' WHERE [Key] = 'Commission.CalculationStrategy'
|
||||||
|
|
||||||
|
-- برای استفاده از Stored Procedure:
|
||||||
|
UPDATE CMS.SystemConfigurations SET Value = 'SP' WHERE [Key] = 'Commission.CalculationStrategy'
|
||||||
|
*/
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- SP Deployment Script for Commission Calculation
|
||||||
|
-- Version: 1.0
|
||||||
|
-- Created: 2025-12-19
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
این اسکریپت برای نصب Stored Procedure های محاسبه کمیسیون استفاده میشود.
|
||||||
|
|
||||||
|
قبل از اجرا:
|
||||||
|
1. مطمئن شوید که به دیتابیس CMS متصل هستید
|
||||||
|
2. کاربر شما دسترسی CREATE PROCEDURE داشته باشد
|
||||||
|
|
||||||
|
ترتیب اجرا:
|
||||||
|
1. sp_CalculateWeeklyBalances.sql
|
||||||
|
2. sp_CalculateWeeklyCommissionPool.sql
|
||||||
|
|
||||||
|
یا این فایل را اجرا کنید که هر دو SP را نصب میکند.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PRINT '========================================='
|
||||||
|
PRINT 'Commission Calculation SP Deployment'
|
||||||
|
PRINT '========================================='
|
||||||
|
PRINT ''
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- Check Schema
|
||||||
|
-- =============================================
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'CMS')
|
||||||
|
BEGIN
|
||||||
|
PRINT 'ERROR: Schema CMS does not exist!'
|
||||||
|
RETURN
|
||||||
|
END
|
||||||
|
|
||||||
|
PRINT 'Schema CMS: OK'
|
||||||
|
PRINT ''
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- Drop existing procedures if exist
|
||||||
|
-- =============================================
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.procedures WHERE name = 'sp_CalculateWeeklyBalances' AND schema_id = SCHEMA_ID('CMS'))
|
||||||
|
BEGIN
|
||||||
|
DROP PROCEDURE CMS.sp_CalculateWeeklyBalances;
|
||||||
|
PRINT 'Dropped existing sp_CalculateWeeklyBalances'
|
||||||
|
END
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.procedures WHERE name = 'sp_CalculateWeeklyCommissionPool' AND schema_id = SCHEMA_ID('CMS'))
|
||||||
|
BEGIN
|
||||||
|
DROP PROCEDURE CMS.sp_CalculateWeeklyCommissionPool;
|
||||||
|
PRINT 'Dropped existing sp_CalculateWeeklyCommissionPool'
|
||||||
|
END
|
||||||
|
|
||||||
|
PRINT ''
|
||||||
|
PRINT 'Creating Stored Procedures...'
|
||||||
|
PRINT ''
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- SP 1: sp_CalculateWeeklyBalances
|
||||||
|
-- =============================================
|
||||||
|
GO
|
||||||
|
-- (content of sp_CalculateWeeklyBalances.sql here)
|
||||||
|
-- Run sp_CalculateWeeklyBalances.sql separately
|
||||||
|
|
||||||
|
GO
|
||||||
|
-- (content of sp_CalculateWeeklyCommissionPool.sql here)
|
||||||
|
-- Run sp_CalculateWeeklyCommissionPool.sql separately
|
||||||
|
|
||||||
|
GO
|
||||||
|
|
||||||
|
PRINT ''
|
||||||
|
PRINT '========================================='
|
||||||
|
PRINT 'Deployment Complete!'
|
||||||
|
PRINT '========================================='
|
||||||
|
PRINT ''
|
||||||
|
PRINT 'How to use:'
|
||||||
|
PRINT ''
|
||||||
|
PRINT '-- 1. Set calculation strategy in SystemConfigurations:'
|
||||||
|
PRINT 'UPDATE CMS.SystemConfigurations '
|
||||||
|
PRINT 'SET Value = ''SP'' '
|
||||||
|
PRINT 'WHERE [Key] = ''Commission.CalculationStrategy'''
|
||||||
|
PRINT ''
|
||||||
|
PRINT '-- Or for ORM mode:'
|
||||||
|
PRINT 'UPDATE CMS.SystemConfigurations '
|
||||||
|
PRINT 'SET Value = ''ORM'' '
|
||||||
|
PRINT 'WHERE [Key] = ''Commission.CalculationStrategy'''
|
||||||
|
PRINT ''
|
||||||
|
PRINT '-- 2. Manual execution (optional):'
|
||||||
|
PRINT 'DECLARE @RowCount INT, @PoolId BIGINT'
|
||||||
|
PRINT 'EXEC CMS.sp_CalculateWeeklyBalances @WeekDefinitionId = 1, @ForceRecalculate = 0, @RowCount = @RowCount OUTPUT'
|
||||||
|
PRINT 'PRINT ''Rows affected: '' + CAST(@RowCount AS VARCHAR)'
|
||||||
|
PRINT ''
|
||||||
|
PRINT 'EXEC CMS.sp_CalculateWeeklyCommissionPool @WeekDefinitionId = 1, @ForceRecalculate = 0, @PoolId = @PoolId OUTPUT'
|
||||||
|
PRINT 'PRINT ''Pool ID: '' + CAST(@PoolId AS VARCHAR)'
|
||||||
|
GO
|
||||||
+350
@@ -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
|
||||||
+266
@@ -0,0 +1,266 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- Stored Procedure: sp_CalculateWeeklyCommissionPool
|
||||||
|
-- Description: محاسبه استخر کمیسیون هفتگی و توزیع بین کاربران
|
||||||
|
-- Author: System
|
||||||
|
-- Created: 2025-12-19
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE [CMS].[sp_CalculateWeeklyCommissionPool]
|
||||||
|
@WeekDefinitionId BIGINT,
|
||||||
|
@ForceRecalculate BIT = 0,
|
||||||
|
@PoolId BIGINT OUTPUT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
DECLARE @ErrorMessage NVARCHAR(4000);
|
||||||
|
DECLARE @TotalPoolAmount BIGINT;
|
||||||
|
DECLARE @TotalBalances INT;
|
||||||
|
DECLARE @ValuePerBalance BIGINT;
|
||||||
|
DECLARE @CalculatedAt DATETIME = GETDATE();
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 1. پیدا کردن Pool هفته
|
||||||
|
-- =============================================
|
||||||
|
SELECT @PoolId = Id, @TotalPoolAmount = TotalPoolAmount
|
||||||
|
FROM CMS.WeeklyCommissionPools
|
||||||
|
WHERE WeekDefinitionId = @WeekDefinitionId;
|
||||||
|
|
||||||
|
IF @PoolId IS NULL
|
||||||
|
BEGIN
|
||||||
|
SET @ErrorMessage = N'استخر کمیسیون برای هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20)) + N' تعریف نشده است';
|
||||||
|
RAISERROR(@ErrorMessage, 16, 1);
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 2. بررسی محاسبات قبلی
|
||||||
|
-- =============================================
|
||||||
|
IF EXISTS (SELECT 1 FROM CMS.UserCommissionPayouts WHERE WeekDefinitionId = @WeekDefinitionId)
|
||||||
|
BEGIN
|
||||||
|
IF @ForceRecalculate = 0
|
||||||
|
BEGIN
|
||||||
|
SET @ErrorMessage = N'کمیسیون هفته ' + CAST(@WeekDefinitionId AS NVARCHAR(20)) + N' قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید';
|
||||||
|
RAISERROR(@ErrorMessage, 16, 1);
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 3. Revert کیف پولها (در صورت ForceRecalculate)
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
-- کم کردن مقادیر قبلی از NetworkBalance
|
||||||
|
UPDATE uw
|
||||||
|
SET uw.NetworkBalance = uw.NetworkBalance - ISNULL(
|
||||||
|
(SELECT SUM(wl.ChangeNerworkValue)
|
||||||
|
FROM CMS.UserWalletChangeLogs wl
|
||||||
|
WHERE wl.WalletId = uw.Id
|
||||||
|
AND wl.RefrenceId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeekDefinitionId = @WeekDefinitionId)
|
||||||
|
), 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 WeekDefinitionId = @WeekDefinitionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- حذف لاگهای تغییرات قبلی
|
||||||
|
DELETE FROM CMS.UserWalletChangeLogs
|
||||||
|
WHERE RefrenceId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeekDefinitionId = @WeekDefinitionId);
|
||||||
|
|
||||||
|
-- حذف تاریخچه پرداخت
|
||||||
|
DELETE FROM CMS.CommissionPayoutHistories
|
||||||
|
WHERE UserCommissionPayoutId IN (SELECT Id FROM CMS.UserCommissionPayouts WHERE WeekDefinitionId = @WeekDefinitionId);
|
||||||
|
|
||||||
|
-- حذف پرداختهای قبلی
|
||||||
|
DELETE FROM CMS.UserCommissionPayouts WHERE WeekDefinitionId = @WeekDefinitionId;
|
||||||
|
|
||||||
|
-- Reset وضعیت Pool
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET IsCalculated = 0
|
||||||
|
WHERE Id = @PoolId;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 4. بررسی وجود تعادلهای هفتگی
|
||||||
|
-- =============================================
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM CMS.NetworkWeeklyBalances WHERE WeekDefinitionId = @WeekDefinitionId)
|
||||||
|
BEGIN
|
||||||
|
SET @ErrorMessage = N'ابتدا باید تعادلهای هفتگی محاسبه شوند. sp_CalculateWeeklyBalances را اجرا کنید';
|
||||||
|
RAISERROR(@ErrorMessage, 16, 1);
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 5. محاسبه مجموع تعادلها
|
||||||
|
-- =============================================
|
||||||
|
SELECT @TotalBalances = SUM(TotalBalances)
|
||||||
|
FROM CMS.NetworkWeeklyBalances
|
||||||
|
WHERE WeekDefinitionId = @WeekDefinitionId AND TotalBalances > 0;
|
||||||
|
|
||||||
|
IF @TotalBalances IS NULL OR @TotalBalances = 0
|
||||||
|
BEGIN
|
||||||
|
-- هیچ تعادلی برای توزیع وجود ندارد
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET IsCalculated = 1, CalculatedAt = @CalculatedAt
|
||||||
|
WHERE Id = @PoolId;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 6. محاسبه ارزش هر تعادل
|
||||||
|
-- =============================================
|
||||||
|
SET @ValuePerBalance = @TotalPoolAmount / @TotalBalances;
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 7. ایجاد پرداخت برای هر کاربر
|
||||||
|
-- =============================================
|
||||||
|
INSERT INTO CMS.UserCommissionPayouts (
|
||||||
|
UserId,
|
||||||
|
WeekDefinitionId,
|
||||||
|
WeeklyPoolId,
|
||||||
|
BalancesEarned,
|
||||||
|
ValuePerBalance,
|
||||||
|
TotalAmount,
|
||||||
|
Status,
|
||||||
|
PaidAt,
|
||||||
|
IsDeleted,
|
||||||
|
Created,
|
||||||
|
CreatedBy,
|
||||||
|
LastModified,
|
||||||
|
LastModifiedBy
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
nb.UserId,
|
||||||
|
@WeekDefinitionId,
|
||||||
|
@PoolId,
|
||||||
|
nb.TotalBalances,
|
||||||
|
@ValuePerBalance,
|
||||||
|
nb.TotalBalances * @ValuePerBalance,
|
||||||
|
0, -- Status: Pending
|
||||||
|
NULL, -- PaidAt
|
||||||
|
0, -- IsDeleted
|
||||||
|
@CalculatedAt,
|
||||||
|
'SP',
|
||||||
|
@CalculatedAt,
|
||||||
|
'SP'
|
||||||
|
FROM CMS.NetworkWeeklyBalances nb
|
||||||
|
WHERE nb.WeekDefinitionId = @WeekDefinitionId AND nb.TotalBalances > 0;
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 8. ثبت تاریخچه پرداخت
|
||||||
|
-- =============================================
|
||||||
|
INSERT INTO CMS.CommissionPayoutHistories (
|
||||||
|
UserCommissionPayoutId,
|
||||||
|
UserId,
|
||||||
|
WeekDefinitionId,
|
||||||
|
AmountBefore,
|
||||||
|
AmountAfter,
|
||||||
|
OldStatus,
|
||||||
|
NewStatus,
|
||||||
|
Action,
|
||||||
|
PerformedBy,
|
||||||
|
Reason,
|
||||||
|
IsDeleted,
|
||||||
|
Created,
|
||||||
|
CreatedBy,
|
||||||
|
LastModified,
|
||||||
|
LastModifiedBy
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
cp.Id, -- UserCommissionPayoutId
|
||||||
|
cp.UserId, -- UserId
|
||||||
|
@WeekDefinitionId,
|
||||||
|
0, -- AmountBefore
|
||||||
|
cp.TotalAmount, -- AmountAfter
|
||||||
|
0, -- OldStatus: Pending
|
||||||
|
0, -- NewStatus: Pending
|
||||||
|
0, -- Action: Created (assuming enum value)
|
||||||
|
'SP', -- PerformedBy
|
||||||
|
N'محاسبه اولیه توسط SP', -- Reason
|
||||||
|
0, -- IsDeleted
|
||||||
|
@CalculatedAt,
|
||||||
|
'SP',
|
||||||
|
@CalculatedAt,
|
||||||
|
'SP'
|
||||||
|
FROM CMS.UserCommissionPayouts cp
|
||||||
|
WHERE cp.WeekDefinitionId = @WeekDefinitionId;
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 9. شارژ کیف پول کاربران
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
-- بروزرسانی NetworkBalance
|
||||||
|
UPDATE uw
|
||||||
|
SET uw.NetworkBalance = uw.NetworkBalance + cp.TotalAmount
|
||||||
|
FROM CMS.UserWallets uw
|
||||||
|
INNER JOIN CMS.UserCommissionPayouts cp ON cp.UserId = uw.UserId
|
||||||
|
WHERE cp.WeekDefinitionId = @WeekDefinitionId;
|
||||||
|
|
||||||
|
-- ثبت لاگ تغییرات کیف پول
|
||||||
|
INSERT INTO CMS.UserWalletChangeLogs (
|
||||||
|
WalletId,
|
||||||
|
CurrentBalance,
|
||||||
|
ChangeValue,
|
||||||
|
CurrentNetworkBalance,
|
||||||
|
ChangeNerworkValue,
|
||||||
|
CurrentDiscountBalance,
|
||||||
|
ChangeDiscountValue,
|
||||||
|
IsIncrease,
|
||||||
|
RefrenceId,
|
||||||
|
IsDeleted,
|
||||||
|
Created,
|
||||||
|
CreatedBy,
|
||||||
|
LastModified,
|
||||||
|
LastModifiedBy
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
uw.Id,
|
||||||
|
uw.Balance, -- CurrentBalance
|
||||||
|
0, -- ChangeValue (no change to main balance)
|
||||||
|
uw.NetworkBalance, -- CurrentNetworkBalance (after update)
|
||||||
|
cp.TotalAmount, -- ChangeNerworkValue
|
||||||
|
uw.DiscountBalance, -- CurrentDiscountBalance
|
||||||
|
0, -- ChangeDiscountValue
|
||||||
|
1, -- IsIncrease: true
|
||||||
|
cp.Id, -- RefrenceId: PayoutId
|
||||||
|
0, -- IsDeleted
|
||||||
|
@CalculatedAt,
|
||||||
|
'SP',
|
||||||
|
@CalculatedAt,
|
||||||
|
'SP'
|
||||||
|
FROM CMS.UserCommissionPayouts cp
|
||||||
|
INNER JOIN CMS.UserWallets uw ON uw.UserId = cp.UserId
|
||||||
|
WHERE cp.WeekDefinitionId = @WeekDefinitionId;
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- 10. بروزرسانی Pool
|
||||||
|
-- =============================================
|
||||||
|
UPDATE CMS.WeeklyCommissionPools
|
||||||
|
SET
|
||||||
|
IsCalculated = 1,
|
||||||
|
CalculatedAt = @CalculatedAt,
|
||||||
|
TotalBalances = @TotalBalances,
|
||||||
|
ValuePerBalance = @ValuePerBalance,
|
||||||
|
LastModified = @CalculatedAt,
|
||||||
|
LastModifiedBy = 'SP'
|
||||||
|
WHERE Id = @PoolId;
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
IF @@TRANCOUNT > 0
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
|
||||||
|
SET @ErrorMessage = ERROR_MESSAGE();
|
||||||
|
RAISERROR(@ErrorMessage, 16, 1);
|
||||||
|
END CATCH
|
||||||
|
END
|
||||||
|
GO
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Infrastructure.Services.Commission;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory برای ایجاد Strategy مناسب براساس Config
|
||||||
|
/// </summary>
|
||||||
|
public class CommissionCalculationStrategyFactory : ICommissionCalculationStrategyFactory
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly IWeekDefinitionRepository _weekRepository;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کلید Config برای انتخاب استراتژی
|
||||||
|
/// مقدار: "ORM" یا "SP"
|
||||||
|
/// پیشفرض: "ORM"
|
||||||
|
/// </summary>
|
||||||
|
private const string ConfigKey = "Commission.CalculationStrategy";
|
||||||
|
|
||||||
|
public CommissionCalculationStrategyFactory(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
IWeekDefinitionRepository weekRepository,
|
||||||
|
IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_weekRepository = weekRepository;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<ICommissionCalculationStrategy> CreateStrategyAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// خواندن Config از دیتابیس
|
||||||
|
var config = await _context.SystemConfigurations
|
||||||
|
.FirstOrDefaultAsync(x => x.Key == ConfigKey && x.IsActive, cancellationToken);
|
||||||
|
|
||||||
|
var strategyValue = config?.Value?.ToUpperInvariant() ?? "ORM";
|
||||||
|
|
||||||
|
var strategyType = strategyValue switch
|
||||||
|
{
|
||||||
|
"SP" or "STOREDPROCEDURE" or "STORED_PROCEDURE" => CommissionCalculationStrategyType.StoredProcedure,
|
||||||
|
_ => CommissionCalculationStrategyType.Orm
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateStrategy(strategyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICommissionCalculationStrategy CreateStrategy(CommissionCalculationStrategyType strategyType)
|
||||||
|
{
|
||||||
|
return strategyType switch
|
||||||
|
{
|
||||||
|
CommissionCalculationStrategyType.StoredProcedure =>
|
||||||
|
new StoredProcedureCommissionCalculationStrategy(_context),
|
||||||
|
|
||||||
|
_ => // Orm is default
|
||||||
|
new OrmCommissionCalculationStrategy(_context, _weekRepository)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+538
@@ -0,0 +1,538 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities.Club;
|
||||||
|
using CMSMicroservice.Domain.Entities.Commission;
|
||||||
|
using CMSMicroservice.Domain.Entities.Network;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Infrastructure.Services.Commission;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پیادهسازی محاسبه کمیسیون با استفاده از Entity Framework (ORM)
|
||||||
|
/// این روش برای دادههای کم مناسب است و دیباگ و تست راحتتری دارد
|
||||||
|
/// </summary>
|
||||||
|
public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
private readonly IWeekDefinitionRepository _weekRepository;
|
||||||
|
|
||||||
|
public OrmCommissionCalculationStrategy(
|
||||||
|
IApplicationDbContext context,
|
||||||
|
IWeekDefinitionRepository weekRepository)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_weekRepository = weekRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<int> CalculateWeeklyBalancesAsync(
|
||||||
|
long weekDefinitionId,
|
||||||
|
bool forceRecalculate,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var regorianWeekNumber = _weekRepository.GetGregorianWeekNumber(weekDefinitionId);
|
||||||
|
if (regorianWeekNumber == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"هفته {weekDefinitionId} در سیستم تعریف نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی وجود محاسبه قبلی
|
||||||
|
var existingBalances = await _context.NetworkWeeklyBalances
|
||||||
|
.Where(x => x.WeekDefinitionId == weekDefinitionId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (existingBalances.Any() && !forceRecalculate)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"تعادلهای هفته {weekDefinitionId} قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید");
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف محاسبات قبلی در صورت ForceRecalculate
|
||||||
|
if (existingBalances.Any())
|
||||||
|
{
|
||||||
|
_context.NetworkWeeklyBalances.RemoveRange(existingBalances);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت همه کاربرانی که عضو فعال باشگاه هستند
|
||||||
|
var activeClubMemberUserIds = await _context.ClubMemberships
|
||||||
|
.Where(c => c.IsActive)
|
||||||
|
.Select(c => c.UserId)
|
||||||
|
.ToHashSetAsync(cancellationToken);
|
||||||
|
|
||||||
|
// دریافت کاربران فعال در شبکه که عضو باشگاه هستند
|
||||||
|
var usersInNetwork = await _context.Users
|
||||||
|
.Where(x => activeClubMemberUserIds.Contains(x.Id))
|
||||||
|
.Select(x => new { x.Id })
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
// دریافت باقیماندههای هفته قبل
|
||||||
|
var previousWeekDefinitionId = GetPreviousWeekDefinitionId(weekDefinitionId);
|
||||||
|
Dictionary<long, (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers;
|
||||||
|
|
||||||
|
if (previousWeekDefinitionId.HasValue)
|
||||||
|
{
|
||||||
|
previousWeekCarryovers = await _context.NetworkWeeklyBalances
|
||||||
|
.Where(x => x.WeekDefinitionId == previousWeekDefinitionId.Value)
|
||||||
|
.ToDictionaryAsync(
|
||||||
|
x => x.UserId,
|
||||||
|
x => (x.LeftLegRemainder, x.RightLegRemainder),
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
previousWeekCarryovers = new Dictionary<long, (int, int)>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var balancesList = new List<NetworkWeeklyBalance>();
|
||||||
|
var calculatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
// خواندن یکباره Configuration ها
|
||||||
|
var configs = await _context.SystemConfigurations
|
||||||
|
.Where(x => x.IsActive && (
|
||||||
|
x.Key == "Commission.MaxWeeklyBalancesPerLeg" ||
|
||||||
|
x.Key == "Commission.MaxNetworkLevel"))
|
||||||
|
.ToDictionaryAsync(x => x.Key, x => x.Value, cancellationToken);
|
||||||
|
|
||||||
|
var maxBalancesPerLeg = int.Parse(configs.GetValueOrDefault("Commission.MaxWeeklyBalancesPerLeg", "300"));
|
||||||
|
var maxNetworkLevel = int.Parse(configs.GetValueOrDefault("Commission.MaxNetworkLevel", "15"));
|
||||||
|
|
||||||
|
foreach (var user in usersInNetwork.OrderBy(o => o.Id))
|
||||||
|
{
|
||||||
|
// دریافت باقیمانده هفته قبل
|
||||||
|
var leftCarryover = 0;
|
||||||
|
var rightCarryover = 0;
|
||||||
|
if (previousWeekCarryovers.ContainsKey(user.Id))
|
||||||
|
{
|
||||||
|
leftCarryover = previousWeekCarryovers[user.Id].LeftLegRemainder;
|
||||||
|
rightCarryover = previousWeekCarryovers[user.Id].RightLegRemainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// محاسبه تعداد اعضای جدید در این هفته (تا maxNetworkLevel لول پایینتر)
|
||||||
|
var leftNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Left, weekDefinitionId, maxNetworkLevel, cancellationToken);
|
||||||
|
var rightNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Right, weekDefinitionId, maxNetworkLevel, cancellationToken);
|
||||||
|
|
||||||
|
// محاسبه مجموع هر پا (جدید + باقیمانده)
|
||||||
|
var leftTotal = leftNewMembers + leftCarryover;
|
||||||
|
var rightTotal = rightNewMembers + rightCarryover;
|
||||||
|
|
||||||
|
// تعادل = کمترین مقدار بین چپ و راست
|
||||||
|
var totalBalances = Math.Min(leftTotal, rightTotal);
|
||||||
|
|
||||||
|
// باقیمانده برای هفته بعد
|
||||||
|
var leftRemainder = leftTotal - totalBalances;
|
||||||
|
var rightRemainder = rightTotal - totalBalances;
|
||||||
|
|
||||||
|
// اعمال سقف 300
|
||||||
|
var cappedBalances = Math.Min(totalBalances, maxBalancesPerLeg);
|
||||||
|
|
||||||
|
// محاسبه فلش
|
||||||
|
var flushedPerSide = totalBalances - cappedBalances;
|
||||||
|
var totalFlushed = flushedPerSide * 2;
|
||||||
|
|
||||||
|
var balance = new NetworkWeeklyBalance
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
WeekDefinitionId = weekDefinitionId,
|
||||||
|
LeftLegNewMembers = leftNewMembers,
|
||||||
|
RightLegNewMembers = rightNewMembers,
|
||||||
|
LeftLegCarryover = leftCarryover,
|
||||||
|
RightLegCarryover = rightCarryover,
|
||||||
|
LeftLegTotal = leftTotal,
|
||||||
|
RightLegTotal = rightTotal,
|
||||||
|
TotalBalances = cappedBalances,
|
||||||
|
LeftLegRemainder = leftRemainder,
|
||||||
|
RightLegRemainder = rightRemainder,
|
||||||
|
FlushedPerSide = flushedPerSide,
|
||||||
|
TotalFlushed = totalFlushed,
|
||||||
|
SubordinateBalances = 0,
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
LeftLegBalances = leftTotal,
|
||||||
|
RightLegBalances = rightTotal,
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
WeeklyPoolContribution = 0,
|
||||||
|
CalculatedAt = calculatedAt,
|
||||||
|
IsExpired = false
|
||||||
|
};
|
||||||
|
|
||||||
|
balancesList.Add(balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.NetworkWeeklyBalances.AddRangeAsync(balancesList, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// محاسبه تعادل زیرمجموعه
|
||||||
|
var balancesDictionary = balancesList.ToDictionary(x => x.UserId);
|
||||||
|
|
||||||
|
foreach (var balance in balancesList)
|
||||||
|
{
|
||||||
|
var subordinateBalances = await CalculateSubordinateBalancesAsync(
|
||||||
|
balance.UserId,
|
||||||
|
balancesDictionary,
|
||||||
|
maxNetworkLevel,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
balance.SubordinateBalances = subordinateBalances;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.NetworkWeeklyBalances.UpdateRange(balancesList);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return balancesList.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<long> CalculateWeeklyCommissionPoolAsync(
|
||||||
|
long weekDefinitionId,
|
||||||
|
bool forceRecalculate,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var weekDefinition = _weekRepository.GetGregorianWeekNumber(weekDefinitionId);
|
||||||
|
if (weekDefinition == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"هفته {weekDefinitionId} در سیستم تعریف نشده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی وجود استخر
|
||||||
|
var existingPool = await _context.WeeklyCommissionPools
|
||||||
|
.FirstOrDefaultAsync(x => x.WeekDefinitionId == weekDefinitionId, cancellationToken);
|
||||||
|
|
||||||
|
if (existingPool == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Pool هفته {weekDefinitionId} وجود ندارد. " +
|
||||||
|
"Pool باید در هنگام فعالسازی باشگاه مشتریان ایجاد شده باشد"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingPool.IsCalculated && !forceRecalculate)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"استخر کمیسیون هفته {weekDefinitionId} قبلاً محاسبه شده است");
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی وجود تعادلهای هفتگی
|
||||||
|
var weeklyBalances = await _context.NetworkWeeklyBalances
|
||||||
|
.Where(x => x.WeekDefinitionId == weekDefinitionId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!weeklyBalances.Any())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"تعادلهای هفته {weekDefinitionId} هنوز محاسبه نشده است. ابتدا CalculateWeeklyBalances را اجرا کنید");
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalPoolAmount = existingPool.TotalPoolAmount;
|
||||||
|
var totalBalancesInNetwork = weeklyBalances.Sum(x => x.TotalBalances);
|
||||||
|
|
||||||
|
long valuePerBalance = 0;
|
||||||
|
if (totalBalancesInNetwork > 0)
|
||||||
|
{
|
||||||
|
valuePerBalance = totalPoolAmount / totalBalancesInNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
// بهروزرسانی Pool
|
||||||
|
existingPool.TotalBalances = totalBalancesInNetwork;
|
||||||
|
existingPool.ValuePerBalance = valuePerBalance;
|
||||||
|
existingPool.IsCalculated = true;
|
||||||
|
existingPool.CalculatedAt = DateTime.Now;
|
||||||
|
|
||||||
|
_context.WeeklyCommissionPools.Update(existingPool);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// حذف پرداختهای قبلی در صورت ForceRecalculate
|
||||||
|
if (forceRecalculate)
|
||||||
|
{
|
||||||
|
var oldPayouts = await _context.UserCommissionPayouts
|
||||||
|
.Where(p => p.WeekDefinitionId == weekDefinitionId)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (oldPayouts.Any())
|
||||||
|
{
|
||||||
|
var oldPayoutIds = oldPayouts.Select(p => p.Id).ToList();
|
||||||
|
|
||||||
|
// برگرداندن NetworkBalance به حالت قبل (Revert)
|
||||||
|
await RevertUserWalletsAsync(oldPayoutIds, cancellationToken);
|
||||||
|
|
||||||
|
// حذف تاریخچهها
|
||||||
|
var oldHistories = await _context.CommissionPayoutHistories
|
||||||
|
.Where(h => oldPayoutIds.Contains(h.UserCommissionPayoutId))
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (oldHistories.Any())
|
||||||
|
{
|
||||||
|
_context.CommissionPayoutHistories.RemoveRange(oldHistories);
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.UserCommissionPayouts.RemoveRange(oldPayouts);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ثبت پرداخت برای کاربرانی که تعادل دارند
|
||||||
|
var payouts = new List<UserCommissionPayout>();
|
||||||
|
|
||||||
|
foreach (var balance in weeklyBalances)
|
||||||
|
{
|
||||||
|
var userBalance = balance.TotalBalances;
|
||||||
|
if (userBalance <= 0) continue;
|
||||||
|
|
||||||
|
var totalAmount = (long)(userBalance * valuePerBalance);
|
||||||
|
|
||||||
|
var payout = new UserCommissionPayout
|
||||||
|
{
|
||||||
|
UserId = balance.UserId,
|
||||||
|
WeekDefinitionId = weekDefinitionId,
|
||||||
|
WeeklyPoolId = existingPool.Id,
|
||||||
|
BalancesEarned = userBalance,
|
||||||
|
ValuePerBalance = valuePerBalance,
|
||||||
|
TotalAmount = totalAmount,
|
||||||
|
Status = CommissionPayoutStatus.Pending,
|
||||||
|
PaidAt = null,
|
||||||
|
WithdrawalMethod = null,
|
||||||
|
IbanNumber = null,
|
||||||
|
WithdrawnAt = null
|
||||||
|
};
|
||||||
|
|
||||||
|
payouts.Add(payout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payouts.Any())
|
||||||
|
{
|
||||||
|
await _context.UserCommissionPayouts.AddRangeAsync(payouts, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// ثبت تاریخچه
|
||||||
|
var historyList = payouts.Select(payout => new CommissionPayoutHistory
|
||||||
|
{
|
||||||
|
UserCommissionPayoutId = payout.Id,
|
||||||
|
UserId = payout.UserId,
|
||||||
|
WeekDefinitionId = weekDefinitionId,
|
||||||
|
AmountBefore = 0,
|
||||||
|
AmountAfter = payout.TotalAmount,
|
||||||
|
OldStatus = default(CommissionPayoutStatus),
|
||||||
|
NewStatus = CommissionPayoutStatus.Pending,
|
||||||
|
Action = CommissionPayoutAction.Created,
|
||||||
|
PerformedBy = "System",
|
||||||
|
Reason = "پردازش خودکار کمیسیون هفتگی (ORM)"
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
await _context.CommissionPayoutHistories.AddRangeAsync(historyList, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// شارژ کیف پول کاربران
|
||||||
|
await ChargeUserWalletsAsync(payouts, weekDefinitionId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingPool.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private long? GetPreviousWeekDefinitionId(long currentWeekDefinitionId)
|
||||||
|
{
|
||||||
|
var currentWeekNumber = _weekRepository.GetGregorianWeekNumber(currentWeekDefinitionId);
|
||||||
|
if (currentWeekNumber == null) return null;
|
||||||
|
|
||||||
|
var previousWeekNumber = _weekRepository.GetPreviousWeekNumber(currentWeekNumber);
|
||||||
|
if (previousWeekNumber == null) return null;
|
||||||
|
|
||||||
|
return _weekRepository.GetWeekDefinitionId(previousWeekNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> CountNewMembersInLeg(long userId, NetworkLeg leg, long weekDefinitionId, int maxLevel, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var startDateEndDate = _weekRepository.GetWeekDateRange(weekDefinitionId);
|
||||||
|
var count = await CountNewMembersRecursive(userId, leg, startDateEndDate.Value.startDate, startDateEndDate.Value.endDate, 0, maxLevel, cancellationToken);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> CountNewMembersRecursive(
|
||||||
|
long userId,
|
||||||
|
NetworkLeg leg,
|
||||||
|
DateTime startDate,
|
||||||
|
DateTime endDate,
|
||||||
|
int currentLevel,
|
||||||
|
int maxLevel,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (currentLevel >= maxLevel)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var child = await _context.Users
|
||||||
|
.FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == leg, cancellationToken);
|
||||||
|
|
||||||
|
if (child == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var membership = await _context.ClubMemberships
|
||||||
|
.FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsActive, cancellationToken);
|
||||||
|
|
||||||
|
if (membership?.ActivatedAt >= startDate && membership?.ActivatedAt <= endDate)
|
||||||
|
{
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childLeft = await CountNewMembersRecursive(child.Id, NetworkLeg.Left, startDate, endDate, currentLevel + 1, maxLevel, cancellationToken);
|
||||||
|
var childRight = await CountNewMembersRecursive(child.Id, NetworkLeg.Right, startDate, endDate, currentLevel + 1, maxLevel, cancellationToken);
|
||||||
|
|
||||||
|
return count + childLeft + childRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> CalculateSubordinateBalancesAsync(
|
||||||
|
long userId,
|
||||||
|
Dictionary<long, NetworkWeeklyBalance> allBalances,
|
||||||
|
int maxLevel,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var subordinates = await GetSubordinatesRecursive(userId, 1, maxLevel, cancellationToken);
|
||||||
|
var totalSubordinateBalances = 0;
|
||||||
|
|
||||||
|
foreach (var subordinateId in subordinates)
|
||||||
|
{
|
||||||
|
if (allBalances.ContainsKey(subordinateId))
|
||||||
|
{
|
||||||
|
totalSubordinateBalances += allBalances[subordinateId].TotalBalances;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalSubordinateBalances;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<long>> GetSubordinatesRecursive(
|
||||||
|
long userId,
|
||||||
|
int currentLevel,
|
||||||
|
int maxLevel,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (currentLevel > maxLevel)
|
||||||
|
{
|
||||||
|
return new List<long>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<long>();
|
||||||
|
|
||||||
|
var children = await _context.Users
|
||||||
|
.Where(x => x.NetworkParentId == userId)
|
||||||
|
.Select(x => x.Id)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
result.AddRange(children);
|
||||||
|
|
||||||
|
foreach (var childId in children)
|
||||||
|
{
|
||||||
|
var grandChildren = await GetSubordinatesRecursive(childId, currentLevel + 1, maxLevel, cancellationToken);
|
||||||
|
result.AddRange(grandChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ChargeUserWalletsAsync(
|
||||||
|
List<UserCommissionPayout> payouts,
|
||||||
|
long weekDefinitionId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var userIds = payouts.Select(p => p.UserId).Distinct().ToList();
|
||||||
|
|
||||||
|
var existingWallets = await _context.UserWallets
|
||||||
|
.Where(w => userIds.Contains(w.UserId))
|
||||||
|
.ToDictionaryAsync(w => w.UserId, cancellationToken);
|
||||||
|
|
||||||
|
var newWallets = new List<UserWallet>();
|
||||||
|
var walletLogs = new List<UserWalletChangeLog>();
|
||||||
|
|
||||||
|
foreach (var payout in payouts)
|
||||||
|
{
|
||||||
|
if (payout.TotalAmount <= 0) continue;
|
||||||
|
|
||||||
|
UserWallet wallet;
|
||||||
|
|
||||||
|
if (existingWallets.TryGetValue(payout.UserId, out var existingWallet))
|
||||||
|
{
|
||||||
|
wallet = existingWallet;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wallet = new UserWallet
|
||||||
|
{
|
||||||
|
UserId = payout.UserId,
|
||||||
|
Balance = 0,
|
||||||
|
NetworkBalance = 0,
|
||||||
|
DiscountBalance = 0
|
||||||
|
};
|
||||||
|
newWallets.Add(wallet);
|
||||||
|
existingWallets[payout.UserId] = wallet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newWallets.Any())
|
||||||
|
{
|
||||||
|
await _context.UserWallets.AddRangeAsync(newWallets, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var payout in payouts)
|
||||||
|
{
|
||||||
|
if (payout.TotalAmount <= 0) continue;
|
||||||
|
|
||||||
|
var wallet = existingWallets[payout.UserId];
|
||||||
|
wallet.NetworkBalance += payout.TotalAmount;
|
||||||
|
|
||||||
|
var walletLog = new UserWalletChangeLog
|
||||||
|
{
|
||||||
|
WalletId = wallet.Id,
|
||||||
|
CurrentBalance = wallet.Balance,
|
||||||
|
ChangeValue = 0,
|
||||||
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
||||||
|
ChangeNerworkValue = payout.TotalAmount,
|
||||||
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
||||||
|
ChangeDiscountValue = 0,
|
||||||
|
IsIncrease = true,
|
||||||
|
RefrenceId = payout.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
walletLogs.Add(walletLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.UserWalletChangeLogs.AddRangeAsync(walletLogs, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RevertUserWalletsAsync(
|
||||||
|
List<long> oldPayoutIds,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var oldWalletLogs = await _context.UserWalletChangeLogs
|
||||||
|
.Where(l => l.RefrenceId.HasValue && oldPayoutIds.Contains(l.RefrenceId.Value))
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!oldWalletLogs.Any()) return;
|
||||||
|
|
||||||
|
var walletIds = oldWalletLogs.Select(l => l.WalletId).Distinct().ToList();
|
||||||
|
var wallets = await _context.UserWallets
|
||||||
|
.Where(w => walletIds.Contains(w.Id))
|
||||||
|
.ToDictionaryAsync(w => w.Id, cancellationToken);
|
||||||
|
|
||||||
|
foreach (var log in oldWalletLogs)
|
||||||
|
{
|
||||||
|
if (wallets.TryGetValue(log.WalletId, out var wallet))
|
||||||
|
{
|
||||||
|
wallet.NetworkBalance -= log.ChangeNerworkValue;
|
||||||
|
if (wallet.NetworkBalance < 0)
|
||||||
|
{
|
||||||
|
wallet.NetworkBalance = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.UserWalletChangeLogs.RemoveRange(oldWalletLogs);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Infrastructure.Services.Commission;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پیادهسازی محاسبه کمیسیون با استفاده از Stored Procedure
|
||||||
|
/// این روش برای دادههای زیاد بهینهتر است و از CTE استفاده میکند
|
||||||
|
/// </summary>
|
||||||
|
public class StoredProcedureCommissionCalculationStrategy : ICommissionCalculationStrategy
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public StoredProcedureCommissionCalculationStrategy(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<int> CalculateWeeklyBalancesAsync(
|
||||||
|
long weekDefinitionId,
|
||||||
|
bool forceRecalculate,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// دسترسی به DbContext واقعی برای اجرای SP
|
||||||
|
var dbContext = _context as DbContext;
|
||||||
|
if (dbContext == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("DbContext برای اجرای Stored Procedure در دسترس نیست");
|
||||||
|
}
|
||||||
|
|
||||||
|
var connection = dbContext.Database.GetDbConnection();
|
||||||
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "CMS.sp_CalculateWeeklyBalances";
|
||||||
|
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||||
|
command.CommandTimeout = 300; // 5 دقیقه timeout
|
||||||
|
|
||||||
|
// پارامترها
|
||||||
|
var paramWeekDefinitionId = command.CreateParameter();
|
||||||
|
paramWeekDefinitionId.ParameterName = "@WeekDefinitionId";
|
||||||
|
paramWeekDefinitionId.Value = weekDefinitionId;
|
||||||
|
command.Parameters.Add(paramWeekDefinitionId);
|
||||||
|
|
||||||
|
var paramForceRecalculate = command.CreateParameter();
|
||||||
|
paramForceRecalculate.ParameterName = "@ForceRecalculate";
|
||||||
|
paramForceRecalculate.Value = forceRecalculate;
|
||||||
|
command.Parameters.Add(paramForceRecalculate);
|
||||||
|
|
||||||
|
// پارامتر خروجی
|
||||||
|
var paramRowCount = command.CreateParameter();
|
||||||
|
paramRowCount.ParameterName = "@RowCount";
|
||||||
|
paramRowCount.Direction = System.Data.ParameterDirection.Output;
|
||||||
|
paramRowCount.DbType = System.Data.DbType.Int32;
|
||||||
|
command.Parameters.Add(paramRowCount);
|
||||||
|
|
||||||
|
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rowCount = (int)(paramRowCount.Value ?? 0);
|
||||||
|
return rowCount;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await connection.CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<long> CalculateWeeklyCommissionPoolAsync(
|
||||||
|
long weekDefinitionId,
|
||||||
|
bool forceRecalculate,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var dbContext = _context as DbContext;
|
||||||
|
if (dbContext == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("DbContext برای اجرای Stored Procedure در دسترس نیست");
|
||||||
|
}
|
||||||
|
|
||||||
|
var connection = dbContext.Database.GetDbConnection();
|
||||||
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "CMS.sp_CalculateWeeklyCommissionPool";
|
||||||
|
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||||
|
command.CommandTimeout = 300; // 5 دقیقه timeout
|
||||||
|
|
||||||
|
// پارامترها
|
||||||
|
var paramWeekDefinitionId = command.CreateParameter();
|
||||||
|
paramWeekDefinitionId.ParameterName = "@WeekDefinitionId";
|
||||||
|
paramWeekDefinitionId.Value = weekDefinitionId;
|
||||||
|
command.Parameters.Add(paramWeekDefinitionId);
|
||||||
|
|
||||||
|
var paramForceRecalculate = command.CreateParameter();
|
||||||
|
paramForceRecalculate.ParameterName = "@ForceRecalculate";
|
||||||
|
paramForceRecalculate.Value = forceRecalculate;
|
||||||
|
command.Parameters.Add(paramForceRecalculate);
|
||||||
|
|
||||||
|
// پارامتر خروجی - شناسه Pool
|
||||||
|
var paramPoolId = command.CreateParameter();
|
||||||
|
paramPoolId.ParameterName = "@PoolId";
|
||||||
|
paramPoolId.Direction = System.Data.ParameterDirection.Output;
|
||||||
|
paramPoolId.DbType = System.Data.DbType.Int64;
|
||||||
|
command.Parameters.Add(paramPoolId);
|
||||||
|
|
||||||
|
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||||
|
|
||||||
|
var poolId = (long)(paramPoolId.Value ?? 0L);
|
||||||
|
return poolId;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await connection.CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.155</Version>
|
<Version>0.0.156</Version>
|
||||||
<DebugType>None</DebugType>
|
<DebugType>None</DebugType>
|
||||||
<DebugSymbols>False</DebugSymbols>
|
<DebugSymbols>False</DebugSymbols>
|
||||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetAllClubMemberships;
|
||||||
|
using CMSMicroservice.Application.ClubMembershipCQ.Queries.GetClubStatistics;
|
||||||
|
using CMSMicroservice.Protobuf.Protos.ClubMembership;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Mapster;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.WebApi.Common.Mappings;
|
||||||
|
|
||||||
|
public class ClubMembershipProfile : IRegister
|
||||||
|
{
|
||||||
|
public void Register(TypeAdapterConfig config)
|
||||||
|
{
|
||||||
|
// =====================
|
||||||
|
// GetAllClubMemberships
|
||||||
|
// =====================
|
||||||
|
|
||||||
|
// GetAllClubMembershipsRequest -> GetAllClubMembershipsQuery
|
||||||
|
config.NewConfig<GetAllClubMembershipsRequest, GetAllClubMembershipsQuery>()
|
||||||
|
.MapWith(src => new GetAllClubMembershipsQuery
|
||||||
|
{
|
||||||
|
PaginationState = new Application.Common.Models.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = src.PageIndex > 0 ? src.PageIndex : 1,
|
||||||
|
PageSize = src.PageSize > 0 ? src.PageSize : 20
|
||||||
|
},
|
||||||
|
Filter = new GetAllClubMembershipsFilter
|
||||||
|
{
|
||||||
|
UserId = src.UserId,
|
||||||
|
IsActive = src.IsActive
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GetAllClubMembershipsResponseDto -> GetAllClubMembershipsResponse
|
||||||
|
config.NewConfig<GetAllClubMembershipsResponseDto, GetAllClubMembershipsResponse>()
|
||||||
|
.MapWith(src => new GetAllClubMembershipsResponse
|
||||||
|
{
|
||||||
|
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
||||||
|
{
|
||||||
|
CurrentPage = src.MetaData.CurrentPage,
|
||||||
|
PageSize = src.MetaData.PageSize,
|
||||||
|
TotalCount = src.MetaData.TotalCount,
|
||||||
|
TotalPage = src.MetaData.TotalPage
|
||||||
|
},
|
||||||
|
Models = { src.Models.Select(MapToModel) }
|
||||||
|
});
|
||||||
|
|
||||||
|
// =====================
|
||||||
|
// GetClubStatistics
|
||||||
|
// =====================
|
||||||
|
|
||||||
|
// GetClubStatisticsRequest -> GetClubStatisticsQuery
|
||||||
|
config.NewConfig<GetClubStatisticsRequest, GetClubStatisticsQuery>()
|
||||||
|
.MapWith(src => new GetClubStatisticsQuery());
|
||||||
|
|
||||||
|
// GetClubStatisticsResponseDto -> GetClubStatisticsResponse
|
||||||
|
config.NewConfig<GetClubStatisticsResponseDto, GetClubStatisticsResponse>()
|
||||||
|
.MapWith(src => new GetClubStatisticsResponse
|
||||||
|
{
|
||||||
|
TotalMembers = src.TotalMembers,
|
||||||
|
ActiveMembers = src.ActiveMembers,
|
||||||
|
InactiveMembers = src.InactiveMembers,
|
||||||
|
ExpiredMembers = src.ExpiredMembers,
|
||||||
|
ActivePercentage = src.ActivePercentage,
|
||||||
|
TotalRevenue = src.TotalRevenue,
|
||||||
|
AverageMembershipDurationDays = src.AverageMembershipDurationDays,
|
||||||
|
ExpiringSoonCount = src.ExpiringSoonCount,
|
||||||
|
PackageDistribution = { src.PackageDistribution.Select(p => new PackageLevelDistribution
|
||||||
|
{
|
||||||
|
PackageId = p.PackageId,
|
||||||
|
PackageName = p.PackageName ?? string.Empty,
|
||||||
|
MemberCount = p.MemberCount,
|
||||||
|
Percentage = p.Percentage
|
||||||
|
}) },
|
||||||
|
MonthlyTrend = { src.MonthlyTrend.Select(t => new MonthlyMembershipTrend
|
||||||
|
{
|
||||||
|
Month = t.Month ?? string.Empty,
|
||||||
|
Activations = t.Activations,
|
||||||
|
Expirations = t.Expirations,
|
||||||
|
NetChange = t.NetChange
|
||||||
|
}) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClubMembershipModel MapToModel(GetAllClubMembershipsResponseModel src)
|
||||||
|
{
|
||||||
|
return new ClubMembershipModel
|
||||||
|
{
|
||||||
|
Id = src.Id,
|
||||||
|
UserId = src.UserId,
|
||||||
|
UserName = src.UserName ?? string.Empty,
|
||||||
|
PackageId = 0, // Not available in current entity
|
||||||
|
PackageName = string.Empty, // Not available in current entity
|
||||||
|
ActivationCode = string.Empty, // Not available in current entity
|
||||||
|
ActivatedAt = src.ActivatedAt.HasValue
|
||||||
|
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.ActivatedAt.Value, DateTimeKind.Utc))
|
||||||
|
: null,
|
||||||
|
ExpiresAt = null, // Not available in current entity
|
||||||
|
IsActive = src.IsActive,
|
||||||
|
IsExpired = false, // Calculate based on ExpiresAt if available
|
||||||
|
Created = Timestamp.FromDateTime(src.Created.UtcDateTime)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
|
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkTree;
|
||||||
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetUserNetworkPosition;
|
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetUserNetworkPosition;
|
||||||
|
using CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;
|
||||||
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
|
||||||
using CMSMicroservice.Domain.Enums;
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
|
||||||
@@ -9,6 +10,10 @@ public class NetworkMembershipProfile : IRegister
|
|||||||
{
|
{
|
||||||
void IRegister.Register(TypeAdapterConfig config)
|
void IRegister.Register(TypeAdapterConfig config)
|
||||||
{
|
{
|
||||||
|
// GetNetworkStatisticsRequest -> GetNetworkStatisticsQuery
|
||||||
|
config.NewConfig<GetNetworkStatisticsRequest, GetNetworkStatisticsQuery>()
|
||||||
|
.MapWith(src => new GetNetworkStatisticsQuery());
|
||||||
|
|
||||||
// Request mapping
|
// Request mapping
|
||||||
config.NewConfig<GetNetworkTreeRequest, GetNetworkTreeQuery>()
|
config.NewConfig<GetNetworkTreeRequest, GetNetworkTreeQuery>()
|
||||||
.Map(dest => dest.UserId, src => src.UserId)
|
.Map(dest => dest.UserId, src => src.UserId)
|
||||||
|
|||||||
Reference in New Issue
Block a user