This commit is contained in:
2026-08-06 18:01:59 +08:00
parent 584f1519d1
commit 6e9739826c
24 changed files with 549 additions and 62 deletions
@@ -75,19 +75,20 @@ public class PlatformFbaMasterDataController {
@GetMapping("/carrier-rates")
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rate:view')")
@Operation(summary = "查询船司 FBA 价格版本", description = "默认查询全部企业;tenantId 可用于筛选目标企业")
@Operation(summary = "查询船司 FBA 价格版本", description = "查询平台公共价格;所有客户共享")
public ApiResponse<List<CarrierFbaRateEntity>> rates(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) Long tenantId,
@RequestParam(required = false) String rateCode) {
return ApiResponse.success(service.listRates(optionalPlatformTenant(user, tenantId), rateCode));
FirstlegScopeGuard.platform(user);
return ApiResponse.success(service.listRates(rateCode));
}
@GetMapping("/carrier-rates/{id}")
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rate:view')")
public ApiResponse<CarrierFbaRateEntity> rate(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId, @PathVariable long id) {
return ApiResponse.success(service.rate(FirstlegScopeGuard.platform(user, tenantId), id));
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long id) {
FirstlegScopeGuard.platform(user);
return ApiResponse.success(service.rate(id));
}
@PostMapping("/carrier-rates/versions")
@@ -95,10 +96,10 @@ public class PlatformFbaMasterDataController {
@BusinessAudit(event = "FBA_RATE_VERSION_CREATE", resourceType = "CARRIER_FBA_RATE",
description = "创建船司FBA价格版本")
public ApiResponse<CarrierFbaRateEntity> createRate(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId,
@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody CarrierRateVersionRequest request) {
long scope = FirstlegScopeGuard.platform(user, tenantId);
return ApiResponse.success(service.createRateVersion(scope, user.userId(), request));
FirstlegScopeGuard.platform(user);
return ApiResponse.success(service.createRateVersion(user.userId(), request));
}
@DeleteMapping("/carrier-rates/{id}")
@@ -106,8 +107,9 @@ public class PlatformFbaMasterDataController {
@BusinessAudit(event = "FBA_RATE_RETIRE", resourceType = "CARRIER_FBA_RATE", resourceId = "#id",
description = "停用船司FBA价格版本")
public ApiResponse<Void> retireRate(@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam long tenantId, @PathVariable long id) {
service.retireRate(FirstlegScopeGuard.platform(user, tenantId), id, user.userId());
@PathVariable long id) {
FirstlegScopeGuard.platform(user);
service.retireRate(id, user.userId());
return ApiResponse.success(null);
}
@@ -15,6 +15,9 @@ import java.time.LocalDateTime;
@Setter
@TableName("fl_carrier_fba_rate")
public class CarrierFbaRateEntity extends BaseEntity {
/** 船司价格是平台公共主数据,不绑定任何客户。 */
public static final long PLATFORM_SCOPE = 0L;
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private Long tenantId;
@@ -50,9 +50,9 @@ public class FbaPricingRepository {
.eq(CustomerChargeRuleEntity::getId, id));
}
public CarrierFbaRateEntity findRate(long tenantId, long id) {
public CarrierFbaRateEntity findRate(long id) {
return rates.selectOne(new LambdaQueryWrapper<CarrierFbaRateEntity>()
.eq(CarrierFbaRateEntity::getTenantId, tenantId)
.eq(CarrierFbaRateEntity::getTenantId, CarrierFbaRateEntity.PLATFORM_SCOPE)
.eq(CarrierFbaRateEntity::getId, id));
}
@@ -64,21 +64,34 @@ public class FbaPricingRepository {
.orderByDesc(CustomerChargeRuleEntity::getRuleVersion));
}
public List<CarrierFbaRateEntity> listRates(Long tenantId, String rateCode) {
public List<CarrierFbaRateEntity> listRates(String rateCode) {
return rates.selectList(new LambdaQueryWrapper<CarrierFbaRateEntity>()
.eq(tenantId != null, CarrierFbaRateEntity::getTenantId, tenantId)
.eq(CarrierFbaRateEntity::getTenantId, CarrierFbaRateEntity.PLATFORM_SCOPE)
.eq(rateCode != null && !rateCode.isBlank(), CarrierFbaRateEntity::getRateCode, rateCode)
.orderByAsc(CarrierFbaRateEntity::getRateCode)
.orderByDesc(CarrierFbaRateEntity::getRateVersion));
}
/**
* 在创建平台公共价格版本时锁定当前公共价格集合,串行化版本号和有效期检查。
* 调用方必须在事务中使用该方法。
*/
public List<CarrierFbaRateEntity> listRatesForUpdate(String rateCode) {
return rates.selectList(new LambdaQueryWrapper<CarrierFbaRateEntity>()
.eq(CarrierFbaRateEntity::getTenantId, CarrierFbaRateEntity.PLATFORM_SCOPE)
.eq(rateCode != null && !rateCode.isBlank(), CarrierFbaRateEntity::getRateCode, rateCode)
.orderByAsc(CarrierFbaRateEntity::getRateCode)
.orderByDesc(CarrierFbaRateEntity::getRateVersion)
.last("FOR UPDATE"));
}
public int nextRuleVersion(long tenantId, String ruleCode) {
List<CustomerChargeRuleEntity> values = listRules(tenantId, ruleCode);
return values.isEmpty() ? 1 : values.get(0).getRuleVersion() + 1;
}
public int nextRateVersion(long tenantId, String rateCode) {
List<CarrierFbaRateEntity> values = listRates(tenantId, rateCode);
public int nextRateVersion(String rateCode) {
List<CarrierFbaRateEntity> values = listRatesForUpdate(rateCode);
return values.isEmpty() ? 1 : values.get(0).getRateVersion() + 1;
}
@@ -91,9 +104,9 @@ public class FbaPricingRepository {
.set(CustomerChargeRuleEntity::getUpdatedBy, operatorId));
}
public int retireRate(long tenantId, long id, long operatorId) {
public int retireRate(long id, long operatorId) {
return rates.update(null, new LambdaUpdateWrapper<CarrierFbaRateEntity>()
.eq(CarrierFbaRateEntity::getTenantId, tenantId)
.eq(CarrierFbaRateEntity::getTenantId, CarrierFbaRateEntity.PLATFORM_SCOPE)
.eq(CarrierFbaRateEntity::getId, id)
.eq(CarrierFbaRateEntity::getStatus, "ACTIVE")
.set(CarrierFbaRateEntity::getStatus, "INACTIVE")
@@ -121,7 +134,7 @@ public class FbaPricingRepository {
public List<CarrierFbaRateEntity> matchRates(InboundPricingEntity inbound, String chargeUnit,
LocalDateTime at) {
return rates.selectList(new LambdaQueryWrapper<CarrierFbaRateEntity>()
.eq(CarrierFbaRateEntity::getTenantId, inbound.getTenantId())
.eq(CarrierFbaRateEntity::getTenantId, CarrierFbaRateEntity.PLATFORM_SCOPE)
.eq(CarrierFbaRateEntity::getOriginWarehouseId, inbound.getWarehouseId())
.eq(CarrierFbaRateEntity::getBusinessType, inbound.getBusinessType())
.eq(CarrierFbaRateEntity::getChannelCode, inbound.getChannelCode())
@@ -59,10 +59,9 @@ public class FbaMasterDataService {
}
@Transactional
public CarrierFbaRateEntity createRateVersion(long tenantId, long operatorId,
CarrierRateVersionRequest request) {
public CarrierFbaRateEntity createRateVersion(long operatorId, CarrierRateVersionRequest request) {
validatePeriod(request.effectiveFrom(), request.effectiveTo());
boolean overlaps = repository.listRates(tenantId, null).stream()
boolean overlaps = repository.listRatesForUpdate(null).stream()
.filter(v -> "ACTIVE".equals(v.getStatus()))
.filter(v -> Objects.equals(v.getOriginWarehouseId(), request.originWarehouseId()))
.filter(v -> Objects.equals(v.getBusinessType(), request.businessType()))
@@ -75,9 +74,9 @@ public class FbaMasterDataService {
if (overlaps) throw conflict("船司 FBA 价格有效期重叠,无法保证唯一匹配");
CarrierFbaRateEntity value = new CarrierFbaRateEntity();
value.setTenantId(tenantId);
value.setTenantId(CarrierFbaRateEntity.PLATFORM_SCOPE);
value.setRateCode(request.rateCode());
value.setRateVersion(repository.nextRateVersion(tenantId, request.rateCode()));
value.setRateVersion(repository.nextRateVersion(request.rateCode()));
value.setOriginWarehouseId(request.originWarehouseId());
value.setOriginWarehouseName(request.originWarehouseName());
value.setBusinessType(request.businessType());
@@ -103,8 +102,8 @@ public class FbaMasterDataService {
return repository.listRules(tenantId, code);
}
public List<CarrierFbaRateEntity> listRates(Long tenantId, String code) {
return repository.listRates(tenantId, code);
public List<CarrierFbaRateEntity> listRates(String code) {
return repository.listRates(code);
}
public CustomerChargeRuleEntity rule(long tenantId, long id) {
@@ -113,8 +112,8 @@ public class FbaMasterDataService {
return value;
}
public CarrierFbaRateEntity rate(long tenantId, long id) {
CarrierFbaRateEntity value = repository.findRate(tenantId, id);
public CarrierFbaRateEntity rate(long id) {
CarrierFbaRateEntity value = repository.findRate(id);
if (value == null) throw new BizException(ResultCode.NOT_FOUND, "船司 FBA 价格版本不存在");
return value;
}
@@ -125,8 +124,8 @@ public class FbaMasterDataService {
}
@Transactional
public void retireRate(long tenantId, long id, long operatorId) {
if (repository.retireRate(tenantId, id, operatorId) != 1) throw conflict("价格已停用或不存在");
public void retireRate(long id, long operatorId) {
if (repository.retireRate(id, operatorId) != 1) throw conflict("价格已停用或不存在");
}
private static void validatePeriod(LocalDateTime from, LocalDateTime to) {
@@ -56,6 +56,61 @@ SET @sql = IF(@resource_tenant_needs_modify > 0,
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 船司 FBA 价格是平台公共主数据;迁移前检查版本和有效期冲突,避免静默覆盖客户价格。
DROP PROCEDURE IF EXISTS phase2_fba_migrate_carrier_rate_scope;
DELIMITER $$
CREATE PROCEDURE phase2_fba_migrate_carrier_rate_scope()
BEGIN
DECLARE v_conflicts INT DEFAULT 0;
SELECT COUNT(*) INTO v_conflicts
FROM (
SELECT rate_code, rate_version
FROM fl_carrier_fba_rate
GROUP BY rate_code, rate_version
HAVING COUNT(*) > 1
) AS duplicate_versions;
IF v_conflicts > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'FBA价格迁移失败:不同客户存在重复价格编码版本,请先合并';
END IF;
SELECT COUNT(*) INTO v_conflicts
FROM (
SELECT first_rate.id
FROM fl_carrier_fba_rate first_rate
JOIN fl_carrier_fba_rate second_rate
ON first_rate.id < second_rate.id
AND first_rate.status = 'ACTIVE'
AND second_rate.status = 'ACTIVE'
AND first_rate.origin_warehouse_id <=> second_rate.origin_warehouse_id
AND first_rate.business_type <=> second_rate.business_type
AND first_rate.channel_code <=> second_rate.channel_code
AND first_rate.carrier_id <=> second_rate.carrier_id
AND first_rate.destination_code <=> second_rate.destination_code
AND first_rate.charge_unit <=> second_rate.charge_unit
AND first_rate.effective_from < COALESCE(second_rate.effective_to, '9999-12-31 23:59:59.999')
AND second_rate.effective_from < COALESCE(first_rate.effective_to, '9999-12-31 23:59:59.999')
LIMIT 1
) AS overlapping_rates;
IF v_conflicts > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'FBA价格迁移失败:公共匹配维度存在有效期重叠,请先合并';
END IF;
UPDATE fl_carrier_fba_rate
SET tenant_id = 0
WHERE tenant_id <> 0;
IF EXISTS (SELECT 1 FROM fl_carrier_fba_rate WHERE tenant_id <> 0) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'FBA价格迁移失败:仍存在非平台价格作用域';
END IF;
END$$
DELIMITER ;
CALL phase2_fba_migrate_carrier_rate_scope();
DROP PROCEDURE IF EXISTS phase2_fba_migrate_carrier_rate_scope;
DROP PROCEDURE IF EXISTS phase2_fba_add_column;
-- 旧索引可能缺少业务类型、船司和结束时间;用新索引名并存,避免破坏线上依赖。
@@ -309,8 +309,8 @@ CREATE TABLE IF NOT EXISTS fl_customer_charge_rule (
CREATE TABLE IF NOT EXISTS fl_carrier_fba_rate (
id BIGINT UNSIGNED NOT NULL COMMENT '船司FBA价格ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
rate_code VARCHAR(50) NOT NULL COMMENT '租户内价格编码',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台价格作用域ID,船司价格固定为0',
rate_code VARCHAR(50) NOT NULL COMMENT '平台公共价格编码',
rate_version INT UNSIGNED NOT NULL COMMENT '价格版本号',
origin_warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '起运仓逻辑ID',
origin_warehouse_name VARCHAR(128) NOT NULL COMMENT '起运仓名称快照',
@@ -32,26 +32,24 @@ paths:
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
/api/platform/v1/fba-pricing/carrier-rates:
get:
summary: 查询船司 FBA 价格版本
summary: 查询平台公共船司 FBA 价格版本
description: 需要 firstleg:fba-pricing:rate:view 权限
parameters:
- {$ref: '#/components/parameters/OptionalTenantId'}
- {name: rateCode, in: query, schema: {type: string, maxLength: 50}}
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
/api/platform/v1/fba-pricing/carrier-rates/versions:
post:
summary: 新建船司 FBA 价格版本
parameters: [{$ref: '#/components/parameters/TenantId'}]
summary: 新建平台公共船司 FBA 价格版本
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/CarrierRateVersionRequest'}}}}
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
/api/platform/v1/fba-pricing/carrier-rates/{id}:
get:
summary: 查询船司 FBA 价格版本详情
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/Id'}]
parameters: [{$ref: '#/components/parameters/Id'}]
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
delete:
summary: 停用船司 FBA 价格版本
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/Id'}]
parameters: [{$ref: '#/components/parameters/Id'}]
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
/api/platform/v1/inbounds/{inboundId}/fees/calculate:
post:
@@ -13,20 +13,26 @@ import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("FBA计费OpenAPI契约")
class FbaPricingContractTest {
@Test
@DisplayName("只有基础资料列表允许省略企业参数")
void onlyMasterDataListsAllowOptionalTenant() {
for (String listMethod : new String[] {"rules", "rates"}) {
Method method = method(listMethod);
assertThat(method.getParameterTypes()[1]).isEqualTo(Long.class);
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
}
@DisplayName("客户规则保留客户参数,船司价格使用平台公共作用域")
void keepsCustomerRuleScopeAndRemovesCarrierRateTenantScope() {
Method rules = method("rules");
assertThat(rules.getParameterTypes()[1]).isEqualTo(Long.class);
assertThat(rules.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule",
"rate", "createRate", "retireRate"}) {
Method rates = method("rates");
assertThat(rates.getParameterTypes()).doesNotContain(Long.class);
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule"}) {
Method method = method(scopedMethod);
assertThat(method.getParameterTypes()[1]).isEqualTo(long.class);
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue();
}
for (String publicMethod : new String[] {"rate", "createRate", "retireRate"}) {
assertThat(java.util.Arrays.stream(method(publicMethod).getParameters())
.map(parameter -> parameter.getAnnotation(RequestParam.class)))
.allMatch(annotation -> annotation == null);
}
}
@Test
@@ -62,7 +68,7 @@ class FbaPricingContractTest {
openapi.indexOf("/api/platform/v1/fba-pricing/carrier-rates:"),
openapi.indexOf("/api/platform/v1/fba-pricing/carrier-rates/versions:"));
assertThat(carrierRates)
.contains("OptionalTenantId")
.doesNotContain("OptionalTenantId")
.contains("name: rateCode");
assertThat(openapi)
.contains("OptionalTenantId: {name: tenantId, in: query, required: false")
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
import com.baiyue.firstleg.entity.InboundPricingEntity;
import com.baiyue.firstleg.mapper.CarrierFbaRateMapper;
import com.baiyue.firstleg.mapper.CustomerChargeRuleMapper;
import com.baiyue.firstleg.mapper.InboundFeeItemMapper;
@@ -48,6 +49,8 @@ class FbaPricingRepositoryRuleListTest {
CustomerChargeRuleEntity.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
CarrierFbaRateEntity.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
InboundPricingEntity.class);
}
@Test
@@ -61,14 +64,52 @@ class FbaPricingRepositoryRuleListTest {
}
@Test
void platformRateListOmitsTenantConditionWhenTenantIsAbsent() {
assertThat(rateQueryValues(null, null)).isEmpty();
void platformRateListAlwaysUsesPublicScope() {
assertThat(rateQueryValues(null)).containsExactly(CarrierFbaRateEntity.PLATFORM_SCOPE);
}
@Test
void platformRateListKeepsTenantAndRateCodeFiltersWhenProvided() {
assertThat(rateQueryValues(10001L, "RATE-1"))
.containsExactlyInAnyOrder(10001L, "RATE-1");
void platformRateListKeepsPublicScopeAndRateCodeFiltersWhenProvided() {
assertThat(rateQueryValues("RATE-1"))
.containsExactlyInAnyOrder(CarrierFbaRateEntity.PLATFORM_SCOPE, "RATE-1");
}
@Test
void platformRateCreateQueryLocksPublicScope() {
when(rates.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
repository.listRatesForUpdate(null);
@SuppressWarnings("unchecked")
ArgumentCaptor<LambdaQueryWrapper<CarrierFbaRateEntity>> wrapper =
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(rates).selectList(wrapper.capture());
assertThat(wrapper.getValue().getCustomSqlSegment()).contains("FOR UPDATE");
assertThat(wrapper.getValue().getParamNameValuePairs().values())
.containsExactly(CarrierFbaRateEntity.PLATFORM_SCOPE);
}
@Test
void carrierRateMatchingAlwaysUsesPlatformScope() {
InboundPricingEntity inbound = new InboundPricingEntity();
inbound.setTenantId(10001L);
inbound.setWarehouseId(21L);
inbound.setBusinessType("FBA_SEA");
inbound.setChannelCode("SEA-TAX");
inbound.setCarrierId(31L);
inbound.setDestinationCode("ONT8");
when(rates.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
repository.matchRates(inbound, "KG", java.time.LocalDateTime.of(2026, 8, 1, 0, 0));
@SuppressWarnings("unchecked")
ArgumentCaptor<LambdaQueryWrapper<CarrierFbaRateEntity>> wrapper =
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
verify(rates).selectList(wrapper.capture());
wrapper.getValue().getSqlSegment();
assertThat(wrapper.getValue().getParamNameValuePairs().values())
.contains(CarrierFbaRateEntity.PLATFORM_SCOPE)
.doesNotContain(10001L);
}
@Test
@@ -122,10 +163,10 @@ class FbaPricingRepositoryRuleListTest {
return wrapper.getValue().getParamNameValuePairs().values();
}
private Collection<Object> rateQueryValues(Long tenantId, String rateCode) {
private Collection<Object> rateQueryValues(String rateCode) {
when(rates.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
repository.listRates(tenantId, rateCode);
repository.listRates(rateCode);
@SuppressWarnings("unchecked")
ArgumentCaptor<LambdaQueryWrapper<CarrierFbaRateEntity>> wrapper =
@@ -38,4 +38,18 @@ class InboundWorkflowSqlContractTest {
.contains("SET warehouse_resource_tenant_id = 0")
.contains("MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL");
}
@Test
@DisplayName("船司价格存量迁移固定到平台作用域并拒绝冲突")
void shouldMigrateCarrierRatesToPublicScopeWithoutOverwritingConflicts() throws Exception {
String migration = new ClassPathResource("db/phase2-fba-pricing-migration.sql")
.getContentAsString(StandardCharsets.UTF_8);
assertThat(migration)
.contains("phase2_fba_migrate_carrier_rate_scope")
.contains("GROUP BY rate_code, rate_version")
.contains("公共匹配维度存在有效期重叠")
.contains("SET tenant_id = 0")
.contains("WHERE tenant_id <> 0");
}
}
@@ -2,6 +2,8 @@ package com.baiyue.firstleg.service;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.FbaPricingDtos.CustomerRuleVersionRequest;
import com.baiyue.firstleg.dto.FbaPricingDtos.CarrierRateVersionRequest;
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
import com.baiyue.firstleg.repository.FbaPricingRepository;
import jakarta.annotation.Resource;
@@ -73,11 +75,33 @@ class FbaMasterDataServiceTest {
.hasMessageContaining("有效期重叠");
}
@Test
@DisplayName("船司价格版本固定写入平台公共作用域")
void createsCarrierRateInPlatformScope() {
when(repository.listRatesForUpdate(null)).thenReturn(List.of());
when(repository.nextRateVersion("RATE-1")).thenReturn(1);
doAnswer(invocation -> { ((CarrierFbaRateEntity) invocation.getArgument(0)).setId(20L); return null; })
.when(repository).insertRate(any());
CarrierFbaRateEntity created = service.createRateVersion(2L, carrierRateRequest());
assertThat(created.getTenantId()).isEqualTo(CarrierFbaRateEntity.PLATFORM_SCOPE);
verify(repository).listRatesForUpdate(null);
verify(repository).nextRateVersion("RATE-1");
verify(repository).insertRate(any());
}
private CustomerRuleVersionRequest request(LocalDateTime from, LocalDateTime to) {
return new CustomerRuleVersionRequest("CR-1", 100L, "客户A", "FBA_SEA", "KG",
"TAX_INCLUDED", new BigDecimal("167"), 3, "CEILING", "USD", from, to, null);
}
private CarrierRateVersionRequest carrierRateRequest() {
return new CarrierRateVersionRequest("RATE-1", 21L, "义乌仓", "FBA_SEA", "SEA-TAX",
31L, "船司A", "ONT8", "KG", new BigDecimal("2.50"), "USD", new BigDecimal("10"),
AUGUST, null, null);
}
private CustomerChargeRuleEntity activeRule(LocalDateTime from, LocalDateTime to) {
CustomerChargeRuleEntity value = new CustomerChargeRuleEntity();
value.setRuleCode("CR-1"); value.setRuleVersion(1); value.setCustomerId(100L);