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
+71 -22
View File
@@ -2086,6 +2086,64 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/admin/pricing": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PricingSettingsDto"];
};
};
};
};
put: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdatePricingSettingsCommand"];
};
};
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PricingSettingsDto"];
};
};
};
};
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/support/roles": {
parameters: {
query?: never;
@@ -2108,7 +2166,7 @@ export interface paths {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SelectableRoleDto"][];
"application/json": components["schemas"]["RoleDto"][];
};
};
};
@@ -3087,10 +3145,6 @@ export interface components {
maxConfigs: number | string;
/** Format: int32 */
maxIpLimit: number | string;
/** Format: int32 */
pricePerConfigPerQuarter: null | number | string;
/** Format: int32 */
pricePerConfigPerYear: null | number | string;
};
CreateRoleRequestBody: {
/** Format: uuid */
@@ -3257,6 +3311,12 @@ export interface components {
/** Format: int32 */
pageSize: number | string;
};
PricingSettingsDto: {
/** Format: int32 */
pricePerConfigPerQuarter: null | number | string;
/** Format: int32 */
pricePerConfigPerYear: null | number | string;
};
PublishInboundBody: {
isPublished: boolean;
displayName: null | string;
@@ -3299,19 +3359,6 @@ export interface components {
/** Format: int32 */
maxIpLimit: number | string;
isSystem: boolean;
/** Format: int32 */
pricePerConfigPerQuarter?: null | number | string;
/** Format: int32 */
pricePerConfigPerYear?: null | number | string;
};
SelectableRoleDto: {
/** Format: uuid */
id: string;
name: string;
/** Format: int32 */
maxConfigs: number | string;
/** Format: int32 */
maxIpLimit: number | string;
};
StatsDto: {
/** Format: int32 */
@@ -3442,15 +3489,17 @@ export interface components {
title: string;
body: string;
};
UpdatePricingSettingsCommand: {
/** Format: int32 */
pricePerConfigPerQuarter: null | number | string;
/** Format: int32 */
pricePerConfigPerYear: null | number | string;
};
UpdateRoleBody: {
/** Format: int32 */
maxConfigs: number | string;
/** Format: int32 */
maxIpLimit: number | string;
/** Format: int32 */
pricePerConfigPerQuarter: null | number | string;
/** Format: int32 */
pricePerConfigPerYear: null | number | string;
};
UserSummaryDto: {
/** Format: uuid */
+4 -8
View File
@@ -173,16 +173,12 @@ export type RoleDto = {
maxConfigs: number
maxIpLimit: number
isSystem: boolean
pricePerConfigPerQuarter: number | null
pricePerConfigPerYear: number | null
}
/** Роль для выбора в заявке на роль (GET /support/roles) — без цены, она видна только админу. */
export type SelectableRoleDto = {
id: string
name: string
maxConfigs: number
maxIpLimit: number
/** Глобальная справочная цена за конфиг (видна только админу) — одна на весь сервис, не per-роль. */
export type PricingSettingsDto = {
pricePerConfigPerQuarter: number | null
pricePerConfigPerYear: number | null
}
export type ActivationRequestAdminDto = {
+18 -10
View File
@@ -191,6 +191,7 @@ const resources = {
users: 'Пользователи',
configs: 'Все конфиги',
roles: 'Роли',
pricing: 'Цены',
nodes: 'Ноды',
apps: 'Приложения',
instructions: 'Инструкции',
@@ -253,13 +254,8 @@ const resources = {
maxConfigsHint: '1 = без лимита.',
maxIpLimit: 'Лимит IP на конфиг',
maxIpLimitHint: '−1 = без лимита. Применяется только к новым конфигам.',
pricePerConfigPerQuarter: 'Цена за конфиг / 3 мес',
pricePerConfigPerQuarterHint: 'Справочно, видно только админу. Минимальный период оплаты.',
pricePerConfigPerYear: 'Цена за конфиг / год',
pricePerConfigPerYearHint: 'Справочно, видно только админу. Задаётся отдельно от квартальной цены.',
totalPerQuarter: 'Итого / 3 мес',
totalPerYear: 'Итого / год',
totalValue: 'Итого для роли: {{total}} ₽.',
noPrice: '—',
system: 'системная',
edit: 'Изменить',
@@ -270,6 +266,14 @@ const resources = {
updated: 'Квота обновлена.',
deleted: 'Роль удалена.',
},
pricing: {
title: 'Справочная цена конфига',
pricePerConfigPerQuarter: 'Цена за конфиг / 3 мес',
pricePerConfigPerQuarterHint: 'Минимальный период оплаты. Видно только админу.',
pricePerConfigPerYear: 'Цена за конфиг / год',
pricePerConfigPerYearHint: 'Задаётся независимо от квартальной цены (можно сделать скидку за год).',
updated: 'Цена обновлена.',
},
nodes: {
create: 'Добавить ноду',
name: 'Название',
@@ -617,6 +621,7 @@ const resources = {
users: 'Users',
configs: 'All configs',
roles: 'Roles',
pricing: 'Pricing',
nodes: 'Nodes',
apps: 'Apps',
instructions: 'Instructions',
@@ -679,13 +684,8 @@ const resources = {
maxConfigsHint: '1 = unlimited.',
maxIpLimit: 'IP limit per config',
maxIpLimitHint: '1 = unlimited. Applies to new configs only.',
pricePerConfigPerQuarter: 'Price per config / 3 months',
pricePerConfigPerQuarterHint: 'Reference only, visible to admin only. Minimum billing period.',
pricePerConfigPerYear: 'Price per config / year',
pricePerConfigPerYearHint: 'Reference only, visible to admin only. Set independently from the quarterly price.',
totalPerQuarter: 'Total / 3 months',
totalPerYear: 'Total / year',
totalValue: 'Total for role: {{total}} ₽.',
noPrice: '—',
system: 'system',
edit: 'Edit',
@@ -696,6 +696,14 @@ const resources = {
updated: 'Quota updated.',
deleted: 'Role deleted.',
},
pricing: {
title: 'Reference config price',
pricePerConfigPerQuarter: 'Price per config / 3 months',
pricePerConfigPerQuarterHint: 'Minimum billing period. Visible to admin only.',
pricePerConfigPerYear: 'Price per config / year',
pricePerConfigPerYearHint: 'Set independently from the quarterly price (allows an annual discount).',
updated: 'Pricing updated.',
},
nodes: {
create: 'Add node',
name: 'Name',