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(); private ISalesStoreOrderRepository SalesStoreOrderRepository => LazyServiceProvider.GetRequiredService(); /// /// 工作台订单统计概览。 /// [HttpGet("/order/workplace-stats")] public async Task 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, }; } /// /// 工作台订单趋势(新增 vs 发货)。 /// [HttpGet("/order/workplace-trend")] public async Task 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(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 BuildScopedListFilterAsync() { var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync( CustomerRepository, CurrentUser, null); var filter = new OrderListFilter { CustomerId = customerId }; await ApplyInternalOrderDataScopeAsync(filter); return filter; } private static List BuildTrendBuckets(string dimension, int count) { var today = DateTime.Today; var buckets = new List(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); }