优化
This commit is contained in:
@@ -3961,6 +3961,24 @@ JOIN wh_warehouse warehouse
|
|||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
tenant_id = VALUES(tenant_id);
|
tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
|
-- 国内固定作业仓必须启用收货能力,FBA 目的地址仍不得创建作业能力。
|
||||||
|
INSERT INTO wh_service_capability (
|
||||||
|
id, tenant_id, warehouse_id, capability_code, capability_name, enabled, cutoff_time,
|
||||||
|
description, created_by, created_at, updated_by, updated_at, deleted, version
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(193000000000400001,0,193000000000200001,'RECEIVING','头程收货',1,'16:00:00',
|
||||||
|
'义乌仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0),
|
||||||
|
(193000000000400002,0,193000000000200002,'RECEIVING','头程收货',1,'16:00:00',
|
||||||
|
'东莞仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
capability_name = VALUES(capability_name),
|
||||||
|
enabled = VALUES(enabled),
|
||||||
|
cutoff_time = VALUES(cutoff_time),
|
||||||
|
description = VALUES(description),
|
||||||
|
deleted = 0,
|
||||||
|
updated_at = CURRENT_TIMESTAMP(3);
|
||||||
|
|
||||||
-- 平台托盘初始化主数据;内部状态仅供预占/占用,不作为平台维护字段。
|
-- 平台托盘初始化主数据;内部状态仅供预占/占用,不作为平台维护字段。
|
||||||
INSERT INTO wh_pallet (
|
INSERT INTO wh_pallet (
|
||||||
id, tenant_id, warehouse_id, pallet_code, pallet_type, capacity_cbm,
|
id, tenant_id, warehouse_id, pallet_code, pallet_type, capacity_cbm,
|
||||||
@@ -4471,6 +4489,16 @@ BEGIN
|
|||||||
) THEN
|
) THEN
|
||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址父子解析失败';
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址父子解析失败';
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
IF (SELECT COUNT(*)
|
||||||
|
FROM wh_service_capability capability
|
||||||
|
WHERE capability.tenant_id = 0
|
||||||
|
AND capability.warehouse_id IN (193000000000200001,193000000000200002)
|
||||||
|
AND capability.capability_code = 'RECEIVING'
|
||||||
|
AND capability.enabled = 1
|
||||||
|
AND capability.deleted = 0) <> 2 THEN
|
||||||
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '国内固定作业仓收货能力写入校验失败';
|
||||||
|
END IF;
|
||||||
END//
|
END//
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
CALL verify_wh_fixed_platform_warehouse();
|
CALL verify_wh_fixed_platform_warehouse();
|
||||||
|
|||||||
@@ -201,15 +201,17 @@ public class FbaPricingRepository {
|
|||||||
.set(InboundFeeEntity::getUpdatedAt, LocalDateTime.now()));
|
.set(InboundFeeEntity::getUpdatedAt, LocalDateTime.now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int markInboundCalculated(long tenantId, long inboundId, String chargeMode,
|
public int markInboundCalculated(long tenantId, long inboundId, String processStatus, String chargeMode,
|
||||||
java.math.BigDecimal chargeable) {
|
java.math.BigDecimal chargeable) {
|
||||||
LambdaUpdateWrapper<InboundPricingEntity> update = new LambdaUpdateWrapper<InboundPricingEntity>()
|
LambdaUpdateWrapper<InboundPricingEntity> update = new LambdaUpdateWrapper<InboundPricingEntity>()
|
||||||
.eq(InboundPricingEntity::getTenantId, tenantId)
|
.eq(InboundPricingEntity::getTenantId, tenantId)
|
||||||
.eq(InboundPricingEntity::getId, inboundId)
|
.eq(InboundPricingEntity::getId, inboundId)
|
||||||
.in(InboundPricingEntity::getProcessStatus, List.of("WAIT_PRICING", "FEE_CONFIRMING"))
|
.eq(InboundPricingEntity::getProcessStatus, processStatus)
|
||||||
.in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED"))
|
.in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED"))
|
||||||
.set(InboundPricingEntity::getProcessStatus, "FEE_CONFIRMING")
|
|
||||||
.set(InboundPricingEntity::getFeeStatus, "CALCULATED");
|
.set(InboundPricingEntity::getFeeStatus, "CALCULATED");
|
||||||
|
if (!"WAIT_SPLIT".equals(processStatus)) {
|
||||||
|
update.set(InboundPricingEntity::getProcessStatus, "FEE_CONFIRMING");
|
||||||
|
}
|
||||||
if ("KG".equals(chargeMode)) update.set(InboundPricingEntity::getChargeableWeightKg, chargeable);
|
if ("KG".equals(chargeMode)) update.set(InboundPricingEntity::getChargeableWeightKg, chargeable);
|
||||||
else update.set(InboundPricingEntity::getChargeableVolumeCbm, chargeable);
|
else update.set(InboundPricingEntity::getChargeableVolumeCbm, chargeable);
|
||||||
return inbounds.update(null, update);
|
return inbounds.update(null, update);
|
||||||
@@ -227,7 +229,7 @@ public class FbaPricingRepository {
|
|||||||
public int markInboundConfirmed(long tenantId, long inboundId) {
|
public int markInboundConfirmed(long tenantId, long inboundId) {
|
||||||
return inbounds.update(null, new LambdaUpdateWrapper<InboundPricingEntity>()
|
return inbounds.update(null, new LambdaUpdateWrapper<InboundPricingEntity>()
|
||||||
.eq(InboundPricingEntity::getTenantId, tenantId).eq(InboundPricingEntity::getId, inboundId)
|
.eq(InboundPricingEntity::getTenantId, tenantId).eq(InboundPricingEntity::getId, inboundId)
|
||||||
.eq(InboundPricingEntity::getProcessStatus, "FEE_CONFIRMING")
|
.in(InboundPricingEntity::getProcessStatus, List.of("FEE_CONFIRMING", "WAIT_SPLIT"))
|
||||||
.eq(InboundPricingEntity::getFeeStatus, "CALCULATED")
|
.eq(InboundPricingEntity::getFeeStatus, "CALCULATED")
|
||||||
.set(InboundPricingEntity::getFeeStatus, "CONFIRMED"));
|
.set(InboundPricingEntity::getFeeStatus, "CONFIRMED"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class FbaPricingService {
|
|||||||
String hash = hash("CALCULATE", inboundId, requestedAt == null ? "CURRENT" : requestedAt.toString());
|
String hash = hash("CALCULATE", inboundId, requestedAt == null ? "CURRENT" : requestedAt.toString());
|
||||||
InboundPricingIdempotencyEntity token = beginIdempotency(
|
InboundPricingIdempotencyEntity token = beginIdempotency(
|
||||||
tenantId, operatorId, inboundId, "FEE_CALCULATE", idempotencyKey, hash);
|
tenantId, operatorId, inboundId, "FEE_CALCULATE", idempotencyKey, hash);
|
||||||
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), "FEE_CONFIRMING");
|
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), null);
|
||||||
|
|
||||||
InboundPricingEntity inbound = requireEligibleInbound(tenantId, inboundId);
|
InboundPricingEntity inbound = requireEligibleInbound(tenantId, inboundId);
|
||||||
List<CustomerChargeRuleEntity> matchedRules = repository.matchRules(inbound, pricingAt);
|
List<CustomerChargeRuleEntity> matchedRules = repository.matchRules(inbound, pricingAt);
|
||||||
@@ -88,11 +88,14 @@ public class FbaPricingService {
|
|||||||
repository.insertFee(fee);
|
repository.insertFee(fee);
|
||||||
repository.insertItem(baseItem(tenantId, fee, rate, chargeable, subtotal, now));
|
repository.insertItem(baseItem(tenantId, fee, rate, chargeable, subtotal, now));
|
||||||
if (additional.signum() > 0) repository.insertItem(additionalItem(tenantId, fee, additional, now));
|
if (additional.signum() > 0) repository.insertItem(additionalItem(tenantId, fee, additional, now));
|
||||||
if (repository.markInboundCalculated(tenantId, inboundId, rule.getChargeMode(), chargeable) != 1) {
|
if (repository.markInboundCalculated(tenantId, inboundId, inbound.getProcessStatus(),
|
||||||
|
rule.getChargeMode(), chargeable) != 1) {
|
||||||
throw conflict("入库单状态已变化,费用试算未保存");
|
throw conflict("入库单状态已变化,费用试算未保存");
|
||||||
}
|
}
|
||||||
complete(token, fee.getId(), now);
|
complete(token, fee.getId(), now);
|
||||||
return response(inbound, fee, rate.getUnitPrice(), "FEE_CONFIRMING");
|
String nextStatus = "WAIT_SPLIT".equals(inbound.getProcessStatus())
|
||||||
|
? "WAIT_SPLIT" : "FEE_CONFIRMING";
|
||||||
|
return response(inbound, fee, rate.getUnitPrice(), nextStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -103,14 +106,14 @@ public class FbaPricingService {
|
|||||||
String hash = hash("CONFIRM", inboundId, current.getId().toString());
|
String hash = hash("CONFIRM", inboundId, current.getId().toString());
|
||||||
InboundPricingIdempotencyEntity token = beginIdempotency(
|
InboundPricingIdempotencyEntity token = beginIdempotency(
|
||||||
tenantId, operatorId, inboundId, "FEE_CONFIRM", idempotencyKey, hash);
|
tenantId, operatorId, inboundId, "FEE_CONFIRM", idempotencyKey, hash);
|
||||||
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), "FEE_CONFIRMING");
|
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), null);
|
||||||
LocalDateTime now = LocalDateTime.now(clock);
|
LocalDateTime now = LocalDateTime.now(clock);
|
||||||
if (repository.confirmFee(tenantId, current.getId(), operatorId, now) != 1
|
if (repository.confirmFee(tenantId, current.getId(), operatorId, now) != 1
|
||||||
|| repository.markInboundConfirmed(tenantId, inboundId) != 1) {
|
|| repository.markInboundConfirmed(tenantId, inboundId) != 1) {
|
||||||
throw conflict("费用或入库单状态已变化,不能重复确认");
|
throw conflict("费用或入库单状态已变化,不能重复确认");
|
||||||
}
|
}
|
||||||
complete(token, current.getId(), now);
|
complete(token, current.getId(), now);
|
||||||
return response(tenantId, current.getId(), "FEE_CONFIRMING");
|
return response(tenantId, current.getId(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -232,7 +235,7 @@ public class FbaPricingService {
|
|||||||
|
|
||||||
private InboundPricingEntity requireEligibleInbound(long tenantId, long inboundId) {
|
private InboundPricingEntity requireEligibleInbound(long tenantId, long inboundId) {
|
||||||
InboundPricingEntity inbound = requireApprovedInbound(tenantId, inboundId);
|
InboundPricingEntity inbound = requireApprovedInbound(tenantId, inboundId);
|
||||||
if (!List.of("WAIT_PRICING", "FEE_CONFIRMING").contains(inbound.getProcessStatus())
|
if (!List.of("WAIT_PRICING", "FEE_CONFIRMING", "WAIT_SPLIT").contains(inbound.getProcessStatus())
|
||||||
|| !List.of("UNPRICED", "CALCULATED").contains(inbound.getFeeStatus())) {
|
|| !List.of("UNPRICED", "CALCULATED").contains(inbound.getFeeStatus())) {
|
||||||
throw conflict("当前入库单状态不允许费用试算");
|
throw conflict("当前入库单状态不允许费用试算");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,19 +54,20 @@ paths:
|
|||||||
/api/platform/v1/inbounds/{inboundId}/fees/calculate:
|
/api/platform/v1/inbounds/{inboundId}/fees/calculate:
|
||||||
post:
|
post:
|
||||||
summary: 按审核通过测量快照试算 FBA 费用
|
summary: 按审核通过测量快照试算 FBA 费用
|
||||||
description: 精确匹配唯一客户规则和唯一船司价格;需要 firstleg:fba-pricing:calculate 权限。
|
description: 精确匹配唯一客户规则和唯一船司价格;测量审核后即使主流程已到 WAIT_SPLIT,也可独立试算且不回退主流程状态;需要 firstleg:fba-pricing:calculate 权限。
|
||||||
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
requestBody: {content: {application/json: {schema: {$ref: '#/components/schemas/CalculateRequest'}}}}
|
requestBody: {content: {application/json: {schema: {$ref: '#/components/schemas/CalculateRequest'}}}}
|
||||||
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
||||||
/api/platform/v1/inbounds/{inboundId}/fees/confirm:
|
/api/platform/v1/inbounds/{inboundId}/fees/confirm:
|
||||||
post:
|
post:
|
||||||
summary: 确认最新费用快照
|
summary: 确认最新费用快照
|
||||||
|
description: 确认费用只更新费用状态;主流程已到 WAIT_SPLIT 时保持不变。
|
||||||
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
||||||
/api/platform/v1/inbounds/{inboundId}/fees/send:
|
/api/platform/v1/inbounds/{inboundId}/fees/send:
|
||||||
post:
|
post:
|
||||||
summary: 手工恢复发送费用快照
|
summary: 发送已确认费用快照
|
||||||
description: 仅作为异常恢复接口;正常业务由客服测量审核通过后自动计费、发布并完成入库。
|
description: 该动作涉及后续仓储流程,本轮二期验收不调用。
|
||||||
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||||
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
|
||||||
/api/platform/v1/inbounds/{inboundId}/fees/latest:
|
/api/platform/v1/inbounds/{inboundId}/fees/latest:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class FbaPricingContractTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("契约声明自动发布、已发布费用调整和幂等头")
|
@DisplayName("契约声明二期边界计费、已发布费用调整和幂等头")
|
||||||
void documentsPricingLifecycle() throws Exception {
|
void documentsPricingLifecycle() throws Exception {
|
||||||
String openapi = new ClassPathResource("openapi/fba-pricing-v1.yaml")
|
String openapi = new ClassPathResource("openapi/fba-pricing-v1.yaml")
|
||||||
.getContentAsString(StandardCharsets.UTF_8);
|
.getContentAsString(StandardCharsets.UTF_8);
|
||||||
@@ -48,7 +48,8 @@ class FbaPricingContractTest {
|
|||||||
.contains("/api/platform/v1/inbounds/{inboundId}/fees/send:")
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/send:")
|
||||||
.contains("/api/platform/v1/inbounds/{inboundId}/fees/published:")
|
.contains("/api/platform/v1/inbounds/{inboundId}/fees/published:")
|
||||||
.contains("Idempotency-Key")
|
.contains("Idempotency-Key")
|
||||||
.contains("客服测量审核通过后自动计费、发布并完成入库")
|
.contains("主流程已到 WAIT_SPLIT,也可独立试算且不回退主流程状态")
|
||||||
|
.contains("该动作涉及后续仓储流程,本轮二期验收不调用")
|
||||||
.contains("不回退 INBOUNDED 状态")
|
.contains("不回退 INBOUNDED 状态")
|
||||||
.doesNotContain("应收")
|
.doesNotContain("应收")
|
||||||
.doesNotContain("应付")
|
.doesNotContain("应付")
|
||||||
|
|||||||
@@ -100,6 +100,51 @@ class FbaPricingServiceTest {
|
|||||||
assertThat(response.totalAmount()).isEqualByComparingTo("100.01");
|
assertThat(response.totalAmount()).isEqualByComparingTo("100.01");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("待分柜边界可独立计费且不回退主流程状态")
|
||||||
|
void calculatesAtWaitSplitWithoutRollingBackProcessStatus() {
|
||||||
|
readyRepository();
|
||||||
|
InboundPricingEntity inbound = inbound("100.000", "1.0000");
|
||||||
|
inbound.setProcessStatus("WAIT_SPLIT");
|
||||||
|
CustomerChargeRuleEntity rule = rule("KG", "167.000000", 3, "CEILING");
|
||||||
|
CarrierFbaRateEntity rate = rate("KG", "2.000000", "0.00");
|
||||||
|
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||||
|
when(repository.matchRules(inbound, NOW)).thenReturn(List.of(rule));
|
||||||
|
when(repository.matchRates(inbound, "KG", NOW)).thenReturn(List.of(rate));
|
||||||
|
|
||||||
|
FeeSnapshotResponse response = service.calculate(TENANT, OPERATOR, INBOUND, NOW, "wait-split");
|
||||||
|
|
||||||
|
assertThat(response.feeStatus()).isEqualTo("CALCULATED");
|
||||||
|
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_SPLIT");
|
||||||
|
verify(repository).markInboundCalculated(TENANT, INBOUND, "WAIT_SPLIT", "KG",
|
||||||
|
new BigDecimal("167.000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("待分柜边界确认费用后仍保持主流程状态")
|
||||||
|
void confirmsAtWaitSplitWithoutRollingBackProcessStatus() {
|
||||||
|
InboundPricingEntity inbound = inbound("10.000", "1.0000");
|
||||||
|
inbound.setProcessStatus("WAIT_SPLIT");
|
||||||
|
inbound.setFeeStatus("CALCULATED");
|
||||||
|
InboundFeeEntity fee = savedFee("CALCULATED");
|
||||||
|
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(fee);
|
||||||
|
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_CONFIRM", "confirm-wait-split"))
|
||||||
|
.thenReturn(null);
|
||||||
|
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(900L); return null; })
|
||||||
|
.when(repository).insertIdempotency(any());
|
||||||
|
when(repository.confirmFee(TENANT, 501L, OPERATOR, NOW)).thenReturn(1);
|
||||||
|
when(repository.markInboundConfirmed(TENANT, INBOUND)).thenReturn(1);
|
||||||
|
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
|
||||||
|
when(repository.findFee(TENANT, 501L)).thenReturn(fee);
|
||||||
|
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||||
|
|
||||||
|
FeeSnapshotResponse response = service.confirm(
|
||||||
|
TENANT, OPERATOR, INBOUND, "confirm-wait-split");
|
||||||
|
|
||||||
|
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_SPLIT");
|
||||||
|
verify(repository).markInboundConfirmed(TENANT, INBOUND);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("审核未通过的测量快照禁止计费")
|
@DisplayName("审核未通过的测量快照禁止计费")
|
||||||
void rejectsUnapprovedMeasurementSnapshot() {
|
void rejectsUnapprovedMeasurementSnapshot() {
|
||||||
@@ -137,15 +182,18 @@ class FbaPricingServiceTest {
|
|||||||
@DisplayName("同一幂等键重复试算直接返回原费用快照")
|
@DisplayName("同一幂等键重复试算直接返回原费用快照")
|
||||||
void returnsOriginalSnapshotForRepeatedCalculation() {
|
void returnsOriginalSnapshotForRepeatedCalculation() {
|
||||||
InboundPricingIdempotencyEntity completed = completed("FEE_CALCULATE", 501L);
|
InboundPricingIdempotencyEntity completed = completed("FEE_CALCULATE", 501L);
|
||||||
|
InboundPricingEntity inbound = inbound("10", "1");
|
||||||
|
inbound.setProcessStatus("WAIT_SPLIT");
|
||||||
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_CALCULATE", "same")).thenReturn(completed);
|
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_CALCULATE", "same")).thenReturn(completed);
|
||||||
when(repository.findFee(TENANT, 501L)).thenReturn(savedFee("CALCULATED"));
|
when(repository.findFee(TENANT, 501L)).thenReturn(savedFee("CALCULATED"));
|
||||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound("10", "1"));
|
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||||
InboundFeeItemEntity item = new InboundFeeItemEntity(); item.setUnitPrice(new BigDecimal("2.00"));
|
InboundFeeItemEntity item = new InboundFeeItemEntity(); item.setUnitPrice(new BigDecimal("2.00"));
|
||||||
when(repository.findBaseItem(TENANT, 501L)).thenReturn(item);
|
when(repository.findBaseItem(TENANT, 501L)).thenReturn(item);
|
||||||
|
|
||||||
FeeSnapshotResponse response = service.calculate(TENANT, OPERATOR, INBOUND, NOW, "same");
|
FeeSnapshotResponse response = service.calculate(TENANT, OPERATOR, INBOUND, NOW, "same");
|
||||||
|
|
||||||
assertThat(response.feeId()).isEqualTo(501L);
|
assertThat(response.feeId()).isEqualTo(501L);
|
||||||
|
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_SPLIT");
|
||||||
verify(repository, never()).matchRules(any(), any());
|
verify(repository, never()).matchRules(any(), any());
|
||||||
verify(repository, never()).insertFee(any());
|
verify(repository, never()).insertFee(any());
|
||||||
}
|
}
|
||||||
@@ -233,7 +281,7 @@ class FbaPricingServiceTest {
|
|||||||
doAnswer(invocation -> { ((InboundFeeEntity) invocation.getArgument(0)).setId(501L); return null; })
|
doAnswer(invocation -> { ((InboundFeeEntity) invocation.getArgument(0)).setId(501L); return null; })
|
||||||
.when(repository).insertFee(any());
|
.when(repository).insertFee(any());
|
||||||
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(1);
|
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(1);
|
||||||
when(repository.markInboundCalculated(eq(TENANT), eq(INBOUND), any(), any())).thenReturn(1);
|
when(repository.markInboundCalculated(eq(TENANT), eq(INBOUND), any(), any(), any())).thenReturn(1);
|
||||||
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
|
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,530 @@
|
|||||||
|
{
|
||||||
|
"reportName": "二期测试环境 API 验收",
|
||||||
|
"scope": [
|
||||||
|
"头程下单",
|
||||||
|
"商户提交",
|
||||||
|
"平台审核",
|
||||||
|
"异步生成入库单",
|
||||||
|
"测量草稿",
|
||||||
|
"称重图片与结果",
|
||||||
|
"人工修正与确认",
|
||||||
|
"测量提交与审核",
|
||||||
|
"计费试算"
|
||||||
|
],
|
||||||
|
"excluded": [
|
||||||
|
"分柜",
|
||||||
|
"预装",
|
||||||
|
"PDA 拣货",
|
||||||
|
"装柜证据",
|
||||||
|
"装柜完成",
|
||||||
|
"派送"
|
||||||
|
],
|
||||||
|
"gateway": "http://8.163.30.128:9000",
|
||||||
|
"marker": "API-E2E-20260806181448",
|
||||||
|
"startedAt": "2026-08-06T18:14:48.0000000",
|
||||||
|
"finishedAt": "2026-08-06T18:14:56.1155716+08:00",
|
||||||
|
"success": false,
|
||||||
|
"passedCount": 42,
|
||||||
|
"failedCount": 1,
|
||||||
|
"ids": {
|
||||||
|
"tenantId": "2085308606829346818",
|
||||||
|
"qualificationReviewId": "2085308609115242497",
|
||||||
|
"warehouseId": "2085308610297528322",
|
||||||
|
"carrierId": "2085308609974566913",
|
||||||
|
"orderId": "2085308610682916865",
|
||||||
|
"inboundId": "2085308616747372546",
|
||||||
|
"measurementId": "2085308626759176194",
|
||||||
|
"weightResultId": "2085308631419047938",
|
||||||
|
"fileId": "2085308627513511938"
|
||||||
|
},
|
||||||
|
"versions": {
|
||||||
|
"inbound": 3,
|
||||||
|
"measurement": 1,
|
||||||
|
"weightResult": 2
|
||||||
|
},
|
||||||
|
"fatal": null,
|
||||||
|
"billingDefect": {
|
||||||
|
"severity": "P0",
|
||||||
|
"summary": "测量审核后进入 WAIT_SPLIT,但计费接口不接受该状态,二期正常链路无法计费",
|
||||||
|
"http": 200,
|
||||||
|
"code": "A0409",
|
||||||
|
"message": "当前入库单状态不允许费用试算",
|
||||||
|
"requestId": "b55826a9-c964-4bb5-91c6-1b862a469eee",
|
||||||
|
"traceId": "44c08299046a4c5ab095b39463091218"
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"name": "平台登录",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "商户登录",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Firstleg 服务健康检查",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "查询商户必备资质",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "注册隔离的 API E2E 测试企业",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "登录隔离的 API E2E 测试企业",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "补齐商户营业执照与法人身份证测试包",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "提交商户资质审核",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "查询平台待审核企业",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "平台审核通过商户资质",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "创建公共测试承运商",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "创建启用收货能力的公共测试仓",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "创建头程订单草稿",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "重放创建订单幂等请求",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "创建订单幂等结果一致",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "orderId=2085308610682916865",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "查询订单草稿",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "订单初始状态为 DRAFT",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "status=DRAFT",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "编辑头程订单草稿",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "回查编辑后的订单草稿",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "订单编辑内容已保存",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "customerRemark=API-E2E-20260806181448 编辑后草稿",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "商户提交订单",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "统一订单详情查询待审核状态",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "统一订单主状态为 PENDING_REVIEW",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "mainStatus=PENDING_REVIEW",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "平台指派承运商",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "平台审核通过订单",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "异步生成入库单",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "inboundId=2085308616747372546",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "查询入库单详情",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "入库单进入待测量状态",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "processStatus=WAIT_MEASURE, version=0",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "查询初始测量详情",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "保存测量草稿",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "上传称重图片并触发 OCR",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "登记称重结果",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "人工修正称重结果",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "确认称重结果",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "提交前回查测量详情",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "确认重量汇总为 10KG",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "actualGrossWeightKg=10",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "提交测量审核",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "测量进入待审核状态",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "processStatus=MEASURE_PENDING, reviewStatus=PENDING",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "审核前回查入库单版本",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "平台审核通过测量",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "审核后回查入库单",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二期边界停在 WAIT_SPLIT",
|
||||||
|
"passed": true,
|
||||||
|
"http": 200,
|
||||||
|
"code": "200",
|
||||||
|
"message": "success",
|
||||||
|
"requestId": null,
|
||||||
|
"traceId": null,
|
||||||
|
"evidence": "processStatus=WAIT_SPLIT, reviewStatus=APPROVED",
|
||||||
|
"details": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "二期计费接口试算",
|
||||||
|
"passed": false,
|
||||||
|
"http": 200,
|
||||||
|
"code": "A0409",
|
||||||
|
"message": "当前入库单状态不允许费用试算",
|
||||||
|
"requestId": "b55826a9-c964-4bb5-91c6-1b862a469eee",
|
||||||
|
"traceId": "44c08299046a4c5ab095b39463091218",
|
||||||
|
"evidence": "inboundStatus=WAIT_SPLIT",
|
||||||
|
"details": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@ param(
|
|||||||
[string]$GatewayBaseUrl = 'http://8.163.30.128:9000',
|
[string]$GatewayBaseUrl = 'http://8.163.30.128:9000',
|
||||||
[string]$PlatformUsername = 'platform_admin',
|
[string]$PlatformUsername = 'platform_admin',
|
||||||
[string]$MerchantUsername = 'merchant_admin',
|
[string]$MerchantUsername = 'merchant_admin',
|
||||||
[string]$Password = 'ChangeMe123!',
|
[string]$PlatformPassword = 'ChangeMe123!',
|
||||||
|
[string]$MerchantPassword = 'ChangeMe123!',
|
||||||
[long]$TenantId = 10001,
|
[long]$TenantId = 10001,
|
||||||
[string]$OutputPath = ''
|
[string]$OutputPath = ''
|
||||||
)
|
)
|
||||||
@@ -16,7 +17,7 @@ if ([string]::IsNullOrWhiteSpace($OutputPath)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$steps = [System.Collections.Generic.List[object]]::new()
|
$steps = [System.Collections.Generic.List[object]]::new()
|
||||||
$ids = [ordered]@{ tenantId = [string]$TenantId; qualificationReviewId = $null; warehouseId = $null; carrierId = $null; orderId = $null; inboundId = $null; measurementId = $null; weightResultId = $null; fileId = $null }
|
$ids = [ordered]@{ tenantId = [string]$TenantId; qualificationReviewId = $null; warehouseId = $null; carrierId = $null; customerRuleId = $null; carrierRateId = $null; orderId = $null; inboundId = $null; measurementId = $null; weightResultId = $null; fileId = $null; feeId = $null }
|
||||||
$versions = [ordered]@{ inbound = $null; measurement = $null; weightResult = $null }
|
$versions = [ordered]@{ inbound = $null; measurement = $null; weightResult = $null }
|
||||||
$fatal = $null
|
$fatal = $null
|
||||||
$billingDefect = $null
|
$billingDefect = $null
|
||||||
@@ -56,19 +57,20 @@ function Invoke-Api(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-Upload([string]$Path, [string]$Token, [string]$FilePath) {
|
function Invoke-Upload([string]$Path, [string]$Token, [string]$FilePath) {
|
||||||
$response = Invoke-WebRequest -Method POST -Uri "$GatewayBaseUrl$Path" `
|
$file = Get-Item -LiteralPath $FilePath
|
||||||
-Headers @{ Authorization = "Bearer $Token" } -Form @{ file = Get-Item -LiteralPath $FilePath } `
|
$content = & curl.exe -sS -X POST "$GatewayBaseUrl$Path" `
|
||||||
-TimeoutSec 60 -SkipHttpErrorCheck
|
-H "Authorization: Bearer $Token" -F "file=@$($file.FullName);type=image/png"
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw '称重图片上传发生传输错误' }
|
||||||
$json = $null
|
$json = $null
|
||||||
if (-not [string]::IsNullOrWhiteSpace($response.Content)) {
|
if (-not [string]::IsNullOrWhiteSpace($content)) {
|
||||||
try { $json = $response.Content | ConvertFrom-Json } catch { }
|
try { $json = $content | ConvertFrom-Json } catch { }
|
||||||
}
|
}
|
||||||
return [pscustomobject]@{
|
return [pscustomobject]@{
|
||||||
http = [int]$response.StatusCode
|
http = 200
|
||||||
body = $json
|
body = $json
|
||||||
raw = if ($null -eq $json) { $response.Content } else { $null }
|
raw = if ($null -eq $json) { [string]$content } else { $null }
|
||||||
requestId = if ($null -ne $json) { $json.requestId } else { $response.Headers['X-Request-Id'] }
|
requestId = if ($null -ne $json) { $json.requestId } else { $null }
|
||||||
traceId = if ($null -ne $json) { $json.traceId } else { $response.Headers['X-Trace-Id'] }
|
traceId = if ($null -ne $json) { $json.traceId } else { $null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,11 +127,11 @@ function Require-Condition([string]$Name, $Response, [bool]$Condition, [string]$
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$platformLoginResponse = Invoke-Api POST '/api/auth/login' '' @{ username = $PlatformUsername; password = $Password }
|
$platformLoginResponse = Invoke-Api POST '/api/auth/login' '' @{ username = $PlatformUsername; password = $PlatformPassword }
|
||||||
$platformLogin = Require-Success '平台登录' $platformLoginResponse
|
$platformLogin = Require-Success '平台登录' $platformLoginResponse
|
||||||
$platformToken = $platformLogin.accessToken
|
$platformToken = $platformLogin.accessToken
|
||||||
|
|
||||||
$merchantLoginResponse = Invoke-Api POST '/api/auth/login' '' @{ username = $MerchantUsername; password = $Password }
|
$merchantLoginResponse = Invoke-Api POST '/api/auth/login' '' @{ username = $MerchantUsername; password = $MerchantPassword }
|
||||||
$merchantLogin = Require-Success '商户登录' $merchantLoginResponse
|
$merchantLogin = Require-Success '商户登录' $merchantLoginResponse
|
||||||
$merchantToken = $merchantLogin.accessToken
|
$merchantToken = $merchantLogin.accessToken
|
||||||
|
|
||||||
@@ -216,6 +218,28 @@ try {
|
|||||||
$warehouseResponse = Invoke-Api POST '/api/platform/v1/warehouse-admin' $platformToken $warehousePayload
|
$warehouseResponse = Invoke-Api POST '/api/platform/v1/warehouse-admin' $platformToken $warehousePayload
|
||||||
$ids.warehouseId = [string](Require-Success '创建启用收货能力的公共测试仓' $warehouseResponse)
|
$ids.warehouseId = [string](Require-Success '创建启用收货能力的公共测试仓' $warehouseResponse)
|
||||||
|
|
||||||
|
$effectiveFrom = (Get-Date).AddDays(-1).ToString('yyyy-MM-ddTHH:mm:ss')
|
||||||
|
$customerRuleResponse = Invoke-Api POST "/api/platform/v1/fba-pricing/customer-rules/versions?tenantId=$TenantId" $platformToken @{
|
||||||
|
ruleCode = "API-E2E-RULE-$stamp"; customerId = '191000000000000001'
|
||||||
|
customerName = '深圳示例贸易有限公司'; businessType = 'FBA_SEA'; chargeMode = 'KG'
|
||||||
|
taxMode = 'TAX_INCLUDED'; conversionFactor = 167; roundingScale = 3
|
||||||
|
roundingMode = 'HALF_UP'; currency = 'CNY'; effectiveFrom = $effectiveFrom
|
||||||
|
effectiveTo = $null; remark = $marker
|
||||||
|
}
|
||||||
|
$customerRule = Require-Success '创建客户计费规则版本' $customerRuleResponse
|
||||||
|
$ids.customerRuleId = [string]$customerRule.id
|
||||||
|
|
||||||
|
$carrierRateResponse = Invoke-Api POST '/api/platform/v1/fba-pricing/carrier-rates/versions' $platformToken @{
|
||||||
|
rateCode = "API-E2E-RATE-$stamp"; originWarehouseId = $ids.warehouseId
|
||||||
|
originWarehouseName = $warehousePayload.warehouseName; businessType = 'FBA_SEA'
|
||||||
|
channelCode = 'API-E2E-SEA'; carrierId = $ids.carrierId
|
||||||
|
carrierName = $carrierPayload.partnerName; destinationCode = 'ABE2'; chargeUnit = 'KG'
|
||||||
|
unitPrice = 2; currency = 'CNY'; additionalFee = 10; effectiveFrom = $effectiveFrom
|
||||||
|
effectiveTo = $null; remark = $marker
|
||||||
|
}
|
||||||
|
$carrierRate = Require-Success '创建承运商 FBA 费率版本' $carrierRateResponse
|
||||||
|
$ids.carrierRateId = [string]$carrierRate.id
|
||||||
|
|
||||||
$sender = [ordered]@{
|
$sender = [ordered]@{
|
||||||
companyName = '深圳示例贸易有限公司'; contactName = 'API Tester'; phone = '13800138000'
|
companyName = '深圳示例贸易有限公司'; contactName = 'API Tester'; phone = '13800138000'
|
||||||
email = 'api-e2e@example.com'; countryCode = 'CN'; province = '广东省'; city = '深圳市'
|
email = 'api-e2e@example.com'; countryCode = 'CN'; province = '广东省'; city = '深圳市'
|
||||||
@@ -387,19 +411,13 @@ try {
|
|||||||
Require-Condition '二期边界停在 WAIT_SPLIT' $approvedInboundResponse ($approvedInbound.summary.processStatus -eq 'WAIT_SPLIT' -and $approvedInbound.summary.measureReviewStatus -eq 'APPROVED') "processStatus=$($approvedInbound.summary.processStatus), reviewStatus=$($approvedInbound.summary.measureReviewStatus)"
|
Require-Condition '二期边界停在 WAIT_SPLIT' $approvedInboundResponse ($approvedInbound.summary.processStatus -eq 'WAIT_SPLIT' -and $approvedInbound.summary.measureReviewStatus -eq 'APPROVED') "processStatus=$($approvedInbound.summary.processStatus), reviewStatus=$($approvedInbound.summary.measureReviewStatus)"
|
||||||
|
|
||||||
$feeResponse = Invoke-Api POST "/api/platform/v1/inbounds/$($ids.inboundId)/fees/calculate?tenantId=$TenantId" $platformToken @{} @{ 'Idempotency-Key' = (New-Key 'fee-calculate') }
|
$feeResponse = Invoke-Api POST "/api/platform/v1/inbounds/$($ids.inboundId)/fees/calculate?tenantId=$TenantId" $platformToken @{} @{ 'Idempotency-Key' = (New-Key 'fee-calculate') }
|
||||||
$feePassed = $feeResponse.http -eq 200 -and [string]$feeResponse.body.code -eq '200'
|
$calculatedFee = Require-Success '二期计费接口试算' $feeResponse
|
||||||
Add-Step '二期计费接口试算' $feeResponse $feePassed "inboundStatus=$($approvedInbound.summary.processStatus)"
|
$ids.feeId = [string]$calculatedFee.feeId
|
||||||
if (-not $feePassed) {
|
Require-Condition '计费试算保持 WAIT_SPLIT 主流程边界' $feeResponse ($calculatedFee.nextProcessStatus -eq 'WAIT_SPLIT') "nextProcessStatus=$($calculatedFee.nextProcessStatus)"
|
||||||
$billingDefect = [ordered]@{
|
|
||||||
severity = 'P0'
|
$feeConfirmResponse = Invoke-Api POST "/api/platform/v1/inbounds/$($ids.inboundId)/fees/confirm?tenantId=$TenantId" $platformToken $null @{ 'Idempotency-Key' = (New-Key 'fee-confirm') }
|
||||||
summary = '测量审核后进入 WAIT_SPLIT,但计费接口不接受该状态,二期正常链路无法计费'
|
$confirmedFee = Require-Success '确认二期费用快照' $feeConfirmResponse
|
||||||
http = $feeResponse.http
|
Require-Condition '费用确认后仍保持 WAIT_SPLIT 主流程边界' $feeConfirmResponse ($confirmedFee.feeStatus -eq 'CONFIRMED' -and $confirmedFee.nextProcessStatus -eq 'WAIT_SPLIT') "feeStatus=$($confirmedFee.feeStatus), nextProcessStatus=$($confirmedFee.nextProcessStatus)"
|
||||||
code = [string]$feeResponse.body.code
|
|
||||||
message = [string]$feeResponse.body.message
|
|
||||||
requestId = $feeResponse.requestId
|
|
||||||
traceId = $feeResponse.traceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
$fatal = $_.Exception.Message
|
$fatal = $_.Exception.Message
|
||||||
|
|||||||
@@ -162,6 +162,24 @@ JOIN wh_warehouse warehouse
|
|||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
tenant_id = VALUES(tenant_id);
|
tenant_id = VALUES(tenant_id);
|
||||||
|
|
||||||
|
-- 国内固定作业仓必须启用收货能力,FBA 目的地址仍不得创建作业能力。
|
||||||
|
INSERT INTO wh_service_capability (
|
||||||
|
id, tenant_id, warehouse_id, capability_code, capability_name, enabled, cutoff_time,
|
||||||
|
description, created_by, created_at, updated_by, updated_at, deleted, version
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(193000000000400001,0,193000000000200001,'RECEIVING','头程收货',1,'16:00:00',
|
||||||
|
'义乌仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0),
|
||||||
|
(193000000000400002,0,193000000000200002,'RECEIVING','头程收货',1,'16:00:00',
|
||||||
|
'东莞仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
capability_name = VALUES(capability_name),
|
||||||
|
enabled = VALUES(enabled),
|
||||||
|
cutoff_time = VALUES(cutoff_time),
|
||||||
|
description = VALUES(description),
|
||||||
|
deleted = 0,
|
||||||
|
updated_at = CURRENT_TIMESTAMP(3);
|
||||||
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fba_public_master_data;
|
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fba_public_master_data;
|
||||||
CREATE TEMPORARY TABLE tmp_wh_fba_public_master_data (
|
CREATE TEMPORARY TABLE tmp_wh_fba_public_master_data (
|
||||||
warehouse_id_seed BIGINT UNSIGNED NOT NULL,
|
warehouse_id_seed BIGINT UNSIGNED NOT NULL,
|
||||||
@@ -655,6 +673,16 @@ BEGIN
|
|||||||
) THEN
|
) THEN
|
||||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址父子解析失败';
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址父子解析失败';
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
IF (SELECT COUNT(*)
|
||||||
|
FROM wh_service_capability capability
|
||||||
|
WHERE capability.tenant_id = 0
|
||||||
|
AND capability.warehouse_id IN (193000000000200001,193000000000200002)
|
||||||
|
AND capability.capability_code = 'RECEIVING'
|
||||||
|
AND capability.enabled = 1
|
||||||
|
AND capability.deleted = 0) <> 2 THEN
|
||||||
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '国内固定作业仓收货能力写入校验失败';
|
||||||
|
END IF;
|
||||||
END//
|
END//
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
CALL verify_wh_fixed_platform_warehouse();
|
CALL verify_wh_fixed_platform_warehouse();
|
||||||
|
|||||||
+15
@@ -97,6 +97,21 @@ class WarehouseSchemaContractTest {
|
|||||||
.contains("idx_wh_express_tenant_status");
|
.contains("idx_wh_express_tenant_status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSeedReceivingCapabilityForDomesticOperatingWarehouses() throws IOException {
|
||||||
|
String masterData = read("warehouse-public-master-data.sql");
|
||||||
|
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
|
||||||
|
|
||||||
|
for (String sql : java.util.List.of(masterData, unified)) {
|
||||||
|
assertThat(sql)
|
||||||
|
.contains("193000000000200001,'RECEIVING','头程收货',1")
|
||||||
|
.contains("193000000000200002,'RECEIVING','头程收货',1")
|
||||||
|
.contains("国内固定作业仓收货能力写入校验失败")
|
||||||
|
.contains("warehouse.warehouse_type = 'FBA'")
|
||||||
|
.contains("FBA公共地址不得创建平台作业能力");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String read(String file) throws IOException {
|
private static String read(String file) throws IOException {
|
||||||
return Files.readString(Path.of("src/main/resources/db", file));
|
return Files.readString(Path.of("src/main/resources/db", file));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user