198 lines
6.8 KiB
C#
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);
|
|
}
|