This commit is contained in:
2026-08-07 17:46:23 +08:00
parent 98ac9840c2
commit 645c483649
22 changed files with 370 additions and 29 deletions
@@ -0,0 +1,21 @@
package com.baiyue.firstleg.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@TableName("fl_price_warehouse_mapping")
public class PriceWarehouseMappingEntity {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String sourceType;
private String sourceValue;
private Long warehouseId;
private String warehouseCode;
private String warehouseType;
private String status;
}
@@ -0,0 +1,6 @@
package com.baiyue.firstleg.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baiyue.firstleg.entity.PriceWarehouseMappingEntity;
public interface PriceWarehouseMappingMapper extends BaseMapper<PriceWarehouseMappingEntity> {}
@@ -7,11 +7,13 @@ import com.baiyue.firstleg.entity.PriceBookChannelEntity;
import com.baiyue.firstleg.entity.PriceBookVersionEntity;
import com.baiyue.firstleg.entity.PriceChannelCatalogEntity;
import com.baiyue.firstleg.entity.PriceOriginAliasEntity;
import com.baiyue.firstleg.entity.PriceWarehouseMappingEntity;
import com.baiyue.firstleg.mapper.CarrierFbaRateMapper;
import com.baiyue.firstleg.mapper.PriceBookChannelMapper;
import com.baiyue.firstleg.mapper.PriceBookVersionMapper;
import com.baiyue.firstleg.mapper.PriceChannelCatalogMapper;
import com.baiyue.firstleg.mapper.PriceOriginAliasMapper;
import com.baiyue.firstleg.mapper.PriceWarehouseMappingMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Repository;
@@ -25,6 +27,7 @@ public class PriceBookRepository {
@Resource private CarrierFbaRateMapper rates;
@Resource private PriceChannelCatalogMapper catalog;
@Resource private PriceOriginAliasMapper aliases;
@Resource private PriceWarehouseMappingMapper warehouseMappings;
public int insertVersion(PriceBookVersionEntity value) { return versions.insert(value); }
public int updateVersion(PriceBookVersionEntity value) {
@@ -109,6 +112,11 @@ public class PriceBookRepository {
return aliases.selectList(new LambdaQueryWrapper<PriceOriginAliasEntity>()
.eq(PriceOriginAliasEntity::getStatus, "ACTIVE"));
}
public List<PriceWarehouseMappingEntity> activeWarehouseMappings() {
return warehouseMappings.selectList(new LambdaQueryWrapper<PriceWarehouseMappingEntity>()
.eq(PriceWarehouseMappingEntity::getStatus, "ACTIVE")
.orderByAsc(PriceWarehouseMappingEntity::getId));
}
public void supersede(PriceBookVersionEntity old, LocalDateTime at, long operatorId) {
old.setStatus("SUPERSEDED"); old.setSupersededAt(at); old.setUpdatedBy(operatorId); old.setUpdatedAt(at);
updateVersion(old);
@@ -16,6 +16,7 @@ import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
import com.baiyue.firstleg.entity.PriceBookChannelEntity;
import com.baiyue.firstleg.entity.PriceBookVersionEntity;
import com.baiyue.firstleg.entity.PriceChannelCatalogEntity;
import com.baiyue.firstleg.entity.PriceWarehouseMappingEntity;
import com.baiyue.firstleg.pricing.importer.PriceWorkbookParseResult;
import com.baiyue.firstleg.pricing.importer.PriceWorkbookParser;
import com.baiyue.firstleg.repository.PriceBookRepository;
@@ -262,7 +263,37 @@ public class PlatformPriceBookService {
List<PriceBookDtos.ParseIssue> issues = parsed.issues().stream().map(value -> issue(
value.severity() == PriceWorkbookParseResult.Severity.ERROR ? "BLOCKING" : "WARNING",
value.code(), value.message(), value.sheetName(), value.rowNumber(), null)).toList();
return new ParsedPriceBook(channels, rates, issues);
return new ParsedPriceBook(channels, applyInitializedWarehouseMappings(rates), issues);
}
private List<PriceBookDtos.ParsedRate> applyInitializedWarehouseMappings(List<PriceBookDtos.ParsedRate> rates) {
Map<String, PriceWarehouseMappingEntity> origins = new HashMap<>();
Map<String, PriceWarehouseMappingEntity> overseas = new HashMap<>();
for (PriceWarehouseMappingEntity mapping : repository.activeWarehouseMappings()) {
if (mapping == null || mapping.getWarehouseId() == null || mapping.getWarehouseId() <= 0) continue;
if ("ORIGIN_CODE".equals(mapping.getSourceType())
&& "DOMESTIC".equals(mapping.getWarehouseType())
&& mappingKey(mapping.getSourceValue()).equals(mappingKey(mapping.getWarehouseCode()))) {
origins.putIfAbsent(mappingKey(mapping.getSourceValue()), mapping);
} else if ("OVERSEAS_NAME".equals(mapping.getSourceType())
&& "OVERSEAS".equals(mapping.getWarehouseType())
&& StringUtils.hasText(mapping.getWarehouseCode())) {
overseas.putIfAbsent(mappingKey(mapping.getSourceValue()), mapping);
}
}
return rates.stream().map(rate -> {
PriceWarehouseMappingEntity origin = origins.get(mappingKey(rate.originWarehouseCode()));
PriceWarehouseMappingEntity destination = rate.destinationType() == PricingDestinationType.OVERSEAS
? overseas.get(mappingKey(rate.destinationName())) : null;
return new PriceBookDtos.ParsedRate(rate.id(), rate.channelCode(), rate.priceModel(),
origin == null ? rate.originWarehouseId() : origin.getWarehouseId(),
rate.originWarehouseCode(), rate.originWarehouseName(),
destination == null ? rate.destinationCode() : mappingKey(destination.getWarehouseCode()), rate.destinationType(),
destination == null ? rate.destinationWarehouseId() : destination.getWarehouseId(),
rate.destinationName(), rate.regionCode(), rate.pricingTaxMode(), rate.chargeUnit(), rate.weightFromKg(),
rate.unitPrice(), rate.currency(), rate.sourceSheetName(), rate.sourceRowNumber(),
rate.sourceDestinationText(), rate.remark());
}).toList();
}
private PriceBookDtos.ParsedRate parsedRate(String channelCode, PriceModel model,
@@ -411,6 +411,21 @@ CREATE TABLE IF NOT EXISTS fl_price_origin_alias (
KEY idx_price_origin_alias_name (alias_name, status)
) ENGINE=InnoDB COMMENT='价格表起运仓名称别名映射';
CREATE TABLE IF NOT EXISTS fl_price_warehouse_mapping (
id BIGINT UNSIGNED NOT NULL COMMENT '价格仓映射ID',
source_type VARCHAR(24) NOT NULL COMMENT '来源维度:ORIGIN_CODE或OVERSEAS_NAME',
source_value VARCHAR(128) NOT NULL COMMENT '解析后的来源仓编码或海外仓名称',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '固定平台公共仓库ID',
warehouse_code VARCHAR(64) NOT NULL COMMENT '平台仓库稳定编码快照',
warehouse_type VARCHAR(16) NOT NULL COMMENT 'DOMESTIC或OVERSEAS',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_price_warehouse_mapping_source (source_type, source_value),
KEY idx_price_warehouse_mapping_target (warehouse_id, status)
) ENGINE=InnoDB COMMENT='价格表来源仓与固定平台公共仓映射';
CREATE TABLE IF NOT EXISTS fl_price_book_version (
id BIGINT UNSIGNED NOT NULL COMMENT '整表价格版本ID',
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共价格作用域固定为0',
@@ -494,9 +509,21 @@ VALUES
(3, 'CN-YIWU-01', '义乌', 'ACTIVE'),
(4, 'CN-DONGGUAN-01', '华南仓(深圳)', 'ACTIVE'),
(5, 'CN-DONGGUAN-01', '东莞仓', 'ACTIVE'),
(6, 'CN-DONGGUAN-01', '深圳仓', 'ACTIVE')
(6, 'CN-DONGGUAN-01', '深圳仓', 'ACTIVE'),
(7, 'CN-DONGGUAN-01', '华南仓(深圳/东莞)', 'ACTIVE')
ON DUPLICATE KEY UPDATE status = VALUES(status);
INSERT INTO fl_price_warehouse_mapping
(id, source_type, source_value, warehouse_id, warehouse_code, warehouse_type, status)
VALUES
(1, 'ORIGIN_CODE', 'CN-YIWU-01', 193000000000200001, 'CN-YIWU-01', 'DOMESTIC', 'ACTIVE'),
(2, 'ORIGIN_CODE', 'CN-DONGGUAN-01', 193000000000200002, 'CN-DONGGUAN-01', 'DOMESTIC', 'ACTIVE'),
(3, 'OVERSEAS_NAME', '洛杉矶仓', 193000000000200003, 'US-LAX-01', 'OVERSEAS', 'ACTIVE'),
(4, 'OVERSEAS_NAME', '纽约仓', 193000000000200004, 'US-NYC-01', 'OVERSEAS', 'ACTIVE')
ON DUPLICATE KEY UPDATE
warehouse_id = VALUES(warehouse_id), warehouse_code = VALUES(warehouse_code),
warehouse_type = VALUES(warehouse_type), status = VALUES(status);
CREATE TABLE IF NOT EXISTS fl_inbound_fee (
id BIGINT UNSIGNED NOT NULL COMMENT '入库费用ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
@@ -28,6 +28,8 @@ class PriceBookSqlContractTest {
assertThat(schema)
.contains("CREATE TABLE IF NOT EXISTS fl_price_channel_catalog")
.contains("CREATE TABLE IF NOT EXISTS fl_price_origin_alias")
.contains("CREATE TABLE IF NOT EXISTS fl_price_warehouse_mapping")
.contains("华南仓(深圳/东莞)")
.contains("CREATE TABLE IF NOT EXISTS fl_price_book_version")
.contains("CREATE TABLE IF NOT EXISTS fl_price_book_channel")
.contains("price_book_version_id")
@@ -40,6 +42,11 @@ class PriceBookSqlContractTest {
.contains("destination_name VARCHAR(128)")
.contains("parse_issues_json JSON NULL")
.contains("DEFAULT 'PENDING_CONFIRMATION'");
assertThat(schema)
.contains("'ORIGIN_CODE', 'CN-YIWU-01', 193000000000200001")
.contains("'ORIGIN_CODE', 'CN-DONGGUAN-01', 193000000000200002")
.contains("'OVERSEAS_NAME', '洛杉矶仓', 193000000000200003")
.contains("'OVERSEAS_NAME', '纽约仓', 193000000000200004");
assertThat(channelTable)
.contains("created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID'")
.contains("updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID'")
@@ -14,6 +14,7 @@ import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
import com.baiyue.firstleg.entity.PriceBookChannelEntity;
import com.baiyue.firstleg.entity.PriceBookVersionEntity;
import com.baiyue.firstleg.entity.PriceChannelCatalogEntity;
import com.baiyue.firstleg.entity.PriceWarehouseMappingEntity;
import com.baiyue.firstleg.repository.PriceBookRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
@@ -230,6 +231,29 @@ class PlatformPriceBookServiceTest {
.doesNotContain("DUPLICATE_RATE");
}
@Test
void initializedWarehouseMappingsBindFixedDomesticAndOverseasWarehouses() {
CarrierFbaRateEntity overseasRate = rate(81L);
overseasRate.setDestinationType(PricingDestinationType.OVERSEAS.name());
overseasRate.setDestinationCode(null);
overseasRate.setDestinationName("洛杉矶仓");
PriceBookDtos.ParsedRate parsed = ReflectionTestUtils.invokeMethod(service, "rateDto", overseasRate);
when(repository.activeWarehouseMappings()).thenReturn(List.of(
warehouseMapping("ORIGIN_CODE", "CN-YIWU-01", 193000000000200001L, "CN-YIWU-01", "DOMESTIC"),
warehouseMapping("OVERSEAS_NAME", "洛杉矶仓", 193000000000200003L, "US-LAX-01", "OVERSEAS")
));
@SuppressWarnings("unchecked")
List<PriceBookDtos.ParsedRate> mapped = ReflectionTestUtils.invokeMethod(
service, "applyInitializedWarehouseMappings", List.of(parsed));
assertThat(mapped).singleElement().satisfies(rate -> {
assertThat(rate.originWarehouseId()).isEqualTo(193000000000200001L);
assertThat(rate.destinationWarehouseId()).isEqualTo(193000000000200003L);
assertThat(rate.destinationCode()).isEqualTo("US-LAX-01");
});
}
@Test
void duplicatePublishIsRejectedAfterPublishLock() {
when(repository.findVersion(31L)).thenReturn(version(31L, PriceBookStatus.ACTIVE));
@@ -300,6 +324,18 @@ class PlatformPriceBookServiceTest {
List.of(new PriceBookConfirmationRequest.OriginMapping(code, id)), List.of());
}
private static PriceWarehouseMappingEntity warehouseMapping(String sourceType, String sourceValue, long warehouseId,
String warehouseCode, String warehouseType) {
PriceWarehouseMappingEntity value = new PriceWarehouseMappingEntity();
value.setSourceType(sourceType);
value.setSourceValue(sourceValue);
value.setWarehouseId(warehouseId);
value.setWarehouseCode(warehouseCode);
value.setWarehouseType(warehouseType);
value.setStatus("ACTIVE");
return value;
}
private void executeTransactionsImmediately() {
when(transactions.execute(any())).thenAnswer(invocation -> {
@SuppressWarnings("unchecked")