Files
KArtSell.Aegis/docs/Design/kbx-foundation-v36/backend/Modules/OMS/Orders/Search/Handler.cs
T

97 lines
3.7 KiB
C#

using Dapper;
using Npgsql;
namespace Modules.OMS.Orders.Search;
public sealed class SearchOrdersHandler(NpgsqlDataSource dataSource)
{
public async Task<SearchOrdersResponse> HandleAsync(SearchOrdersRequest request, CancellationToken ct)
{
var page = Math.Max(request.Page, 1);
var pageSize = Math.Clamp(request.PageSize, 1, 500);
var offset = (page - 1) * pageSize;
await using var connection = await dataSource.OpenConnectionAsync(ct);
// Read model query: UI-required projection in one server-side query boundary.
const string rowsSql = """
select
o.id,
o.order_no as OrderNo,
ch.name as ChannelName,
o.ordered_at as OrderedAt,
o.customer_name as CustomerName,
o.item_summary as ItemSummary,
o.total_qty as TotalQty,
o.amount as Amount,
o.allocation_status as AllocationStatus,
o.shipment_status as ShipmentStatus,
o.exception_count as ExceptionCount
from oms_order_search_projection o
join sales_channel ch on ch.id = o.channel_id
where o.ordered_at >= @From
and o.ordered_at < @ToExclusive
and (@ChannelId is null or o.channel_id = @ChannelId)
and (@Status is null or o.shipment_status = @Status)
and (@Keyword is null or o.search_text ilike '%' || @Keyword || '%')
and (@ExceptionOnly = false or o.exception_count > 0)
order by o.ordered_at desc, o.id desc
limit @PageSize offset @Offset;
""";
const string summarySql = """
select
count(*)::int as TotalCount,
coalesce(sum(o.total_qty), 0) as TotalQty,
coalesce(sum(o.amount), 0) as TotalAmount,
count(*) filter (where o.order_status = 'NEW')::int as NewCount,
count(*) filter (where o.shipment_status = 'READY')::int as ReadyToShipCount,
count(*) filter (where o.exception_count > 0)::int as ExceptionCount
from oms_order_search_projection o
where o.ordered_at >= @From
and o.ordered_at < @ToExclusive
and (@ChannelId is null or o.channel_id = @ChannelId)
and (@Status is null or o.shipment_status = @Status)
and (@Keyword is null or o.search_text ilike '%' || @Keyword || '%')
and (@ExceptionOnly = false or o.exception_count > 0);
""";
var args = new
{
From = request.From.ToDateTime(TimeOnly.MinValue),
ToExclusive = request.To.AddDays(1).ToDateTime(TimeOnly.MinValue),
request.ChannelId,
Status = string.IsNullOrWhiteSpace(request.Status) ? null : request.Status,
Keyword = string.IsNullOrWhiteSpace(request.Keyword) ? null : request.Keyword.Trim(),
request.ExceptionOnly,
PageSize = pageSize,
Offset = offset,
};
var rows = (await connection.QueryAsync<OrderSearchRow>(
new CommandDefinition(rowsSql, args, cancellationToken: ct))).AsList();
var summary = await connection.QuerySingleAsync<SummaryRow>(
new CommandDefinition(summarySql, args, cancellationToken: ct));
return new SearchOrdersResponse(
rows,
summary.TotalCount,
summary.TotalQty,
summary.TotalAmount,
new OrderSearchCounters(
summary.TotalCount,
summary.NewCount,
summary.ReadyToShipCount,
summary.ExceptionCount));
}
private sealed record SummaryRow(
int TotalCount,
decimal TotalQty,
decimal TotalAmount,
int NewCount,
int ReadyToShipCount,
int ExceptionCount);
}