This commit is contained in:
zzh 2026-06-27 11:56:06 +08:00
parent d766343ffe
commit fdbdfa8124
6 changed files with 199 additions and 4 deletions

View File

@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS `t_customer_account_flow` (
`Currency` varchar(8) NOT NULL DEFAULT 'CNY' COMMENT '币种',
`Remark` varchar(512) DEFAULT NULL COMMENT '备注',
`IsDeleted` tinyint(1) NOT NULL DEFAULT '0',
`ConcurrencyStamp` char(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` int NOT NULL DEFAULT '0',
`CreateTime` datetime NOT NULL,
`UpdateUserId` int DEFAULT NULL,

View File

@ -0,0 +1,8 @@
-- 已执行旧版 migrate_t_customer_account_flow.sql 的库补 ConcurrencyStamp(与 AggregateRoot 实体对齐)
-- 勿在 ALTER 中使用 DEFAULT(UUID()),复制环境会报 Error 1674
ALTER TABLE `t_customer_account_flow`
ADD COLUMN `ConcurrencyStamp` char(36) NOT NULL DEFAULT '' COMMENT '并发戳' AFTER `IsDeleted`;
UPDATE `t_customer_account_flow`
SET `ConcurrencyStamp` = UUID()
WHERE `ConcurrencyStamp` = '';

View File

@ -0,0 +1,77 @@
namespace Oms.Application.Dtos.Operations;
/// <summary>
/// 出库扫描按单号查订单(只读展示销售产品、目的国、服务渠道)。
/// </summary>
public class OutboundOrderLookupOutput
{
/// <summary>
/// 订单主键。
/// </summary>
public int OrderId { get; set; }
/// <summary>
/// 服务单号。
/// </summary>
public string ServiceNo { get; set; } = string.Empty;
/// <summary>
/// 客户单号。
/// </summary>
public string? ClientNo { get; set; }
/// <summary>
/// 跟踪号。
/// </summary>
public string? TrackNo { get; set; }
/// <summary>
/// 产品编码。
/// </summary>
public string? ProductCode { get; set; }
/// <summary>
/// 销售产品主键。
/// </summary>
public int? SalesProductId { get; set; }
/// <summary>
/// 销售产品名称。
/// </summary>
public string? SalesProductName { get; set; }
/// <summary>
/// 目的国家编码。
/// </summary>
public string? DestinationCountry { get; set; }
/// <summary>
/// 目的国家名称。
/// </summary>
public string? DestinationCountryName { get; set; }
/// <summary>
/// 服务渠道主键。
/// </summary>
public int? ServiceChannelId { get; set; }
/// <summary>
/// 服务渠道编码。
/// </summary>
public string? ChannelCode { get; set; }
/// <summary>
/// 服务渠道展示名。
/// </summary>
public string? ServiceChannelName { get; set; }
/// <summary>
/// 订单状态码。
/// </summary>
public string OrderStatus { get; set; } = string.Empty;
/// <summary>
/// 订单状态名称。
/// </summary>
public string? OrderStatusName { get; set; }
}

View File

@ -2,8 +2,12 @@ using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Operations;
using Oms.Application.Dtos.Shared;
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.Repositories;
using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.Operations;
@ -15,7 +19,73 @@ namespace Oms.Application.Services.Operations;
public class OutboundRecordService : ApplicationService
{
private IOutboundRecordRepository OutboundRecordRepository => LazyServiceProvider.GetRequiredService<IOutboundRecordRepository>();
private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService<IOrderRepository>();
private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService<ISalesProductRepository>();
private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService<IServiceChannelRepository>();
private IServiceChannelGroupRepository ServiceChannelGroupRepository => LazyServiceProvider.GetRequiredService<IServiceChannelGroupRepository>();
private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService<IWarehouseAddressRepository>();
private ICountryRepository CountryRepository => LazyServiceProvider.GetRequiredService<ICountryRepository>();
/// <summary>
/// 按单号查询订单(出库扫描展示销售产品、目的国、服务渠道,只读)。
/// </summary>
[HttpGet("/operation/outbound/order-lookup")]
public async Task<OutboundOrderLookupOutput> 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.OrderStatus);
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;
}
/// <summary>
/// 新增出库记录。
@ -31,12 +101,25 @@ public class OutboundRecordService : ApplicationService
[HttpPost("/operation/outbound/create")]
public async Task<OutboundRecordOutput> CreateAsync(CreateOutboundRecordInput input)
{
if (string.IsNullOrWhiteSpace(input.WaybillNo))
{
throw new BusinessException("运单号不能为空");
}
var order = await OrderRepository.GetByWaybillNoAsync(input.WaybillNo.Trim());
if (order == null)
{
throw new BusinessException("未找到对应订单,请检查单号");
}
EnsureOutboundAllowed(order.OrderStatus);
var now = DateTime.Now;
var operatorId = CurrentUser.GetId<int>();
var entity = new OutboundRecord
{
WaybillNo = input.WaybillNo,
WarehouseId = input.WarehouseId,
WaybillNo = input.WaybillNo.Trim(),
WarehouseId = input.WarehouseId ?? order.WarehouseId,
OperateTime = now,
OperatorId = operatorId
};
@ -86,4 +169,30 @@ public class OutboundRecordService : ApplicationService
return new OmsItemsResult<OutboundRecordOutput> { Items = items };
}
private static void EnsureOutboundAllowed(string orderStatus)
{
if (orderStatus is OrderStatusCodes.PendingPayment or OrderStatusCodes.Received)
{
throw new BusinessException("订单未付款,禁止出库");
}
if (orderStatus != OrderStatusCodes.PendingShip)
{
throw new BusinessException($"订单状态为「{OmsDocHelper.GetOrderStatusName(orderStatus)}」,不允许出库");
}
}
private async Task<string> 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;
}
}

View File

@ -1142,7 +1142,7 @@ public partial class OrderService
OrderFeeChargingStageCodes.Inbound,
replaceExistingReceivable: true);
order.OrderStatus = OrderStatusCodes.Received;
order.OrderStatus = OrderStatusCodes.PendingPayment;
order.ReceiveTime = DateTime.Now;
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);

View File

@ -119,7 +119,7 @@ public partial class OrderService : ApplicationService
throw new BusinessException("订单已支付");
}
if (order.OrderStatus != OrderStatusCodes.PendingPayment)
if (order.OrderStatus is not (OrderStatusCodes.PendingPayment or OrderStatusCodes.Received))
{
throw new BusinessException("当前订单状态不可支付");
}