feat: SkuImage 存 storage key,包裹 summary,修复批次测试
All checks were successful
Build and Deploy / deploy (push) Successful in 1m48s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m48s
避免签名 URL 超长写库;新增包裹状态汇总;OrderBatchJobCoordinator 注入 IFileStorage 后补齐单测。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
40997f7d71
commit
43fcb4136a
2
db.sql
2
db.sql
@ -403,7 +403,7 @@ CREATE TABLE `t_order_declaration` (
|
||||
`HsCode` VARCHAR(50) NULL COMMENT 'HsCode',
|
||||
`ImHsCode` VARCHAR(50) NULL COMMENT '进口HsCode',
|
||||
`ExHsCode` VARCHAR(50) NULL COMMENT '出口HsCode',
|
||||
`SkuImage` VARCHAR(255) NULL COMMENT '图片',
|
||||
`SkuImage` VARCHAR(512) NULL COMMENT '图片(存 storage key 或短 URL)',
|
||||
`Category` VARCHAR(20) NULL COMMENT '类别',
|
||||
`Brand` VARCHAR(50) NULL COMMENT '品牌',
|
||||
`BrandType` VARCHAR(50) NULL COMMENT '品牌类型',
|
||||
|
||||
27
sql/migrate_t_order_declaration_sku_image.sql
Normal file
27
sql/migrate_t_order_declaration_sku_image.sql
Normal file
@ -0,0 +1,27 @@
|
||||
-- t_order_declaration.SkuImage:库内 SKU 选图后写入签名 URL 易超 VARCHAR(255)
|
||||
-- 后端已改为存 storage key;列宽对齐仓库 SkuImageUrl,兼容外链
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
SET @need := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_declaration'
|
||||
AND COLUMN_NAME = 'SkuImage'
|
||||
AND (
|
||||
DATA_TYPE <> 'varchar'
|
||||
OR CHARACTER_MAXIMUM_LENGTH IS NULL
|
||||
OR CHARACTER_MAXIMUM_LENGTH < 512
|
||||
)
|
||||
);
|
||||
|
||||
SET @sql := IF(
|
||||
@need > 0,
|
||||
'ALTER TABLE `t_order_declaration` MODIFY COLUMN `SkuImage` VARCHAR(512) NULL COMMENT ''图片(存 storage key 或短 URL)''',
|
||||
'SELECT ''skip t_order_declaration.SkuImage'' AS msg'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@ -335,6 +335,15 @@ public class WarehouseInboundSummaryOutput
|
||||
public int Completed { get; set; }
|
||||
}
|
||||
|
||||
public class WarehouseParcelSummaryOutput
|
||||
{
|
||||
public int InTransit { get; set; }
|
||||
|
||||
public int Arrived { get; set; }
|
||||
|
||||
public int Stored { get; set; }
|
||||
}
|
||||
|
||||
// --- Parcel ---
|
||||
|
||||
public class WarehouseParcelListInput : WarehousePageInput
|
||||
|
||||
@ -6,6 +6,7 @@ using Oms.Application.Integrations.Itfx;
|
||||
using Oms.Application.Integrations.SmallBagTrack;
|
||||
using Oms.Application.Messaging;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
@ -16,6 +17,7 @@ namespace Oms.Application.Services.Orders.Batch;
|
||||
public sealed class OrderBatchActionExecutor(
|
||||
IOrderRepository orderRepository,
|
||||
ICountryRepository countryRepository,
|
||||
IFileStorage fileStorage,
|
||||
ISmallBagTrackOrderClient smallBagTrackOrderClient,
|
||||
ISmallBagTrackUpsertRequestBuilder smallBagTrackUpsertRequestBuilder,
|
||||
OrderDownstreamCredentialResolver downstreamCredentialResolver,
|
||||
@ -176,6 +178,7 @@ public sealed class OrderBatchActionExecutor(
|
||||
bundle.Declarations,
|
||||
bundle.Weights,
|
||||
countryRepository,
|
||||
fileStorage,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Integrations.SmallBagTrack;
|
||||
using Oms.Application.Messaging;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using System.Linq.Expressions;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
@ -23,6 +24,7 @@ public sealed class OrderBatchJobCoordinator(
|
||||
IOrderBatchJobRepository batchJobRepository,
|
||||
IOrderRepository orderRepository,
|
||||
ICountryRepository countryRepository,
|
||||
IFileStorage fileStorage,
|
||||
IOrderBatchJobPublisher publisher,
|
||||
OrderBatchJobProcessor batchJobProcessor,
|
||||
IOptions<OrderBatchRabbitMqOptions> batchOptions,
|
||||
@ -649,6 +651,7 @@ public sealed class OrderBatchJobCoordinator(
|
||||
bundle.Declarations,
|
||||
bundle.Weights,
|
||||
countryRepository,
|
||||
fileStorage,
|
||||
cancellationToken);
|
||||
result[orderId] = JsonConvert.SerializeObject(body);
|
||||
}
|
||||
|
||||
@ -97,7 +97,8 @@ public partial class OrderService
|
||||
bundle.Recipient,
|
||||
bundle.Declarations,
|
||||
bundle.Weights,
|
||||
CountryRepository);
|
||||
CountryRepository,
|
||||
FileStorage);
|
||||
|
||||
return (bundle.Order, body);
|
||||
}
|
||||
|
||||
@ -743,7 +743,7 @@ public partial class OrderService
|
||||
};
|
||||
}
|
||||
|
||||
private static OrderItemOutput MapDeclaration(OrderDeclaration x)
|
||||
private OrderItemOutput MapDeclaration(OrderDeclaration x)
|
||||
{
|
||||
return new OrderItemOutput
|
||||
{
|
||||
@ -759,7 +759,7 @@ public partial class OrderService
|
||||
HsCode = x.ExHsCode ?? x.ImHsCode ?? x.HsCode,
|
||||
ImHsCode = x.ImHsCode ?? x.HsCode,
|
||||
ExHsCode = x.ExHsCode ?? x.HsCode,
|
||||
SkuImage = x.SkuImage,
|
||||
SkuImage = FileStorage.GetPublicUrl(x.SkuImage),
|
||||
Category = x.Category,
|
||||
Brand = x.Brand,
|
||||
BrandType = x.BrandType,
|
||||
|
||||
@ -894,7 +894,7 @@ public partial class OrderService
|
||||
_ => (OrderWeightTypeCodes.Forecast, OrderFeeChargingStageCodes.Forecast)
|
||||
};
|
||||
|
||||
private static OrderDeclaration MapDeclarationInput(int orderId, OrderParcelItemInput x)
|
||||
private OrderDeclaration MapDeclarationInput(int orderId, OrderParcelItemInput x)
|
||||
{
|
||||
return new OrderDeclaration
|
||||
{
|
||||
@ -911,7 +911,7 @@ public partial class OrderService
|
||||
HsCode = x.ExHsCode ?? x.ImHsCode,
|
||||
ImHsCode = x.ImHsCode,
|
||||
ExHsCode = x.ExHsCode ?? x.ImHsCode,
|
||||
SkuImage = x.SkuImage,
|
||||
SkuImage = FileStorage.ToStorageKey(x.SkuImage),
|
||||
Category = x.Category,
|
||||
Brand = x.Brand,
|
||||
BrandType = x.BrandType,
|
||||
|
||||
@ -1965,7 +1965,7 @@ public partial class OrderService
|
||||
HsCode = x.ExHsCode ?? x.ImHsCode,
|
||||
ImHsCode = x.ImHsCode,
|
||||
ExHsCode = x.ExHsCode,
|
||||
SkuImage = x.SkuImage,
|
||||
SkuImage = FileStorage.ToStorageKey(x.SkuImage),
|
||||
Category = x.Category,
|
||||
Brand = x.Brand,
|
||||
BrandType = x.BrandType,
|
||||
|
||||
@ -79,6 +79,43 @@ public partial class WarehouseService
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询包裹生命周期状态汇总。
|
||||
/// </summary>
|
||||
/// <param name="customerId">客户 ID(可选)。</param>
|
||||
/// <param name="warehouseId">仓库 ID(可选)。</param>
|
||||
/// <returns>各状态数量汇总。</returns>
|
||||
[HttpGet("/warehouse/parcel/summary")]
|
||||
public async Task<WarehouseParcelSummaryOutput> GetParcelSummaryAsync(
|
||||
[FromQuery] int? customerId = null,
|
||||
[FromQuery] int? warehouseId = null)
|
||||
{
|
||||
var resolvedCustomerId = await ResolveListCustomerIdAsync(customerId);
|
||||
var (hasCustomerFilter, customerIdValue) = CustomerFilter(resolvedCustomerId);
|
||||
var hasWarehouseFilter = warehouseId.HasValue;
|
||||
var warehouseIdValue = warehouseId.GetValueOrDefault();
|
||||
|
||||
var inTransit = await ParcelRepository.CountAsync(x =>
|
||||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||||
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue)
|
||||
&& x.LifecycleStatus == WarehouseParcelLifecycleCodes.InTransit);
|
||||
var arrived = await ParcelRepository.CountAsync(x =>
|
||||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||||
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue)
|
||||
&& x.LifecycleStatus == WarehouseParcelLifecycleCodes.Arrived);
|
||||
var stored = await ParcelRepository.CountAsync(x =>
|
||||
(!hasCustomerFilter || x.CustomerId == customerIdValue)
|
||||
&& (!hasWarehouseFilter || x.WarehouseId == warehouseIdValue)
|
||||
&& x.LifecycleStatus == WarehouseParcelLifecycleCodes.Stored);
|
||||
|
||||
return new WarehouseParcelSummaryOutput
|
||||
{
|
||||
InTransit = inTransit,
|
||||
Arrived = arrived,
|
||||
Stored = stored
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规范化列表状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。
|
||||
/// </summary>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Oms.Application.Dtos.Orders;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.SystemAggregate.Repositories;
|
||||
@ -17,6 +18,7 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
||||
IReadOnlyList<OrderDeclaration> declarations,
|
||||
IReadOnlyList<OrderWeight> weights,
|
||||
ICountryRepository countryRepository,
|
||||
IFileStorage? fileStorage = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var countryIds = new List<int>();
|
||||
@ -66,7 +68,7 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
||||
Quantity = order.Quantity,
|
||||
Receiver = MapReceiver(recipient, order.ConsigneeCountry, idToCode),
|
||||
Sender = MapSender(sender, idToCode),
|
||||
Items = declarations.Select(MapItem).ToList()
|
||||
Items = declarations.Select(x => MapItem(x, fileStorage)).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
@ -150,7 +152,7 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
||||
};
|
||||
}
|
||||
|
||||
private static OrderParcelItemInput MapItem(OrderDeclaration x) =>
|
||||
private static OrderParcelItemInput MapItem(OrderDeclaration x, IFileStorage? fileStorage) =>
|
||||
new()
|
||||
{
|
||||
CnName = x.CnName,
|
||||
@ -164,7 +166,7 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
||||
Weight = x.Weight,
|
||||
ImHsCode = x.ImHsCode,
|
||||
ExHsCode = x.ExHsCode ?? x.HsCode,
|
||||
SkuImage = x.SkuImage,
|
||||
SkuImage = fileStorage?.GetPublicUrl(x.SkuImage) ?? x.SkuImage,
|
||||
Category = x.Category,
|
||||
Brand = x.Brand,
|
||||
BrandType = x.BrandType,
|
||||
|
||||
@ -6,6 +6,7 @@ using Moq;
|
||||
using Oms.Application.Dtos.Orders;
|
||||
using Oms.Application.Messaging;
|
||||
using Oms.Application.Services.Orders.Batch;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
@ -18,6 +19,7 @@ public class OrderBatchJobCoordinatorGovernanceTests
|
||||
private readonly Mock<IOrderBatchJobRepository> _batchJobRepository = new(MockBehavior.Strict);
|
||||
private readonly Mock<IOrderRepository> _orderRepository = new(MockBehavior.Loose);
|
||||
private readonly Mock<ICountryRepository> _countryRepository = new(MockBehavior.Loose);
|
||||
private readonly Mock<IFileStorage> _fileStorage = new(MockBehavior.Loose);
|
||||
private readonly Mock<IOrderBatchJobPublisher> _publisher = new(MockBehavior.Strict);
|
||||
private readonly OrderBatchJobCoordinator _coordinator;
|
||||
|
||||
@ -36,6 +38,7 @@ public class OrderBatchJobCoordinatorGovernanceTests
|
||||
_batchJobRepository.Object,
|
||||
_orderRepository.Object,
|
||||
_countryRepository.Object,
|
||||
_fileStorage.Object,
|
||||
_publisher.Object,
|
||||
processor,
|
||||
batchOptions,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user