1142 lines
40 KiB
C#
1142 lines
40 KiB
C#
using HoneyBox.Admin.Business.Models;
|
||
using HoneyBox.Admin.Business.Models.Order;
|
||
using HoneyBox.Admin.Business.Services.Interfaces;
|
||
using HoneyBox.Model.Data;
|
||
using HoneyBox.Model.Entities;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
namespace HoneyBox.Admin.Business.Services;
|
||
|
||
/// <summary>
|
||
/// 订单管理服务实现
|
||
/// </summary>
|
||
public class OrderService : IOrderService
|
||
{
|
||
private readonly HoneyBoxDbContext _dbContext;
|
||
private readonly ILogger<OrderService> _logger;
|
||
|
||
// 订单状态名称映射
|
||
private static readonly Dictionary<int, string> OrderStatusNames = new()
|
||
{
|
||
{ 0, "待支付" },
|
||
{ 1, "已支付" },
|
||
{ 2, "已取消" }
|
||
};
|
||
|
||
// 支付方式名称映射
|
||
private static readonly Dictionary<int, string> PayTypeNames = new()
|
||
{
|
||
{ 0, "未支付" },
|
||
{ 1, "微信支付" },
|
||
{ 2, "支付宝" }
|
||
};
|
||
|
||
// 发货状态名称映射
|
||
private static readonly Dictionary<int, string> ShippingStatusNames = new()
|
||
{
|
||
{ 0, "待支付" },
|
||
{ 1, "待发货" },
|
||
{ 2, "已发货" },
|
||
{ 3, "已签收" },
|
||
{ 4, "已取消" }
|
||
};
|
||
|
||
// 物流状态名称映射
|
||
private static readonly Dictionary<int, string> DeliveryStatusNames = new()
|
||
{
|
||
{ -1, "未查询" },
|
||
{ 0, "在途" },
|
||
{ 1, "揽收" },
|
||
{ 2, "疑难" },
|
||
{ 3, "签收" },
|
||
{ 4, "退签" },
|
||
{ 5, "派件" },
|
||
{ 6, "退回" }
|
||
};
|
||
|
||
// 订单项状态名称映射
|
||
private static readonly Dictionary<int, string> OrderItemStatusNames = new()
|
||
{
|
||
{ 0, "待处理" },
|
||
{ 1, "已回收" },
|
||
{ 2, "已发货" }
|
||
};
|
||
|
||
public OrderService(
|
||
HoneyBoxDbContext dbContext,
|
||
ILogger<OrderService> logger)
|
||
{
|
||
_dbContext = dbContext;
|
||
_logger = logger;
|
||
}
|
||
|
||
#region 订单查询
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<OrderListResponse>> GetOrderListAsync(OrderListRequest request)
|
||
{
|
||
var query = _dbContext.Orders.AsNoTracking();
|
||
|
||
// 应用过滤条件(包含手机号过滤)
|
||
query = await ApplyOrderFiltersWithMobileAsync(query, request);
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 获取订单列表
|
||
var orders = await query
|
||
.OrderByDescending(o => o.Id)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.ToListAsync();
|
||
|
||
// 获取用户信息
|
||
var userIds = orders.Select(o => o.UserId).Distinct().ToList();
|
||
var users = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id);
|
||
|
||
// 映射结果
|
||
var list = orders.Select(o => MapToOrderListResponse(o, users)).ToList();
|
||
|
||
return PagedResult<OrderListResponse>.Create(list, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<OrderDetailResponse?> GetOrderDetailAsync(int orderId)
|
||
{
|
||
var order = await _dbContext.Orders
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(o => o.Id == orderId);
|
||
|
||
if (order == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
// 获取用户信息
|
||
var user = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(u => u.Id == order.UserId);
|
||
|
||
// 获取订单项
|
||
var orderItems = await _dbContext.OrderItems
|
||
.AsNoTracking()
|
||
.Where(oi => oi.OrderId == orderId)
|
||
.ToListAsync();
|
||
|
||
// 映射基本信息
|
||
var response = MapToOrderDetailResponse(order, user);
|
||
|
||
// 按prize_code分组
|
||
response.PrizeGroups = GroupOrderItemsByPrizeCode(orderItems);
|
||
|
||
return response;
|
||
}
|
||
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<OrderListResponse>> GetStuckOrdersAsync(OrderListRequest request)
|
||
{
|
||
// 卡单:有订单项但状态为待处理的订单
|
||
var query = _dbContext.Orders
|
||
.AsNoTracking()
|
||
.Where(o => o.Status == 1); // 已支付的订单
|
||
|
||
// 应用过滤条件
|
||
query = ApplyOrderFilters(query, request);
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(o => mobileUserIds.Contains(o.UserId));
|
||
}
|
||
|
||
// 只获取有待处理订单项的订单
|
||
var ordersWithStuckItems = query
|
||
.Where(o => _dbContext.OrderItems.Any(oi => oi.OrderId == o.Id && oi.Status == 0));
|
||
|
||
// 获取总数
|
||
var total = await ordersWithStuckItems.CountAsync();
|
||
|
||
// 获取订单列表
|
||
var orders = await ordersWithStuckItems
|
||
.OrderByDescending(o => o.Id)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.ToListAsync();
|
||
|
||
// 获取用户信息
|
||
var userIds = orders.Select(o => o.UserId).Distinct().ToList();
|
||
var users = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id);
|
||
|
||
// 映射结果
|
||
var list = orders.Select(o => MapToOrderListResponse(o, users)).ToList();
|
||
|
||
return PagedResult<OrderListResponse>.Create(list, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<RecoveryOrderResponse>> GetRecoveryOrdersAsync(OrderListRequest request)
|
||
{
|
||
var query = _dbContext.OrderItemsRecoveries.AsNoTracking();
|
||
|
||
// 应用过滤条件
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(r => r.UserId == request.UserId.Value);
|
||
}
|
||
|
||
if (request.StartDate.HasValue)
|
||
{
|
||
var startTimestamp = (int)((DateTimeOffset)request.StartDate.Value).ToUnixTimeSeconds();
|
||
query = query.Where(r => r.Addtime >= startTimestamp);
|
||
}
|
||
|
||
if (request.EndDate.HasValue)
|
||
{
|
||
var endTimestamp = (int)((DateTimeOffset)request.EndDate.Value.AddDays(1)).ToUnixTimeSeconds();
|
||
query = query.Where(r => r.Addtime < endTimestamp);
|
||
}
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(r => mobileUserIds.Contains(r.UserId));
|
||
}
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 获取回收订单列表
|
||
var recoveries = await query
|
||
.OrderByDescending(r => r.Id)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.ToListAsync();
|
||
|
||
// 获取用户信息
|
||
var userIds = recoveries.Select(r => r.UserId).Distinct().ToList();
|
||
var users = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id);
|
||
|
||
// 获取回收的奖品信息
|
||
var recoveryNums = recoveries.Select(r => r.RecoveryNum).ToList();
|
||
var recoveryItems = await _dbContext.OrderItems
|
||
.AsNoTracking()
|
||
.Where(oi => oi.RecoveryNum != null && recoveryNums.Contains(oi.RecoveryNum))
|
||
.ToListAsync();
|
||
|
||
// 映射结果
|
||
var list = recoveries.Select(r => MapToRecoveryOrderResponse(r, users, recoveryItems)).ToList();
|
||
|
||
return PagedResult<RecoveryOrderResponse>.Create(list, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 发货管理
|
||
|
||
/// <inheritdoc />
|
||
public async Task<PagedResult<ShippingOrderResponse>> GetShippingOrdersAsync(ShippingOrderListRequest request)
|
||
{
|
||
var query = _dbContext.OrderItemsSends.AsNoTracking();
|
||
|
||
// 应用过滤条件(包含手机号过滤)
|
||
query = await ApplyShippingFiltersWithMobileAsync(query, request);
|
||
|
||
// 获取总数
|
||
var total = await query.CountAsync();
|
||
|
||
// 获取发货订单列表
|
||
var shippingOrders = await query
|
||
.OrderByDescending(s => s.Id)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.ToListAsync();
|
||
|
||
// 获取用户信息
|
||
var userIds = shippingOrders.Select(s => s.UserId).Distinct().ToList();
|
||
var users = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id);
|
||
|
||
// 映射结果(不包含奖品详情)
|
||
var list = shippingOrders.Select(s => MapToShippingOrderResponse(s, users, null)).ToList();
|
||
|
||
return PagedResult<ShippingOrderResponse>.Create(list, total, request.Page, request.PageSize);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<ShippingOrderResponse?> GetShippingOrderDetailAsync(int sendId)
|
||
{
|
||
var shippingOrder = await _dbContext.OrderItemsSends
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(s => s.Id == sendId);
|
||
|
||
if (shippingOrder == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
// 获取用户信息
|
||
var user = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.FirstOrDefaultAsync(u => u.Id == shippingOrder.UserId);
|
||
|
||
// 获取发货的奖品信息
|
||
var shippingItems = await _dbContext.OrderItems
|
||
.AsNoTracking()
|
||
.Where(oi => oi.SendNum == shippingOrder.SendNum)
|
||
.ToListAsync();
|
||
|
||
var users = user != null ? new Dictionary<int, User> { { user.Id, user } } : new Dictionary<int, User>();
|
||
|
||
return MapToShippingOrderResponse(shippingOrder, users, shippingItems);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> ShipOrderAsync(int sendId, ShipOrderRequest request, int operatorId)
|
||
{
|
||
var shippingOrder = await _dbContext.OrderItemsSends
|
||
.FirstOrDefaultAsync(s => s.Id == sendId);
|
||
|
||
if (shippingOrder == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "发货订单不存在");
|
||
}
|
||
|
||
if (shippingOrder.Status != 1)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "只有待发货状态的订单才能发货");
|
||
}
|
||
|
||
// 更新发货信息
|
||
shippingOrder.CourierName = request.CourierName;
|
||
shippingOrder.CourierNumber = request.CourierNumber;
|
||
shippingOrder.CourierCode = request.CourierCode;
|
||
shippingOrder.Status = 2; // 已发货
|
||
shippingOrder.SendTime = (int)DateTimeOffset.Now.ToUnixTimeSeconds();
|
||
shippingOrder.AdminId = operatorId;
|
||
shippingOrder.UpdatedAt = DateTime.Now;
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
// 记录操作日志
|
||
await LogOrderOperationAsync(sendId, "ship", $"发货处理: SendNum={shippingOrder.SendNum}, Courier={request.CourierName}, Number={request.CourierNumber}", operatorId);
|
||
|
||
_logger.LogInformation("发货处理成功: SendId={SendId}, SendNum={SendNum}, Operator={Operator}",
|
||
sendId, shippingOrder.SendNum, operatorId);
|
||
|
||
return result;
|
||
}
|
||
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> CancelShippingOrderAsync(int sendId, int operatorId)
|
||
{
|
||
var shippingOrder = await _dbContext.OrderItemsSends
|
||
.FirstOrDefaultAsync(s => s.Id == sendId);
|
||
|
||
if (shippingOrder == null)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.NotFound, "发货订单不存在");
|
||
}
|
||
|
||
if (shippingOrder.Status != 1 && shippingOrder.Status != 2)
|
||
{
|
||
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "只有待发货或已发货状态的订单才能取消");
|
||
}
|
||
|
||
// 获取发货的奖品
|
||
var shippingItems = await _dbContext.OrderItems
|
||
.Where(oi => oi.SendNum == shippingOrder.SendNum)
|
||
.ToListAsync();
|
||
|
||
// 恢复奖品到用户盒柜(将状态改回待处理)
|
||
foreach (var item in shippingItems)
|
||
{
|
||
item.Status = 0; // 待处理
|
||
item.SendNum = null;
|
||
item.FhStatus = 0;
|
||
item.UpdatedAt = DateTime.Now;
|
||
}
|
||
|
||
// 更新发货订单状态
|
||
shippingOrder.Status = 4; // 已取消
|
||
shippingOrder.CancelTime = (int)DateTimeOffset.Now.ToUnixTimeSeconds();
|
||
shippingOrder.AdminId = operatorId;
|
||
shippingOrder.UpdatedAt = DateTime.Now;
|
||
|
||
var result = await _dbContext.SaveChangesAsync() > 0;
|
||
|
||
// 记录操作日志
|
||
await LogOrderOperationAsync(sendId, "cancel_ship", $"取消发货: SendNum={shippingOrder.SendNum}, 恢复奖品数量={shippingItems.Count}", operatorId);
|
||
|
||
_logger.LogInformation("取消发货成功: SendId={SendId}, SendNum={SendNum}, RestoredItems={RestoredItems}, Operator={Operator}",
|
||
sendId, shippingOrder.SendNum, shippingItems.Count, operatorId);
|
||
|
||
return result;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 订单导出
|
||
|
||
/// <inheritdoc />
|
||
public async Task<byte[]> ExportOrdersAsync(OrderExportRequest request)
|
||
{
|
||
var query = _dbContext.Orders.AsNoTracking();
|
||
|
||
// 应用过滤条件
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(o => o.UserId == request.UserId.Value);
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(request.OrderNum))
|
||
{
|
||
query = query.Where(o => o.OrderNum.Contains(request.OrderNum));
|
||
}
|
||
|
||
if (request.StartDate.HasValue)
|
||
{
|
||
query = query.Where(o => o.CreatedAt >= request.StartDate.Value);
|
||
}
|
||
|
||
if (request.EndDate.HasValue)
|
||
{
|
||
var endDate = request.EndDate.Value.AddDays(1);
|
||
query = query.Where(o => o.CreatedAt < endDate);
|
||
}
|
||
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(o => o.Status == request.Status.Value);
|
||
}
|
||
|
||
if (request.OrderType.HasValue)
|
||
{
|
||
query = query.Where(o => o.OrderType == request.OrderType.Value);
|
||
}
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(o => mobileUserIds.Contains(o.UserId));
|
||
}
|
||
|
||
// 获取订单数据
|
||
var orders = await query
|
||
.OrderByDescending(o => o.Id)
|
||
.Take(10000) // 限制导出数量
|
||
.ToListAsync();
|
||
|
||
// 获取用户信息
|
||
var userIds = orders.Select(o => o.UserId).Distinct().ToList();
|
||
var users = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id);
|
||
|
||
// 映射导出数据
|
||
var exportData = orders.Select(o => MapToOrderExportDto(o, users)).ToList();
|
||
|
||
// 生成CSV格式(简单实现,可以替换为Excel库)
|
||
return GenerateCsvBytes(exportData);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<byte[]> ExportShippingOrdersAsync(ShippingExportRequest request)
|
||
{
|
||
var query = _dbContext.OrderItemsSends.AsNoTracking();
|
||
|
||
// 应用过滤条件
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(s => s.UserId == request.UserId.Value);
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(request.SendNum))
|
||
{
|
||
query = query.Where(s => s.SendNum.Contains(request.SendNum));
|
||
}
|
||
|
||
if (request.StartDate.HasValue)
|
||
{
|
||
var startTimestamp = (int)((DateTimeOffset)request.StartDate.Value).ToUnixTimeSeconds();
|
||
query = query.Where(s => s.Addtime >= startTimestamp);
|
||
}
|
||
|
||
if (request.EndDate.HasValue)
|
||
{
|
||
var endTimestamp = (int)((DateTimeOffset)request.EndDate.Value.AddDays(1)).ToUnixTimeSeconds();
|
||
query = query.Where(s => s.Addtime < endTimestamp);
|
||
}
|
||
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(s => s.Status == request.Status.Value);
|
||
}
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(s => mobileUserIds.Contains(s.UserId));
|
||
}
|
||
|
||
// 获取发货订单数据
|
||
var shippingOrders = await query
|
||
.OrderByDescending(s => s.Id)
|
||
.Take(10000) // 限制导出数量
|
||
.ToListAsync();
|
||
|
||
// 获取用户信息
|
||
var userIds = shippingOrders.Select(s => s.UserId).Distinct().ToList();
|
||
var users = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id);
|
||
|
||
// 映射导出数据
|
||
var exportData = shippingOrders.Select(s => MapToShippingExportDto(s, users)).ToList();
|
||
|
||
// 生成CSV格式
|
||
return GenerateShippingCsvBytes(exportData);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<byte[]> ExportRecoveryOrdersAsync(RecoveryExportRequest request)
|
||
{
|
||
var query = _dbContext.OrderItemsRecoveries.AsNoTracking();
|
||
|
||
// 应用过滤条件
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(r => r.UserId == request.UserId.Value);
|
||
}
|
||
|
||
if (request.StartDate.HasValue)
|
||
{
|
||
var startTimestamp = (int)((DateTimeOffset)request.StartDate.Value).ToUnixTimeSeconds();
|
||
query = query.Where(r => r.Addtime >= startTimestamp);
|
||
}
|
||
|
||
if (request.EndDate.HasValue)
|
||
{
|
||
var endTimestamp = (int)((DateTimeOffset)request.EndDate.Value.AddDays(1)).ToUnixTimeSeconds();
|
||
query = query.Where(r => r.Addtime < endTimestamp);
|
||
}
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(r => mobileUserIds.Contains(r.UserId));
|
||
}
|
||
|
||
// 获取回收订单数据
|
||
var recoveryOrders = await query
|
||
.OrderByDescending(r => r.Id)
|
||
.Take(10000) // 限制导出数量
|
||
.ToListAsync();
|
||
|
||
// 获取用户信息
|
||
var userIds = recoveryOrders.Select(r => r.UserId).Distinct().ToList();
|
||
var users = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => userIds.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id);
|
||
|
||
// 映射导出数据
|
||
var exportData = recoveryOrders.Select(r => MapToRecoveryExportDto(r, users)).ToList();
|
||
|
||
// 生成CSV格式
|
||
return GenerateRecoveryCsvBytes(exportData);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<ShippingStatsResponse> GetShippingStatsAsync(ShippingOrderListRequest request)
|
||
{
|
||
var query = _dbContext.OrderItemsSends.AsNoTracking();
|
||
|
||
// 应用过滤条件
|
||
query = ApplyShippingFilters(query, request);
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(s => mobileUserIds.Contains(s.UserId));
|
||
}
|
||
|
||
// 获取总条数
|
||
var totalCount = await query.CountAsync();
|
||
|
||
// 获取所有发货单号
|
||
var sendNums = await query.Select(s => s.SendNum).ToListAsync();
|
||
|
||
// 计算总价值(所有发货奖品的价值总和)
|
||
var totalValue = await _dbContext.OrderItems
|
||
.AsNoTracking()
|
||
.Where(oi => oi.SendNum != null && sendNums.Contains(oi.SendNum))
|
||
.SumAsync(oi => oi.GoodslistPrice);
|
||
|
||
// 获取当前页的发货单号
|
||
var pageShippingOrders = await query
|
||
.OrderByDescending(s => s.Id)
|
||
.Skip(request.Skip)
|
||
.Take(request.PageSize)
|
||
.Select(s => s.SendNum)
|
||
.ToListAsync();
|
||
|
||
// 计算本页总价值
|
||
var pageValue = await _dbContext.OrderItems
|
||
.AsNoTracking()
|
||
.Where(oi => oi.SendNum != null && pageShippingOrders.Contains(oi.SendNum))
|
||
.SumAsync(oi => oi.GoodslistPrice);
|
||
|
||
return new ShippingStatsResponse
|
||
{
|
||
TotalCount = totalCount,
|
||
TotalValue = totalValue,
|
||
PageValue = pageValue
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Private Helper Methods
|
||
|
||
/// <summary>
|
||
/// 应用订单过滤条件
|
||
/// </summary>
|
||
private IQueryable<Order> ApplyOrderFilters(IQueryable<Order> query, OrderListRequest request)
|
||
{
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(o => o.UserId == request.UserId.Value);
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(request.OrderNum))
|
||
{
|
||
query = query.Where(o => o.OrderNum.Contains(request.OrderNum));
|
||
}
|
||
|
||
if (request.StartDate.HasValue)
|
||
{
|
||
query = query.Where(o => o.CreatedAt >= request.StartDate.Value);
|
||
}
|
||
|
||
if (request.EndDate.HasValue)
|
||
{
|
||
var endDate = request.EndDate.Value.AddDays(1);
|
||
query = query.Where(o => o.CreatedAt < endDate);
|
||
}
|
||
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(o => o.Status == request.Status.Value);
|
||
}
|
||
|
||
if (request.OrderType.HasValue)
|
||
{
|
||
query = query.Where(o => o.OrderType == request.OrderType.Value);
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用订单过滤条件(包含手机号过滤,异步版本)
|
||
/// </summary>
|
||
private async Task<IQueryable<Order>> ApplyOrderFiltersWithMobileAsync(IQueryable<Order> query, OrderListRequest request)
|
||
{
|
||
query = ApplyOrderFilters(query, request);
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(o => mobileUserIds.Contains(o.UserId));
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用发货订单过滤条件
|
||
/// </summary>
|
||
private IQueryable<OrderItemsSend> ApplyShippingFilters(IQueryable<OrderItemsSend> query, ShippingOrderListRequest request)
|
||
{
|
||
if (request.UserId.HasValue)
|
||
{
|
||
query = query.Where(s => s.UserId == request.UserId.Value);
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(request.SendNum))
|
||
{
|
||
query = query.Where(s => s.SendNum.Contains(request.SendNum));
|
||
}
|
||
|
||
if (request.StartDate.HasValue)
|
||
{
|
||
var startTimestamp = (int)((DateTimeOffset)request.StartDate.Value).ToUnixTimeSeconds();
|
||
query = query.Where(s => s.Addtime >= startTimestamp);
|
||
}
|
||
|
||
if (request.EndDate.HasValue)
|
||
{
|
||
var endTimestamp = (int)((DateTimeOffset)request.EndDate.Value.AddDays(1)).ToUnixTimeSeconds();
|
||
query = query.Where(s => s.Addtime < endTimestamp);
|
||
}
|
||
|
||
if (request.Status.HasValue)
|
||
{
|
||
query = query.Where(s => s.Status == request.Status.Value);
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用发货订单过滤条件(包含手机号过滤,异步版本)
|
||
/// </summary>
|
||
private async Task<IQueryable<OrderItemsSend>> ApplyShippingFiltersWithMobileAsync(IQueryable<OrderItemsSend> query, ShippingOrderListRequest request)
|
||
{
|
||
query = ApplyShippingFilters(query, request);
|
||
|
||
// 按手机号过滤需要关联用户表
|
||
if (!string.IsNullOrWhiteSpace(request.Mobile))
|
||
{
|
||
var mobileUserIds = await _dbContext.Users
|
||
.AsNoTracking()
|
||
.Where(u => u.Mobile != null && u.Mobile.Contains(request.Mobile))
|
||
.Select(u => u.Id)
|
||
.ToListAsync();
|
||
query = query.Where(s => mobileUserIds.Contains(s.UserId));
|
||
}
|
||
|
||
return query;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 映射订单到列表响应
|
||
/// </summary>
|
||
private OrderListResponse MapToOrderListResponse(Order order, Dictionary<int, User> users)
|
||
{
|
||
users.TryGetValue(order.UserId, out var user);
|
||
|
||
return new OrderListResponse
|
||
{
|
||
Id = order.Id,
|
||
OrderNum = order.OrderNum,
|
||
UserId = order.UserId,
|
||
UserNickname = user?.Nickname,
|
||
UserMobile = user?.Mobile,
|
||
UserUid = user?.Uid,
|
||
GoodsId = order.GoodsId,
|
||
GoodsTitle = order.GoodsTitle,
|
||
GoodsImgUrl = order.GoodsImgurl,
|
||
OrderType = order.OrderType,
|
||
OrderTotal = order.OrderTotal,
|
||
Discount = order.Zhe,
|
||
DiscountTotal = order.OrderZheTotal,
|
||
WeChatPayment = order.Price,
|
||
BalancePayment = order.UseMoney,
|
||
IntegralPayment = order.UseIntegral,
|
||
ScorePayment = order.UseScore,
|
||
CouponPayment = order.UseCoupon,
|
||
Num = order.Num,
|
||
PrizeNum = order.PrizeNum,
|
||
Status = order.Status,
|
||
StatusName = OrderStatusNames.GetValueOrDefault(order.Status, "未知"),
|
||
PayType = order.PayType,
|
||
PayTypeName = PayTypeNames.GetValueOrDefault(order.PayType, "未知"),
|
||
CreatedAt = order.CreatedAt,
|
||
PayTime = order.PayTime > 0 ? DateTimeOffset.FromUnixTimeSeconds(order.PayTime).LocalDateTime : null
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射订单到详情响应
|
||
/// </summary>
|
||
private OrderDetailResponse MapToOrderDetailResponse(Order order, User? user)
|
||
{
|
||
return new OrderDetailResponse
|
||
{
|
||
Id = order.Id,
|
||
OrderNum = order.OrderNum,
|
||
UserId = order.UserId,
|
||
UserNickname = user?.Nickname,
|
||
UserMobile = user?.Mobile,
|
||
UserUid = user?.Uid,
|
||
GoodsId = order.GoodsId,
|
||
GoodsTitle = order.GoodsTitle,
|
||
GoodsImgUrl = order.GoodsImgurl,
|
||
OrderType = order.OrderType,
|
||
OrderTotal = order.OrderTotal,
|
||
Discount = order.Zhe,
|
||
DiscountTotal = order.OrderZheTotal,
|
||
WeChatPayment = order.Price,
|
||
BalancePayment = order.UseMoney,
|
||
IntegralPayment = order.UseIntegral,
|
||
ScorePayment = order.UseScore,
|
||
CouponPayment = order.UseCoupon,
|
||
Num = order.Num,
|
||
PrizeNum = order.PrizeNum,
|
||
Status = order.Status,
|
||
StatusName = OrderStatusNames.GetValueOrDefault(order.Status, "未知"),
|
||
PayType = order.PayType,
|
||
PayTypeName = PayTypeNames.GetValueOrDefault(order.PayType, "未知"),
|
||
CreatedAt = order.CreatedAt,
|
||
PayTime = order.PayTime > 0 ? DateTimeOffset.FromUnixTimeSeconds(order.PayTime).LocalDateTime : null,
|
||
PrizeGroups = new List<OrderPrizeGroupDto>()
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按prize_code分组订单项
|
||
/// </summary>
|
||
private List<OrderPrizeGroupDto> GroupOrderItemsByPrizeCode(List<OrderItem> orderItems)
|
||
{
|
||
return orderItems
|
||
.GroupBy(oi => oi.PrizeCode ?? "UNKNOWN")
|
||
.Select(g => new OrderPrizeGroupDto
|
||
{
|
||
PrizeCode = g.Key == "UNKNOWN" ? null : g.Key,
|
||
Title = g.First().GoodslistTitle,
|
||
ImgUrl = g.First().GoodslistImgurl,
|
||
Price = g.First().GoodslistPrice,
|
||
RecoveryMoney = g.First().GoodslistMoney,
|
||
Count = g.Count(),
|
||
GoodsType = g.First().GoodslistType,
|
||
ShangId = g.First().ShangId,
|
||
Items = g.Select(oi => new OrderPrizeItemDto
|
||
{
|
||
Id = oi.Id,
|
||
Status = oi.Status,
|
||
StatusName = OrderItemStatusNames.GetValueOrDefault(oi.Status, "未知"),
|
||
RecoveryNum = oi.RecoveryNum,
|
||
SendNum = oi.SendNum,
|
||
LuckNo = oi.LuckNo,
|
||
CreatedAt = oi.CreatedAt
|
||
}).ToList()
|
||
})
|
||
.ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射回收订单响应
|
||
/// </summary>
|
||
private RecoveryOrderResponse MapToRecoveryOrderResponse(OrderItemsRecovery recovery, Dictionary<int, User> users, List<OrderItem> allRecoveryItems)
|
||
{
|
||
users.TryGetValue(recovery.UserId, out var user);
|
||
|
||
var items = allRecoveryItems
|
||
.Where(oi => oi.RecoveryNum == recovery.RecoveryNum)
|
||
.Select(oi => new RecoveryPrizeDto
|
||
{
|
||
Id = oi.Id,
|
||
PrizeCode = oi.PrizeCode,
|
||
Title = oi.GoodslistTitle,
|
||
ImgUrl = oi.GoodslistImgurl,
|
||
Price = oi.GoodslistPrice,
|
||
RecoveryMoney = oi.GoodslistMoney,
|
||
GoodsType = oi.GoodslistType
|
||
})
|
||
.ToList();
|
||
|
||
return new RecoveryOrderResponse
|
||
{
|
||
Id = recovery.Id,
|
||
UserId = recovery.UserId,
|
||
UserNickname = user?.Nickname,
|
||
UserMobile = user?.Mobile,
|
||
UserUid = user?.Uid,
|
||
RecoveryNum = recovery.RecoveryNum,
|
||
Money = recovery.Money,
|
||
Count = recovery.Count,
|
||
CreatedAt = recovery.CreatedAt,
|
||
Prizes = items
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射发货订单响应
|
||
/// </summary>
|
||
private ShippingOrderResponse MapToShippingOrderResponse(OrderItemsSend shippingOrder, Dictionary<int, User> users, List<OrderItem>? shippingItems)
|
||
{
|
||
users.TryGetValue(shippingOrder.UserId, out var user);
|
||
|
||
var prizes = shippingItems?.Select(oi => new ShippingPrizeDto
|
||
{
|
||
Id = oi.Id,
|
||
PrizeCode = oi.PrizeCode,
|
||
Title = oi.GoodslistTitle,
|
||
ImgUrl = oi.GoodslistImgurl,
|
||
Price = oi.GoodslistPrice,
|
||
GoodsType = oi.GoodslistType
|
||
}).ToList() ?? new List<ShippingPrizeDto>();
|
||
|
||
return new ShippingOrderResponse
|
||
{
|
||
Id = shippingOrder.Id,
|
||
UserId = shippingOrder.UserId,
|
||
UserNickname = user?.Nickname,
|
||
UserMobile = user?.Mobile,
|
||
UserUid = user?.Uid,
|
||
SendNum = shippingOrder.SendNum,
|
||
Freight = shippingOrder.Freight,
|
||
Status = shippingOrder.Status,
|
||
StatusName = ShippingStatusNames.GetValueOrDefault(shippingOrder.Status, "未知"),
|
||
Count = shippingOrder.Count,
|
||
Name = shippingOrder.Name,
|
||
ReceiverMobile = shippingOrder.Mobile,
|
||
Address = shippingOrder.Address,
|
||
Message = shippingOrder.Message,
|
||
CourierNumber = shippingOrder.CourierNumber,
|
||
CourierName = shippingOrder.CourierName,
|
||
CourierCode = shippingOrder.CourierCode,
|
||
DeliveryStatus = shippingOrder.DeliveryStatus,
|
||
DeliveryStatusName = DeliveryStatusNames.GetValueOrDefault(shippingOrder.DeliveryStatus, "未知"),
|
||
CreatedAt = shippingOrder.CreatedAt,
|
||
PayTime = shippingOrder.PayTime > 0 ? DateTimeOffset.FromUnixTimeSeconds(shippingOrder.PayTime).LocalDateTime : null,
|
||
SendTime = shippingOrder.SendTime > 0 ? DateTimeOffset.FromUnixTimeSeconds(shippingOrder.SendTime).LocalDateTime : null,
|
||
ReceiveTime = shippingOrder.ShouTime > 0 ? DateTimeOffset.FromUnixTimeSeconds(shippingOrder.ShouTime).LocalDateTime : null,
|
||
Prizes = prizes
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射订单导出数据
|
||
/// </summary>
|
||
private OrderExportDto MapToOrderExportDto(Order order, Dictionary<int, User> users)
|
||
{
|
||
users.TryGetValue(order.UserId, out var user);
|
||
|
||
return new OrderExportDto
|
||
{
|
||
OrderNum = order.OrderNum,
|
||
UserId = order.UserId,
|
||
UserNickname = user?.Nickname,
|
||
UserMobile = user?.Mobile,
|
||
GoodsTitle = order.GoodsTitle,
|
||
OrderTotal = order.OrderTotal,
|
||
DiscountTotal = order.OrderZheTotal,
|
||
WeChatPayment = order.Price,
|
||
BalancePayment = order.UseMoney,
|
||
IntegralPayment = order.UseIntegral,
|
||
ScorePayment = order.UseScore,
|
||
CouponPayment = order.UseCoupon,
|
||
Num = order.Num,
|
||
PrizeNum = order.PrizeNum,
|
||
StatusName = OrderStatusNames.GetValueOrDefault(order.Status, "未知"),
|
||
PayTypeName = PayTypeNames.GetValueOrDefault(order.PayType, "未知"),
|
||
CreatedAt = order.CreatedAt,
|
||
PayTime = order.PayTime > 0 ? DateTimeOffset.FromUnixTimeSeconds(order.PayTime).LocalDateTime : null
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成CSV字节数组
|
||
/// </summary>
|
||
private byte[] GenerateCsvBytes(List<OrderExportDto> data)
|
||
{
|
||
var sb = new System.Text.StringBuilder();
|
||
|
||
// 添加表头
|
||
sb.AppendLine("订单编号,用户ID,用户昵称,用户手机号,商品标题,订单总金额,折后金额,微信支付,余额支付,积分支付,钻石支付,优惠券抵扣,购买数量,中奖数量,订单状态,支付方式,创建时间,支付时间");
|
||
|
||
// 添加数据行
|
||
foreach (var item in data)
|
||
{
|
||
sb.AppendLine($"{item.OrderNum},{item.UserId},{EscapeCsvField(item.UserNickname)},{item.UserMobile},{EscapeCsvField(item.GoodsTitle)},{item.OrderTotal},{item.DiscountTotal},{item.WeChatPayment},{item.BalancePayment},{item.IntegralPayment},{item.ScorePayment},{item.CouponPayment ?? 0},{item.Num},{item.PrizeNum},{item.StatusName},{item.PayTypeName},{item.CreatedAt:yyyy-MM-dd HH:mm:ss},{item.PayTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""}");
|
||
}
|
||
|
||
// 使用UTF-8 BOM以支持中文
|
||
var preamble = System.Text.Encoding.UTF8.GetPreamble();
|
||
var content = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
|
||
var result = new byte[preamble.Length + content.Length];
|
||
preamble.CopyTo(result, 0);
|
||
content.CopyTo(result, preamble.Length);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 转义CSV字段
|
||
/// </summary>
|
||
private string EscapeCsvField(string? field)
|
||
{
|
||
if (string.IsNullOrEmpty(field))
|
||
return "";
|
||
|
||
if (field.Contains(',') || field.Contains('"') || field.Contains('\n'))
|
||
{
|
||
return $"\"{field.Replace("\"", "\"\"")}\"";
|
||
}
|
||
|
||
return field;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射发货订单导出数据
|
||
/// </summary>
|
||
private ShippingExportDto MapToShippingExportDto(OrderItemsSend shippingOrder, Dictionary<int, User> users)
|
||
{
|
||
users.TryGetValue(shippingOrder.UserId, out var user);
|
||
|
||
return new ShippingExportDto
|
||
{
|
||
SendNum = shippingOrder.SendNum,
|
||
UserId = shippingOrder.UserId,
|
||
UserNickname = user?.Nickname,
|
||
UserMobile = user?.Mobile,
|
||
Name = shippingOrder.Name,
|
||
ReceiverMobile = shippingOrder.Mobile,
|
||
Address = shippingOrder.Address,
|
||
Count = shippingOrder.Count,
|
||
Freight = shippingOrder.Freight,
|
||
StatusName = ShippingStatusNames.GetValueOrDefault(shippingOrder.Status, "未知"),
|
||
CourierName = shippingOrder.CourierName,
|
||
CourierNumber = shippingOrder.CourierNumber,
|
||
Message = shippingOrder.Message,
|
||
CreatedAt = shippingOrder.CreatedAt,
|
||
SendTime = shippingOrder.SendTime > 0 ? DateTimeOffset.FromUnixTimeSeconds(shippingOrder.SendTime).LocalDateTime : null
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 映射回收订单导出数据
|
||
/// </summary>
|
||
private RecoveryExportDto MapToRecoveryExportDto(OrderItemsRecovery recovery, Dictionary<int, User> users)
|
||
{
|
||
users.TryGetValue(recovery.UserId, out var user);
|
||
|
||
return new RecoveryExportDto
|
||
{
|
||
RecoveryNum = recovery.RecoveryNum,
|
||
UserId = recovery.UserId,
|
||
UserNickname = user?.Nickname,
|
||
UserMobile = user?.Mobile,
|
||
Money = recovery.Money,
|
||
Count = recovery.Count,
|
||
CreatedAt = recovery.CreatedAt
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成发货订单CSV字节数组
|
||
/// </summary>
|
||
private byte[] GenerateShippingCsvBytes(List<ShippingExportDto> data)
|
||
{
|
||
var sb = new System.Text.StringBuilder();
|
||
|
||
// 添加表头
|
||
sb.AppendLine("发货单号,用户ID,用户昵称,用户手机号,收货人,收货手机号,收货地址,发货数量,运费,状态,快递公司,快递单号,备注,创建时间,发货时间");
|
||
|
||
// 添加数据行
|
||
foreach (var item in data)
|
||
{
|
||
sb.AppendLine($"{item.SendNum},{item.UserId},{EscapeCsvField(item.UserNickname)},{item.UserMobile},{EscapeCsvField(item.Name)},{item.ReceiverMobile},{EscapeCsvField(item.Address)},{item.Count},{item.Freight},{item.StatusName},{EscapeCsvField(item.CourierName)},{item.CourierNumber},{EscapeCsvField(item.Message)},{item.CreatedAt:yyyy-MM-dd HH:mm:ss},{item.SendTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? ""}");
|
||
}
|
||
|
||
// 使用UTF-8 BOM以支持中文
|
||
var preamble = System.Text.Encoding.UTF8.GetPreamble();
|
||
var content = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
|
||
var result = new byte[preamble.Length + content.Length];
|
||
preamble.CopyTo(result, 0);
|
||
content.CopyTo(result, preamble.Length);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成回收订单CSV字节数组
|
||
/// </summary>
|
||
private byte[] GenerateRecoveryCsvBytes(List<RecoveryExportDto> data)
|
||
{
|
||
var sb = new System.Text.StringBuilder();
|
||
|
||
// 添加表头
|
||
sb.AppendLine("回收单号,用户ID,用户昵称,用户手机号,回收金额,回收数量,创建时间");
|
||
|
||
// 添加数据行
|
||
foreach (var item in data)
|
||
{
|
||
sb.AppendLine($"{item.RecoveryNum},{item.UserId},{EscapeCsvField(item.UserNickname)},{item.UserMobile},{item.Money},{item.Count},{item.CreatedAt:yyyy-MM-dd HH:mm:ss}");
|
||
}
|
||
|
||
// 使用UTF-8 BOM以支持中文
|
||
var preamble = System.Text.Encoding.UTF8.GetPreamble();
|
||
var content = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
|
||
var result = new byte[preamble.Length + content.Length];
|
||
preamble.CopyTo(result, 0);
|
||
content.CopyTo(result, preamble.Length);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 记录订单操作日志
|
||
/// </summary>
|
||
private async Task LogOrderOperationAsync(int orderId, string operation, string content, int operatorId)
|
||
{
|
||
var log = new AdminOperationLog
|
||
{
|
||
AdminId = operatorId,
|
||
Operation = $"order:{operation}",
|
||
Content = $"OrderId={orderId}, {content}",
|
||
Ip = string.Empty,
|
||
CreatedAt = DateTime.Now
|
||
};
|
||
|
||
_dbContext.AdminOperationLogs.Add(log);
|
||
await _dbContext.SaveChangesAsync();
|
||
}
|
||
|
||
#endregion
|
||
}
|