using System.Linq.Expressions; using Fake.Application; using Fake.Domain.Exceptions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; using Oms.Application.Dtos.Orders; using Oms.Application.Dtos.Shared; using Oms.Application.Shared; using Oms.Domain.ChannelsAggregate.Repositories; using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Entities; using Oms.Domain.OrdersAggregate.Repositories; using Oms.Domain.SystemAggregate.Repositories; namespace Oms.Application.Services.Orders; [ApiExplorerSettings(GroupName = "OMS-Order")] public partial class OrderService : ApplicationService { private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService(); private IOrderTemplateRepository OrderTemplateRepository => LazyServiceProvider.GetRequiredService(); private IOrderTaskRepository OrderTaskRepository => LazyServiceProvider.GetRequiredService(); private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService(); private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService(); private IWebHostEnvironment WebHostEnvironment => LazyServiceProvider.GetRequiredService(); /// /// 查询订单列表。 /// /// 入参:订单分页查询条件。 /// 订单分页结果,包含列表数据和总记录数。 /// /// 核心业务逻辑: /// 1. 根据查询条件构造筛选规则。 /// 2. 查询目标数据并补齐关联展示字段。 /// 3. 按接口约定返回列表结果。 /// [HttpPost("/order/list")] public async Task> GetPaginatedListAsync(OrderPaginatedInput input) { var predicate = BuildPaginatedPredicate(input); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var orders = await OrderRepository.GetPagedListAsync( predicate, pageIndex, pageSize, new Dictionary { [nameof(Order.UpdateTime)] = false, [nameof(Order.Id)] = false }); var total = await OrderRepository.CountAsync(predicate); return new OmsPageResult { Items = await MapOrderOutputsAsync(orders), Total = total }; } /// /// 查询订单详情。 /// /// 入参:目标主键。 /// 订单详情。 /// /// 核心业务逻辑: /// 1. 根据标识查询目标对象。 /// 2. 目标不存在时抛出业务异常。 /// 3. 映射并返回详情结果。 /// [HttpGet("/order/detail")] public async Task GetDetailAsync([FromQuery] long id) { var order = await GetOrderOrThrowAsync(id); return (await MapOrderOutputsAsync([order])).First(); } /// /// 执行订单支付。 /// /// 入参:订单操作参数。 /// 无返回体,仅完成订单支付处理。 /// /// 核心业务逻辑: /// 1. 查询并校验目标对象及当前状态。 /// 2. 执行业务状态流转或敏感字段更新。 /// 3. 写入审计字段并保存结果。 /// [HttpPost("/order/pay")] public async Task PayAsync(OrderActionInput input) { var order = await GetOrderOrThrowAsync(input.Id); order.PayTime = DateTime.Now; order.OrderStatus = OrderStatusCodes.PendingShip; await UpdateOrderAsync(order); } /// /// 执行订单预报。 /// /// 入参:订单操作参数。 /// 无返回体,仅完成订单预报处理。 /// /// 核心业务逻辑: /// 1. 查询并校验目标对象及当前状态。 /// 2. 执行业务状态流转或敏感字段更新。 /// 3. 写入审计字段并保存结果。 /// [HttpPost("/order/pre-declare")] public async Task PreDeclareAsync(OrderActionInput input) { var order = await GetOrderOrThrowAsync(input.Id); order.OrderStatus = OrderStatusCodes.Predeclared; await UpdateOrderAsync(order); } /// /// 执行订单拦截。 /// /// 入参:订单操作参数。 /// 无返回体,仅完成订单拦截处理。 /// /// 核心业务逻辑: /// 1. 查询并校验目标对象及当前状态。 /// 2. 执行业务状态流转或敏感字段更新。 /// 3. 写入审计字段并保存结果。 /// [HttpPost("/order/intercept")] public async Task InterceptAsync(OrderActionInput input) { var order = await GetOrderOrThrowAsync(input.Id); order.IsIntercept = 1; await UpdateOrderAsync(order); } /// /// 取消订单拦截。 /// /// 入参:订单操作参数。 /// 无返回体,仅完成取消订单拦截。 /// /// 核心业务逻辑: /// 1. 查询并校验目标对象及当前状态。 /// 2. 执行业务状态流转或敏感字段更新。 /// 3. 写入审计字段并保存结果。 /// [HttpPost("/order/cancel-intercept")] public async Task CancelInterceptAsync(OrderActionInput input) { var order = await GetOrderOrThrowAsync(input.Id); order.IsIntercept = 0; await UpdateOrderAsync(order); } /// /// 删除订单。 /// /// 入参:订单操作参数。 /// 无返回体,仅完成删除订单。 /// /// 核心业务逻辑: /// 1. 查询并校验目标对象。 /// 2. 执行软删除或删除关联明细。 /// 3. 持久化删除结果。 /// [HttpPost("/order/delete")] public async Task DeleteAsync(OrderActionInput input) { var order = await GetOrderOrThrowAsync(input.Id); order.IsDeleted = true; order.OrderStatus = OrderStatusCodes.Deleted; await UpdateOrderAsync(order); } /// /// 打印订单。 /// /// 入参:订单操作参数。 /// 订单打印结果。 /// /// 核心业务逻辑: /// 1. 校验模板、目标单据等前置依赖数据。 /// 2. 执行打印逻辑并记录打印结果。 /// 3. 返回打印结果信息。 /// [HttpPost("/order/print")] public async Task PrintAsync(OrderActionInput input) { var order = await GetOrderOrThrowAsync(input.Id); var printUrl = await CreateOrderPrintFileAsync((await MapOrderOutputsAsync([order])).First()); return new OrderPrintOutput { PrintUrl = printUrl }; } /// /// 导出订单。 /// /// 入参:订单导出筛选条件。 /// 订单导出结果。 /// /// 核心业务逻辑: /// 1. 根据筛选条件查询待导出数据或定位模板资源。 /// 2. 生成文件内容或组装下载响应。 /// 3. 以文件形式返回结果。 /// [HttpPost("/order/export")] public async Task ExportAsync(OrderExportInput input) { await CleanupExpiredFilesAsync(GetOrderExportDirectory()); var orders = await OrderRepository.GetListAsync( BuildExportPredicate(input), new Dictionary { [nameof(Order.CreateTime)] = false, [nameof(Order.Id)] = false }); var fileName = $"orders-{DateTime.Now:yyyyMMddHHmmss}.xlsx"; var directory = GetOrderExportDirectory(); Directory.CreateDirectory(directory); await MiniExcel.SaveAsAsync(Path.Combine(directory, fileName), await BuildOrderExportRowsAsync(orders)); return new OrderExportOutput { DownloadUrl = $"/uploads/exports/orders/{fileName}", ExpireAt = DateTime.Now.AddHours(24) }; } /// /// 执行订单运费试算。 /// /// 入参:订单运费试算参数。 /// 订单运费试算列表结果。 /// /// 核心业务逻辑: /// 1. 根据试算参数构造报价请求。 /// 2. 汇总费用、时效等试算结果。 /// 3. 按接口约定返回结果列表。 /// [HttpPost("/order/price-check")] public async Task> PriceCheckAsync(OrderPriceCheckInput input) { var items = await BuildPriceCheckOutputsAsync(input); return new OmsItemsResult { Items = items }; } /// /// 查询订单模板列表。 /// /// 入参:订单模板列表查询条件。 /// 订单模板分页结果,包含列表数据和总记录数。 /// /// 核心业务逻辑: /// 1. 根据查询条件构造筛选规则。 /// 2. 查询目标数据并补齐关联展示字段。 /// 3. 按接口约定返回列表结果。 /// [HttpGet("/order/template/list")] public async Task> GetTemplateListAsync([FromQuery] OrderTemplateListInput input) { var items = await GetTemplateOutputsAsync(input); return items; } /// /// 下载订单模板文件。 /// /// 入参:模板主键。 /// 文件流响应。 /// /// 核心业务逻辑: /// 1. 根据筛选条件查询待导出数据或定位模板资源。 /// 2. 生成文件内容或组装下载响应。 /// 3. 以文件形式返回结果。 /// [HttpGet("/order/template/download")] public async Task DownloadTemplateAsync([FromQuery] long templateId) { return await BuildTemplateDownloadAsync(templateId); } /// /// 导入订单文件。 /// /// 入参:订单导入参数。 /// 订单导入结果。 /// /// 核心业务逻辑: /// 1. 校验上传文件及导入参数。 /// 2. 解析文件内容并执行导入流程。 /// 3. 汇总成功失败结果后返回。 /// [HttpPost("/order/upload")] public async Task UploadAsync([FromForm] OrderUploadInput input) { return await ImportOrdersAsync(input); } /// /// 查询订单任务列表。 /// /// 入参:订单任务列表查询条件。 /// 订单任务分页结果,包含列表数据和总记录数。 /// /// 核心业务逻辑: /// 1. 根据查询条件构造筛选规则。 /// 2. 查询目标数据并补齐关联展示字段。 /// 3. 按接口约定返回列表结果。 /// [HttpGet("/order/task/list")] public async Task> GetTaskListAsync([FromQuery] OrderTaskListInput input) { return await GetTaskOutputsAsync(input); } /// /// 查询订单任务详情。 /// /// 入参:任务主键。 /// 订单任务详情。 /// /// 核心业务逻辑: /// 1. 根据标识查询目标对象。 /// 2. 目标不存在时抛出业务异常。 /// 3. 映射并返回详情结果。 /// [HttpGet("/order/task/detail")] public async Task GetTaskDetailAsync([FromQuery] long taskId) { return await GetTaskDetailCoreAsync(taskId); } /// /// 创建订单包裹。 /// /// 入参:订单包裹创建参数。 /// 订单包裹创建结果。 /// /// 核心业务逻辑: /// 1. 校验入参与关联对象、唯一性等前置条件。 /// 2. 组装目标实体及相关明细数据。 /// 3. 写入审计字段并持久化结果。 /// [HttpPost("/order/parcel/create")] public async Task CreateParcelAsync(OrderParcelCreateInput input) { return await CreateParcelCoreAsync(input); } /// /// 将订单列表请求对象转换为仓储可执行的查询表达式。 /// /// 入参:分页查询条件对象。 /// 出参:订单查询表达式。 private Expression> BuildPaginatedPredicate(OrderPaginatedInput input) { return BuildOrderPredicate(input.Status, input.Keyword, input.CreateTimeStart, input.CreateTimeEnd); } /// /// 将订单导出请求对象转换为仓储可执行的查询表达式。 /// /// 入参:导出筛选条件对象。 /// 出参:订单查询表达式。 private Expression> BuildExportPredicate(OrderExportInput input) { return BuildOrderPredicate(input.Status, input.Keyword, input.CreateTimeStart, input.CreateTimeEnd); } /// /// 统一构造订单查询条件。 /// /// 入参:订单状态,传入 all 或空值时表示不按状态过滤。 /// 入参:关键词,可匹配运单号、客户单号、跟踪号。 /// 入参:创建时间开始值。 /// 入参:创建时间结束值。 /// 出参:可直接交给 SqlSugar 仓储执行的订单筛选表达式。 /// /// 核心业务逻辑: /// 1. 先把状态和关键词做统一清洗; /// 2. 再按时间区间是否存在拆分成不同表达式; /// 3. 避免把 / 放进可翻译 SQL 的表达式中, /// 从而规避 MySQL 下时间条件被错误翻译的问题。 /// private static Expression> BuildOrderPredicate( string? status, string? keyword, DateTime? createTimeStart, DateTime? createTimeEnd) { status = status?.Trim(); keyword = keyword?.Trim(); var hasStatusFilter = !string.IsNullOrWhiteSpace(status) && status != "all"; var hasKeywordFilter = !string.IsNullOrWhiteSpace(keyword); // 显式拆开时间分支,避免 SqlSugar 在翻译 Nullable 判空时生成异常 SQL。 if (createTimeStart.HasValue && createTimeEnd.HasValue) { var start = createTimeStart.Value; var end = createTimeEnd.Value; return order => !order.IsDeleted && (!hasStatusFilter || order.OrderStatus == status) && (!hasKeywordFilter || order.ServiceNo.Contains(keyword!) || (order.ClientNo ?? string.Empty).Contains(keyword!) || (order.TrackNo ?? string.Empty).Contains(keyword!)) && order.CreateTime >= start && order.CreateTime <= end; } if (createTimeStart.HasValue) { var start = createTimeStart.Value; return order => !order.IsDeleted && (!hasStatusFilter || order.OrderStatus == status) && (!hasKeywordFilter || order.ServiceNo.Contains(keyword!) || (order.ClientNo ?? string.Empty).Contains(keyword!) || (order.TrackNo ?? string.Empty).Contains(keyword!)) && order.CreateTime >= start; } if (createTimeEnd.HasValue) { var end = createTimeEnd.Value; return order => !order.IsDeleted && (!hasStatusFilter || order.OrderStatus == status) && (!hasKeywordFilter || order.ServiceNo.Contains(keyword!) || (order.ClientNo ?? string.Empty).Contains(keyword!) || (order.TrackNo ?? string.Empty).Contains(keyword!)) && order.CreateTime <= end; } return order => !order.IsDeleted && (!hasStatusFilter || order.OrderStatus == status) && (!hasKeywordFilter || order.ServiceNo.Contains(keyword!) || (order.ClientNo ?? string.Empty).Contains(keyword!) || (order.TrackNo ?? string.Empty).Contains(keyword!)); } private async Task GetOrderOrThrowAsync(long id) { var entity = await OrderRepository.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted); if (entity == null) { throw new BusinessException("订单不存在"); } return entity; } private async Task UpdateOrderAsync(Order entity) { entity.UpdateUserId = CurrentUser.GetId(); entity.UpdateTime = DateTime.Now; await OrderRepository.UpdateAsync(entity); } }