From 3c85b7325f811b60c8b7a7d2969b96220184f409 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Mon, 26 Oct 2026 11:46:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=90=E5=8D=95=E5=8F=91=E8=B4=A7?= =?UTF-8?q?=E5=90=8E=E7=8A=B6=E6=80=81=E5=8F=98=E6=9B=B4=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 ChangePostShipStatus,限制发货后四态白名单,妥投成功仅可进入不可改出。 Co-authored-by: Cursor --- .../Orders/OrderChangePostShipStatusInput.cs | 29 ++++++ .../Services/Orders/OrderService.cs | 94 +++++++++++++++++++ .../Consts/OrderOperationActionCodes.cs | 2 + 3 files changed, 125 insertions(+) create mode 100644 src/Oms.Application/Dtos/Orders/OrderChangePostShipStatusInput.cs diff --git a/src/Oms.Application/Dtos/Orders/OrderChangePostShipStatusInput.cs b/src/Oms.Application/Dtos/Orders/OrderChangePostShipStatusInput.cs new file mode 100644 index 0000000..3babb24 --- /dev/null +++ b/src/Oms.Application/Dtos/Orders/OrderChangePostShipStatusInput.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; + +namespace Oms.Application.Dtos.Orders; + +/// +/// 出库后状态校正入参:仅允许在待上网 / 运输中 / 派送成功 / 派送失败之间变更;派送成功只进不出。 +/// +public class OrderChangePostShipStatusInput +{ + /// + /// 订单主键。 + /// + [Required] + public int Id { get; set; } + + /// + /// 目标订单状态(pendingOnline / shipping / deliverySuccess / deliveryFailed)。 + /// + [Required] + [StringLength(32)] + public string TargetStatus { get; set; } = string.Empty; + + /// + /// 变更备注(必填,落操作日志)。 + /// + [Required] + [StringLength(512)] + public string Remark { get; set; } = string.Empty; +} diff --git a/src/Oms.Application/Services/Orders/OrderService.cs b/src/Oms.Application/Services/Orders/OrderService.cs index ced7591..9f6dd5a 100644 --- a/src/Oms.Application/Services/Orders/OrderService.cs +++ b/src/Oms.Application/Services/Orders/OrderService.cs @@ -237,7 +237,101 @@ public partial class OrderService : ApplicationService order.OrderStatus = OrderStatusCodes.DeliverySuccess; order.SignTime = DateTime.Now; await UpdateOrderAsync(order); + await ApplyDeliverySuccessMarketingAsync(order); + } + /// + /// 出库后状态校正:仅待上网 / 运输中 / 派送成功 / 派送失败互改;派送成功只进不出。 + /// 目标为派送成功时同步 SignTime 并触发引荐分成 / 首签券(与 mark-delivery-success 一致)。 + /// + /// 订单 Id、目标状态、必填备注。 + [HttpPost("/order/change-post-ship-status")] + [UnitOfWork(IsTransactional = true, IsolationLevel = IsolationLevel.ReadCommitted)] + public async Task ChangePostShipStatusAsync([FromBody] OrderChangePostShipStatusInput input) + { + var target = NormalizePostShipStatus(input.TargetStatus); + if (target == null) + { + throw new BusinessException("目标状态仅支持:待上网、运输中、派送成功、派送失败"); + } + + var remark = input.Remark?.Trim(); + if (string.IsNullOrWhiteSpace(remark)) + { + throw new BusinessException("请填写变更备注"); + } + + var order = await GetOrderOrThrowAsync(input.Id); + var fromStatus = order.OrderStatus?.Trim() ?? string.Empty; + if (!IsPostShipChangeableStatus(fromStatus)) + { + throw new BusinessException( + $"订单状态为「{OmsDocHelper.GetOrderStatusName(fromStatus)}」,仅出库后状态可校正"); + } + + if (string.Equals(fromStatus, OrderStatusCodes.DeliverySuccess, StringComparison.OrdinalIgnoreCase)) + { + throw new BusinessException("派送成功不可回退"); + } + + if (string.Equals(fromStatus, target, StringComparison.OrdinalIgnoreCase)) + { + throw new BusinessException("目标状态与当前状态相同"); + } + + var beforeSnap = await CaptureOrderAuditSnapshotAsync(order); + order.OrderStatus = target; + if (string.Equals(target, OrderStatusCodes.DeliverySuccess, StringComparison.OrdinalIgnoreCase)) + { + order.SignTime = DateTime.Now; + } + + order.UpdateTime = DateTime.Now; + await UpdateOrderAsync(order); + + if (string.Equals(target, OrderStatusCodes.DeliverySuccess, StringComparison.OrdinalIgnoreCase)) + { + order = await GetOrderOrThrowAsync(order.Id); + await ApplyDeliverySuccessMarketingAsync(order); + } + + order = await GetOrderOrThrowAsync(order.Id); + var afterSnap = await CaptureOrderAuditSnapshotAsync(order); + var fromName = OmsDocHelper.GetOrderStatusName(fromStatus) ?? fromStatus; + var toName = OmsDocHelper.GetOrderStatusName(target) ?? target; + await OperationLogWriter.TryWriteAsync( + order, + OrderOperationActionCodes.ChangePostShipStatus, + beforeSnap, + afterSnap, + $"{fromName} → {toName};{remark}"); + } + + private static readonly HashSet PostShipChangeableStatuses = new(StringComparer.OrdinalIgnoreCase) + { + OrderStatusCodes.PendingOnline, + OrderStatusCodes.Shipping, + OrderStatusCodes.DeliverySuccess, + OrderStatusCodes.DeliveryFailed + }; + + private static bool IsPostShipChangeableStatus(string? status) => + !string.IsNullOrWhiteSpace(status) && PostShipChangeableStatuses.Contains(status.Trim()); + + private static string? NormalizePostShipStatus(string? raw) + { + var value = raw?.Trim(); + if (string.IsNullOrWhiteSpace(value) || !PostShipChangeableStatuses.Contains(value)) + { + return null; + } + + return PostShipChangeableStatuses.First(x => + string.Equals(x, value, StringComparison.OrdinalIgnoreCase)); + } + + private async Task ApplyDeliverySuccessMarketingAsync(Order order) + { var commissionManager = LazyServiceProvider.GetRequiredService(); await commissionManager.OnOrderDeliverySuccessAsync(order.Id); diff --git a/src/Oms.Domain/OrdersAggregate/Consts/OrderOperationActionCodes.cs b/src/Oms.Domain/OrdersAggregate/Consts/OrderOperationActionCodes.cs index b164f5d..71173e7 100644 --- a/src/Oms.Domain/OrdersAggregate/Consts/OrderOperationActionCodes.cs +++ b/src/Oms.Domain/OrdersAggregate/Consts/OrderOperationActionCodes.cs @@ -17,6 +17,7 @@ public static class OrderOperationActionCodes public const string Print = "print"; public const string Inbound = "inbound"; public const string Outbound = "outbound"; + public const string ChangePostShipStatus = "changePostShipStatus"; public static string GetActionName(string action) => action switch { @@ -35,6 +36,7 @@ public static class OrderOperationActionCodes Print => "打印面单", Inbound => "入库", Outbound => "出库", + ChangePostShipStatus => "修改出库后状态", _ => action }; }