diff --git a/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs b/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs index 0681549..aca478e 100644 --- a/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs +++ b/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs @@ -96,6 +96,9 @@ public class PlatformStoreOrderListInput : PlatformStorePageInput { public int? StoreId { get; set; } + /// + /// 履约状态:pending / toShip / shipped / canceled / suspended / completed;空或 all 表示全部。 + /// public string? FulfillStatus { get; set; } public string? PlatformOrderNo { get; set; } diff --git a/src/Oms.Application/Dtos/Warehouse/WarehouseDtos.cs b/src/Oms.Application/Dtos/Warehouse/WarehouseDtos.cs index 63e601a..3c803a4 100644 --- a/src/Oms.Application/Dtos/Warehouse/WarehouseDtos.cs +++ b/src/Oms.Application/Dtos/Warehouse/WarehouseDtos.cs @@ -152,6 +152,9 @@ public class WarehouseInboundListInput : WarehousePageInput { public int? WarehouseId { get; set; } + /// + /// 收货状态:forecasted / received / completed;空或 all 表示全部。 + /// public string? ReceiptStatus { get; set; } public string? Keyword { get; set; } @@ -314,6 +317,9 @@ public class WarehouseParcelListInput : WarehousePageInput { public int? WarehouseId { get; set; } + /// + /// 生命周期状态:inTransit / arrived / stored;空或 all 表示全部。 + /// public string? LifecycleStatus { get; set; } public string? Keyword { get; set; } diff --git a/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs b/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs index eef3c11..11167d7 100644 --- a/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs +++ b/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs @@ -599,21 +599,73 @@ public class PlatformStoreOrderService : ApplicationService private static Expression> BuildListPredicate(int? customerId, PlatformStoreOrderListInput input) { var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(customerId); - var fulfillStatus = input.FulfillStatus?.Trim(); + // 前端 Tab「全部」会传 all / 空;需忽略,不能按字面 FulfillStatus == "all" 过滤 + var fulfillStatus = NormalizeFulfillStatusFilter(input.FulfillStatus); + var hasFulfillStatusFilter = !string.IsNullOrWhiteSpace(fulfillStatus); var platformOrderNo = input.PlatformOrderNo?.Trim(); var hasStoreFilter = input.StoreId.HasValue; var storeIdValue = input.StoreId.GetValueOrDefault(); - var hasFulfillStatusFilter = !string.IsNullOrWhiteSpace(fulfillStatus); var hasPlatformOrderNoFilter = !string.IsNullOrWhiteSpace(platformOrderNo); var hasSuspendedFilter = input.IsSuspended.HasValue; var isSuspendedValue = input.IsSuspended.GetValueOrDefault(); - return x => - (!hasCustomerFilter || x.CustomerId == customerIdValue) - && (!hasStoreFilter || x.StoreId == storeIdValue) - && (!hasFulfillStatusFilter || x.FulfillStatus == fulfillStatus) - && (!hasSuspendedFilter || x.IsSuspended == isSuspendedValue) - && (!hasPlatformOrderNoFilter || x.PlatformOrderNo.Contains(platformOrderNo!)); + return hasFulfillStatusFilter + ? x => + (!hasCustomerFilter || x.CustomerId == customerIdValue) + && (!hasStoreFilter || x.StoreId == storeIdValue) + && x.FulfillStatus == fulfillStatus + && (!hasSuspendedFilter || x.IsSuspended == isSuspendedValue) + && (!hasPlatformOrderNoFilter || x.PlatformOrderNo.Contains(platformOrderNo!)) + : x => + (!hasCustomerFilter || x.CustomerId == customerIdValue) + && (!hasStoreFilter || x.StoreId == storeIdValue) + && (!hasSuspendedFilter || x.IsSuspended == isSuspendedValue) + && (!hasPlatformOrderNoFilter || x.PlatformOrderNo.Contains(platformOrderNo!)); + } + + /// + /// 规范化履约状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。 + /// + private static string? NormalizeFulfillStatusFilter(string? status) + { + var value = status?.Trim(); + if (string.IsNullOrEmpty(value) + || value.Equals("all", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + if (value.Equals(SalesStoreFulfillStatusCodes.Pending, StringComparison.OrdinalIgnoreCase)) + { + return SalesStoreFulfillStatusCodes.Pending; + } + + if (value.Equals(SalesStoreFulfillStatusCodes.ToShip, StringComparison.OrdinalIgnoreCase)) + { + return SalesStoreFulfillStatusCodes.ToShip; + } + + if (value.Equals(SalesStoreFulfillStatusCodes.Canceled, StringComparison.OrdinalIgnoreCase)) + { + return SalesStoreFulfillStatusCodes.Canceled; + } + + if (value.Equals(SalesStoreFulfillStatusCodes.Suspended, StringComparison.OrdinalIgnoreCase)) + { + return SalesStoreFulfillStatusCodes.Suspended; + } + + if (value.Equals(SalesStoreFulfillStatusCodes.Shipped, StringComparison.OrdinalIgnoreCase)) + { + return SalesStoreFulfillStatusCodes.Shipped; + } + + if (value.Equals(SalesStoreFulfillStatusCodes.Completed, StringComparison.OrdinalIgnoreCase)) + { + return SalesStoreFulfillStatusCodes.Completed; + } + + return value; } private static PlatformStoreOrderOutput MapOrderOutput( diff --git a/src/Oms.Application/Services/Plugin/PluginBrowserService.cs b/src/Oms.Application/Services/Plugin/PluginBrowserService.cs index 6b55a8e..1aefa6b 100644 --- a/src/Oms.Application/Services/Plugin/PluginBrowserService.cs +++ b/src/Oms.Application/Services/Plugin/PluginBrowserService.cs @@ -10,12 +10,13 @@ using Oms.Domain.WarehouseAggregate.Repositories; namespace Oms.Application.Services.Plugin; /// -/// 浏览器采集插件草稿 / 查重(对齐哪哪发 wepost API)。 +/// 浏览器采集插件草稿 / 查重(对齐哪哪发插件 API)。 /// [ApiExplorerSettings(GroupName = "OMS-Plugin")] public class PluginBrowserService : ApplicationService { - private static readonly TimeSpan DraftTtl = TimeSpan.FromSeconds(90); + // 确认页打开 / SPA 冷启动可能超过 90s,适当放宽避免「草稿已过期」误判为解析失败 + private static readonly TimeSpan DraftTtl = TimeSpan.FromMinutes(5); private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService(); private ICParcelRepository ParcelRepository => LazyServiceProvider.GetRequiredService(); @@ -25,19 +26,20 @@ public class PluginBrowserService : ApplicationService /// /// 缓存采集草稿,返回 key(确认页 getPackage 使用)。 + /// 管理端须传 customerId(或会话可解析客户)。 /// [HttpPost("/plugin/browser/add")] [Consumes("application/x-www-form-urlencoded", "multipart/form-data")] - public async Task AddDraftAsync([FromForm] string? item) + public async Task AddDraftAsync([FromForm] string? item, [FromForm] int? customerId) { - var customerId = await ResolveRequiredCustomerIdAsync(); + var resolvedCustomerId = await ResolveRequiredCustomerIdAsync(customerId); var payload = item; if (string.IsNullOrWhiteSpace(payload)) { throw new BusinessException("草稿内容不能为空"); } - var key = $"{customerId}{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}{Random.Shared.Next(100, 999)}"; + var key = $"{resolvedCustomerId}{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}{Random.Shared.Next(100, 999)}"; await Cache.SetAsync( CacheKey(key), Encoding.UTF8.GetBytes(payload!), @@ -68,21 +70,28 @@ public class PluginBrowserService : ApplicationService /// /// 查重:快递单号是否已存在于当前客户的「我的包裹」。 + /// 无客户上下文(如管理员未选客户)时不抛错,按「未添加」返回,避免插件按钮卡在「系统错误」。 /// [HttpGet("/plugin/browser/selectPackage")] - public async Task SelectPackageAsync([FromQuery] string? expressNum, [FromQuery] string? orderNo) + public async Task SelectPackageAsync( + [FromQuery] string? expressNum, + [FromQuery] string? orderNo, + [FromQuery] int? customerId) { - var customerId = await ResolveRequiredCustomerIdAsync(); + var resolvedCustomerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync( + CustomerRepository, + CurrentUser, + customerId); var trackingNo = (expressNum ?? string.Empty).Trim(); var trackExists = false; - if (!string.IsNullOrWhiteSpace(trackingNo)) + if (resolvedCustomerId is > 0 && !string.IsNullOrWhiteSpace(trackingNo)) { var hit = await ParcelRepository.FirstOrDefaultAsync(x => - x.CustomerId == customerId && x.TrackingNo == trackingNo); + x.CustomerId == resolvedCustomerId.Value && x.TrackingNo == trackingNo); trackExists = hit != null; } - // OMS 包裹表无独立订单号字段;orderNo 仅作兼容保留,默认同 track 不分别标记 + // OMS 包裹表无独立订单号字段;orderNo 仅作兼容保留 return new { trackNo = trackExists, @@ -96,7 +105,11 @@ public class PluginBrowserService : ApplicationService [HttpGet("/plugin/browser/getPluginWhseInfo")] public async Task GetPluginWhseInfoAsync() { - await ResolveRequiredCustomerIdAsync(); + // 管理员无客户上下文时仍可取仓库列表,供确认页默认值 + _ = await WarehouseCustomerHelper.ResolveListCustomerIdAsync( + CustomerRepository, + CurrentUser, + null); var list = await WarehouseAddressRepository.GetPagedListAsync( _ => true, 1, @@ -115,11 +128,11 @@ public class PluginBrowserService : ApplicationService }; } - private async Task ResolveRequiredCustomerIdAsync() => + private async Task ResolveRequiredCustomerIdAsync(int? requestedCustomerId) => await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync( CustomerRepository, CurrentUser, - null); + requestedCustomerId); private static string CacheKey(string key) => $"oms:plugin:parcel:draft:{key}"; } diff --git a/src/Oms.Application/Services/Warehouse/WarehouseService.Inbound.cs b/src/Oms.Application/Services/Warehouse/WarehouseService.Inbound.cs index 5039bcb..3228673 100644 --- a/src/Oms.Application/Services/Warehouse/WarehouseService.Inbound.cs +++ b/src/Oms.Application/Services/Warehouse/WarehouseService.Inbound.cs @@ -23,16 +23,22 @@ public partial class WarehouseService var (hasCustomerFilter, customerIdValue) = CustomerFilter(customerId); var keyword = input.Keyword?.Trim(); var hasKeyword = !string.IsNullOrWhiteSpace(keyword); - var receiptStatus = input.ReceiptStatus?.Trim(); + // 前端 Tab「全部」会传 all / 空;需忽略,不能按字面 ReceiptStatus == "all" 过滤 + var receiptStatus = NormalizeInboundReceiptStatusFilter(input.ReceiptStatus); + var hasReceiptStatusFilter = !string.IsNullOrWhiteSpace(receiptStatus); var hasWarehouseFilter = input.WarehouseId.HasValue; var warehouseIdValue = input.WarehouseId.GetValueOrDefault(); - var hasReceiptStatusFilter = !string.IsNullOrWhiteSpace(receiptStatus); - Expression> predicate = x => - (!hasCustomerFilter || x.CustomerId == customerIdValue) - && (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue) - && (!hasReceiptStatusFilter || x.ReceiptStatus == receiptStatus) - && (!hasKeyword || x.NoticeNo.Contains(keyword!) || (x.MarkReference != null && x.MarkReference.Contains(keyword!))); + Expression> predicate = hasReceiptStatusFilter + ? x => + (!hasCustomerFilter || x.CustomerId == customerIdValue) + && (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue) + && x.ReceiptStatus == receiptStatus + && (!hasKeyword || x.NoticeNo.Contains(keyword!) || (x.MarkReference != null && x.MarkReference.Contains(keyword!))) + : x => + (!hasCustomerFilter || x.CustomerId == customerIdValue) + && (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue) + && (!hasKeyword || x.NoticeNo.Contains(keyword!) || (x.MarkReference != null && x.MarkReference.Contains(keyword!))); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); @@ -59,6 +65,36 @@ public partial class WarehouseService }; } + /// + /// 规范化入库状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。 + /// + private static string? NormalizeInboundReceiptStatusFilter(string? status) + { + var value = status?.Trim(); + if (string.IsNullOrEmpty(value) + || value.Equals("all", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + if (value.Equals(WarehouseReceiptStatusCodes.Forecasted, StringComparison.OrdinalIgnoreCase)) + { + return WarehouseReceiptStatusCodes.Forecasted; + } + + if (value.Equals(WarehouseReceiptStatusCodes.Received, StringComparison.OrdinalIgnoreCase)) + { + return WarehouseReceiptStatusCodes.Received; + } + + if (value.Equals(WarehouseReceiptStatusCodes.Completed, StringComparison.OrdinalIgnoreCase)) + { + return WarehouseReceiptStatusCodes.Completed; + } + + return value; + } + /// /// 查询入库预报详情。 /// diff --git a/src/Oms.Application/Services/Warehouse/WarehouseService.Parcel.cs b/src/Oms.Application/Services/Warehouse/WarehouseService.Parcel.cs index 26f63f2..194a3c7 100644 --- a/src/Oms.Application/Services/Warehouse/WarehouseService.Parcel.cs +++ b/src/Oms.Application/Services/Warehouse/WarehouseService.Parcel.cs @@ -23,16 +23,22 @@ public partial class WarehouseService var (hasCustomerFilter, customerIdValue) = CustomerFilter(customerId); var keyword = input.Keyword?.Trim(); var hasKeyword = !string.IsNullOrWhiteSpace(keyword); - var lifecycleStatus = input.LifecycleStatus?.Trim(); + // 前端 Tab「全部」会传 all / 空;需忽略,不能按字面 LifecycleStatus == "all" 过滤 + var lifecycleStatus = NormalizeParcelLifecycleStatusFilter(input.LifecycleStatus); + var hasLifecycleStatusFilter = !string.IsNullOrWhiteSpace(lifecycleStatus); var hasWarehouseFilter = input.WarehouseId.HasValue; var warehouseIdValue = input.WarehouseId.GetValueOrDefault(); - var hasLifecycleStatusFilter = !string.IsNullOrWhiteSpace(lifecycleStatus); - Expression> predicate = x => - (!hasCustomerFilter || x.CustomerId == customerIdValue) - && (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue) - && (!hasLifecycleStatusFilter || x.LifecycleStatus == lifecycleStatus) - && (!hasKeyword || x.TrackingNo.Contains(keyword!) || (x.CarrierName != null && x.CarrierName.Contains(keyword!))); + Expression> predicate = hasLifecycleStatusFilter + ? x => + (!hasCustomerFilter || x.CustomerId == customerIdValue) + && (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue) + && x.LifecycleStatus == lifecycleStatus + && (!hasKeyword || x.TrackingNo.Contains(keyword!) || (x.CarrierName != null && x.CarrierName.Contains(keyword!))) + : x => + (!hasCustomerFilter || x.CustomerId == customerIdValue) + && (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue) + && (!hasKeyword || x.TrackingNo.Contains(keyword!) || (x.CarrierName != null && x.CarrierName.Contains(keyword!))); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); @@ -55,6 +61,36 @@ public partial class WarehouseService }; } + /// + /// 规范化列表状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。 + /// + private static string? NormalizeParcelLifecycleStatusFilter(string? status) + { + var value = status?.Trim(); + if (string.IsNullOrEmpty(value) + || value.Equals("all", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + if (value.Equals(WarehouseParcelLifecycleCodes.InTransit, StringComparison.OrdinalIgnoreCase)) + { + return WarehouseParcelLifecycleCodes.InTransit; + } + + if (value.Equals(WarehouseParcelLifecycleCodes.Arrived, StringComparison.OrdinalIgnoreCase)) + { + return WarehouseParcelLifecycleCodes.Arrived; + } + + if (value.Equals(WarehouseParcelLifecycleCodes.Stored, StringComparison.OrdinalIgnoreCase)) + { + return WarehouseParcelLifecycleCodes.Stored; + } + + return value; + } + /// /// 保存包裹(新增或编辑)。 /// diff --git a/src/Oms.Application/Shared/WarehouseCustomerHelper.cs b/src/Oms.Application/Shared/WarehouseCustomerHelper.cs index 5c67aff..8afa976 100644 --- a/src/Oms.Application/Shared/WarehouseCustomerHelper.cs +++ b/src/Oms.Application/Shared/WarehouseCustomerHelper.cs @@ -35,7 +35,7 @@ public static class WarehouseCustomerHelper var customerId = await ResolveListCustomerIdAsync(customerRepository, currentUser, requestedCustomerId); if (!customerId.HasValue || customerId.Value <= 0) { - throw new BusinessException("客户ID不能为空"); + throw new BusinessException("客户ID不能为空(管理端请先在「我的包裹」选择客户后再使用采集插件)"); } return customerId.Value;