feat(api): pagination on GET product prices (closes #47)

- GET /api/v1/products/{id}/prices now returns PagedResult<ProductPriceDto>
  with OFFSET/FETCH + COUNT via Dapper (two queries on same connection)
- Query params: ?page (default 1) and ?pageSize (default 20, max 100)
- Clamping: Math.Max(1, page) + Math.Clamp(pageSize, 1, 100) in handler
- Auth upgraded from [Authorize] to [RequirePermission("catalogo:productos:gestionar")]
- IProductPriceRepository.GetByProductIdAsync signature updated to paginated form
- AddProductPriceCommandHandler adapted to read back via page=1, pageSize=2
- TDD cycle: RED (tests updated to PagedResult shape) -> GREEN (implementation) -> REFACTOR
- Tests: 1418 total (1106 Application + 312 Api), 0 failures

closes #47
This commit is contained in:
2026-04-19 19:47:18 -03:00
parent da063ad677
commit 0dce3ee4ac
11 changed files with 425 additions and 98 deletions

View File

@@ -1,3 +1,4 @@
using SIGCM2.Application.Common;
using SIGCM2.Domain.Entities;
namespace SIGCM2.Application.Abstractions.Persistence;
@@ -21,11 +22,14 @@ public interface IProductPriceRepository
CancellationToken ct = default);
/// <summary>
/// Returns all price rows for the product, ordered descending by PriceValidFrom (active first).
/// Returns empty list when the product has no price history.
/// Returns a paginated page of price rows for the product, ordered descending by PriceValidFrom.
/// Caller is responsible for clamping page (≥ 1) and pageSize (1100) before calling.
/// Returns PagedResult with empty Items when the product has no price history or page is beyond total.
/// </summary>
Task<IReadOnlyList<ProductPrice>> GetByProductIdAsync(
Task<PagedResult<ProductPrice>> GetByProductIdAsync(
int productId,
int page,
int pageSize,
CancellationToken ct = default);
/// <summary>