This commit is contained in:
2026-09-02 10:27:47 +08:00
parent 371aa3dbcb
commit e0a1f15777
6 changed files with 32 additions and 5 deletions
@@ -202,7 +202,7 @@ public class FbaPricingRepository {
LambdaUpdateWrapper<InboundPricingEntity> update = new LambdaUpdateWrapper<InboundPricingEntity>()
.eq(InboundPricingEntity::getTenantId, inbound.getTenantId())
.eq(InboundPricingEntity::getId, inbound.getId())
.in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED"))
.in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED", "VOIDED"))
.set(InboundPricingEntity::getChannelCode, channelCode)
.set(InboundPricingEntity::getChannelName, channelName)
.set(InboundPricingEntity::getFeeStatus, "UNPRICED")
@@ -129,7 +129,8 @@ public class OrderChannelChangePricingService {
|| feeItems.findPublishedFreight(inbound.getTenantId(), inbound.getId()) != null) {
throw conflict("费用已确认或已发布,渠道不可变更;请先按费用作废流程处理");
}
if (!"UNPRICED".equals(inbound.getFeeStatus()) && !"CALCULATED".equals(inbound.getFeeStatus())) {
if (!"UNPRICED".equals(inbound.getFeeStatus()) && !"CALCULATED".equals(inbound.getFeeStatus())
&& !"VOIDED".equals(inbound.getFeeStatus())) {
throw conflict("当前费用状态不可逆,渠道不可变更");
}
}
@@ -93,7 +93,7 @@ paths:
仅供 Order 在渠道变更成功后调用。tenantId + orderId + orderVersion 构成幂等边界;
旧版本重放不会覆盖新版本。只将 CALCULATED 费用及草稿运费标记为 SUPERSEDED,
入库单恢复 UNPRICED 并可重新计价;实际净重、实际体积和测量审核事实保持不变。
已确认、已发送或已发布费用一律拒绝,不能由本命令覆盖。
已确认、已发送或已发布费用一律拒绝,不能由本命令覆盖;费用正式作废为 VOIDED 后可重新计价。
security:
- internalServiceToken: []
requestBody:
@@ -1066,7 +1066,7 @@ components:
orderVersion: {type: integer, format: int64}
channelCode: {type: string}
priceBookVersionId: {type: integer, format: int64, nullable: true}
feeStatus: {type: string, enum: [UNPRICED, CALCULATED]}
feeStatus: {type: string, enum: [UNPRICED, CALCULATED, VOIDED]}
calculatedFeeWillBeInvalidated: {type: boolean}
invalidatedFeeId: {type: integer, format: int64, nullable: true}
UnifiedMainStatus:
@@ -218,6 +218,8 @@ paths:
/internal/firstleg/v1/pricing/order-channel-changes/validate,两个服务均失败关闭。
国内收货仓或中转仓如需变化必须重新下单。成功后通过 ORDER_DECLARATION_UPDATED_V1 Outbox
同步 Firstleg;消费者按 eventId 幂等、version 顺序刷新申报快照,不覆盖实测、箱码或已确认费用。
本次 changes 含渠道字段时,同版本 Outbox 还必须调用
/internal/firstleg/v1/pricing/order-channel-changes/apply,使未确认价格预估显式失效;普通申报更新不得调用。
parameters:
- $ref: '#/components/parameters/UnifiedOrderId'
- $ref: '#/components/parameters/UnifiedTenantId'
@@ -51,7 +51,8 @@ class OrderDeclarationUpdateContractTest {
assertThat(map(row.get("actualVolumeCbm")).get("description").toString()).contains("仅 Firstleg 实测");
assertThat(source).contains("ORDER_DECLARATION_UPDATED_V1", "platform:order:declaration:edit", "A0409",
"/internal/firstleg/v1/inbounds/order-channel-changes/validate",
"/internal/firstleg/v1/pricing/order-channel-changes/validate");
"/internal/firstleg/v1/pricing/order-channel-changes/validate",
"/internal/firstleg/v1/pricing/order-channel-changes/apply");
}
@Test
@@ -60,10 +61,14 @@ class OrderDeclarationUpdateContractTest {
String.class, FirstlegInboundClient.OrderChannelChangeValidationRequest.class);
var pricing = FirstlegPricingClient.class.getDeclaredMethod("validateOrderChannelChange",
String.class, FirstlegPricingClient.OrderChannelChangeRequest.class);
var applyPricing = FirstlegPricingClient.class.getDeclaredMethod("applyOrderChannelChange",
String.class, FirstlegPricingClient.OrderChannelChangeRequest.class);
assertThat(fulfillment.getAnnotation(PostMapping.class).value())
.containsExactly("/order-channel-changes/validate");
assertThat(pricing.getAnnotation(PostMapping.class).value())
.containsExactly("/order-channel-changes/validate");
assertThat(applyPricing.getAnnotation(PostMapping.class).value())
.containsExactly("/order-channel-changes/apply");
}
@SuppressWarnings("unchecked")
@@ -171,6 +171,25 @@ class OrderInboundOutboxPublisherTest {
verify(fixture.repository).markOutboxPublished(eq(11L), eq(41L), any(LocalDateTime.class));
}
@Test
void channelPricingApplyFailureKeepsOutboxPendingAndDoesNotAdvanceFulfillmentSnapshot() {
String payload = "{\"schemaVersion\":1,\"eventId\":\"evt-3\",\"tenantId\":11,"
+ "\"orderId\":21,\"orderNo\":\"ORD-21\",\"inboundCode\":\"RK-21\","
+ "\"version\":4,\"operatorId\":7,\"channelCode\":\"SEA\","
+ "\"channelName\":\"海运\",\"channelChanged\":true,\"cargoes\":[],"
+ "\"changes\":[{\"field\":\"channelName\",\"beforeValue\":\"旧渠道\",\"afterValue\":\"海运\"}]}";
Fixture fixture = fixture(event("ORDER_DECLARATION_UPDATED_V1", 11L, 21L, payload));
when(fixture.pricingClient.applyOrderChannelChange(eq("internal-token"), any())).thenReturn(
ApiResponse.failure(com.baiyue.common.core.api.ResultCode.CONFLICT,
"费用已确认或已发布,渠道不可变更", null, null));
fixture.publisher.publishApprovedOrders();
verify(fixture.repository).markOutboxRetry(eq(11L), eq(41L), any(LocalDateTime.class));
verify(fixture.client, never()).updateDeclaration(any(), any());
verify(fixture.repository, never()).markOutboxPublished(anyLong(), anyLong(), any());
}
private Fixture fixture(OutboxEventEntity event) {
return fixture(event, "BUY_DECLARATION");
}