This commit is contained in:
2026-08-07 17:36:02 +08:00
parent 91f31d9567
commit 98ac9840c2
10 changed files with 172 additions and 5 deletions
@@ -395,8 +395,12 @@ public class PlatformPriceBookService {
issues.add(issue("BLOCKING", "SOURCE_LOCATION_MISSING", "价格行缺少Excel来源位置", rate.sourceSheetName(), rate.sourceRowNumber(), null));
validateOrigin(rate, warehouseCache, issues);
validateDestination(rate, warehouseCache, issues);
String key = String.join("|", code, text(rate.originWarehouseId()), enumText(rate.destinationType()),
text(rate.destinationWarehouseId()), nullToEmpty(upper(rate.destinationCode())),
String originIdentity = rate.originWarehouseId() == null
? "CODE:" + mappingKey(rate.originWarehouseCode()) : "ID:" + rate.originWarehouseId();
String destinationIdentity = rate.destinationWarehouseId() == null
? "NAME:" + mappingKey(rate.destinationName()) : "ID:" + rate.destinationWarehouseId();
String key = String.join("|", code, originIdentity, enumText(rate.destinationType()),
destinationIdentity, nullToEmpty(upper(rate.destinationCode())),
rate.regionCode() == null ? "" : rate.regionCode().name(), rate.pricingTaxMode() == null ? "" : rate.pricingTaxMode().name(),
rate.chargeUnit(), text(rate.weightFromKg()));
if (!unique.add(key)) issues.add(issue("BLOCKING", "DUPLICATE_RATE", "存在重复价格组合", rate.sourceSheetName(), rate.sourceRowNumber(), null));
@@ -368,7 +368,7 @@ CREATE TABLE IF NOT EXISTS fl_carrier_fba_rate (
source_destination_text VARCHAR(1000) NULL COMMENT '原始组合仓库或区域文本',
effective_from DATETIME(3) NOT NULL COMMENT '生效开始时间',
effective_to DATETIME(3) NULL COMMENT '生效结束时间',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '价格状态',
status VARCHAR(24) NOT NULL DEFAULT 'ACTIVE' COMMENT '价格状态:PENDING_CONFIRMATION、ACTIVE或SUPERSEDED',
remark VARCHAR(500) NULL COMMENT '价格备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
@@ -163,6 +163,35 @@ paths:
responses:
'200': {description: 成功或业务失败,按响应体 code 判断}
'403': {description: 无查看权限}
/api/platform/inbounds/{inboundId}/carton-label-pdf:
post:
summary: 生成或补打入仓箱唛 PDF
description: 需要 platform:firstleg:inbound:view 权限;首次打印冻结整单箱号,重复打印和部分补打复用原映射。
parameters:
- {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- name: cartonIndex
in: query
required: false
style: form
explode: true
schema: {type: array, items: {type: integer, minimum: 1}}
description: 不传时打印整单;可重复传入以部分补打指定箱下标。
responses:
'200':
description: 成功返回 PDF;业务失败返回统一 JSON 响应,调用方必须按 Content-Type 区分。
content:
application/pdf: {schema: {type: string, format: binary}}
application/json:
schema:
type: object
required: [code, message]
properties:
code: {type: string}
message: {type: string}
requestId: {type: string, nullable: true}
traceId: {type: string, nullable: true}
'403': {description: 无查看权限}
/api/platform/v1/inbounds/{inboundId}/measurements/approve:
post:
summary: 审核通过测量
@@ -44,6 +44,7 @@ class InboundOrderControllerContractTest {
assertThat(openapi)
.contains("/api/platform/inbounds:")
.contains("/api/platform/inbounds/{inboundId}:")
.contains("/api/platform/inbounds/{inboundId}/carton-label-pdf:")
.contains("required: false")
.contains("InboundOrderSummary:")
.contains("required: [id, tenantId]")
@@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
@@ -12,6 +14,7 @@ class InboundCartonLabelSqlContractTest {
void baseSchemaAndExistingDatabaseMigrationKeepStableCartonKeys() throws Exception {
String schema = resource("db/phase2-inbound-schema.sql");
String migration = resource("db/phase2-inbound-carton-label.sql");
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
assertThat(schema).contains("CREATE TABLE IF NOT EXISTS fl_inbound_carton_label")
.contains("UNIQUE KEY uk_carton_label_box_code (tenant_id, box_code)")
@@ -20,6 +23,10 @@ class InboundCartonLabelSqlContractTest {
assertThat(migration).contains("CREATE TABLE IF NOT EXISTS fl_inbound_carton_label")
.contains("固定二维码内容:入仓码#总箱数-箱下标")
.contains("idx_carton_label_detail (tenant_id, inbound_id, inbound_detail_id)");
assertThat(unified).contains("CREATE TABLE IF NOT EXISTS fl_inbound_carton_label")
.contains("uk_carton_label_box_code")
.contains("uk_carton_label_index")
.contains("稳定箱号映射表未创建");
}
private String resource(String path) throws Exception {
@@ -5,6 +5,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
@@ -46,6 +48,7 @@ class InboundWorkflowSqlContractTest {
.getContentAsString(StandardCharsets.UTF_8);
String migration = new ClassPathResource("db/phase2-carton-measurement-current-record.sql")
.getContentAsString(StandardCharsets.UTF_8);
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
assertThat(schema)
.contains("box_code VARCHAR(128)")
.contains("machine_no VARCHAR(64)")
@@ -55,5 +58,9 @@ class InboundWorkflowSqlContractTest {
.contains("phase2_add_carton_measurement_column")
.contains("uk_measure_box_code")
.contains("information_schema.STATISTICS");
assertThat(unified)
.contains("CALL phase2_measure_add_column('fl_inbound_measurement_item', 'box_code'")
.contains("uk_measure_box_code")
.contains("单箱当前测量字段未完整创建");
}
}
@@ -21,6 +21,10 @@ class PriceBookSqlContractTest {
schema.indexOf("CREATE TABLE IF NOT EXISTS fl_price_book_channel"),
schema.indexOf("ENGINE=InnoDB COMMENT='价格版本渠道及船期快照'")
);
String rateTable = schema.substring(
schema.indexOf("CREATE TABLE IF NOT EXISTS fl_carrier_fba_rate"),
schema.indexOf("ENGINE=InnoDB COMMENT='船司FBA目的仓价格版本'")
);
assertThat(schema)
.contains("CREATE TABLE IF NOT EXISTS fl_price_channel_catalog")
.contains("CREATE TABLE IF NOT EXISTS fl_price_origin_alias")
@@ -41,6 +45,9 @@ class PriceBookSqlContractTest {
.contains("updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID'")
.contains("deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记'")
.contains("version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'");
assertThat(rateTable)
.contains("status VARCHAR(24) NOT NULL DEFAULT 'ACTIVE'")
.contains("PENDING_CONFIRMATION、ACTIVE或SUPERSEDED");
assertThat(migration)
.doesNotContain("fl_price_book_version")
.doesNotContain("'pricing_tax_mode'")
@@ -205,6 +205,31 @@ class PlatformPriceBookServiceTest {
verify(repository, never()).deletePendingChildren(22L);
}
@Test
void previewDoesNotTreatDifferentUnmappedOriginsAsDuplicateRates() {
CarrierFbaRateEntity yiwu = rate(79L);
CarrierFbaRateEntity dongguan = rate(80L);
dongguan.setOriginWarehouseCode("CN-DONGGUAN-01");
dongguan.setOriginWarehouseName("东莞仓");
when(repository.activeCatalog()).thenReturn(List.of(catalog("MATSON_CLX_TRUCK")));
PriceBookDtos.ParsedChannel parsedChannel = ReflectionTestUtils.invokeMethod(
service, "channelDto", channel("MATSON_CLX_TRUCK"));
PriceBookDtos.ParsedRate parsedYiwu = ReflectionTestUtils.invokeMethod(service, "rateDto", yiwu);
PriceBookDtos.ParsedRate parsedDongguan = ReflectionTestUtils.invokeMethod(service, "rateDto", dongguan);
@SuppressWarnings("unchecked")
List<PriceBookDtos.ParseIssue> issues = ReflectionTestUtils.invokeMethod(
service,
"validate",
List.of(parsedChannel),
List.of(parsedYiwu, parsedDongguan)
);
assertThat(issues).extracting(PriceBookDtos.ParseIssue::issueCode)
.contains("ORIGIN_MAPPING_REQUIRED")
.doesNotContain("DUPLICATE_RATE");
}
@Test
void duplicatePublishIsRejectedAfterPublishLock() {
when(repository.findVersion(31L)).thenReturn(version(31L, PriceBookStatus.ACTIVE));