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
+22
View File
@@ -188,6 +188,7 @@ const resources = {
pricingHalfYear: 'Полгода: {{price}}',
pricingYear: 'Год: {{price}}',
pricingDisclaimer: 'Цены на данный момент ознакомительные.',
pricingDiscounted: '{{price}} ₽ (вместо {{original}} ₽, скидка {{percent}}%)',
justification: 'Обоснование',
ticketCreated: 'Обращение отправлено.',
roleRequestPending: 'У вас уже есть необработанная заявка на роль.',
@@ -340,6 +341,16 @@ const resources = {
yearCheaperThanHalfYear: 'Цена за год (в пересчёте на 12 месяцев) не может быть меньше цены за полгода.',
yearCheaperThanQuarter: 'Цена за год (в пересчёте на 12 месяцев) не может быть меньше цены за 3 месяца.',
updated: 'Цена обновлена.',
discountTiersTitle: 'Скидка за объём',
discountTiersHint: 'Роль с квотой конфигов не меньше порога получает указанную скидку от итоговой цены за период. Действует наивысший подходящий порог, скидки не суммируются.',
discountTierMinConfigs: 'От скольки конфигов',
discountTierPercent: 'Скидка, %',
addDiscountTier: 'Добавить ступень',
removeDiscountTier: 'Удалить',
discountTierInvalidMinConfigs: 'Порог — целое число конфигов, не меньше 1.',
discountTierInvalidPercent: 'Скидка — целое число от 1 до 99.',
discountTiersDuplicate: 'Пороги скидочной лесенки не должны повторяться.',
discountTiersNotProgressive: 'Скидка на более высоком пороге не может быть меньше скидки на более низком.',
},
billing: {
settingsTitle: 'Настройки биллинга',
@@ -715,6 +726,7 @@ const resources = {
pricingHalfYear: '6 months: {{price}}',
pricingYear: 'Year: {{price}}',
pricingDisclaimer: 'Prices are indicative only at this time.',
pricingDiscounted: '{{price}} ₽ (instead of {{original}} ₽, {{percent}}% off)',
justification: 'Justification',
ticketCreated: 'Ticket submitted.',
roleRequestPending: 'You already have a pending role request.',
@@ -867,6 +879,16 @@ const resources = {
yearCheaperThanHalfYear: 'The annual price (over 12 months) cannot be lower than the 6-month price.',
yearCheaperThanQuarter: 'The annual price (over 12 months) cannot be lower than the 3-month price.',
updated: 'Pricing updated.',
discountTiersTitle: 'Volume discount',
discountTiersHint: 'A role with a config quota at or above the threshold gets the listed discount off the period total. The highest applicable threshold wins — discounts do not stack.',
discountTierMinConfigs: 'From how many configs',
discountTierPercent: 'Discount, %',
addDiscountTier: 'Add tier',
removeDiscountTier: 'Remove',
discountTierInvalidMinConfigs: 'Threshold must be a whole number of configs, at least 1.',
discountTierInvalidPercent: 'Discount must be a whole number from 1 to 99.',
discountTiersDuplicate: 'Discount tier thresholds cannot repeat.',
discountTiersNotProgressive: 'A higher threshold cannot have a smaller discount than a lower one.',
},
billing: {
settingsTitle: 'Billing settings',