Implement discount tiers for pricing settings and enhance related functionalities
- 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:
@@ -8,6 +8,7 @@ import { Badge } from '@/shared/ui/badge'
|
||||
import { listRoles, deleteRole } from '@/features/admin/roles/api'
|
||||
import { RoleFormDialog } from '@/features/admin/roles/RoleFormDialog'
|
||||
import { getPricingSettings } from '@/features/admin/pricing/api'
|
||||
import { applyDiscount, resolveDiscountPercent } from '@/shared/lib/pricing'
|
||||
import type { RoleDto } from '@/shared/api/types'
|
||||
|
||||
export const Route = createFileRoute('/admin/roles')({ component: AdminRolesPage })
|
||||
@@ -20,9 +21,26 @@ function AdminRolesPage() {
|
||||
const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['admin-roles'], queryFn: listRoles })
|
||||
const { data: pricing } = useQuery({ queryKey: ['admin-pricing'], queryFn: getPricingSettings })
|
||||
|
||||
// Ставки — цена за конфиг В МЕСЯЦ при данном тарифе; итог за период = ставка × месяцев × квота.
|
||||
const totalPrice = (monthlyRate: number | null | undefined, maxConfigs: number, months: number) =>
|
||||
monthlyRate == null || maxConfigs < 0 ? t('admin.roles.noPrice') : `${monthlyRate * months * maxConfigs} ₽`
|
||||
// Ставки — цена за конфиг В МЕСЯЦ при данном тарифе; итог за период = ставка × месяцев × квота,
|
||||
// затем скидка по лесенке (см. shared/lib/pricing) — за роль с большей квотой конфигов.
|
||||
const priceCell = (monthlyRate: number | null | undefined, maxConfigs: number, months: number) => {
|
||||
if (monthlyRate == null || maxConfigs < 0) return <span>{t('admin.roles.noPrice')}</span>
|
||||
|
||||
const original = monthlyRate * months * maxConfigs
|
||||
const percent = resolveDiscountPercent(pricing?.discountTiers ?? [], maxConfigs)
|
||||
if (percent <= 0) return <span>{original} ₽</span>
|
||||
|
||||
const discounted = applyDiscount(original, percent)
|
||||
return (
|
||||
<span className="flex flex-col">
|
||||
<span className="text-xs text-muted-foreground line-through">{original} ₽</span>
|
||||
<span className="flex items-center gap-1">
|
||||
{discounted} ₽
|
||||
<Badge variant="success">-{percent}%</Badge>
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: deleteRole,
|
||||
@@ -74,9 +92,9 @@ function AdminRolesPage() {
|
||||
</td>
|
||||
<td className="py-2">{role.maxConfigs < 0 ? t('unlimited') : role.maxConfigs}</td>
|
||||
<td className="py-2">{role.maxIpLimit < 0 ? t('unlimited') : role.maxIpLimit}</td>
|
||||
<td className="py-2">{totalPrice(pricing?.pricePerConfigPerQuarter, role.maxConfigs, 3)}</td>
|
||||
<td className="py-2">{totalPrice(pricing?.pricePerConfigPerHalfYear, role.maxConfigs, 6)}</td>
|
||||
<td className="py-2">{totalPrice(pricing?.pricePerConfigPerYear, role.maxConfigs, 12)}</td>
|
||||
<td className="py-2">{priceCell(pricing?.pricePerConfigPerQuarter, role.maxConfigs, 3)}</td>
|
||||
<td className="py-2">{priceCell(pricing?.pricePerConfigPerHalfYear, role.maxConfigs, 6)}</td>
|
||||
<td className="py-2">{priceCell(pricing?.pricePerConfigPerYear, role.maxConfigs, 12)}</td>
|
||||
<td className="py-2 text-right">
|
||||
<Button size="sm" variant="outline" onClick={() => setEditing(role)}>
|
||||
{t('admin.roles.edit')}
|
||||
|
||||
Reference in New Issue
Block a user