This commit is contained in:
2026-08-15 11:12:19 +08:00
parent 557ab75319
commit c4adba57db
22 changed files with 95 additions and 32 deletions
@@ -4788,7 +4788,8 @@ CREATE TABLE IF NOT EXISTS fl_container_booking (
destination_warehouse_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
capacity_cbm DECIMAL(18,4) NULL COMMENT '货柜容量m3',
max_weight_kg DECIMAL(18,3) NULL COMMENT '货柜最大承重kg',
terminal_trucking_company VARCHAR(128) NULL COMMENT '码头拖车公司',
terminal_trucking_company_id BIGINT UNSIGNED NULL COMMENT '码头拖车Supplier逻辑ID快照;历史数据无法推导时为空',
terminal_trucking_company VARCHAR(128) NULL COMMENT '码头拖车公司名称快照',
loading_at DATETIME(3) NULL COMMENT '计划或实际装柜时间',
estimated_sailing_at DATETIME(3) NULL COMMENT '预计开船时间',
actual_sailing_at DATETIME(3) NULL COMMENT '实际开船时间',
@@ -9507,7 +9508,7 @@ CREATE TABLE IF NOT EXISTS fl_customs_supplement_request (
CREATE TABLE IF NOT EXISTS fl_customs_submission_batch (
id BIGINT UNSIGNED NOT NULL COMMENT '报关代理提交批次逻辑ID', customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID', batch_no VARCHAR(40) NOT NULL COMMENT '全局唯一代理提交批次号',
batch_version INT UNSIGNED NOT NULL COMMENT '同一报关协同单内的提交批次版本序号', customs_agent_name VARCHAR(128) NOT NULL COMMENT '本批次接收报关资料的报关代理名称快照', external_reference VARCHAR(128) NULL COMMENT '报关代理或外部系统返回的业务参考号',
batch_version INT UNSIGNED NOT NULL COMMENT '同一报关协同单内的提交批次版本序号', customs_agent_id BIGINT UNSIGNED NULL COMMENT '本批次报关代理Supplier逻辑ID快照;历史数据无法推导时为空', customs_agent_name VARCHAR(128) NOT NULL COMMENT '本批次接收报关资料的报关代理名称快照', external_reference VARCHAR(128) NULL COMMENT '报关代理或外部系统返回的业务参考号',
file_snapshot_json JSON NOT NULL COMMENT '提交时冻结的已审核资料数组;元素包含documentId、fileId、itemCode、documentVersion和fileHash', status VARCHAR(16) NOT NULL DEFAULT 'SUBMITTED' COMMENT '代理提交状态:SUBMITTED已提交、ACKNOWLEDGED代理已确认、REJECTED代理已拒绝、COMPLETED代理处理完成', note VARCHAR(500) NULL COMMENT '提交报关代理时的平台备注',
submitted_by BIGINT UNSIGNED NOT NULL COMMENT '提交报关代理的平台操作人ID', submitted_by_name VARCHAR(64) NULL COMMENT '提交时的平台操作人名称快照',
submitted_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '提交报关代理时间', PRIMARY KEY (id),
@@ -9516,6 +9517,26 @@ CREATE TABLE IF NOT EXISTS fl_customs_submission_batch (
CONSTRAINT chk_customs_batch_status CHECK (status IN ('SUBMITTED','ACKNOWLEDGED','REJECTED','COMPLETED'))
) ENGINE=InnoDB COMMENT='报关代理提交批次及冻结文件版本';
-- 供应商引用快照收敛:历史名称无法反推Supplier ID,保留为空;新写入由Firstleg服务校验并持久化。
DROP PROCEDURE IF EXISTS phase3_supplier_snapshot_add_columns;
DELIMITER $$
CREATE PROCEDURE phase3_supplier_snapshot_add_columns()
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='fl_container_booking')
AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='terminal_trucking_company_id') THEN
ALTER TABLE fl_container_booking
ADD COLUMN terminal_trucking_company_id BIGINT UNSIGNED NULL COMMENT '码头拖车Supplier逻辑ID快照;历史数据无法推导时为空' AFTER max_weight_kg;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='fl_customs_submission_batch')
AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_customs_submission_batch' AND column_name='customs_agent_id') THEN
ALTER TABLE fl_customs_submission_batch
ADD COLUMN customs_agent_id BIGINT UNSIGNED NULL COMMENT '本批次报关代理Supplier逻辑ID快照;历史数据无法推导时为空' AFTER batch_version;
END IF;
END$$
DELIMITER ;
CALL phase3_supplier_snapshot_add_columns();
DROP PROCEDURE phase3_supplier_snapshot_add_columns;
CREATE TABLE IF NOT EXISTS fl_customs_receipt (
id BIGINT UNSIGNED NOT NULL COMMENT '报关回执文件版本逻辑ID', customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID', submission_batch_id BIGINT UNSIGNED NOT NULL COMMENT '产生本回执的报关代理提交批次ID',
receipt_type VARCHAR(16) NOT NULL COMMENT '回执类型:RECEIVED已收件、DECLARED已申报、REJECTED已拒绝、INSPECTION查验通知、RELEASED放行回执、OTHER其他回执', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务回执文件逻辑ID;跨库不建立外键', receipt_version INT UNSIGNED NOT NULL COMMENT '同一提交批次和回执类型下的版本序号',
@@ -11998,6 +12019,7 @@ CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'max_weight
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'shipping_company_id', '船司ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'shipping_company_name', '船司名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'terminal_trucking_company', '码头拖车公司');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'terminal_trucking_company_id', '码头拖车Supplier逻辑ID快照;历史数据无法推导时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'updated_at', '货柜资料最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'updated_by_name', '最后修改人名称快照');
@@ -12167,6 +12189,7 @@ CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'visibility',
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'batch_no', '全局唯一代理提交批次号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'batch_version', '同一报关协同单内的提交批次版本序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'customs_agent_name', '本批次接收报关资料的报关代理名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'customs_agent_id', '本批次报关代理Supplier逻辑ID快照;历史数据无法推导时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'customs_case_id', '所属报关协同单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'external_reference', '报关代理或外部系统返回的业务参考号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'file_snapshot_json', '提交时冻结的已审核资料数组;元素包含documentId、fileId、itemCode、documentVersion和fileHash');
@@ -11,6 +11,7 @@ public class CustomsSubmissionBatchEntity {
private Long customsCaseId;
private String batchNo;
private Integer batchVersion;
private Long customsAgentId;
private String customsAgentName;
private String externalReference;
private String fileSnapshotJson;
@@ -181,6 +181,7 @@ public class CustomsService {
batch.setId(IdWorker.getId()); batch.setCustomsCaseId(caseId); batch.setBatchVersion(repository.batches(caseId).size() + 1);
batch.setBatchNo("CB" + batch.getId());
var customsAgent = suppliers.customs(request.customsAgentId(), request.customsAgentName());
batch.setCustomsAgentId(customsAgent == null ? null : customsAgent.supplierId());
batch.setCustomsAgentName(customsAgent == null ? null : customsAgent.name());
batch.setExternalReference(trim(request.externalReference())); batch.setFileSnapshotJson(json(current.stream()
.map(v -> Map.of("documentId", v.getId(), "fileId", v.getFileId(), "itemCode", v.getChecklistItemCode(),
@@ -64,6 +64,7 @@ public final class ContainerDtos {
@Size(max = 128) String destinationWarehouseName,
@DecimalMin(value = "0", inclusive = false) BigDecimal capacityCbm,
@DecimalMin(value = "0", inclusive = false) BigDecimal maxWeightKg,
@Positive Long terminalTruckingCompanyId,
@Size(max = 128) String terminalTruckingCompany,
LocalDateTime loadingAt,
LocalDateTime estimatedSailingAt,
@@ -42,6 +42,7 @@ public class ContainerBookingEntity {
private String destinationWarehouseName;
private BigDecimal capacityCbm;
private BigDecimal maxWeightKg;
private Long terminalTruckingCompanyId;
private String terminalTruckingCompany;
private LocalDateTime loadingAt;
private LocalDateTime estimatedSailingAt;
@@ -85,7 +85,8 @@ public interface ContainerBookingMapper extends BaseMapper<ContainerBookingEntit
departure_port_name=#{value.departurePortName}, destination_port_code=#{value.destinationPortCode},
destination_port_name=#{value.destinationPortName}, destination_warehouse_code=#{value.destinationWarehouseCode},
destination_warehouse_name=#{value.destinationWarehouseName}, capacity_cbm=#{value.capacityCbm},
max_weight_kg=#{value.maxWeightKg}, terminal_trucking_company=#{value.terminalTruckingCompany},
max_weight_kg=#{value.maxWeightKg}, terminal_trucking_company_id=#{value.terminalTruckingCompanyId},
terminal_trucking_company=#{value.terminalTruckingCompany},
loading_at=#{value.loadingAt}, estimated_sailing_at=#{value.estimatedSailingAt},
actual_sailing_at=#{value.actualSailingAt}, estimated_port_arrival_at=#{value.estimatedPortArrivalAt},
actual_port_arrival_at=#{value.actualPortArrivalAt},
@@ -44,6 +44,7 @@ public class ContainerService {
@Resource private LoadingPickService loadingPickService;
@Resource private PriceBookQueryRepository priceBooks;
@Resource private OceanBookingRepository oceanBookings;
@Resource private WarehouseSupplierSnapshotService suppliers;
@Autowired(required = false) private ContainerTransportHistoryMapper transportHistories;
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
@Value("${baiyue.warehouse.public-tenant-id:0}") private long publicWarehouseTenantId;
@@ -130,9 +131,12 @@ public class ContainerService {
}
validateSchedule(request.data());
WarehouseMasterDataClient.Warehouse warehouse = requireDomesticTransitWarehouse(request.data().warehouseId());
var trucking = suppliers.trucking(request.data().terminalTruckingCompanyId(), request.data().terminalTruckingCompany());
ensureUnique(trim(request.data().containerNo()), request.data().cabinetNo().trim(), id);
copy(current, request.data());
current.setWarehouseId(warehouse.id()); current.setWarehouseName(warehouse.warehouseName());
current.setTerminalTruckingCompanyId(trucking == null ? null : trucking.supplierId());
current.setTerminalTruckingCompany(trucking == null ? null : trucking.name());
try {
if (repository.updateDraft(current, request.version(), operatorId, operatorName) != 1) {
throw conflict("货柜状态或版本已变化,请刷新后重试");
@@ -100,6 +100,7 @@ CREATE TABLE IF NOT EXISTS fl_customs_submission_batch (
customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID',
batch_no VARCHAR(40) NOT NULL COMMENT '全局唯一代理提交批次号',
batch_version INT UNSIGNED NOT NULL COMMENT '同一报关协同单内的提交批次版本序号',
customs_agent_id BIGINT UNSIGNED NULL COMMENT '本批次报关代理Supplier逻辑ID快照;历史数据无法推导时为空',
customs_agent_name VARCHAR(128) NOT NULL COMMENT '本批次接收报关资料的报关代理名称快照',
external_reference VARCHAR(128) NULL COMMENT '报关代理或外部系统返回的业务参考号',
file_snapshot_json JSON NOT NULL COMMENT '提交时冻结的已审核资料数组;元素包含documentId、fileId、itemCode、documentVersion和fileHash',
@@ -53,7 +53,8 @@ CREATE TABLE IF NOT EXISTS fl_container_booking (
destination_warehouse_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
capacity_cbm DECIMAL(18,4) NULL COMMENT '货柜容量m3',
max_weight_kg DECIMAL(18,3) NULL COMMENT '货柜最大承重kg',
terminal_trucking_company VARCHAR(128) NULL COMMENT '码头拖车公司',
terminal_trucking_company_id BIGINT UNSIGNED NULL COMMENT '码头拖车Supplier逻辑ID快照;历史数据无法推导时为空',
terminal_trucking_company VARCHAR(128) NULL COMMENT '码头拖车公司名称快照',
loading_at DATETIME(3) NULL COMMENT '计划或实际装柜时间',
estimated_sailing_at DATETIME(3) NULL COMMENT '预计开船时间',
actual_sailing_at DATETIME(3) NULL COMMENT '实际开船时间',
@@ -57,7 +57,12 @@ paths:
summary: Submit to customs agent and freeze approved file versions in a batch snapshot
description: customsAgentId must identify an ACTIVE Warehouse supplier in category CUSTOMS; the persisted agent name is resolved server-side and free text cannot bypass supplier validation.
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/SubmitBatchRequest'}}}}
responses: {'200': {description: Submission batch}}
responses:
'200':
description: Submission batch
content:
application/json:
schema: {$ref: '#/components/schemas/CustomsSubmissionBatch'}
/api/platform/v1/customs/submission-batches/{batchId}/receipts:
parameters:
- {name: batchId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
@@ -103,3 +108,20 @@ components:
externalReference: {type: string, maxLength: 128, nullable: true}
note: {type: string, maxLength: 500, nullable: true}
expectedVersion: {type: integer, minimum: 0}
CustomsSubmissionBatch:
type: object
required: [id, customsCaseId, batchNo, batchVersion, customsAgentName, status, submittedBy, submittedAt]
properties:
id: {type: string, pattern: '^[1-9][0-9]*$'}
customsCaseId: {type: string, pattern: '^[1-9][0-9]*$'}
batchNo: {type: string, maxLength: 40}
batchVersion: {type: integer, minimum: 1}
customsAgentId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: Server-validated CUSTOMS supplier ID snapshot; null only for non-migrated historical rows.}
customsAgentName: {type: string, maxLength: 128, description: Server-frozen Warehouse supplier name snapshot.}
externalReference: {type: string, maxLength: 128, nullable: true}
fileSnapshotJson: {type: string}
status: {type: string, enum: [SUBMITTED, ACKNOWLEDGED, REJECTED, COMPLETED]}
note: {type: string, maxLength: 500, nullable: true}
submittedBy: {type: string, pattern: '^[1-9][0-9]*$'}
submittedByName: {type: string, maxLength: 64, nullable: true}
submittedAt: {type: string, format: date-time}
@@ -1234,7 +1234,8 @@ components:
destinationWarehouseName: {type: string, maxLength: 128, nullable: true}
capacityCbm: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
maxWeightKg: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
terminalTruckingCompany: {type: string, maxLength: 128, nullable: true}
terminalTruckingCompanyId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: ACTIVE Warehouse Supplier ID validated as TRUCKING}
terminalTruckingCompany: {type: string, maxLength: 128, nullable: true, description: Server-frozen Warehouse supplier name snapshot}
loadingAt: {type: string, format: date-time, nullable: true}
estimatedSailingAt: {type: string, format: date-time, nullable: true}
actualSailingAt: {type: string, format: date-time, nullable: true}
@@ -1266,7 +1267,8 @@ components:
destinationWarehouseName: {type: string, maxLength: 128, nullable: true}
capacityCbm: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
maxWeightKg: {type: number, exclusiveMinimum: true, minimum: 0, nullable: true}
terminalTruckingCompany: {type: string, maxLength: 128, nullable: true}
terminalTruckingCompanyId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: ACTIVE Warehouse Supplier ID validated as TRUCKING}
terminalTruckingCompany: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint; persisted name always comes from Warehouse and cannot replace the ID}
loadingAt: {type: string, format: date-time, nullable: true}
estimatedSailingAt: {type: string, format: date-time, nullable: true}
actualSailingAt: {type: string, format: date-time, nullable: true}
@@ -91,6 +91,8 @@ class CustomsServiceTest {
new SubmitBatchRequest(88L, "线下报关代理", "EXT-1", "提交", 4));
assertThat(result.getFileSnapshotJson()).contains("documentVersion").contains("INVOICE");
assertThat(result.getCustomsAgentId()).isEqualTo(88L);
assertThat(result.getCustomsAgentName()).isEqualTo("线下报关代理");
verify(repository).freeze(51L);
verify(repository).insert(any(CustomsSubmissionBatchEntity.class));
}
@@ -30,6 +30,9 @@ class Phase3FinalUnifiedSqlContractTest {
.contains("CREATE TABLE IF NOT EXISTS fl_customs_case")
.contains("CREATE TABLE IF NOT EXISTS fl_customs_document")
.contains("CREATE TABLE IF NOT EXISTS fl_customs_submission_batch")
.contains("customs_agent_id BIGINT UNSIGNED NULL")
.contains("terminal_trucking_company_id BIGINT UNSIGNED NULL")
.contains("phase3_supplier_snapshot_add_columns")
.contains("CREATE TABLE IF NOT EXISTS fl_customs_receipt")
.contains("CREATE TABLE IF NOT EXISTS fl_port_schedule")
.contains("CREATE TABLE IF NOT EXISTS fl_port_schedule_history")
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
import com.baiyue.firstleg.client.WarehouseSupplierClient;
import com.baiyue.firstleg.dto.ContainerDtos.FullUpdateRequest;
import com.baiyue.firstleg.dto.ContainerDtos.SaveRequest;
import com.baiyue.firstleg.dto.ContainerDtos.SimulationCreateRequest;
@@ -51,6 +52,7 @@ class ContainerServiceTest {
@MockBean private LoadingPickService loadingPickService;
@MockBean private PriceBookQueryRepository priceBooks;
@MockBean private OceanBookingRepository oceanBookings;
@MockBean private WarehouseSupplierSnapshotService suppliers;
@MockBean private ContainerTransportHistoryMapper transportHistories;
@Resource private ContainerService service;
@@ -58,6 +60,8 @@ class ContainerServiceTest {
void setUpWarehouse() {
when(warehouses.active(anyString(), anyLong(), anyLong())).thenAnswer(invocation ->
activeWarehouse(invocation.getArgument(2, Long.class), "DOMESTIC", "上海中转仓"));
when(suppliers.trucking(202L, "拖车公司A"))
.thenReturn(new WarehouseSupplierClient.Snapshot(202L, "拖车公司A", "TRUCKING", null, "ACTIVE"));
}
@Test
@@ -320,7 +324,7 @@ class ContainerServiceTest {
private static SaveRequest request(String cabinetNo) {
return new SaveRequest("MSCU1234567", cabinetNo, 101L, "上海仓", "BOL-001", "柏悦轮",
"V001", "40HQ", 201L, "船司A", "CNSHA", "上海港", "USLAX", "洛杉矶港",
"ONT8", "ONT8", new BigDecimal("68.0000"), new BigDecimal("26000.000"), "拖车公司A",
"ONT8", "ONT8", new BigDecimal("68.0000"), new BigDecimal("26000.000"), 202L, "拖车公司A",
LocalDateTime.of(2026, 8, 10, 8, 0), LocalDateTime.of(2026, 8, 12, 8, 0), null,
LocalDateTime.of(2026, 8, 25, 8, 0), null,
LocalDateTime.of(2026, 8, 27, 8, 0), null);
+1 -1
View File
@@ -4,7 +4,7 @@
- 负责头程订单主数据、货物明细、快照、状态编排、幂等、Outbox 和内部货物查询。
- 订单交易状态归本模块;履约/入库状态归 `firstleg/`,仓储资源归 `warehouse/`,不得跨库读取。
- `tenant_id` 是客户隔离键;`customer_id` 是业务客户/往来单位快照,不能替代前者。
- `tenant_id` 是客户隔离键;`customer_id` 是客户/企业账号 ID,商户下单时固定来自认证上下文,不得指向 Warehouse 往来单位。发货单位、收货单位只保存为订单 Party 引用与业务快照。
## 模块入口
@@ -1,16 +0,0 @@
package com.baiyue.order.client;
import com.baiyue.common.core.api.ApiResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(name="warehouse-service", contextId="warehousePartnerClient",
url="${baiyue.clients.warehouse.url:}", path="/internal/warehouse-partners")
public interface PartnerClient {
@GetMapping("/{tenantId}/{id}/active")
ApiResponse<Partner> active(@RequestHeader("X-Internal-Service-Token") String token,
@PathVariable("tenantId") long tenantId, @PathVariable("id") long id);
record Partner(Long id, Long tenantId, String partnerName, String status,
String partnerType, List<String> types) { }
}
@@ -28,7 +28,8 @@ public final class OrderRequests {
@Schema(name = "FirstLegOrderSaveRequest", description = "First-leg order create or update request / 头程订单新增或更新请求")
public record SaveRequest(
@Schema(description = "Customer ID / 客户 ID", example = "30001") Long customerId,
@Schema(description = "Deprecated and ignored. Customer ID is always taken from authenticated enterprise context / 已废弃并忽略,客户 ID 固定取认证企业上下文", deprecated = true)
Long customerId,
@Schema(description = "Customer name / 客户名称", example = "Shenzhen Example Trading Co., Ltd. / 深圳示例贸易有限公司", maxLength = 128, requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank @Size(max = 128) String customerName,
@Schema(description = "Transport mode / 运输方式", example = "SEA", allowableValues = {"SEA", "AIR", "RAIL", "TRUCK"}, requiredMode = Schema.RequiredMode.REQUIRED)
@@ -590,7 +590,8 @@ components:
items: {$ref: '#/components/schemas/FirstLegOrderCargo'}
description: >-
订单保存请求中的完整字段以服务端 FirstLegOrderSaveRequest 为准;pricingTaxMode
是新建和编辑订单的必填报价维度;历史存量订单的响应字段仍允许为空。
是新建和编辑订单的必填报价维度;customerId 不接受客户端指定,服务端始终使用认证企业 ID;
发货单位和收货单位仅通过 sender/receiver 保存业务快照。历史存量订单的响应字段仍允许为空。
FirstLegBatchDraftItem:
type: object
required: [clientRowId, draft]
@@ -12,7 +12,6 @@ import static org.assertj.core.api.Assertions.assertThat;
class WarehouseFeignContractTest {
@Test void clientsUseNacosServiceNameAndInternalPathsByDefault() throws Exception {
assertClient(WarehouseClient.class, "/internal/warehouses");
assertClient(PartnerClient.class, "/internal/warehouse-partners");
}
private void assertClient(Class<?> type, String expectedPath) throws Exception {
@@ -363,7 +363,7 @@ class OrderApplicationServiceStateTest {
}
@Test
void publicDestinationCanBeSavedWithoutPartnerSenderOrReceiver() {
void publicDestinationUsesAuthenticatedEnterpriseAsCustomerWithoutPartySnapshots() {
OrderRepository repository = mock(OrderRepository.class);
OrderEligibilityGuard eligibility = mock(OrderEligibilityGuard.class);
OrderMasterDataGuard masterData = mock(OrderMasterDataGuard.class);
@@ -374,7 +374,7 @@ class OrderApplicationServiceStateTest {
service(repository, eligibility, masterData)
.createDraft(20002L, 2L, request, "public-destination-no-parties");
verify(repository).insertOrder(argThat(order -> order.getCustomerId() == null
verify(repository).insertOrder(argThat(order -> Long.valueOf(20002L).equals(order.getCustomerId())
&& "当前企业".equals(order.getCustomerName())));
verify(repository, never()).insertParty(any());
}
@@ -75,7 +75,7 @@ class OrderMasterDataGuardTest {
"CN", null, "深圳", null, "地址", null);
var cargo = new OrderRequests.Cargo("货物", null, "CARTON", 1, BigDecimal.ONE, BigDecimal.ONE,
null, null, null, null, null, false, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null, null);
return new OrderRequests.SaveRequest(null, "当前企业", "SEA", null, "CN", destinationWarehouseId,
"目的仓", null, null, party, party, List.of(cargo), "PICKUP", receivingWarehouseId,
"收货仓", "CH", "渠道", null, null, "BUY_DECLARATION", null, "LCL", false,
@@ -28,7 +28,10 @@ class WarehouseSchemaContractTest {
"contact_phone", "contact_email", "country_code", "province", "city", "district", "address_line",
"postal_code", "status", "remark");
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_partner_type")
.contains("idx_wh_partner_type_scope");
.contains("idx_wh_partner_type_scope")
.contains("chk_wh_partner_type CHECK (partner_type IN ('CUSTOMER','CARRIER','SHIPPER','CONSIGNEE'))")
.contains("chk_wh_partner_type_value CHECK (partner_type IN ('CUSTOMER','CARRIER','SHIPPER','CONSIGNEE'))")
.doesNotContain("CUSTOMS_BROKER", "SUPPLIER或BOTH");
assertBaseColumns(sql);
}
@@ -138,6 +141,14 @@ class WarehouseSchemaContractTest {
.contains("库位活动预占或占用唯一约束不完整");
}
@Test
void unifiedSqlUsesOnlyNonSupplierPartnerTypes() throws IOException {
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
assertThat(tableDefinition(unified, "wh_partner"))
.contains("chk_wh_partner_type CHECK (partner_type IN ('CUSTOMER','CARRIER','SHIPPER','CONSIGNEE'))")
.doesNotContain("CUSTOMS_BROKER", "SUPPLIER或BOTH");
}
private static String read(String file) throws IOException {
return Files.readString(Path.of("src/main/resources/db", file));
}