优化
This commit is contained in:
@@ -863,6 +863,19 @@ CALL phase2_fba_add_column('fl_inbound_fee', 'destination_code', "VARCHAR(32) NO
|
||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_from', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '价格生效开始快照' AFTER destination_code");
|
||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_to', "DATETIME(3) NULL COMMENT '价格生效结束快照' AFTER rate_effective_from");
|
||||
CALL phase2_fba_add_column('fl_inbound_idempotency', 'result_resource_id', "BIGINT UNSIGNED NULL COMMENT '成功结果资源ID' AFTER result_json");
|
||||
CALL phase2_fba_add_column('fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '仓储资源所属租户ID' AFTER warehouse_id");
|
||||
|
||||
UPDATE fl_inbound_pallet_allocation
|
||||
SET warehouse_resource_tenant_id = tenant_id
|
||||
WHERE warehouse_resource_tenant_id IS NULL OR warehouse_resource_tenant_id = 0;
|
||||
SET @resource_tenant_nullable = (SELECT IS_NULLABLE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'fl_inbound_pallet_allocation'
|
||||
AND COLUMN_NAME = 'warehouse_resource_tenant_id');
|
||||
SET @sql = IF(@resource_tenant_nullable = 'YES',
|
||||
"ALTER TABLE fl_inbound_pallet_allocation MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储资源所属租户ID'",
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
DROP PROCEDURE IF EXISTS phase2_fba_add_column;
|
||||
|
||||
|
||||
@@ -858,6 +858,19 @@ CALL phase2_fba_add_column('fl_inbound_fee', 'destination_code', "VARCHAR(32) NO
|
||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_from', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '价格生效开始快照' AFTER destination_code");
|
||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_to', "DATETIME(3) NULL COMMENT '价格生效结束快照' AFTER rate_effective_from");
|
||||
CALL phase2_fba_add_column('fl_inbound_idempotency', 'result_resource_id', "BIGINT UNSIGNED NULL COMMENT '成功结果资源ID' AFTER result_json");
|
||||
CALL phase2_fba_add_column('fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '仓储资源所属租户ID' AFTER warehouse_id");
|
||||
|
||||
UPDATE fl_inbound_pallet_allocation
|
||||
SET warehouse_resource_tenant_id = tenant_id
|
||||
WHERE warehouse_resource_tenant_id IS NULL OR warehouse_resource_tenant_id = 0;
|
||||
SET @resource_tenant_nullable = (SELECT IS_NULLABLE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'fl_inbound_pallet_allocation'
|
||||
AND COLUMN_NAME = 'warehouse_resource_tenant_id');
|
||||
SET @sql = IF(@resource_tenant_nullable = 'YES',
|
||||
"ALTER TABLE fl_inbound_pallet_allocation MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储资源所属租户ID'",
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
DROP PROCEDURE IF EXISTS phase2_fba_add_column;
|
||||
|
||||
|
||||
+4
-2
@@ -76,10 +76,12 @@ public class PlatformFbaMasterDataController {
|
||||
|
||||
@GetMapping("/carrier-rates")
|
||||
@PreAuthorize("hasAuthority('firstleg:fba-pricing:rate:view')")
|
||||
@Operation(summary = "查询船司 FBA 价格版本", description = "默认查询全部企业;tenantId 可用于筛选目标企业")
|
||||
public ApiResponse<List<CarrierFbaRateEntity>> rates(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam long tenantId,
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) Long tenantId,
|
||||
@RequestParam(required = false) String rateCode) {
|
||||
return ApiResponse.success(service.listRates(FirstlegScopeGuard.platform(user, tenantId), rateCode));
|
||||
return ApiResponse.success(service.listRates(optionalPlatformTenant(user, tenantId), rateCode));
|
||||
}
|
||||
|
||||
@GetMapping("/carrier-rates/{id}")
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.springframework.stereotype.Repository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/** FBA 计费仓储;平台客户规则列表可跨企业读取,其他业务操作强制带租户条件。 */
|
||||
/** FBA 计费仓储;平台基础资料列表可跨企业读取,其他业务操作强制带租户条件。 */
|
||||
@Repository
|
||||
public class FbaPricingRepository {
|
||||
private final CustomerChargeRuleMapper rules;
|
||||
@@ -69,9 +69,9 @@ public class FbaPricingRepository {
|
||||
.orderByDesc(CustomerChargeRuleEntity::getRuleVersion));
|
||||
}
|
||||
|
||||
public List<CarrierFbaRateEntity> listRates(long tenantId, String rateCode) {
|
||||
public List<CarrierFbaRateEntity> listRates(Long tenantId, String rateCode) {
|
||||
return rates.selectList(new LambdaQueryWrapper<CarrierFbaRateEntity>()
|
||||
.eq(CarrierFbaRateEntity::getTenantId, tenantId)
|
||||
.eq(tenantId != null, CarrierFbaRateEntity::getTenantId, tenantId)
|
||||
.eq(rateCode != null && !rateCode.isBlank(), CarrierFbaRateEntity::getRateCode, rateCode)
|
||||
.orderByAsc(CarrierFbaRateEntity::getRateCode)
|
||||
.orderByDesc(CarrierFbaRateEntity::getRateVersion));
|
||||
|
||||
@@ -102,7 +102,7 @@ public class FbaMasterDataService {
|
||||
return repository.listRules(tenantId, code);
|
||||
}
|
||||
|
||||
public List<CarrierFbaRateEntity> listRates(long tenantId, String code) {
|
||||
public List<CarrierFbaRateEntity> listRates(Long tenantId, String code) {
|
||||
return repository.listRates(tenantId, code);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,20 @@ CALL phase2_fba_add_column('fl_inbound_fee', 'destination_code', "VARCHAR(32) NO
|
||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_from', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '价格生效开始快照' AFTER destination_code");
|
||||
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_to', "DATETIME(3) NULL COMMENT '价格生效结束快照' AFTER rate_effective_from");
|
||||
CALL phase2_fba_add_column('fl_inbound_idempotency', 'result_resource_id', "BIGINT UNSIGNED NULL COMMENT '成功结果资源ID' AFTER result_json");
|
||||
CALL phase2_fba_add_column('fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '仓储资源所属租户ID' AFTER warehouse_id");
|
||||
|
||||
-- 旧分配记录创建于共享仓储资源租户拆分前,按原业务租户回填后再收口非空约束。
|
||||
UPDATE fl_inbound_pallet_allocation
|
||||
SET warehouse_resource_tenant_id = tenant_id
|
||||
WHERE warehouse_resource_tenant_id IS NULL OR warehouse_resource_tenant_id = 0;
|
||||
SET @resource_tenant_nullable = (SELECT IS_NULLABLE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'fl_inbound_pallet_allocation'
|
||||
AND COLUMN_NAME = 'warehouse_resource_tenant_id');
|
||||
SET @sql = IF(@resource_tenant_nullable = 'YES',
|
||||
"ALTER TABLE fl_inbound_pallet_allocation MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储资源所属租户ID'",
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
DROP PROCEDURE IF EXISTS phase2_fba_add_column;
|
||||
|
||||
|
||||
@@ -34,7 +34,9 @@ paths:
|
||||
get:
|
||||
summary: 查询船司 FBA 价格版本
|
||||
description: 需要 firstleg:fba-pricing:rate:view 权限
|
||||
parameters: [{$ref: '#/components/parameters/TenantId'}]
|
||||
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:
|
||||
|
||||
@@ -13,13 +13,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@DisplayName("FBA计费OpenAPI契约")
|
||||
class FbaPricingContractTest {
|
||||
@Test
|
||||
@DisplayName("只有客户规则列表允许省略企业参数")
|
||||
void onlyCustomerRuleListAllowsOptionalTenant() {
|
||||
Method rules = method("rules");
|
||||
assertThat(rules.getParameterTypes()[1]).isEqualTo(Long.class);
|
||||
assertThat(rules.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
|
||||
@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();
|
||||
}
|
||||
|
||||
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule", "rates",
|
||||
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule",
|
||||
"rate", "createRate", "retireRate"}) {
|
||||
Method method = method(scopedMethod);
|
||||
assertThat(method.getParameterTypes()[1]).isEqualTo(long.class);
|
||||
@@ -56,6 +58,12 @@ class FbaPricingContractTest {
|
||||
.doesNotContain("name: customerName")
|
||||
.doesNotContain("name: chargeMode")
|
||||
.doesNotContain("name: status");
|
||||
String carrierRates = openapi.substring(
|
||||
openapi.indexOf("/api/platform/v1/fba-pricing/carrier-rates:"),
|
||||
openapi.indexOf("/api/platform/v1/fba-pricing/carrier-rates/versions:"));
|
||||
assertThat(carrierRates)
|
||||
.contains("OptionalTenantId")
|
||||
.contains("name: rateCode");
|
||||
assertThat(openapi)
|
||||
.contains("OptionalTenantId: {name: tenantId, in: query, required: false")
|
||||
.contains("schema: {type: string, pattern: '^[1-9][0-9]*$'}")
|
||||
|
||||
+35
-1
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.repository;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
|
||||
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
|
||||
import com.baiyue.firstleg.mapper.CarrierFbaRateMapper;
|
||||
import com.baiyue.firstleg.mapper.CustomerChargeRuleMapper;
|
||||
import com.baiyue.firstleg.mapper.InboundFeeItemMapper;
|
||||
@@ -29,6 +30,8 @@ class FbaPricingRepositoryRuleListTest {
|
||||
static void initializeEntityMetadata() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
|
||||
CustomerChargeRuleEntity.class);
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"),
|
||||
CarrierFbaRateEntity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -41,6 +44,17 @@ class FbaPricingRepositoryRuleListTest {
|
||||
assertThat(queryValues(10001L, "CR-1")).containsExactlyInAnyOrder(10001L, "CR-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformRateListOmitsTenantConditionWhenTenantIsAbsent() {
|
||||
assertThat(rateQueryValues(null, null)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformRateListKeepsTenantAndRateCodeFiltersWhenProvided() {
|
||||
assertThat(rateQueryValues(10001L, "RATE-1"))
|
||||
.containsExactlyInAnyOrder(10001L, "RATE-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalVersionCalculationKeepsTenantAndRuleCodeFilters() {
|
||||
CustomerChargeRuleMapper rules = mock(CustomerChargeRuleMapper.class);
|
||||
@@ -76,8 +90,28 @@ class FbaPricingRepositoryRuleListTest {
|
||||
return wrapper.getValue().getParamNameValuePairs().values();
|
||||
}
|
||||
|
||||
private static Collection<Object> rateQueryValues(Long tenantId, String rateCode) {
|
||||
CarrierFbaRateMapper rates = mock(CarrierFbaRateMapper.class);
|
||||
when(rates.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of());
|
||||
FbaPricingRepository repository = repository(mock(CustomerChargeRuleMapper.class), rates);
|
||||
|
||||
repository.listRates(tenantId, rateCode);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<LambdaQueryWrapper<CarrierFbaRateEntity>> wrapper =
|
||||
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(rates).selectList(wrapper.capture());
|
||||
wrapper.getValue().getSqlSegment();
|
||||
return wrapper.getValue().getParamNameValuePairs().values();
|
||||
}
|
||||
|
||||
private static FbaPricingRepository repository(CustomerChargeRuleMapper rules) {
|
||||
return new FbaPricingRepository(rules, mock(CarrierFbaRateMapper.class),
|
||||
return repository(rules, mock(CarrierFbaRateMapper.class));
|
||||
}
|
||||
|
||||
private static FbaPricingRepository repository(
|
||||
CustomerChargeRuleMapper rules, CarrierFbaRateMapper rates) {
|
||||
return new FbaPricingRepository(rules, rates,
|
||||
mock(InboundPricingMapper.class), mock(InboundFeeMapper.class), mock(InboundFeeItemMapper.class),
|
||||
mock(InboundPricingIdempotencyMapper.class), mock(InboundOutboxMapper.class));
|
||||
}
|
||||
|
||||
+12
@@ -26,4 +26,16 @@ class InboundWorkflowSqlContractTest {
|
||||
.contains("information_schema.statistics")
|
||||
.contains("idx_review_exception_release");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("存量托盘分配表会补齐仓储资源租户")
|
||||
void shouldMigrateWarehouseResourceTenantForExistingAllocationTable() throws Exception {
|
||||
String migration = new ClassPathResource("db/phase2-fba-pricing-migration.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(migration)
|
||||
.contains("phase2_fba_add_column('fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id'")
|
||||
.contains("SET warehouse_resource_tenant_id = tenant_id")
|
||||
.contains("MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user