Phase 1: Package-Based Domain Infrastructure

ENTITIES:
- Package: +11 fields (SortOrder, IsActive, IsBasePackage, SupportsDaya/Direct,
  ActivationFee, DiscountMultiplier, MagicWalletMultiplier, MaxBalancesPerLeg,
  MaxNetworkLevel, MagicWalletMaxDeposit/Credit) + nav props
- PackageFeature: NEW junction entity (Package × ClubFeature) for feature DIFF
- ClubMembership: +4 fields (FirstActivationDate/PackageId, LastActivation/PackageId)
  ActivatedAt kept as [Obsolete] for backward compat
- ClubMembershipCycle: +PackageId FK (tracks which package per cycle)
- WeeklyCommissionPool: +PackageId FK (separate pool per package)
- UserCommissionPayout: +PackageId FK (payout per package)
- NetworkWeeklyBalance: +PackageId FK (balance per package)
- UserWalletChangeLog: +PackageId? FK (optional package tracking)

EF CONFIGS:
- PackageConfiguration: 11 new field configs + indexes
- PackageFeatureConfiguration: NEW (unique PackageId+ClubFeatureId)
- ClubMembershipConfiguration: First/Last Package FKs + LastActivationDate index
- ClubMembershipCycleConfiguration: Package FK
- WeeklyCommissionPoolConfiguration: Package FK + unique(WeekDef,Package)
- UserCommissionPayoutConfiguration: Package FK + unique(User,WeekDef,Package)
- NetworkWeeklyBalanceConfiguration: Package FK + unique(User,WeekDef,Package)
- UserWalletChangeLogConfiguration: Package FK (nullable)

DBCONTEXT:
- DbSet<PackageFeature> added to ApplicationDbContext + IApplicationDbContext

SYSTEMCONSTANTS:
- 9 constants marked [Obsolete] → moved to Package entity
  (ClubMembershipGiftValue, ClubActivationFee, BasePackageAmount, DayaLoanAmount,
   MagicWalletMultiplier, MagicWalletMaxDeposit, MagicWalletMaxCredit,
   CommissionMaxWeeklyBalancesPerLeg, CommissionMaxNetworkLevel)

Build: 0 errors, 610 warnings (40 new = expected Obsolete CS0618)
This commit is contained in:
masoodafar-web
2026-02-25 22:32:42 +03:30
parent fe3edd178d
commit ae92ab8697
19 changed files with 339 additions and 17 deletions
@@ -110,6 +110,7 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext
public DbSet<UserClubFeature> UserClubFeatures => Set<UserClubFeature>();
public DbSet<ClubMembershipHistory> ClubMembershipHistories => Set<ClubMembershipHistory>();
public DbSet<ClubMembershipCycle> ClubMembershipCycles => Set<ClubMembershipCycle>();
public DbSet<PackageFeature> PackageFeatures => Set<PackageFeature>();
// Network
public DbSet<NetworkWeeklyBalance> NetworkWeeklyBalances => Set<NetworkWeeklyBalance>();
@@ -23,12 +23,32 @@ public class ClubMembershipConfiguration : IEntityTypeConfiguration<ClubMembersh
builder.Property(entity => entity.GiftValue).IsRequired();
builder.Property(entity => entity.TotalEarned).IsRequired();
// فیلدهای جدید First/Last Activation
builder.Property(entity => entity.FirstActivationDate).IsRequired(false);
builder.Property(entity => entity.FirstPackageId).IsRequired(false);
builder.Property(entity => entity.LastActivationDate).IsRequired(false);
builder.Property(entity => entity.LastPackageId).IsRequired(false);
// رابطه یک‌به‌یک با User
builder.HasOne(entity => entity.User)
.WithOne(u => u.ClubMembership)
.HasForeignKey<ClubMembership>(entity => entity.UserId)
.OnDelete(DeleteBehavior.Restrict);
// رابطه با Package — اولین پکیج خریداری‌شده
builder.HasOne(entity => entity.FirstPackage)
.WithMany()
.HasForeignKey(entity => entity.FirstPackageId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
// رابطه با Package — آخرین پکیج خریداری‌شده
builder.HasOne(entity => entity.LastPackage)
.WithMany()
.HasForeignKey(entity => entity.LastPackageId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
// Index برای UserId (یونیک برای یک‌به‌یک)
builder.HasIndex(e => e.UserId)
.IsUnique()
@@ -37,5 +57,9 @@ public class ClubMembershipConfiguration : IEntityTypeConfiguration<ClubMembersh
// Index برای IsActive
builder.HasIndex(e => e.IsActive)
.HasDatabaseName("IX_ClubMembership_IsActive");
// Index برای LastActivationDate (تشخیص فعال‌سازی هفتگی Q22)
builder.HasIndex(e => e.LastActivationDate)
.HasDatabaseName("IX_ClubMembership_LastActivationDate");
}
}
@@ -21,6 +21,7 @@ public class ClubMembershipCycleConfiguration : IEntityTypeConfiguration<ClubMem
builder.Property(entity => entity.MagicCompletedAt).IsRequired(false);
builder.Property(entity => entity.PurchaseMethod).IsRequired();
builder.Property(entity => entity.PackageAmount).IsRequired();
builder.Property(entity => entity.PackageId).IsRequired();
builder.Property(entity => entity.IsCurrentCycle)
.IsRequired()
.HasDefaultValue(false);
@@ -36,6 +37,11 @@ public class ClubMembershipCycleConfiguration : IEntityTypeConfiguration<ClubMem
.HasForeignKey(entity => entity.ClubMembershipId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(entity => entity.Package)
.WithMany()
.HasForeignKey(entity => entity.PackageId)
.OnDelete(DeleteBehavior.Restrict);
// Indexes
builder.HasIndex(e => new { e.UserId, e.IsCurrentCycle })
.HasDatabaseName("IX_ClubMembershipCycle_UserId_IsCurrentCycle");
@@ -18,6 +18,7 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
builder.Property(entity => entity.UserId).IsRequired();
builder.Property(entity => entity.WeekDefinitionId).IsRequired();
builder.Property(entity => entity.PackageId).IsRequired();
builder.Property(entity => entity.LeftLegBalances).IsRequired();
builder.Property(entity => entity.RightLegBalances).IsRequired();
builder.Property(entity => entity.TotalBalances).IsRequired();
@@ -37,10 +38,16 @@ public class NetworkWeeklyBalanceConfiguration : IEntityTypeConfiguration<Networ
.HasForeignKey(entity => entity.WeekDefinitionId)
.OnDelete(DeleteBehavior.Restrict);
// Composite Index برای UserId و WeekDefinitionId
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId })
// رابطه با Package
builder.HasOne(entity => entity.Package)
.WithMany()
.HasForeignKey(entity => entity.PackageId)
.OnDelete(DeleteBehavior.Restrict);
// Composite Index: هر کاربر × هر هفته × هر پکیج فقط یک رکورد
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId })
.IsUnique()
.HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId");
.HasDatabaseName("IX_NetworkWeeklyBalance_User_WeekDef_Package");
// Index برای WeekDefinitionId
builder.HasIndex(e => e.WeekDefinitionId)
@@ -15,6 +15,28 @@ public class PackageConfiguration : IEntityTypeConfiguration<Package>
builder.Property(entity => entity.Description).IsRequired(true);
builder.Property(entity => entity.ImagePath).IsRequired(true);
builder.Property(entity => entity.Price).IsRequired(true);
// فیلدهای جدید پکیج
builder.Property(entity => entity.SortOrder).IsRequired().HasDefaultValue(0);
builder.Property(entity => entity.IsActive).IsRequired().HasDefaultValue(true);
builder.Property(entity => entity.IsBasePackage).IsRequired().HasDefaultValue(false);
builder.Property(entity => entity.SupportsDayaPurchase).IsRequired().HasDefaultValue(false);
builder.Property(entity => entity.SupportsDirectPurchase).IsRequired().HasDefaultValue(true);
builder.Property(entity => entity.ActivationFee).IsRequired().HasDefaultValue(0L);
builder.Property(entity => entity.DiscountMultiplier).IsRequired().HasDefaultValue(2.0m)
.HasPrecision(18, 4);
builder.Property(entity => entity.MagicWalletMultiplier).IsRequired().HasDefaultValue(2.5m)
.HasPrecision(18, 4);
builder.Property(entity => entity.MaxBalancesPerLeg).IsRequired().HasDefaultValue(300);
builder.Property(entity => entity.MaxNetworkLevel).IsRequired().HasDefaultValue(15);
builder.Property(entity => entity.MagicWalletMaxDeposit).IsRequired().HasDefaultValue(1_000_000_000L);
builder.Property(entity => entity.MagicWalletMaxCredit).IsRequired().HasDefaultValue(2_500_000_000L);
// Indexes
builder.HasIndex(e => e.IsActive)
.HasDatabaseName("IX_Package_IsActive");
builder.HasIndex(e => e.SortOrder)
.HasDatabaseName("IX_Package_SortOrder");
}
}
@@ -0,0 +1,41 @@
using CMSMicroservice.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CMSMicroservice.Infrastructure.Persistence.Configurations;
/// <summary>
/// EF Configuration — ارتباط پکیج–فیچر
/// </summary>
public class PackageFeatureConfiguration : IEntityTypeConfiguration<PackageFeature>
{
public void Configure(EntityTypeBuilder<PackageFeature> builder)
{
builder.HasQueryFilter(p => !p.IsDeleted);
builder.Ignore(entity => entity.DomainEvents);
builder.HasKey(entity => entity.Id);
builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.PackageId).IsRequired();
builder.Property(entity => entity.ClubFeatureId).IsRequired();
builder.Property(entity => entity.IsIncluded).IsRequired().HasDefaultValue(true);
// رابطه با Package
builder.HasOne(entity => entity.Package)
.WithMany(p => p.PackageFeatures)
.HasForeignKey(entity => entity.PackageId)
.OnDelete(DeleteBehavior.Cascade);
// رابطه با ClubFeature
builder.HasOne(entity => entity.ClubFeature)
.WithMany()
.HasForeignKey(entity => entity.ClubFeatureId)
.OnDelete(DeleteBehavior.Restrict);
// Unique: هر فیچر فقط یک بار در هر پکیج
builder.HasIndex(e => new { e.PackageId, e.ClubFeatureId })
.IsUnique()
.HasDatabaseName("IX_PackageFeature_PackageId_ClubFeatureId");
}
}
@@ -19,6 +19,7 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration<UserCo
builder.Property(entity => entity.UserId).IsRequired();
builder.Property(entity => entity.WeekDefinitionId).IsRequired();
builder.Property(entity => entity.WeeklyPoolId).IsRequired();
builder.Property(entity => entity.PackageId).IsRequired();
builder.Property(entity => entity.BalancesEarned).IsRequired();
builder.Property(entity => entity.ValuePerBalance).IsRequired();
builder.Property(entity => entity.TotalAmount).IsRequired();
@@ -50,10 +51,16 @@ public class UserCommissionPayoutConfiguration : IEntityTypeConfiguration<UserCo
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
// Composite Index برای UserId و WeekDefinitionId
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId })
// رابطه با Package
builder.HasOne(entity => entity.Package)
.WithMany()
.HasForeignKey(entity => entity.PackageId)
.OnDelete(DeleteBehavior.Restrict);
// Composite Index برای UserId، WeekDefinitionId و PackageId
builder.HasIndex(e => new { e.UserId, e.WeekDefinitionId, e.PackageId })
.IsUnique()
.HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId");
.HasDatabaseName("IX_UserCommissionPayout_User_WeekDef_Package");
// Index برای WeeklyPoolId
builder.HasIndex(e => e.WeeklyPoolId)
@@ -22,6 +22,14 @@ public class UserWalletChangeLogConfiguration : IEntityTypeConfiguration<UserWal
builder.Property(entity => entity.ChangeNerworkValue).IsRequired(true);
builder.Property(entity => entity.IsIncrease).IsRequired(true);
builder.Property(entity => entity.RefrenceId).IsRequired(false);
builder.Property(entity => entity.PackageId).IsRequired(false);
// رابطه با Package (nullable)
builder.HasOne(entity => entity.Package)
.WithMany()
.HasForeignKey(entity => entity.PackageId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -17,6 +17,7 @@ public class WeeklyCommissionPoolConfiguration : IEntityTypeConfiguration<Weekly
builder.Property(entity => entity.Id).UseIdentityColumn();
builder.Property(entity => entity.WeekDefinitionId).IsRequired();
builder.Property(entity => entity.PackageId).IsRequired();
builder.Property(entity => entity.TotalPoolAmount).IsRequired();
builder.Property(entity => entity.TotalBalances).IsRequired();
builder.Property(entity => entity.ValuePerBalance).IsRequired();
@@ -30,10 +31,16 @@ public class WeeklyCommissionPoolConfiguration : IEntityTypeConfiguration<Weekly
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
// Index یونیک برای WeekDefinitionId
builder.HasIndex(e => e.WeekDefinitionId)
// رابطه با Package
builder.HasOne(entity => entity.Package)
.WithMany()
.HasForeignKey(entity => entity.PackageId)
.OnDelete(DeleteBehavior.Restrict);
// Index یونیک ترکیبی: هر هفته × هر پکیج فقط یک استخر
builder.HasIndex(e => new { e.WeekDefinitionId, e.PackageId })
.IsUnique()
.HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId");
.HasDatabaseName("IX_WeeklyCommissionPool_WeekDef_Package");
// Index برای IsCalculated
builder.HasIndex(e => e.IsCalculated)