Implement pricing management functionality and update related components
CI / Backend (build + test) (push) Successful in 1m19s
CI / Frontend (lint + typecheck + build) (push) Successful in 32s

- Added new endpoints for managing global pricing settings, including retrieval and updates for `PricePerConfigPerQuarter` and `PricePerConfigPerYear`.
- Updated `RoleService` and related commands to remove pricing fields from role management, ensuring a clear separation between role configurations and global pricing.
- Enhanced the `FactoryResetCommandHandler` to include seeding of pricing settings during a factory reset.
- Modified frontend components to support new pricing settings, including forms for creating and updating pricing information.
- Updated API documentation to reflect changes in pricing management endpoints and their expected request/response formats.
- Adjusted tests to ensure proper coverage for new pricing functionalities and their integration with existing role management features.
This commit is contained in:
Leonid Pershin
2026-07-18 19:34:29 +03:00
parent 285d8180c8
commit ae379f8e0f
54 changed files with 2516 additions and 268 deletions
+25 -13
View File
@@ -5,9 +5,9 @@
`IdentityRole<Guid>`); чистый `PnvPanel.Domain` ссылается на пользователя/роль только по `Guid`.
Тарифы `Plan` и лимиты трафика на конфиг (`TrafficLimit`) не реализованы — единственная квота:
число активных конфигов на роль (`AppRole.MaxConfigs`). У роли есть справочная цена за конфиг
(`AppRole.PricePerConfigPer*`, видна только админу) — это не биллинг: без статусов оплаты, дат
окончания и интеграций с платёжными системами, см. ниже.
число активных конфигов на роль (`AppRole.MaxConfigs`). Есть глобальная справочная цена за один
конфиг (`PricingSettings`, видна только админу) — это не биллинг: без статусов оплаты, дат окончания
и интеграций с платёжными системами, см. ниже.
## Диаграмма связей
@@ -280,16 +280,6 @@ UI **настойчиво напоминает** привязать его (ед
| `MaxConfigs` | `int` | Квота активных конфигов (-1 = без лимита; для `admin` — без лимита) |
| `MaxIpLimit` | `int` | Лимит одновременных IP на клиента (`limitIp` в 3x-ui; -1 = без лимита; для `admin` — без лимита) |
| `IsSystem` | `bool` | Системная (`admin`, `user`) — нельзя удалить/переименовать |
| `PricePerConfigPerQuarter` | `int?` | Справочная цена за конфиг за минимальный период оплаты (3 месяца), руб. Видна только админу (Admin-only эндпоинты); у `admin` всегда `null` |
| `PricePerConfigPerYear` | `int?` | Справочная цена за конфиг за год, руб. Задаётся независимо от квартальной (не производная — позволяет скидку); у `admin` всегда `null` |
Итоговая цена роли для админа — `Price × MaxConfigs` (напр. `user` с `MaxConfigs=3`, 200₽/конфиг/3мес →
600₽/3мес; 400₽/конфиг/год → 1200₽/год); считается на фронте, не хранится отдельно. Чисто
информационное поле: без биллинга, статусов оплаты, дат окончания подписки, интеграций с платёжными
системами. `IRoleService.UpdateRoleAsync` игнорирует цену при обновлении роли `admin` (остаётся
`null`), даже если она передана в запросе. `GET /api/support/roles` (список ролей для заявки на роль,
доступен любому активированному пользователю) отдаёт отдельный `SelectableRoleDto` без цены — она не
протекает за пределы `/api/admin/*`.
Сидируются: `admin` (оба лимита без ограничения) и `user` (`MaxConfigs` = `Roles__DefaultUserMaxConfigs`,
по умолчанию 3; `MaxIpLimit` = `Roles__DefaultUserMaxIpLimit`, по умолчанию 2).
@@ -307,6 +297,28 @@ UI **настойчиво напоминает** привязать его (ед
одобряет заявку на понижение самому себе — этот путь специально не блокируется отдельно, чтобы не
плодить тикеты, которые некому обработать, если админ единственный.
### PricingSettings — глобальная справочная цена конфига
Единственная строка в таблице (singleton) — цена за один конфиг, редактируется админом. Не привязана
к роли: одна цена на весь сервис. Не биллинг — без статусов оплаты, дат окончания, интеграций с
платёжными системами.
| Поле | Тип | Заметки |
| --------------------------- | ----------------- | --------------------------------------------------- |
| `Id` | `Guid` | PK |
| `PricePerConfigPerQuarter` | `int?` | Цена за конфиг за минимальный период оплаты (3 месяца), руб. |
| `PricePerConfigPerYear` | `int?` | Цена за конфиг за год, руб. Задаётся независимо от квартальной (не производная — позволяет скидку) |
| `UpdatedAt` | `DateTimeOffset` | |
Итоговая цена для роли = `цена × AppRole.MaxConfigs` (напр. `user` с `MaxConfigs=3`, 200₽/конфиг/3мес →
600₽/3мес; 400₽/конфиг/год → 1200₽/год) — считается на фронте (таблица ролей в админке), нигде не
хранится. Для ролей с `MaxConfigs = -1` (unlimited, в т.ч. `admin`) итог не считается — отображается
как «не задано».
`GET/PUT /api/admin/pricing` — только `admin` (в отличие от `RoleDto`, цена никогда не попадает в
`GET /api/support/roles`, доступный любому активированному пользователю, — это два независимых DTO).
Сидируется пустой строкой при старте (`IPricingSettingsSeeder`, если таблица пуста) и заново после
полного сброса панели (см. «Полный сброс панели» выше).
### ActivationRequest — запрос активации
Пользователь просит активацию у админа; админ одобряет/отклоняет на сайте или в Telegram.