using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Operations;
using Oms.Application.Dtos.Shared;
using Oms.Application.Services.Orders;
using Oms.Application.Services.Orders.Issues;
using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.OperationsAggregate.Entities;
using Oms.Domain.OperationsAggregate.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.Operations;
///
/// 出库记录 API 服务。
///
[ApiExplorerSettings(GroupName = "OMS-Operation")]
public class OutboundRecordService : ApplicationService
{
private IOutboundRecordRepository OutboundRecordRepository => LazyServiceProvider.GetRequiredService();
private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService();
private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService();
private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService();
private IServiceChannelGroupRepository ServiceChannelGroupRepository => LazyServiceProvider.GetRequiredService();
private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService();
private ICountryRepository CountryRepository => LazyServiceProvider.GetRequiredService();
private OrderService OrderService => LazyServiceProvider.GetRequiredService();
private AutoTriggerRuntimeService AutoTriggerRuntimeService =>
LazyServiceProvider.GetRequiredService();
///
/// 按单号查询订单(出库扫描展示销售产品、目的国、服务渠道,只读)。
///
[HttpGet("/operation/outbound/order-lookup")]
public async Task LookupOrderAsync([FromQuery] string waybillNo)
{
if (string.IsNullOrWhiteSpace(waybillNo))
{
throw new BusinessException("请扫描或输入单号");
}
var order = await OrderRepository.GetByWaybillNoAsync(waybillNo.Trim());
if (order == null)
{
throw new BusinessException("未找到对应订单,请检查单号");
}
EnsureOutboundAllowed(order);
var output = new OutboundOrderLookupOutput
{
OrderId = order.Id,
ServiceNo = order.ServiceNo,
ClientNo = order.ClientNo,
TrackNo = order.TrackNo,
ProductCode = order.ProductCode,
DestinationCountry = order.ConsigneeCountry,
OrderStatus = order.OrderStatus,
OrderStatusName = OmsDocHelper.GetOrderStatusName(order.OrderStatus)
};
if (!string.IsNullOrWhiteSpace(order.ProductCode))
{
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
if (product != null)
{
output.SalesProductId = product.Id;
output.SalesProductName = product.ProductName;
}
}
if (!string.IsNullOrWhiteSpace(order.ConsigneeCountry))
{
var country = await CountryRepository.FirstOrDefaultAsync(x => x.CountryCode == order.ConsigneeCountry);
output.DestinationCountryName = country?.ChineseName ?? country?.EnglishName ?? order.ConsigneeCountry;
}
if (!string.IsNullOrWhiteSpace(order.ChannelCode))
{
var channel = await ServiceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim());
if (channel != null)
{
output.ServiceChannelId = channel.Id;
output.ChannelCode = channel.ChannelCode;
output.ServiceChannelName = await BuildServiceChannelDisplayNameAsync(channel.GroupId, channel.ChannelName);
}
}
return output;
}
///
/// 新增出库记录。
///
/// 入参:出库记录创建参数。
/// 出库记录结果。
[HttpPost("/operation/outbound/create")]
public async Task CreateAsync(CreateOutboundRecordInput input)
{
if (string.IsNullOrWhiteSpace(input.WaybillNo))
{
throw new BusinessException("运单号不能为空");
}
if (!input.Weight.HasValue || !input.Length.HasValue || !input.Width.HasValue || !input.Height.HasValue
|| input.Weight <= 0 || input.Length <= 0 || input.Width <= 0 || input.Height <= 0)
{
throw new BusinessException("发货实重与长宽高不能为空且须大于 0");
}
var order = await OrderRepository.GetByWaybillNoAsync(input.WaybillNo.Trim());
if (order == null)
{
throw new BusinessException("未找到对应订单,请检查单号");
}
EnsureOutboundAllowed(order);
int? salesProductId = input.SalesProductId;
if (!salesProductId.HasValue && !string.IsNullOrWhiteSpace(order.ProductCode))
{
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
salesProductId = product?.Id;
}
var destinationCountry = input.DestinationCountry ?? order.ConsigneeCountry;
var serviceChannelId = input.ServiceChannelId;
if (!serviceChannelId.HasValue && !string.IsNullOrWhiteSpace(order.ChannelCode))
{
var channel = await ServiceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim());
serviceChannelId = channel?.Id;
}
var now = DateTime.Now;
var operatorId = CurrentUser.GetId();
var entity = new OutboundRecord
{
WaybillNo = input.WaybillNo.Trim(),
Weight = input.Weight,
Length = input.Length,
Width = input.Width,
Height = input.Height,
SalesProductId = salesProductId,
DestinationCountry = destinationCountry,
ServiceChannelId = serviceChannelId,
WarehouseId = input.WarehouseId ?? order.WarehouseId,
OperateTime = now,
OperatorId = operatorId
};
await OrderService.ApplyShipBillingByWaybillAsync(
input.WaybillNo.Trim(),
input.Length,
input.Width,
input.Height,
input.Weight);
var savedEntity = await OutboundRecordRepository.InsertAsync(entity);
try
{
var refreshed = await OrderRepository.GetByWaybillNoAsync(input.WaybillNo.Trim());
if (refreshed != null)
{
await AutoTriggerRuntimeService.FireAsync(refreshed, AutoTriggerTiming.Outbound, operatorId);
}
}
catch
{
// 自动触发失败不影响出库主流程
}
return ObjectMapper.Map(savedEntity);
}
///
/// 查询最近出库记录列表。
///
/// 入参:返回记录条数上限。
/// 入参:warehouseId。
/// 出库记录列表结果。
[HttpGet("/operation/outbound/recent-list")]
public async Task> GetRecentListAsync([FromQuery] int limit = 20, [FromQuery] int? warehouseId = null)
{
var hasWarehouseFilter = warehouseId.HasValue;
var warehouseIdValue = warehouseId.GetValueOrDefault();
var entities = await OutboundRecordRepository.GetPagedListAsync(
x => !hasWarehouseFilter || x.WarehouseId == warehouseIdValue,
1,
limit <= 0 ? 20 : limit,
new Dictionary { [nameof(OutboundRecord.OperateTime)] = false, [nameof(OutboundRecord.Id)] = false });
var salesProductIds = entities.Where(x => x.SalesProductId.HasValue).Select(x => x.SalesProductId!.Value).Distinct().ToArray();
var serviceChannelIds = entities.Where(x => x.ServiceChannelId.HasValue).Select(x => x.ServiceChannelId!.Value).Distinct().ToArray();
var warehouseIds = entities.Where(x => x.WarehouseId.HasValue).Select(x => x.WarehouseId!.Value).Distinct().ToArray();
var products = salesProductIds.Length == 0
? []
: await SalesProductRepository.GetListAsync(x => salesProductIds.Contains(x.Id));
var channels = serviceChannelIds.Length == 0
? []
: await ServiceChannelRepository.GetListAsync(x => serviceChannelIds.Contains(x.Id));
var warehouses = warehouseIds.Length == 0
? []
: await WarehouseAddressRepository.GetListAsync(x => warehouseIds.Contains(x.Id));
var productMap = products.ToDictionary(x => x.Id);
var channelMap = channels.ToDictionary(x => x.Id);
var warehouseMap = warehouses.ToDictionary(x => x.Id);
var groupIds = channels.Where(x => x.GroupId.HasValue).Select(x => x.GroupId!.Value).Distinct().ToArray();
var groups = groupIds.Length == 0
? []
: await ServiceChannelGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
var groupMap = groups.ToDictionary(x => x.Id);
var items = new List();
foreach (var entity in entities)
{
var output = ObjectMapper.Map(entity);
output.SalesProductName = entity.SalesProductId.HasValue && productMap.TryGetValue(entity.SalesProductId.Value, out var product)
? product.ProductName
: null;
output.WarehouseName = entity.WarehouseId.HasValue && warehouseMap.TryGetValue(entity.WarehouseId.Value, out var warehouse)
? warehouse.WarehouseName
: null;
if (entity.ServiceChannelId.HasValue && channelMap.TryGetValue(entity.ServiceChannelId.Value, out var channel))
{
string? groupName = null;
if (channel.GroupId.HasValue && groupMap.TryGetValue(channel.GroupId.Value, out var group))
{
groupName = group.Name;
}
var displayName = $"{groupName}{channel.ChannelName}".Trim();
output.ServiceChannelName = string.IsNullOrWhiteSpace(displayName) ? channel.ChannelName : displayName;
}
output.OperatorName = entity.OperatorId?.ToString();
items.Add(output);
}
return new OmsItemsResult { Items = items };
}
private static void EnsureOutboundAllowed(Order order)
{
if (order.IsPaid == 0)
{
throw new BusinessException("订单未付款,禁止出库");
}
var orderStatus = order.OrderStatus;
if (orderStatus != OrderStatusCodes.PendingShip)
{
if (orderStatus is OrderStatusCodes.PendingOnline or OrderStatusCodes.Shipping
or OrderStatusCodes.DeliverySuccess or OrderStatusCodes.DeliveryFailed)
{
throw new BusinessException("订单已出库");
}
throw new BusinessException($"订单状态为「{OmsDocHelper.GetOrderStatusName(orderStatus)}」,不允许出库");
}
}
private async Task BuildServiceChannelDisplayNameAsync(int? groupId, string channelName)
{
string? groupName = null;
if (groupId.HasValue)
{
var group = await ServiceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == groupId.Value);
groupName = group?.Name;
}
var displayName = $"{groupName}{channelName}".Trim();
return string.IsNullOrWhiteSpace(displayName) ? channelName : displayName;
}
}