admin/src/Oms.Application/Services/Orders/OrderService.Workplace.cs
2026-07-17 14:17:00 +08:00

198 lines
6.8 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Orders;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.WarehouseAggregate.Consts;
namespace Oms.Application.Services.Orders;
public partial class OrderService
{
private ISalesStoreRepository SalesStoreRepository =>
LazyServiceProvider.GetRequiredService<ISalesStoreRepository>();
private ISalesStoreOrderRepository SalesStoreOrderRepository =>
LazyServiceProvider.GetRequiredService<ISalesStoreOrderRepository>();
/// <summary>
/// 工作台订单统计概览。
/// </summary>
[HttpGet("/order/workplace-stats")]
public async Task<OrderWorkplaceStatsOutput> GetWorkplaceStatsAsync()
{
var filter = await BuildScopedListFilterAsync();
var rows = await OrderRepository.GetStatusCountsByFilterAsync(filter);
var counts = rows
.Where(x => !string.Equals(x.Status, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
.ToDictionary(x => x.Status, x => x.Count, StringComparer.OrdinalIgnoreCase);
int GetCount(string status) =>
counts.TryGetValue(status, out var value) ? value : 0;
var today = DateTime.Today;
var tomorrow = today.AddDays(1);
var yesterday = today.AddDays(-1);
var todayCreated = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(filter, today, tomorrow);
var yesterdayCreated = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(filter, yesterday, today);
decimal? growth = null;
if (yesterdayCreated > 0)
{
growth = Math.Round((todayCreated - yesterdayCreated) * 100m / yesterdayCreated, 1);
}
else if (todayCreated > 0)
{
growth = 100m;
}
var shipped =
GetCount(OrderStatusCodes.PendingOnline)
+ GetCount(OrderStatusCodes.Shipping)
+ GetCount(OrderStatusCodes.DeliverySuccess)
+ GetCount(OrderStatusCodes.DeliveryFailed);
var customerId = filter.CustomerId;
var storeCount = customerId is > 0
? await SalesStoreRepository.CountAsync(x => x.CustomerId == customerId.Value)
: await SalesStoreRepository.CountAsync(_ => true);
var storeOrderPending = customerId is > 0
? await SalesStoreOrderRepository.CountAsync(x =>
x.CustomerId == customerId.Value
&& x.FulfillStatus == SalesStoreFulfillStatusCodes.Pending)
: await SalesStoreOrderRepository.CountAsync(x =>
x.FulfillStatus == SalesStoreFulfillStatusCodes.Pending);
return new OrderWorkplaceStatsOutput
{
Total = counts.Values.Sum(),
Counts = counts,
TodayCreated = todayCreated,
YesterdayCreated = yesterdayCreated,
DayOverDayGrowthPercent = growth,
PendingShip = GetCount(OrderStatusCodes.PendingShip),
Shipped = shipped,
PendingOnline = GetCount(OrderStatusCodes.PendingOnline),
Problematic = GetCount(OrderStatusCodes.Problematic),
StoreCount = storeCount,
StoreOrderPending = storeOrderPending,
};
}
/// <summary>
/// 工作台订单趋势(新增 vs 发货)。
/// </summary>
[HttpGet("/order/workplace-trend")]
public async Task<OrderWorkplaceTrendOutput> GetWorkplaceTrendAsync(
[FromQuery] string? dimension = "day",
[FromQuery] int count = 7)
{
var dim = (dimension ?? "day").Trim().ToLowerInvariant();
if (dim is not ("day" or "week" or "month"))
{
dim = "day";
}
count = dim switch
{
"week" => Math.Clamp(count, 1, 12),
"month" => Math.Clamp(count, 1, 12),
_ => Math.Clamp(count, 1, 31),
};
var filter = await BuildScopedListFilterAsync();
var buckets = BuildTrendBuckets(dim, count);
var points = new List<OrderWorkplaceTrendPointOutput>(buckets.Count);
foreach (var bucket in buckets)
{
var forecast = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(
filter,
bucket.Start,
bucket.EndExclusive);
var shipped = await OrderRepository.CountByFilterAndShipTimeRangeAsync(
filter,
bucket.Start,
bucket.EndExclusive);
points.Add(new OrderWorkplaceTrendPointOutput
{
Label = bucket.Label,
ForecastCount = forecast,
ShippedCount = shipped,
});
}
return new OrderWorkplaceTrendOutput
{
Dimension = dim,
Points = points,
};
}
private async Task<OrderListFilter> BuildScopedListFilterAsync()
{
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
null);
var filter = new OrderListFilter { CustomerId = customerId };
await ApplyInternalOrderDataScopeAsync(filter);
return filter;
}
private static List<TrendBucket> BuildTrendBuckets(string dimension, int count)
{
var today = DateTime.Today;
var buckets = new List<TrendBucket>(count);
if (dimension == "week")
{
var currentWeekStart = GetWeekStart(today);
for (var i = count - 1; i >= 0; i--)
{
var start = currentWeekStart.AddDays(-7 * i);
var end = start.AddDays(7);
var weekNo = global::System.Globalization.ISOWeek.GetWeekOfYear(start);
buckets.Add(new TrendBucket(start, end, $"第{weekNo:D2}周"));
}
return buckets;
}
if (dimension == "month")
{
var currentMonthStart = new DateTime(today.Year, today.Month, 1);
for (var i = count - 1; i >= 0; i--)
{
var start = currentMonthStart.AddMonths(-i);
var end = start.AddMonths(1);
buckets.Add(new TrendBucket(start, end, start.ToString("yyyy-MM")));
}
return buckets;
}
for (var i = count - 1; i >= 0; i--)
{
var start = today.AddDays(-i);
var end = start.AddDays(1);
buckets.Add(new TrendBucket(start, end, start.ToString("MM-dd")));
}
return buckets;
}
private static DateTime GetWeekStart(DateTime date)
{
var day = (int)date.DayOfWeek;
var mondayOffset = day == 0 ? 6 : day - 1;
return date.Date.AddDays(-mondayOffset);
}
private sealed record TrendBucket(DateTime Start, DateTime EndExclusive, string Label);
}