diff --git a/src/Oms.Application/Dtos/Rbac/OmsUserProfileDtos.cs b/src/Oms.Application/Dtos/Rbac/OmsUserProfileDtos.cs
index 903a8f2..415831c 100644
--- a/src/Oms.Application/Dtos/Rbac/OmsUserProfileDtos.cs
+++ b/src/Oms.Application/Dtos/Rbac/OmsUserProfileDtos.cs
@@ -54,3 +54,14 @@ public class OmsUserDetailDto : UserDto
public string? WecomKfUrl { get; set; }
}
+
+///
+/// 管理员重置用户密码入参。
+///
+public class ResetUserPasswordInput
+{
+ ///
+ /// 新密码。
+ ///
+ public string NewPassword { get; set; } = string.Empty;
+}
diff --git a/src/Oms.Application/Integrations/SmallBagTrack/PickupBigBagTrackSyncer.cs b/src/Oms.Application/Integrations/SmallBagTrack/PickupBigBagTrackSyncer.cs
index e3bca81..fc63b2c 100644
--- a/src/Oms.Application/Integrations/SmallBagTrack/PickupBigBagTrackSyncer.cs
+++ b/src/Oms.Application/Integrations/SmallBagTrack/PickupBigBagTrackSyncer.cs
@@ -9,11 +9,36 @@ using Oms.Domain.OrdersAggregate.Repositories;
namespace Oms.Application.Integrations.SmallBagTrack;
///
-/// 揽收单预报下游成功后:按序 UpsertBigBag → Link → Track/Upsert(A0201)。
+/// 揽收大包轨迹同步结果(UpsertBigBag → Link → Track A0201)。
+///
+public class PickupBigBagTrackSyncResult
+{
+ public string PickupNo { get; set; } = string.Empty;
+
+ public bool Success { get; set; }
+
+ public string? FailStep { get; set; }
+
+ public string? Message { get; set; }
+
+ public SmallBagTrackUpsertResult? UpsertBigBag { get; set; }
+
+ public SmallBagTrackUpsertResult? Link { get; set; }
+
+ public SmallBagTrackUpsertResult? TrackUpsert { get; set; }
+}
+
+///
+/// 揽收单预报下游成功后 / 手动:按序 UpsertBigBag → Link → Track/Upsert(A0201)。
///
public interface IPickupBigBagTrackSyncer
{
Task SyncAfterDownstreamForwardAsync(PickupOrder pickup, CancellationToken cancellationToken = default);
+
+ ///
+ /// 同步执行三步并返回逐步请求/响应(手动「创建轨迹订单」联调)。
+ ///
+ Task SyncDebugAsync(PickupOrder pickup, CancellationToken cancellationToken = default);
}
internal sealed class PickupBigBagTrackSyncer(
@@ -29,13 +54,29 @@ internal sealed class PickupBigBagTrackSyncer(
PickupOrder pickup,
CancellationToken cancellationToken = default)
{
+ await SyncDebugAsync(pickup, cancellationToken);
+ }
+
+ public async Task SyncDebugAsync(
+ PickupOrder pickup,
+ CancellationToken cancellationToken = default)
+ {
+ var result = new PickupBigBagTrackSyncResult
+ {
+ PickupNo = pickup?.PickupNo?.Trim() ?? string.Empty
+ };
+
if (pickup == null || string.IsNullOrWhiteSpace(pickup.PickupNo))
{
+ result.Success = false;
+ result.FailStep = "validate";
+ result.Message = "pickup or PickupNo empty";
logger.LogWarning("Skip big-bag track sync: pickup or PickupNo empty");
- return;
+ return result;
}
var bagNo = pickup.PickupNo.Trim();
+ result.PickupNo = bagNo;
var authorization = httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString();
if (string.IsNullOrWhiteSpace(authorization))
{
@@ -62,22 +103,29 @@ internal sealed class PickupBigBagTrackSyncer(
Authorization = authorization
},
cancellationToken);
+ result.UpsertBigBag = upsertResult;
if (upsertResult.Skipped)
{
+ result.Success = false;
+ result.FailStep = "UpsertBigBag";
+ result.Message = upsertResult.SkipReason ?? "skipped";
logger.LogDebug(
"UpsertBigBag skipped for pickup {PickupNo}: {Reason}",
bagNo,
upsertResult.SkipReason);
- return;
+ return result;
}
if (!upsertResult.Success)
{
+ result.Success = false;
+ result.FailStep = "UpsertBigBag";
+ result.Message = $"HTTP {upsertResult.StatusCode}";
logger.LogWarning(
"Stop big-bag track sync after UpsertBigBag failure for pickup {PickupNo}",
bagNo);
- return;
+ return result;
}
// 2) Link(无明细单号则跳过,继续写轨迹)
@@ -98,6 +146,7 @@ internal sealed class PickupBigBagTrackSyncer(
SmallBagTrackLifecycleEvents.OrderType,
authorization,
cancellationToken);
+ result.Link = linkResult;
if (linkResult.Skipped)
{
@@ -108,10 +157,13 @@ internal sealed class PickupBigBagTrackSyncer(
}
else if (!linkResult.Success)
{
+ result.Success = false;
+ result.FailStep = "Link";
+ result.Message = $"HTTP {linkResult.StatusCode}";
logger.LogWarning(
"Stop big-bag track sync after Link failure for pickup {PickupNo}",
bagNo);
- return;
+ return result;
}
}
else
@@ -126,28 +178,44 @@ internal sealed class PickupBigBagTrackSyncer(
SmallBagTrackLifecycleEvents.BigBagOrderType,
authorization,
cancellationToken);
+ result.TrackUpsert = trackResult;
if (trackResult.Skipped)
{
+ result.Success = false;
+ result.FailStep = "TrackUpsert";
+ result.Message = trackResult.SkipReason ?? "skipped";
logger.LogDebug(
"Track A0201 skipped for pickup {PickupNo}: {Reason}",
bagNo,
trackResult.SkipReason);
- return;
+ return result;
}
if (!trackResult.Success)
{
+ result.Success = false;
+ result.FailStep = "TrackUpsert";
+ result.Message = $"HTTP {trackResult.StatusCode}";
logger.LogWarning(
"Track A0201 failed for pickup {PickupNo}: {StatusCode} {Body}",
bagNo,
trackResult.StatusCode,
trackResult.ResponseBody);
+ return result;
}
+
+ result.Success = true;
+ result.Message = "ok";
+ return result;
}
catch (Exception ex)
{
+ result.Success = false;
+ result.FailStep ??= "exception";
+ result.Message = ex.Message;
logger.LogWarning(ex, "Big-bag track sync failed for pickup {PickupNo}", bagNo);
+ return result;
}
}
diff --git a/src/Oms.Application/Services/Orders/PickupOrderService.cs b/src/Oms.Application/Services/Orders/PickupOrderService.cs
index bd33958..9020543 100644
--- a/src/Oms.Application/Services/Orders/PickupOrderService.cs
+++ b/src/Oms.Application/Services/Orders/PickupOrderService.cs
@@ -1005,6 +1005,16 @@ public class PickupOrderService : ApplicationService
};
}
+ ///
+ /// 手动创建大包轨迹订单:按序 UpsertBigBag → Link → Track/Upsert(A0201),返回逐步请求/响应。
+ ///
+ [HttpPost("/order/pickup-order/track-upsert-big-bag")]
+ public async Task TrackUpsertBigBagAsync([FromBody] PickupOrderIdInput input)
+ {
+ var pickup = await GetOwnedPickupOrThrowAsync(input.Id);
+ return await PickupBigBagTrackSyncer.SyncDebugAsync(pickup);
+ }
+
///
/// 凭证:优先揽收单 CarrierCode 作为 supplierCode;AppKey/Secret 仍按承运服务→渠道分组解析。
///
diff --git a/src/Oms.Application/Services/Rbac/OmsUserService.cs b/src/Oms.Application/Services/Rbac/OmsUserService.cs
index d4f4a2e..8abb5fb 100644
--- a/src/Oms.Application/Services/Rbac/OmsUserService.cs
+++ b/src/Oms.Application/Services/Rbac/OmsUserService.cs
@@ -4,6 +4,7 @@ using Fake.Rbac.Application.Dtos.Common;
using Fake.Rbac.Application.Dtos.Role;
using Fake.Rbac.Application.Dtos.User;
using Fake.Rbac.Application.Services;
+using Fake.Rbac.Domain.Managers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Fake.ObjectStorage;
@@ -23,6 +24,7 @@ namespace Oms.Application.Services.Rbac;
[ApiExplorerSettings(GroupName = "RBAC-User")]
public class OmsUserService(
UserService userService,
+ AccountManager accountManager,
UserProfileRepository profileRepository,
IEmployeeRepository employeeRepository,
IObjectStorage fileStorage) : ApplicationService
@@ -167,6 +169,34 @@ public class OmsUserService(
CancellationToken cancellationToken = default) =>
userService.UpdatePasswordAsync(id, input, cancellationToken);
+ ///
+ /// 管理员重置用户密码(无需旧密码)。
+ ///
+ [HttpPost("/rbac/user/reset-password")]
+ public async Task ResetPasswordAsync(
+ [FromQuery] Guid id,
+ [FromBody] ResetUserPasswordInput input,
+ CancellationToken cancellationToken = default)
+ {
+ if (id == Guid.Empty)
+ {
+ throw new BusinessException("用户 Id 无效");
+ }
+
+ var newPassword = input?.NewPassword?.Trim();
+ if (string.IsNullOrWhiteSpace(newPassword))
+ {
+ throw new BusinessException("新密码不能为空");
+ }
+
+ if (newPassword.Length < 6)
+ {
+ throw new BusinessException("密码长度不能少于6位");
+ }
+
+ await accountManager.ResetPasswordAsync(id, newPassword, cancellationToken);
+ }
+
///
/// 更新用户头像。
///