Refactor pricing endpoints and enhance support for pricing retrieval
CI / Backend (build + test) (push) Successful in 1m21s
CI / Frontend (lint + typecheck + build) (push) Successful in 32s

- Added a new endpoint `/api/support/pricing` to allow users to retrieve pricing information, making it accessible for role request dialogs.
- Introduced the `GetSupportPricing` method to handle pricing queries, ensuring that pricing data is available to non-admin users.
- Updated frontend components to integrate the new pricing retrieval functionality, displaying estimated costs based on user-selected configurations.
- Removed the `PricingSettingsDto` as it is no longer needed, streamlining the pricing data structure.
- Enhanced API documentation to reflect the new endpoint and its usage in the support context.
This commit is contained in:
Leonid Pershin
2026-07-18 21:19:40 +03:00
parent 32221af503
commit 3304eed4b3
14 changed files with 127 additions and 24 deletions
@@ -1,5 +1,6 @@
using PnvPanel.Api.Common;
using PnvPanel.Application.Admin.Pricing;
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Infrastructure.Identity;
@@ -8,6 +8,7 @@ using PnvPanel.Application.Support.AddComment;
using PnvPanel.Application.Support.CreateBugReport;
using PnvPanel.Application.Support.CreateRoleRequest;
using PnvPanel.Application.Support.GetAttachment;
using PnvPanel.Application.Support.GetSupportPricing;
using PnvPanel.Application.Support.GetTicket;
using PnvPanel.Application.Support.ListMyTickets;
using PnvPanel.Application.Support.ListSelectableRoles;
@@ -23,6 +24,7 @@ public static class SupportEndpoints
var group = app.MapGroup("/api/support").WithTags("Support").RequireAuthorization();
group.MapGet("/roles", ListSelectableRoles).Produces<IReadOnlyList<RoleDto>>();
group.MapGet("/pricing", GetSupportPricing).Produces<PricingSettingsDto>();
group
.MapPost("/tickets/bug-reports", CreateBugReport)
.DisableAntiforgery()
@@ -49,6 +51,15 @@ public static class SupportEndpoints
return result.ToHttpResult();
}
private static async Task<IResult> GetSupportPricing(
ISender sender,
CancellationToken cancellationToken
)
{
var result = await sender.Send(new GetSupportPricingQuery(), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> CreateBugReport(
[FromForm] string message,
IFormFileCollection? files,
@@ -1,3 +1,4 @@
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
@@ -1,17 +0,0 @@
using PnvPanel.Domain.Pricing;
namespace PnvPanel.Application.Admin.Pricing;
public sealed record PricingSettingsDto(
int? PricePerConfigPerQuarter,
int? PricePerConfigPerHalfYear,
int? PricePerConfigPerYear
)
{
public static PricingSettingsDto FromDomain(PricingSettings settings) =>
new(
settings.PricePerConfigPerQuarter,
settings.PricePerConfigPerHalfYear,
settings.PricePerConfigPerYear
);
}
@@ -1,3 +1,4 @@
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
@@ -0,0 +1,20 @@
using PnvPanel.Domain.Pricing;
namespace PnvPanel.Application.Common.Interfaces;
/// <summary>Все поля — цена за конфиг В МЕСЯЦ при соответствующем тарифе оплаты; итог за период =
/// ставка × число месяцев. Используется и Admin (редактирование), и Support (справка при заявке на
/// роль) — см. PricingSettingsDto.FromDomain.</summary>
public sealed record PricingSettingsDto(
int? PricePerConfigPerQuarter,
int? PricePerConfigPerHalfYear,
int? PricePerConfigPerYear
)
{
public static PricingSettingsDto FromDomain(PricingSettings settings) =>
new(
settings.PricePerConfigPerQuarter,
settings.PricePerConfigPerHalfYear,
settings.PricePerConfigPerYear
);
}
@@ -0,0 +1,9 @@
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Support.GetSupportPricing;
/// <summary>Справочная цена конфига для заявки на роль (не редактирование, только чтение) — в отличие
/// от GetPricingSettingsQuery (Admin/Pricing), доступна любому активированному пользователю.</summary>
public sealed record GetSupportPricingQuery : IQuery<Result<PricingSettingsDto>>, IRequiresActivation;
@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore;
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Common.Models;
namespace PnvPanel.Application.Support.GetSupportPricing;
public sealed class GetSupportPricingQueryHandler(IAppDbContext dbContext)
: IQueryHandler<GetSupportPricingQuery, Result<PricingSettingsDto>>
{
public async Task<Result<PricingSettingsDto>> Handle(
GetSupportPricingQuery query,
CancellationToken cancellationToken
)
{
var settings = await dbContext
.PricingSettings.AsNoTracking()
.FirstOrDefaultAsync(cancellationToken);
// Ещё не сидировано/не сохранено ни разу — цена не задана, а не ошибка.
return Result.Success(
settings is null
? new PricingSettingsDto(null, null, null)
: PricingSettingsDto.FromDomain(settings)
);
}
}