Implement discount tiers for pricing settings and enhance related functionalities
CI / Backend (build + test) (push) Successful in 1m20s
CI / Frontend (lint + typecheck + build) (push) Successful in 32s

- Introduced a new `DiscountTierDto` to represent volume discount tiers, allowing roles with a config quota at or above specified thresholds to receive discounts on pricing.
- Updated the `PricingSettingsDto` to include a list of discount tiers, enhancing the pricing model to support more flexible pricing strategies.
- Modified the `GetPricingSettingsQueryHandler` and `UpdatePricingSettingsCommandHandler` to handle discount tiers, ensuring they are correctly retrieved and updated in the database.
- Enhanced validation in `UpdatePricingSettingsCommandValidator` to enforce uniqueness and progressive discount tiers, preventing invalid configurations.
- Updated frontend components to support the new discount tier functionality, including forms for adding and managing discount tiers in the admin interface.
- Revised API documentation to reflect the new discount tier features and their usage in pricing settings.
This commit is contained in:
Leonid Pershin
2026-07-19 15:51:01 +03:00
parent 6a2d2d2318
commit 0dcaf1203f
27 changed files with 1645 additions and 43 deletions
@@ -49,6 +49,8 @@ public interface IAppDbContext
DbSet<PricingSettings> PricingSettings { get; }
DbSet<PricingDiscountTier> PricingDiscountTiers { get; }
DbSet<BillingSettings> BillingSettings { get; }
DbSet<PaymentRequest> PaymentRequests { get; }
@@ -2,19 +2,31 @@ using PnvPanel.Domain.Pricing;
namespace PnvPanel.Application.Common.Interfaces;
/// <summary>Все поля — цена за конфиг В МЕСЯЦ при соответствующем тарифе оплаты; итог за период =
/// ставка × число месяцев. Используется и Admin (редактирование), и Support (справка при заявке на
/// роль) — см. PricingSettingsDto.FromDomain.</summary>
/// <summary>Одна ступень скидочной лесенки за объём — см. PricingDiscount.ResolvePercent для алгоритма
/// выбора действующей ступени.</summary>
public sealed record DiscountTierDto(int MinConfigs, int DiscountPercent);
/// <summary>Все ценовые поля — цена за конфиг В МЕСЯЦ при соответствующем тарифе; итог за период =
/// ставка × число месяцев, скидка (DiscountTiers) применяется к этому итогу по квоте роли. Используется
/// и Admin (редактирование), и Support (справка при заявке на роль) — см. PricingSettingsDto.FromDomain.</summary>
public sealed record PricingSettingsDto(
int? PricePerConfigPerQuarter,
int? PricePerConfigPerHalfYear,
int? PricePerConfigPerYear
int? PricePerConfigPerYear,
IReadOnlyList<DiscountTierDto> DiscountTiers
)
{
public static PricingSettingsDto FromDomain(PricingSettings settings) =>
public static PricingSettingsDto FromDomain(
PricingSettings settings,
IReadOnlyList<PricingDiscountTier> discountTiers
) =>
new(
settings.PricePerConfigPerQuarter,
settings.PricePerConfigPerHalfYear,
settings.PricePerConfigPerYear
settings.PricePerConfigPerYear,
discountTiers
.OrderBy(t => t.MinConfigs)
.Select(t => new DiscountTierDto(t.MinConfigs, t.DiscountPercent))
.ToList()
);
}