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',
|
`HsCode` VARCHAR(50) NULL COMMENT 'HsCode',
|
||||||
`ImHsCode` VARCHAR(50) NULL COMMENT '进口HsCode',
|
`ImHsCode` VARCHAR(50) NULL COMMENT '进口HsCode',
|
||||||
`ExHsCode` 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 '类别',
|
`Category` VARCHAR(20) NULL COMMENT '类别',
|
||||||
`Brand` VARCHAR(50) NULL COMMENT '品牌',
|
`Brand` VARCHAR(50) NULL COMMENT '品牌',
|
||||||
`BrandType` 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 int Completed { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class WarehouseParcelSummaryOutput
|
||||||
|
{
|
||||||
|
public int InTransit { get; set; }
|
||||||
|
|
||||||
|
public int Arrived { get; set; }
|
||||||
|
|
||||||
|
public int Stored { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
// --- Parcel ---
|
// --- Parcel ---
|
||||||
|
|
||||||
public class WarehouseParcelListInput : WarehousePageInput
|
public class WarehouseParcelListInput : WarehousePageInput
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using Oms.Application.Integrations.Itfx;
|
|||||||
using Oms.Application.Integrations.SmallBagTrack;
|
using Oms.Application.Integrations.SmallBagTrack;
|
||||||
using Oms.Application.Messaging;
|
using Oms.Application.Messaging;
|
||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
|
using Oms.Application.Storage;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
using Oms.Domain.OrdersAggregate.Repositories;
|
using Oms.Domain.OrdersAggregate.Repositories;
|
||||||
@ -16,6 +17,7 @@ namespace Oms.Application.Services.Orders.Batch;
|
|||||||
public sealed class OrderBatchActionExecutor(
|
public sealed class OrderBatchActionExecutor(
|
||||||
IOrderRepository orderRepository,
|
IOrderRepository orderRepository,
|
||||||
ICountryRepository countryRepository,
|
ICountryRepository countryRepository,
|
||||||
|
IFileStorage fileStorage,
|
||||||
ISmallBagTrackOrderClient smallBagTrackOrderClient,
|
ISmallBagTrackOrderClient smallBagTrackOrderClient,
|
||||||
ISmallBagTrackUpsertRequestBuilder smallBagTrackUpsertRequestBuilder,
|
ISmallBagTrackUpsertRequestBuilder smallBagTrackUpsertRequestBuilder,
|
||||||
OrderDownstreamCredentialResolver downstreamCredentialResolver,
|
OrderDownstreamCredentialResolver downstreamCredentialResolver,
|
||||||
@ -176,6 +178,7 @@ public sealed class OrderBatchActionExecutor(
|
|||||||
bundle.Declarations,
|
bundle.Declarations,
|
||||||
bundle.Weights,
|
bundle.Weights,
|
||||||
countryRepository,
|
countryRepository,
|
||||||
|
fileStorage,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using Oms.Application.Dtos.Shared;
|
|||||||
using Oms.Application.Integrations.SmallBagTrack;
|
using Oms.Application.Integrations.SmallBagTrack;
|
||||||
using Oms.Application.Messaging;
|
using Oms.Application.Messaging;
|
||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
|
using Oms.Application.Storage;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
@ -23,6 +24,7 @@ public sealed class OrderBatchJobCoordinator(
|
|||||||
IOrderBatchJobRepository batchJobRepository,
|
IOrderBatchJobRepository batchJobRepository,
|
||||||
IOrderRepository orderRepository,
|
IOrderRepository orderRepository,
|
||||||
ICountryRepository countryRepository,
|
ICountryRepository countryRepository,
|
||||||
|
IFileStorage fileStorage,
|
||||||
IOrderBatchJobPublisher publisher,
|
IOrderBatchJobPublisher publisher,
|
||||||
OrderBatchJobProcessor batchJobProcessor,
|
OrderBatchJobProcessor batchJobProcessor,
|
||||||
IOptions<OrderBatchRabbitMqOptions> batchOptions,
|
IOptions<OrderBatchRabbitMqOptions> batchOptions,
|
||||||
@ -649,6 +651,7 @@ public sealed class OrderBatchJobCoordinator(
|
|||||||
bundle.Declarations,
|
bundle.Declarations,
|
||||||
bundle.Weights,
|
bundle.Weights,
|
||||||
countryRepository,
|
countryRepository,
|
||||||
|
fileStorage,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
result[orderId] = JsonConvert.SerializeObject(body);
|
result[orderId] = JsonConvert.SerializeObject(body);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,8 @@ public partial class OrderService
|
|||||||
bundle.Recipient,
|
bundle.Recipient,
|
||||||
bundle.Declarations,
|
bundle.Declarations,
|
||||||
bundle.Weights,
|
bundle.Weights,
|
||||||
CountryRepository);
|
CountryRepository,
|
||||||
|
FileStorage);
|
||||||
|
|
||||||
return (bundle.Order, body);
|
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
|
return new OrderItemOutput
|
||||||
{
|
{
|
||||||
@ -759,7 +759,7 @@ public partial class OrderService
|
|||||||
HsCode = x.ExHsCode ?? x.ImHsCode ?? x.HsCode,
|
HsCode = x.ExHsCode ?? x.ImHsCode ?? x.HsCode,
|
||||||
ImHsCode = x.ImHsCode ?? x.HsCode,
|
ImHsCode = x.ImHsCode ?? x.HsCode,
|
||||||
ExHsCode = x.ExHsCode ?? x.HsCode,
|
ExHsCode = x.ExHsCode ?? x.HsCode,
|
||||||
SkuImage = x.SkuImage,
|
SkuImage = FileStorage.GetPublicUrl(x.SkuImage),
|
||||||
Category = x.Category,
|
Category = x.Category,
|
||||||
Brand = x.Brand,
|
Brand = x.Brand,
|
||||||
BrandType = x.BrandType,
|
BrandType = x.BrandType,
|
||||||
|
|||||||
@ -894,7 +894,7 @@ public partial class OrderService
|
|||||||
_ => (OrderWeightTypeCodes.Forecast, OrderFeeChargingStageCodes.Forecast)
|
_ => (OrderWeightTypeCodes.Forecast, OrderFeeChargingStageCodes.Forecast)
|
||||||
};
|
};
|
||||||
|
|
||||||
private static OrderDeclaration MapDeclarationInput(int orderId, OrderParcelItemInput x)
|
private OrderDeclaration MapDeclarationInput(int orderId, OrderParcelItemInput x)
|
||||||
{
|
{
|
||||||
return new OrderDeclaration
|
return new OrderDeclaration
|
||||||
{
|
{
|
||||||
@ -911,7 +911,7 @@ public partial class OrderService
|
|||||||
HsCode = x.ExHsCode ?? x.ImHsCode,
|
HsCode = x.ExHsCode ?? x.ImHsCode,
|
||||||
ImHsCode = x.ImHsCode,
|
ImHsCode = x.ImHsCode,
|
||||||
ExHsCode = x.ExHsCode ?? x.ImHsCode,
|
ExHsCode = x.ExHsCode ?? x.ImHsCode,
|
||||||
SkuImage = x.SkuImage,
|
SkuImage = FileStorage.ToStorageKey(x.SkuImage),
|
||||||
Category = x.Category,
|
Category = x.Category,
|
||||||
Brand = x.Brand,
|
Brand = x.Brand,
|
||||||
BrandType = x.BrandType,
|
BrandType = x.BrandType,
|
||||||
|
|||||||
@ -1965,7 +1965,7 @@ public partial class OrderService
|
|||||||
HsCode = x.ExHsCode ?? x.ImHsCode,
|
HsCode = x.ExHsCode ?? x.ImHsCode,
|
||||||
ImHsCode = x.ImHsCode,
|
ImHsCode = x.ImHsCode,
|
||||||
ExHsCode = x.ExHsCode,
|
ExHsCode = x.ExHsCode,
|
||||||
SkuImage = x.SkuImage,
|
SkuImage = FileStorage.ToStorageKey(x.SkuImage),
|
||||||
Category = x.Category,
|
Category = x.Category,
|
||||||
Brand = x.Brand,
|
Brand = x.Brand,
|
||||||
BrandType = x.BrandType,
|
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>
|
/// <summary>
|
||||||
/// 规范化列表状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。
|
/// 规范化列表状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Oms.Application.Dtos.Orders;
|
using Oms.Application.Dtos.Orders;
|
||||||
|
using Oms.Application.Storage;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
using Oms.Domain.SystemAggregate.Repositories;
|
using Oms.Domain.SystemAggregate.Repositories;
|
||||||
@ -17,6 +18,7 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
|||||||
IReadOnlyList<OrderDeclaration> declarations,
|
IReadOnlyList<OrderDeclaration> declarations,
|
||||||
IReadOnlyList<OrderWeight> weights,
|
IReadOnlyList<OrderWeight> weights,
|
||||||
ICountryRepository countryRepository,
|
ICountryRepository countryRepository,
|
||||||
|
IFileStorage? fileStorage = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var countryIds = new List<int>();
|
var countryIds = new List<int>();
|
||||||
@ -66,7 +68,7 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
|||||||
Quantity = order.Quantity,
|
Quantity = order.Quantity,
|
||||||
Receiver = MapReceiver(recipient, order.ConsigneeCountry, idToCode),
|
Receiver = MapReceiver(recipient, order.ConsigneeCountry, idToCode),
|
||||||
Sender = MapSender(sender, 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()
|
new()
|
||||||
{
|
{
|
||||||
CnName = x.CnName,
|
CnName = x.CnName,
|
||||||
@ -164,7 +166,7 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
|||||||
Weight = x.Weight,
|
Weight = x.Weight,
|
||||||
ImHsCode = x.ImHsCode,
|
ImHsCode = x.ImHsCode,
|
||||||
ExHsCode = x.ExHsCode ?? x.HsCode,
|
ExHsCode = x.ExHsCode ?? x.HsCode,
|
||||||
SkuImage = x.SkuImage,
|
SkuImage = fileStorage?.GetPublicUrl(x.SkuImage) ?? x.SkuImage,
|
||||||
Category = x.Category,
|
Category = x.Category,
|
||||||
Brand = x.Brand,
|
Brand = x.Brand,
|
||||||
BrandType = x.BrandType,
|
BrandType = x.BrandType,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using Moq;
|
|||||||
using Oms.Application.Dtos.Orders;
|
using Oms.Application.Dtos.Orders;
|
||||||
using Oms.Application.Messaging;
|
using Oms.Application.Messaging;
|
||||||
using Oms.Application.Services.Orders.Batch;
|
using Oms.Application.Services.Orders.Batch;
|
||||||
|
using Oms.Application.Storage;
|
||||||
using Oms.Domain.OrdersAggregate.Consts;
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
using Oms.Domain.OrdersAggregate.Repositories;
|
using Oms.Domain.OrdersAggregate.Repositories;
|
||||||
@ -18,6 +19,7 @@ public class OrderBatchJobCoordinatorGovernanceTests
|
|||||||
private readonly Mock<IOrderBatchJobRepository> _batchJobRepository = new(MockBehavior.Strict);
|
private readonly Mock<IOrderBatchJobRepository> _batchJobRepository = new(MockBehavior.Strict);
|
||||||
private readonly Mock<IOrderRepository> _orderRepository = new(MockBehavior.Loose);
|
private readonly Mock<IOrderRepository> _orderRepository = new(MockBehavior.Loose);
|
||||||
private readonly Mock<ICountryRepository> _countryRepository = 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 Mock<IOrderBatchJobPublisher> _publisher = new(MockBehavior.Strict);
|
||||||
private readonly OrderBatchJobCoordinator _coordinator;
|
private readonly OrderBatchJobCoordinator _coordinator;
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ public class OrderBatchJobCoordinatorGovernanceTests
|
|||||||
_batchJobRepository.Object,
|
_batchJobRepository.Object,
|
||||||
_orderRepository.Object,
|
_orderRepository.Object,
|
||||||
_countryRepository.Object,
|
_countryRepository.Object,
|
||||||
|
_fileStorage.Object,
|
||||||
_publisher.Object,
|
_publisher.Object,
|
||||||
processor,
|
processor,
|
||||||
batchOptions,
|
batchOptions,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user