blocker phase 6, catalog admin
This commit is contained in:
@@ -7,7 +7,10 @@ using Baya.Application.Features.Catalog.Commands.SetServiceCategoryActive;
|
||||
using Baya.Application.Features.Catalog.Commands.UpdateServiceCategory;
|
||||
using Baya.Application.Features.Catalog.Commands.UpdateServiceOptionGroup;
|
||||
using Baya.Application.Features.Catalog.Commands.UpdateServiceOptionValue;
|
||||
using Baya.Application.Features.Catalog.Queries.GetAdminOptionGroups;
|
||||
using Baya.Application.Features.Catalog.Queries.GetAdminServiceCategories;
|
||||
using Baya.Application.Models.Catalog;
|
||||
using Baya.Application.Models.Common;
|
||||
using Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
@@ -24,6 +27,16 @@ namespace Baya.Web.Api.Controllers.V1;
|
||||
[Display(Description = "Admin: curate the catalog skeleton (categories + option groups/values; no delete)")]
|
||||
public sealed class AdminCatalogController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpGet("[action]")]
|
||||
[ProducesOkApiResponseType<PagedResult<ServiceCategoryDto>>]
|
||||
public async Task<IActionResult> ListCategories([FromQuery] GetAdminServiceCategoriesQuery query, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(query, cancellationToken));
|
||||
|
||||
[HttpGet("[action]")]
|
||||
[ProducesOkApiResponseType<IReadOnlyList<OptionGroupDto>>]
|
||||
public async Task<IActionResult> ListOptionGroups([FromQuery(Name = "category_id")] long categoryId, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(new GetAdminOptionGroupsQuery(categoryId), cancellationToken));
|
||||
|
||||
[HttpPost("[action]")]
|
||||
[ProducesOkApiResponseType<ServiceCategoryDto>]
|
||||
public async Task<IActionResult> CreateCategory(CreateServiceCategoryCommand command, CancellationToken cancellationToken)
|
||||
|
||||
@@ -19,6 +19,15 @@ public interface ICatalogRepository
|
||||
/// its active values, ordered by sort order. Empty is valid (no dimensions defined yet).</summary>
|
||||
Task<IReadOnlyList<OptionGroupDto>> GetApplicableGroupsAsync(long categoryId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Admin: every category regardless of active state, ordered by sort order — so a deactivated
|
||||
/// category stays reachable to review or reactivate. Not cached (admin traffic, always fresh).</summary>
|
||||
Task<IReadOnlyList<ServiceCategoryDto>> ListAllCategoriesAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Admin: the category's own groups plus every cross-category group, <b>including inactive
|
||||
/// groups and inactive values</b>, ordered by sort order — the read side of the active-flag toggles.
|
||||
/// Not cached.</summary>
|
||||
Task<IReadOnlyList<OptionGroupDto>> GetAllApplicableGroupsAsync(long categoryId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Active-only category projection for variant creation — null if missing or deactivated.</summary>
|
||||
Task<ServiceCategoryDto?> GetActiveCategoryAsync(long id, CancellationToken cancellationToken);
|
||||
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
using Baya.Application.Contracts.Persistence;
|
||||
using Baya.Application.Models.Catalog;
|
||||
using Baya.Application.Models.Common;
|
||||
using Mediator;
|
||||
|
||||
namespace Baya.Application.Features.Catalog.Queries.GetAdminOptionGroups;
|
||||
|
||||
internal sealed class GetAdminOptionGroupsQueryHandler(IUnitOfWork unitOfWork)
|
||||
: IRequestHandler<GetAdminOptionGroupsQuery, OperationResult<IReadOnlyList<OptionGroupDto>>>
|
||||
{
|
||||
public async ValueTask<OperationResult<IReadOnlyList<OptionGroupDto>>> Handle(GetAdminOptionGroupsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var groups = await unitOfWork.CatalogRepository.GetAllApplicableGroupsAsync(request.CategoryId, cancellationToken);
|
||||
return OperationResult<IReadOnlyList<OptionGroupDto>>.SuccessResult(groups);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using Baya.Application.Models.Catalog;
|
||||
using Baya.Application.Models.Common;
|
||||
using Mediator;
|
||||
|
||||
namespace Baya.Application.Features.Catalog.Queries.GetAdminOptionGroups;
|
||||
|
||||
/// <summary>
|
||||
/// Admin: a category's applicable option groups — its own plus every cross-category (NULL) group —
|
||||
/// <b>including inactive groups, each with every value including inactive ones</b>. The read side of
|
||||
/// group/value management; the public <c>GetCategoryOptionGroupsQuery</c> deliberately hides both.
|
||||
/// </summary>
|
||||
public record GetAdminOptionGroupsQuery(long CategoryId) : IRequest<OperationResult<IReadOnlyList<OptionGroupDto>>>;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using Baya.Application.Common;
|
||||
using Baya.Application.Contracts.Persistence;
|
||||
using Baya.Application.Models.Catalog;
|
||||
using Baya.Application.Models.Common;
|
||||
using Mediator;
|
||||
|
||||
namespace Baya.Application.Features.Catalog.Queries.GetAdminServiceCategories;
|
||||
|
||||
internal sealed class GetAdminServiceCategoriesQueryHandler(IUnitOfWork unitOfWork)
|
||||
: IRequestHandler<GetAdminServiceCategoriesQuery, OperationResult<PagedResult<ServiceCategoryDto>>>
|
||||
{
|
||||
public async ValueTask<OperationResult<PagedResult<ServiceCategoryDto>>> Handle(GetAdminServiceCategoriesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var (page, pageSize) = Pagination.Normalize(request.Page, request.PageSize);
|
||||
|
||||
// Unlike the public read, this is deliberately uncached — an admin editing a category expects to
|
||||
// see their own write reflected immediately, and admin list traffic is negligible.
|
||||
var all = await unitOfWork.CatalogRepository.ListAllCategoriesAsync(cancellationToken);
|
||||
var items = all.Skip((page - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return OperationResult<PagedResult<ServiceCategoryDto>>.SuccessResult(
|
||||
new PagedResult<ServiceCategoryDto>(items, all.Count, page, pageSize));
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using Baya.Application.Models.Catalog;
|
||||
using Baya.Application.Models.Common;
|
||||
using Mediator;
|
||||
|
||||
namespace Baya.Application.Features.Catalog.Queries.GetAdminServiceCategories;
|
||||
|
||||
/// <summary>Admin: every category regardless of active state, ordered by sort order, paginated — the read
|
||||
/// side of category management (a deactivated category must stay reachable to review or reactivate).</summary>
|
||||
public record GetAdminServiceCategoriesQuery(int Page = 1, int PageSize = 50)
|
||||
: IRequest<OperationResult<PagedResult<ServiceCategoryDto>>>;
|
||||
+30
@@ -45,6 +45,36 @@ internal sealed class CatalogRepository : ICatalogRepository
|
||||
.ToList()))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
public async Task<IReadOnlyList<ServiceCategoryDto>> ListAllCategoriesAsync(CancellationToken cancellationToken)
|
||||
=> await _db.Set<ServiceCategory>()
|
||||
.AsNoTracking()
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.ThenBy(c => c.Id)
|
||||
.Select(c => new ServiceCategoryDto(
|
||||
c.Id, c.NameFa, c.NameEn, c.DescriptionFa, c.DescriptionEn, c.IconKey, c.SortOrder, c.IsActive))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
public async Task<IReadOnlyList<OptionGroupDto>> GetAllApplicableGroupsAsync(long categoryId, CancellationToken cancellationToken)
|
||||
=> await _db.Set<ServiceOptionGroup>()
|
||||
.AsNoTracking()
|
||||
.Where(g => g.ServiceCategoryId == categoryId || g.ServiceCategoryId == null)
|
||||
.OrderBy(g => g.SortOrder)
|
||||
.ThenBy(g => g.Id)
|
||||
.Select(g => new OptionGroupDto(
|
||||
g.Id,
|
||||
g.ServiceCategoryId,
|
||||
g.NameFa,
|
||||
g.NameEn,
|
||||
g.IsRequired,
|
||||
g.SortOrder,
|
||||
g.IsActive,
|
||||
g.Values
|
||||
.OrderBy(v => v.SortOrder)
|
||||
.ThenBy(v => v.Id)
|
||||
.Select(v => new OptionValueDto(v.Id, v.NameFa, v.NameEn, v.SortOrder, v.IsActive))
|
||||
.ToList()))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
public Task<ServiceCategoryDto?> GetActiveCategoryAsync(long id, CancellationToken cancellationToken)
|
||||
=> _db.Set<ServiceCategory>()
|
||||
.AsNoTracking()
|
||||
|
||||
Reference in New Issue
Block a user