Merge remote-tracking branch 'origin/test' into dev
# Conflicts: # deploy/sql/头程数据库统一增量-MySQL8.sql # event-bridge-service/src/main/java/com/baiyue/eventbridge/metrics/EventBridgeMetrics.java # firstleg/src/main/resources/db/phase3-container-customs-schema.sql # order/src/main/resources/db/order.sql # scripts/db/incremental.sql
This commit is contained in:
+5
@@ -51,6 +51,7 @@ public class PlatformContainerFeeDetailController {
|
||||
public ApiResponse<List<ContainerFeeDetailEntity>> createAllocationGroup(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long loadingPlanId,
|
||||
@Valid @RequestBody AllocationGroupRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.createAllocationGroup(loadingPlanId, user.userId(), request));
|
||||
}
|
||||
|
||||
@@ -59,6 +60,7 @@ public class PlatformContainerFeeDetailController {
|
||||
public ApiResponse<List<ContainerFeeDetailEntity>> approveAllocationGroup(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long loadingPlanId,
|
||||
@PathVariable long groupId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.approveAllocationGroup(loadingPlanId, groupId, user.userId(), user.username()));
|
||||
}
|
||||
|
||||
@@ -67,6 +69,7 @@ public class PlatformContainerFeeDetailController {
|
||||
public ApiResponse<List<ContainerFeeDetailEntity>> rejectAllocationGroup(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long loadingPlanId,
|
||||
@PathVariable long groupId, @Valid @RequestBody RejectRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.rejectAllocationGroup(loadingPlanId, groupId, user.userId(), user.username(), request.reason()));
|
||||
}
|
||||
|
||||
@@ -75,6 +78,7 @@ public class PlatformContainerFeeDetailController {
|
||||
public ApiResponse<List<ContainerFeeDetailEntity>> adjustAllocationGroup(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long loadingPlanId,
|
||||
@PathVariable long groupId, @Valid @RequestBody AllocationGroupAdjustRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.adjustAllocationGroup(loadingPlanId, groupId, user.userId(), request));
|
||||
}
|
||||
|
||||
@@ -83,6 +87,7 @@ public class PlatformContainerFeeDetailController {
|
||||
public ApiResponse<Void> voidAllocationGroup(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long loadingPlanId,
|
||||
@PathVariable long groupId, @Valid @RequestBody VersionRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
service.voidAllocationGroup(loadingPlanId, groupId, request.expectedSourceVersion(), user.userId());
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@@ -47,8 +47,10 @@ public final class ContainerFeeDetailDtos {
|
||||
|
||||
public record Allocation(
|
||||
@NotNull @Min(1) Long customerId,
|
||||
@NotNull @Min(1) Long inboundId,
|
||||
@NotBlank @Size(max = 64) String inboundCode,
|
||||
@NotNull @Min(1) Long orderId,
|
||||
@NotBlank @Size(max = 64) String orderNo,
|
||||
@Min(1) Long inboundId,
|
||||
@Size(max = 64) String inboundCode,
|
||||
@NotNull @DecimalMin(value = "0.00", inclusive = false) BigDecimal amount
|
||||
) { }
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ public class ContainerFeeDetailEntity {
|
||||
private Long id;
|
||||
private Long tenantId;
|
||||
private Long allocationGroupId;
|
||||
private Long allocationOrderId;
|
||||
private String allocationOrderNo;
|
||||
private Long allocationInboundId;
|
||||
private String allocationInboundCode;
|
||||
private BigDecimal allocationGroupAmount;
|
||||
|
||||
+2
@@ -24,6 +24,8 @@ public class ContainerFeeSourceSnapshotEntity {
|
||||
private Integer sourceVersion;
|
||||
private Long tenantId;
|
||||
private Long allocationGroupId;
|
||||
private Long allocationOrderId;
|
||||
private String allocationOrderNo;
|
||||
private Long allocationInboundId;
|
||||
private String allocationInboundCode;
|
||||
private BigDecimal allocationGroupAmount;
|
||||
|
||||
+2
@@ -20,6 +20,8 @@ public class ContainerFeeSourceSnapshotRepository {
|
||||
value.setSourceVersion(source.getSourceVersion());
|
||||
value.setTenantId(source.getTenantId());
|
||||
value.setAllocationGroupId(source.getAllocationGroupId());
|
||||
value.setAllocationOrderId(source.getAllocationOrderId());
|
||||
value.setAllocationOrderNo(source.getAllocationOrderNo());
|
||||
value.setAllocationInboundId(source.getAllocationInboundId());
|
||||
value.setAllocationInboundCode(source.getAllocationInboundCode());
|
||||
value.setAllocationGroupAmount(source.getAllocationGroupAmount());
|
||||
|
||||
@@ -52,12 +52,15 @@ public class ContainerFeeDetailService {
|
||||
PlanContext plan = requireOwnedPlan(tenantId, loadingPlanId);
|
||||
List<InboundAllocationKey> owned = loading.items(loadingPlanId).stream()
|
||||
.filter(item -> Long.valueOf(tenantId).equals(item.getAccountId()))
|
||||
.map(item -> new InboundAllocationKey(item.getInboundId(), item.getInboundCode()))
|
||||
.map(item -> new InboundAllocationKey(item.getOrderId(), text(item.getOrderNo()),
|
||||
item.getInboundId(), text(item.getInboundCode())))
|
||||
.distinct().toList();
|
||||
if (owned.size() != 1) throw conflict("柜次包含多个入仓码,必须使用柜级费用分摊录入");
|
||||
if (owned.size() != 1) throw conflict("柜次包含多个业务归属,必须使用柜级费用分摊录入");
|
||||
FeeItemEntity item = requireItem(request.feeItemId(), request.direction());
|
||||
ContainerFeeDetailEntity value = new ContainerFeeDetailEntity();
|
||||
value.setTenantId(tenantId);
|
||||
value.setAllocationOrderId(owned.get(0).orderId());
|
||||
value.setAllocationOrderNo(owned.get(0).orderNo());
|
||||
value.setAllocationInboundId(owned.get(0).inboundId());
|
||||
value.setAllocationInboundCode(owned.get(0).inboundCode());
|
||||
value.setAllocationGroupAmount(request.amount().setScale(2, RoundingMode.HALF_UP));
|
||||
@@ -86,19 +89,25 @@ public class ContainerFeeDetailService {
|
||||
if (allocated.compareTo(total) != 0) throw conflict("柜级费用分摊合计必须等于费用总额");
|
||||
Map<InboundAllocationKey, Long> ownership = new HashMap<>();
|
||||
loading.items(loadingPlanId).forEach(value -> ownership.put(
|
||||
new InboundAllocationKey(value.getInboundId(), value.getInboundCode()), value.getAccountId()));
|
||||
new InboundAllocationKey(value.getOrderId(), text(value.getOrderNo()), value.getInboundId(),
|
||||
text(value.getInboundCode())), value.getAccountId()));
|
||||
Set<InboundAllocationKey> unique = new HashSet<>();
|
||||
long groupId = IdWorker.getId();
|
||||
List<ContainerFeeDetailEntity> rows = new java.util.ArrayList<>();
|
||||
for (Allocation allocation : request.allocations()) {
|
||||
InboundAllocationKey key = new InboundAllocationKey(allocation.inboundId(), allocation.inboundCode().trim());
|
||||
if (!unique.add(key)) throw conflict("同一入仓码不得重复分摊");
|
||||
String inboundCode = text(allocation.inboundCode());
|
||||
if ((allocation.inboundId() == null) != (inboundCode == null))
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "入仓ID与入仓码必须同时填写或同时留空");
|
||||
InboundAllocationKey key = new InboundAllocationKey(allocation.orderId(), allocation.orderNo().trim(),
|
||||
allocation.inboundId(), inboundCode);
|
||||
if (!unique.add(key)) throw conflict("同一业务归属不得重复分摊");
|
||||
if (!allocation.customerId().equals(ownership.get(key)))
|
||||
throw conflict("分摊客户、入仓码与柜内真实归属不一致");
|
||||
throw conflict("分摊客户、订单及入仓信息与柜内真实归属不一致");
|
||||
ContainerFeeDetailEntity value = new ContainerFeeDetailEntity();
|
||||
value.setId(IdWorker.getId()); value.setTenantId(allocation.customerId());
|
||||
value.setAllocationGroupId(groupId); value.setAllocationInboundId(allocation.inboundId());
|
||||
value.setAllocationInboundCode(allocation.inboundCode().trim()); value.setAllocationGroupAmount(total);
|
||||
value.setAllocationGroupId(groupId); value.setAllocationOrderId(allocation.orderId());
|
||||
value.setAllocationOrderNo(allocation.orderNo().trim()); value.setAllocationInboundId(allocation.inboundId());
|
||||
value.setAllocationInboundCode(inboundCode); value.setAllocationGroupAmount(total);
|
||||
value.setLoadingPlanId(loadingPlanId); value.setContainerBookingId(plan.plan().getContainerBookingId());
|
||||
value.setContainerNo(plan.booking() == null ? null : plan.booking().getContainerNo());
|
||||
value.setSourceVersion(1); value.setSourceStatus("ACTIVE"); value.setReviewStatus("PENDING");
|
||||
@@ -359,5 +368,7 @@ public class ContainerFeeDetailService {
|
||||
|
||||
private record PlanContext(LoadingPlanEntity plan, ContainerBookingEntity booking) {
|
||||
}
|
||||
private record InboundAllocationKey(Long inboundId, String inboundCode) { }
|
||||
private static String text(String value) { return value == null || value.isBlank() ? null : value.trim(); }
|
||||
|
||||
private record InboundAllocationKey(Long orderId, String orderNo, Long inboundId, String inboundCode) { }
|
||||
}
|
||||
|
||||
+3
-2
@@ -70,12 +70,13 @@ public class FinanceFeeSourceSnapshotService {
|
||||
nullSafe(detail.getSettlementMode()), nullSafe(detail.getPaidAt()),
|
||||
nullSafe(detail.getPaymentMethod()), nullSafe(detail.getPaymentReferenceNo()),
|
||||
nullSafe(detail.getPaymentVoucherFileIds()),
|
||||
nullSafe(detail.getAllocationGroupId()), nullSafe(detail.getAllocationInboundId()),
|
||||
nullSafe(detail.getAllocationGroupId()), nullSafe(detail.getAllocationOrderId()),
|
||||
nullSafe(detail.getAllocationOrderNo()), nullSafe(detail.getAllocationInboundId()),
|
||||
nullSafe(detail.getAllocationInboundCode()), nullSafe(detail.getAllocationGroupAmount()),
|
||||
detail.getAmount().toPlainString(), nullSafe(detail.getCurrency()),
|
||||
nullSafe(detail.getReviewStatus()));
|
||||
return new FinanceFeeSourceSnapshot(detail.getTenantId(), "CONTAINER_FEE", feeId,
|
||||
version, detail.getAllocationInboundId(), detail.getAllocationInboundCode(), null, containerId, detail.getContainerBookingId(),
|
||||
version, detail.getAllocationInboundId(), detail.getAllocationInboundCode(), detail.getAllocationOrderId(), containerId, detail.getContainerBookingId(),
|
||||
detail.getContainerNo(), detail.getTenantId(), null, detail.getSupplierId(),
|
||||
detail.getSupplierName(), detail.getSupplierCategory(), detail.getSettlementMode(),
|
||||
detail.getPaidAt(), detail.getPaymentMethod(), detail.getPaymentReferenceNo(),
|
||||
|
||||
@@ -65,6 +65,8 @@ CREATE TABLE IF NOT EXISTS fl_container_fee_detail (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '柜次费用来源ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
allocation_group_id BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID;单入仓直接费用为空',
|
||||
allocation_order_id BIGINT UNSIGNED NULL COMMENT '本分摊行业务订单ID',
|
||||
allocation_order_no VARCHAR(64) NULL COMMENT '本分摊行业务订单号',
|
||||
allocation_inbound_id BIGINT UNSIGNED NULL COMMENT '本分摊行对应入仓单ID',
|
||||
allocation_inbound_code VARCHAR(64) NULL COMMENT '本分摊行对应入仓码',
|
||||
allocation_group_amount DECIMAL(18,2) NULL COMMENT '柜级原始费用总额快照',
|
||||
@@ -123,6 +125,8 @@ CREATE TABLE IF NOT EXISTS fl_container_fee_source_snapshot (
|
||||
source_version INT UNSIGNED NOT NULL COMMENT '费用来源版本',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
allocation_group_id BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID',
|
||||
allocation_order_id BIGINT UNSIGNED NULL COMMENT '分摊业务订单ID快照',
|
||||
allocation_order_no VARCHAR(64) NULL COMMENT '分摊业务订单号快照',
|
||||
allocation_inbound_id BIGINT UNSIGNED NULL COMMENT '分摊入仓单ID快照',
|
||||
allocation_inbound_code VARCHAR(64) NULL COMMENT '分摊入仓码快照',
|
||||
allocation_group_amount DECIMAL(18,2) NULL COMMENT '柜级原始费用总额快照',
|
||||
@@ -196,8 +200,12 @@ CALL firstleg_finance_add_column('fl_container_fee_detail', 'payment_voucher_fil
|
||||
"JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组' AFTER payment_reference_no");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_group_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID;单入仓直接费用为空' AFTER tenant_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_order_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '本分摊行业务订单ID' AFTER allocation_group_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_order_no',
|
||||
"VARCHAR(64) NULL COMMENT '本分摊行业务订单号' AFTER allocation_order_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_inbound_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '本分摊行对应入仓单ID' AFTER allocation_group_id");
|
||||
"BIGINT UNSIGNED NULL COMMENT '本分摊行对应入仓单ID' AFTER allocation_order_no");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_inbound_code',
|
||||
"VARCHAR(64) NULL COMMENT '本分摊行对应入仓码' AFTER allocation_inbound_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_group_amount',
|
||||
@@ -214,8 +222,12 @@ CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'payment_vo
|
||||
"JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组' AFTER payment_reference_no");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_group_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID' AFTER tenant_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_order_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '分摊业务订单ID快照' AFTER allocation_group_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_order_no',
|
||||
"VARCHAR(64) NULL COMMENT '分摊业务订单号快照' AFTER allocation_order_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_inbound_id',
|
||||
"BIGINT UNSIGNED NULL COMMENT '分摊入仓单ID快照' AFTER allocation_group_id");
|
||||
"BIGINT UNSIGNED NULL COMMENT '分摊入仓单ID快照' AFTER allocation_order_no");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_inbound_code',
|
||||
"VARCHAR(64) NULL COMMENT '分摊入仓码快照' AFTER allocation_inbound_id");
|
||||
CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_group_amount',
|
||||
|
||||
@@ -834,8 +834,8 @@ CREATE TABLE IF NOT EXISTS fl_order_declaration_snapshot (
|
||||
cargoes_json JSON NOT NULL COMMENT '客户申报货物行完整快照,不覆盖实测事实',
|
||||
changes_json JSON NULL COMMENT '本版本申报字段变化摘要',
|
||||
occurred_at DATETIME(3) NOT NULL COMMENT 'Order事件发生时间',
|
||||
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),
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '快照创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '快照更新时间',
|
||||
PRIMARY KEY (tenant_id, order_id),
|
||||
UNIQUE KEY uk_order_declaration_event (tenant_id, event_id),
|
||||
KEY idx_order_declaration_version (tenant_id, order_id, order_version)
|
||||
@@ -847,7 +847,7 @@ CREATE TABLE IF NOT EXISTS fl_order_declaration_event (
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT 'Order订单ID',
|
||||
order_version INT NOT NULL COMMENT '事件携带的Order聚合版本',
|
||||
request_hash CHAR(64) NOT NULL COMMENT '完整事件SHA-256,用于异参重放拒绝',
|
||||
consumed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
consumed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '事件消费完成时间',
|
||||
PRIMARY KEY (tenant_id, event_id),
|
||||
KEY idx_order_declaration_event_order (tenant_id, order_id, order_version)
|
||||
) ENGINE=InnoDB COMMENT='Order客户申报事件永久幂等账本';
|
||||
|
||||
@@ -5,16 +5,16 @@ CREATE TABLE IF NOT EXISTS fl_customs_container (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '整柜报关聚合ID',
|
||||
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '已完成装柜计划ID;报关柜级聚合唯一键',
|
||||
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '物理货柜ID',
|
||||
status VARCHAR(40) NOT NULL COMMENT '报关状态:WAITING_CUSTOMER_DOCUMENTS待客户资料、READY_TO_START待开始、DECLARING报关中、COMPLETED已完成',
|
||||
status VARCHAR(40) NOT NULL COMMENT '报关状态:WAITING_CUSTOMER_DOCUMENTS待资料、READY_TO_START可开始、DECLARING申报中、COMPLETED已完成',
|
||||
started_by BIGINT UNSIGNED NULL COMMENT '开始报关操作人ID',
|
||||
started_by_name VARCHAR(64) NULL COMMENT '开始报关操作人姓名快照',
|
||||
started_by_name VARCHAR(64) NULL COMMENT '开始报关操作人名称快照',
|
||||
started_at DATETIME(3) NULL COMMENT '开始报关时间',
|
||||
completed_by BIGINT UNSIGNED NULL COMMENT '完成报关操作人ID',
|
||||
completed_by_name VARCHAR(64) NULL COMMENT '完成报关操作人姓名快照',
|
||||
completed_at DATETIME(3) NULL COMMENT '完成报关时间',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '报关聚合创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '报关聚合更新时间',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '报关聚合乐观锁版本',
|
||||
completed_by_name VARCHAR(64) NULL COMMENT '完成报关操作人名称快照',
|
||||
completed_at DATETIME(3) NULL COMMENT '报关完成时间',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '聚合创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '聚合更新时间',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_container_plan(loading_plan_id),
|
||||
UNIQUE KEY uk_customs_container_booking(container_booking_id),
|
||||
@@ -24,24 +24,24 @@ CREATE TABLE IF NOT EXISTS fl_customs_container (
|
||||
) ENGINE=InnoDB COMMENT='装柜完成后的整柜出口报关聚合';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_customer_document (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '客户报关资料ID',
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '客户报送资料ID',
|
||||
customs_container_id BIGINT UNSIGNED NOT NULL COMMENT '所属整柜报关聚合ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键;只允许关联同客户退税票',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '客户资料文件名快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '客户资料文件SHA-256摘要',
|
||||
content_type VARCHAR(128) NULL COMMENT '客户资料文件内容类型',
|
||||
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '客户资料文件大小(字节)',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '文件名称快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '文件SHA-256摘要',
|
||||
content_type VARCHAR(128) NULL COMMENT '文件媒体类型',
|
||||
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '文件大小(字节)',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '资料状态:ACTIVE有效、DELETED已删除',
|
||||
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID',
|
||||
uploaded_at DATETIME(3) NOT NULL COMMENT '上传时间',
|
||||
frozen_version INT UNSIGNED NULL COMMENT '开始报关时冻结的资料版本',
|
||||
frozen_at DATETIME(3) NULL COMMENT '资料冻结时间',
|
||||
deleted_by BIGINT UNSIGNED NULL COMMENT '删除操作人ID',
|
||||
deleted_at DATETIME(3) NULL COMMENT '逻辑删除时间',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '客户资料当前版本',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '客户资料创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '客户资料更新时间',
|
||||
deleted_at DATETIME(3) NULL COMMENT '删除时间',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '资料版本号',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '资料创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '资料更新时间',
|
||||
PRIMARY KEY(id),
|
||||
KEY idx_customs_customer_document_container(customs_container_id,status,tenant_id),
|
||||
CONSTRAINT fk_customs_customer_document_container FOREIGN KEY(customs_container_id) REFERENCES fl_customs_container(id),
|
||||
@@ -51,30 +51,30 @@ CREATE TABLE IF NOT EXISTS fl_customs_customer_document (
|
||||
) ENGINE=InnoDB COMMENT='客户整份退税报送资料逻辑根';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_customer_document_inbound (
|
||||
document_id BIGINT UNSIGNED NOT NULL COMMENT '客户报关资料ID',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '覆盖的入仓单ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '入仓单客户隔离键',
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT '入仓单所属订单ID',
|
||||
inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '资料覆盖关系创建时间',
|
||||
document_id BIGINT UNSIGNED NOT NULL COMMENT '客户报送资料ID',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '覆盖入仓单ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '覆盖入仓单客户ID',
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT '覆盖订单ID',
|
||||
inbound_code VARCHAR(40) NOT NULL COMMENT '覆盖入仓码快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '关联创建时间',
|
||||
PRIMARY KEY(document_id,inbound_id),
|
||||
KEY idx_customs_document_inbound(inbound_id,tenant_id),
|
||||
CONSTRAINT fk_customs_document_inbound_document FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id)
|
||||
) ENGINE=InnoDB COMMENT='客户报送资料与当前柜退税入仓码多对多关系';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_customer_document_version (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '客户资料版本记录ID',
|
||||
document_id BIGINT UNSIGNED NOT NULL COMMENT '客户报关资料ID',
|
||||
document_version INT UNSIGNED NOT NULL COMMENT '客户资料版本号',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT '该版本File服务文件ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '该版本文件名快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '该版本文件SHA-256摘要',
|
||||
content_type VARCHAR(128) NULL COMMENT '该版本文件内容类型',
|
||||
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '该版本文件大小(字节)',
|
||||
scope_snapshot_json JSON NOT NULL COMMENT '该版本覆盖入仓码范围快照',
|
||||
action VARCHAR(24) NOT NULL COMMENT '版本动作:UPLOAD上传、REPLACE替换、SCOPE_CHANGE调整范围、DELETE删除',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '版本操作人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '版本记录创建时间',
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '资料版本记录ID',
|
||||
document_id BIGINT UNSIGNED NOT NULL COMMENT '客户报送资料ID',
|
||||
document_version INT UNSIGNED NOT NULL COMMENT '资料版本号',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT '本版本File服务文件ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '本版本文件名称快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '本版本文件SHA-256摘要',
|
||||
content_type VARCHAR(128) NULL COMMENT '本版本文件媒体类型',
|
||||
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '本版本文件大小(字节)',
|
||||
scope_snapshot_json JSON NOT NULL COMMENT '本版本覆盖入仓码范围快照',
|
||||
action VARCHAR(24) NOT NULL COMMENT '版本动作:UPLOAD上传、REPLACE替换、SCOPE_CHANGE范围变化、DELETE删除',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '版本创建时间',
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_customer_document_version(document_id,document_version),
|
||||
CONSTRAINT fk_customs_customer_document_version FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id),
|
||||
@@ -82,12 +82,12 @@ CREATE TABLE IF NOT EXISTS fl_customs_customer_document_version (
|
||||
) ENGINE=InnoDB COMMENT='客户报送资料文件与覆盖范围不可变版本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_frozen_document_inbound (
|
||||
document_id BIGINT UNSIGNED NOT NULL COMMENT '冻结的客户报关资料ID',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '冻结覆盖的入仓单ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '冻结入仓单客户隔离键',
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT '冻结入仓单所属订单ID',
|
||||
inbound_code VARCHAR(40) NOT NULL COMMENT '冻结入仓码快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '覆盖范围冻结时间',
|
||||
document_id BIGINT UNSIGNED NOT NULL COMMENT '冻结的客户报送资料ID',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '冻结覆盖入仓单ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '冻结覆盖客户ID',
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT '冻结覆盖订单ID',
|
||||
inbound_code VARCHAR(40) NOT NULL COMMENT '冻结覆盖入仓码快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '冻结关联创建时间',
|
||||
PRIMARY KEY(document_id,inbound_id),
|
||||
CONSTRAINT fk_customs_frozen_document FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id)
|
||||
) ENGINE=InnoDB COMMENT='开始报关时冻结的客户资料覆盖范围';
|
||||
@@ -95,15 +95,15 @@ CREATE TABLE IF NOT EXISTS fl_customs_frozen_document_inbound (
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_completion_document (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关完成文件ID',
|
||||
source_document_id BIGINT UNSIGNED NOT NULL COMMENT '一份冻结客户资料严格对应一份完成文件逻辑根',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT '完成文件的File服务文件ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '完成文件名快照',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '完成文件名称快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '完成文件SHA-256摘要',
|
||||
content_type VARCHAR(128) NULL COMMENT '完成文件内容类型',
|
||||
content_type VARCHAR(128) NULL COMMENT '完成文件媒体类型',
|
||||
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '完成文件大小(字节)',
|
||||
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '完成文件上传操作人ID',
|
||||
uploaded_by_name VARCHAR(64) NULL COMMENT '完成文件上传操作人姓名快照',
|
||||
uploaded_at DATETIME(3) NOT NULL COMMENT '完成文件上传时间',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '完成文件当前版本',
|
||||
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID',
|
||||
uploaded_by_name VARCHAR(64) NULL COMMENT '上传操作人名称快照',
|
||||
uploaded_at DATETIME(3) NOT NULL COMMENT '上传时间',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '完成文件版本号',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '完成文件创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '完成文件更新时间',
|
||||
PRIMARY KEY(id),
|
||||
@@ -115,44 +115,44 @@ CREATE TABLE IF NOT EXISTS fl_customs_completion_document_version (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '完成文件版本记录ID',
|
||||
completion_document_id BIGINT UNSIGNED NOT NULL COMMENT '报关完成文件ID',
|
||||
document_version INT UNSIGNED NOT NULL COMMENT '完成文件版本号',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT '该版本File服务文件ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '该版本文件名快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '该版本文件SHA-256摘要',
|
||||
content_type VARCHAR(128) NULL COMMENT '该版本文件内容类型',
|
||||
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '该版本文件大小(字节)',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '版本上传操作人ID',
|
||||
operator_name VARCHAR(64) NULL COMMENT '版本上传操作人姓名快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '完成文件版本创建时间',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT '本版本File服务文件ID',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '本版本文件名称快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '本版本文件SHA-256摘要',
|
||||
content_type VARCHAR(128) NULL COMMENT '本版本文件媒体类型',
|
||||
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '本版本文件大小(字节)',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
|
||||
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '版本创建时间',
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_completion_version(completion_document_id,document_version),
|
||||
CONSTRAINT fk_customs_completion_version FOREIGN KEY(completion_document_id) REFERENCES fl_customs_completion_document(id)
|
||||
) ENGINE=InnoDB COMMENT='报关完成文件不可变覆盖版本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_container_history (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '整柜报关历史ID',
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关历史记录ID',
|
||||
customs_container_id BIGINT UNSIGNED NOT NULL COMMENT '所属整柜报关聚合ID',
|
||||
action VARCHAR(40) NOT NULL COMMENT '报关状态或系统触发动作',
|
||||
from_status VARCHAR(40) NULL COMMENT '动作前报关状态',
|
||||
to_status VARCHAR(40) NULL COMMENT '动作后报关状态',
|
||||
remark VARCHAR(500) NULL COMMENT '动作说明或备注',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '动作操作人ID',
|
||||
operator_name VARCHAR(64) NULL COMMENT '动作操作人姓名快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '报关历史创建时间',
|
||||
action VARCHAR(40) NOT NULL COMMENT '报关操作类型',
|
||||
from_status VARCHAR(40) NULL COMMENT '变更前报关状态',
|
||||
to_status VARCHAR(40) NULL COMMENT '变更后报关状态',
|
||||
remark VARCHAR(500) NULL COMMENT '操作备注',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
|
||||
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '历史创建时间',
|
||||
PRIMARY KEY(id),
|
||||
KEY idx_customs_container_history(customs_container_id,created_at,id),
|
||||
CONSTRAINT fk_customs_container_history FOREIGN KEY(customs_container_id) REFERENCES fl_customs_container(id)
|
||||
) ENGINE=InnoDB COMMENT='整柜报关状态与系统触发历史';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_command (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关写命令ID',
|
||||
actor_id BIGINT UNSIGNED NOT NULL COMMENT '命令发起操作人ID',
|
||||
action VARCHAR(80) NOT NULL COMMENT '报关写命令动作',
|
||||
idempotency_key VARCHAR(128) NOT NULL COMMENT '调用方幂等键',
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关幂等命令ID',
|
||||
actor_id BIGINT UNSIGNED NOT NULL COMMENT '命令操作人ID',
|
||||
action VARCHAR(80) NOT NULL COMMENT '命令动作类型',
|
||||
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键',
|
||||
request_hash CHAR(64) NOT NULL COMMENT '规范化请求SHA-256摘要',
|
||||
status VARCHAR(16) NOT NULL COMMENT '命令状态:PROCESSING处理中、SUCCESS成功',
|
||||
result_id BIGINT UNSIGNED NULL COMMENT '命令成功生成的业务结果ID',
|
||||
result_id BIGINT UNSIGNED NULL COMMENT '命令结果业务记录ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '命令创建时间',
|
||||
completed_at DATETIME(3) NULL COMMENT '命令成功完成时间',
|
||||
completed_at DATETIME(3) NULL COMMENT '命令完成时间',
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_command(actor_id,action,idempotency_key),
|
||||
CONSTRAINT chk_customs_command_status CHECK(status IN('PROCESSING','SUCCESS'))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
USE baiyue_firstleg;
|
||||
CREATE TABLE IF NOT EXISTS fl_container_exception_history (
|
||||
id BIGINT UNSIGNED NOT NULL, container_booking_id BIGINT UNSIGNED NOT NULL,
|
||||
action VARCHAR(16) NOT NULL COMMENT 'MARK/CANCEL', reason VARCHAR(500) NOT NULL,
|
||||
operator_id BIGINT UNSIGNED NOT NULL, operator_name VARCHAR(64),
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '货柜异常历史ID', container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '物理货柜ID',
|
||||
action VARCHAR(16) NOT NULL COMMENT '操作类型:MARK标记异常、CANCEL取消异常', reason VARCHAR(500) NOT NULL COMMENT '异常原因',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', operator_name VARCHAR(64) COMMENT '操作人名称快照',
|
||||
operation_id VARCHAR(128) NOT NULL COMMENT '同一操作人幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求摘要',
|
||||
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作发生时间',
|
||||
PRIMARY KEY(id), UNIQUE KEY uk_container_exception_operation(container_booking_id,operator_id,operation_id),
|
||||
KEY idx_container_exception_history(container_booking_id,occurred_at,id),
|
||||
CONSTRAINT fk_container_exception_booking FOREIGN KEY(container_booking_id) REFERENCES fl_container_booking(id),
|
||||
|
||||
@@ -344,11 +344,11 @@ CREATE TABLE IF NOT EXISTS fl_container_document (
|
||||
) ENGINE=InnoDB COMMENT='平台货柜清关、DO与PU文件关联';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_container_exception_history (
|
||||
id BIGINT UNSIGNED NOT NULL, container_booking_id BIGINT UNSIGNED NOT NULL,
|
||||
action VARCHAR(16) NOT NULL, reason VARCHAR(500) NOT NULL,
|
||||
operator_id BIGINT UNSIGNED NOT NULL, operator_name VARCHAR(64) NULL,
|
||||
operation_id VARCHAR(128) NOT NULL, request_hash CHAR(64) NOT NULL,
|
||||
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY(id),
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '货柜异常历史ID', container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '物理货柜ID',
|
||||
action VARCHAR(16) NOT NULL COMMENT '操作类型:MARK标记异常、CANCEL取消异常', reason VARCHAR(500) NOT NULL COMMENT '异常原因',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
|
||||
operation_id VARCHAR(128) NOT NULL COMMENT '同一操作人幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求摘要',
|
||||
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作发生时间', PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_container_exception_operation(container_booking_id,operator_id,operation_id),
|
||||
KEY idx_container_exception_history(container_booking_id,occurred_at,id),
|
||||
CONSTRAINT fk_container_exception_booking FOREIGN KEY(container_booking_id) REFERENCES fl_container_booking(id),
|
||||
@@ -587,7 +587,7 @@ CREATE TABLE IF NOT EXISTS fl_loading_completion_command (
|
||||
pick_task_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA任务ID',
|
||||
operation_id VARCHAR(128) NOT NULL COMMENT '客户端全局幂等操作ID',
|
||||
request_hash CHAR(64) NOT NULL COMMENT '规范化请求摘要',
|
||||
command_status VARCHAR(16) NOT NULL COMMENT 'PROCESSING、RETRYABLE、FAILED、COMPLETED',
|
||||
command_status VARCHAR(16) NOT NULL COMMENT '命令状态:PROCESSING处理中、RETRYABLE可重试、FAILED失败、COMPLETED已完成',
|
||||
expected_task_version INT UNSIGNED NOT NULL COMMENT '受理时客户端任务版本',
|
||||
remark VARCHAR(500) NULL COMMENT '完成备注',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
|
||||
@@ -600,9 +600,9 @@ CREATE TABLE IF NOT EXISTS fl_loading_completion_command (
|
||||
failure_code VARCHAR(32) NULL COMMENT '下游或业务错误码',
|
||||
failure_request_id VARCHAR(64) NULL COMMENT '下游请求ID',
|
||||
failure_trace_id VARCHAR(64) NULL COMMENT '下游链路ID',
|
||||
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),
|
||||
completed_at DATETIME(3) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '命令创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '命令更新时间',
|
||||
completed_at DATETIME(3) NULL COMMENT '命令完成时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_loading_completion_operation (operation_id),
|
||||
KEY idx_loading_completion_dispatch (command_status,next_retry_at,lease_until),
|
||||
@@ -611,13 +611,13 @@ CREATE TABLE IF NOT EXISTS fl_loading_completion_command (
|
||||
) ENGINE=InnoDB COMMENT='PDA装柜完成可靠异步命令';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_loading_completion_checkpoint (
|
||||
command_id BIGINT UNSIGNED NOT NULL,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||
owner_sequence INT UNSIGNED NOT NULL,
|
||||
checkpoint_stage VARCHAR(24) NOT NULL COMMENT 'READY、SETTLED、INBOUND_COMPLETED',
|
||||
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),
|
||||
command_id BIGINT UNSIGNED NOT NULL COMMENT '可靠装柜完成命令ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '入仓单客户ID',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入仓单ID',
|
||||
owner_sequence INT UNSIGNED NOT NULL COMMENT '稳定处理顺序',
|
||||
checkpoint_stage VARCHAR(24) NOT NULL COMMENT '处理阶段:READY待处理、SETTLED资源已结算、INBOUND_COMPLETED入仓已完成',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '检查点创建时间',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '检查点更新时间',
|
||||
PRIMARY KEY (command_id,tenant_id,inbound_id),
|
||||
KEY idx_loading_completion_owner (command_id,owner_sequence),
|
||||
CONSTRAINT fk_loading_completion_checkpoint_command FOREIGN KEY (command_id)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
openapi: 3.0.3
|
||||
info: { title: Firstleg Container Fee Allocation API, version: 1.0.0 }
|
||||
paths:
|
||||
/api/platform/v1/loading-plans/{loadingPlanId}/fee-details/allocation-groups:
|
||||
post:
|
||||
summary: 原子创建柜级公共费用分摊组
|
||||
description: 客户、订单必填;入仓ID与入仓码同时填写或同时留空。各分摊金额必须大于0且合计等于费用总额。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ $ref: '#/components/parameters/LoadingPlanId' }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/AllocationGroupRequest' } } } }
|
||||
responses: { '200': { description: 按客户和业务归属生成的待审核费用行 }, '409': { description: 分摊合计或柜内真实归属不一致 } }
|
||||
/api/platform/v1/loading-plans/{loadingPlanId}/fee-details/allocation-groups/{groupId}/{action}:
|
||||
post:
|
||||
summary: 整组审核、驳回、调整或作废柜级分摊费用
|
||||
description: action 仅为 approve、reject、adjust、void;单行接口拒绝处理分摊组,避免部分成功。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/LoadingPlanId' }
|
||||
- { in: path, name: groupId, required: true, schema: { type: string, pattern: '^[1-9][0-9]{0,19}$' } }
|
||||
- { in: path, name: action, required: true, schema: { type: string, enum: [approve, reject, adjust, void] } }
|
||||
responses: { '200': { description: 整组处理成功 }, '409': { description: 版本、状态或完整性冲突 } }
|
||||
components:
|
||||
parameters:
|
||||
LoadingPlanId: { in: path, name: loadingPlanId, required: true, schema: { type: string, pattern: '^[1-9][0-9]{0,19}$' } }
|
||||
schemas:
|
||||
AllocationGroupRequest:
|
||||
type: object
|
||||
required: [fee, allocations]
|
||||
properties:
|
||||
fee: { type: object, description: 公共费用字段;amount为原始柜级总额 }
|
||||
allocations:
|
||||
type: array
|
||||
minItems: 1
|
||||
maxItems: 200
|
||||
items:
|
||||
type: object
|
||||
required: [customerId, orderId, orderNo, amount]
|
||||
properties:
|
||||
customerId: { type: string }
|
||||
orderId: { type: string }
|
||||
orderNo: { type: string, maxLength: 64 }
|
||||
inboundId: { type: string, nullable: true }
|
||||
inboundCode: { type: string, maxLength: 64, nullable: true }
|
||||
amount: { type: number, format: decimal, exclusiveMinimum: 0 }
|
||||
securitySchemes: { bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT } }
|
||||
@@ -3,6 +3,8 @@ package com.baiyue.firstleg.service;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.ContainerFeeDetailDtos;
|
||||
import com.baiyue.firstleg.dto.ContainerFeeDetailDtos.AdjustRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerFeeDetailDtos.Allocation;
|
||||
import com.baiyue.firstleg.dto.ContainerFeeDetailDtos.AllocationGroupRequest;
|
||||
import com.baiyue.firstleg.entity.ContainerFeeDetailEntity;
|
||||
import com.baiyue.firstleg.entity.FeeItemEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
||||
@@ -233,15 +235,71 @@ class ContainerFeeDetailServiceTest {
|
||||
assertThat(captor.getValue().getPaymentVoucherFileIds()).containsExactly(901L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsAtomicCrossCustomerAllocationAndAllowsMissingInboundCode() {
|
||||
LoadingPlanEntity plan = new LoadingPlanEntity();
|
||||
plan.setId(41L);
|
||||
when(loading.plan(41L)).thenReturn(plan);
|
||||
LoadingPlanItemEntity first = loadingItem(10001L, 501L, "FL-501", null, null);
|
||||
LoadingPlanItemEntity second = loadingItem(10002L, 502L, "FL-502", 602L, "IB-602");
|
||||
when(loading.items(41L)).thenReturn(List.of(first, second));
|
||||
when(feeItems.findItem(11L)).thenReturn(item());
|
||||
var fee = new ContainerFeeDetailDtos.SaveRequest(11L, "RECEIVABLE",
|
||||
LocalDate.of(2026, 8, 20), new BigDecimal("300.00"), "CNY", null, "柜级操作费");
|
||||
var request = new AllocationGroupRequest(fee, List.of(
|
||||
new Allocation(10001L, 501L, "FL-501", null, null, new BigDecimal("120.00")),
|
||||
new Allocation(10002L, 502L, "FL-502", 602L, "IB-602", new BigDecimal("180.00"))));
|
||||
|
||||
List<ContainerFeeDetailEntity> result = service.createAllocationGroup(41L, 9L, request);
|
||||
|
||||
assertThat(result).hasSize(2);
|
||||
ArgumentCaptor<ContainerFeeDetailEntity> captor = ArgumentCaptor.forClass(ContainerFeeDetailEntity.class);
|
||||
verify(repository, org.mockito.Mockito.times(2)).insert(captor.capture());
|
||||
assertThat(captor.getAllValues()).extracting(ContainerFeeDetailEntity::getTenantId)
|
||||
.containsExactly(10001L, 10002L);
|
||||
assertThat(captor.getAllValues().get(0).getAllocationOrderNo()).isEqualTo("FL-501");
|
||||
assertThat(captor.getAllValues().get(0).getAllocationInboundId()).isNull();
|
||||
assertThat(captor.getAllValues().get(0).getAllocationInboundCode()).isNull();
|
||||
assertThat(captor.getAllValues()).extracting(ContainerFeeDetailEntity::getAllocationGroupId)
|
||||
.doesNotContainNull().allMatch(captor.getAllValues().get(0).getAllocationGroupId()::equals);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsAllocationWhenInboundIdAndCodeAreOnlyPartiallyProvided() {
|
||||
LoadingPlanEntity plan = new LoadingPlanEntity();
|
||||
plan.setId(41L);
|
||||
when(loading.plan(41L)).thenReturn(plan);
|
||||
when(loading.items(41L)).thenReturn(List.of(loadingItem(10001L, 501L, "FL-501", null, null)));
|
||||
when(feeItems.findItem(11L)).thenReturn(item());
|
||||
var fee = new ContainerFeeDetailDtos.SaveRequest(11L, "RECEIVABLE",
|
||||
LocalDate.of(2026, 8, 20), new BigDecimal("100.00"), "CNY", null, null);
|
||||
var request = new AllocationGroupRequest(fee, List.of(
|
||||
new Allocation(10001L, 501L, "FL-501", 601L, null, new BigDecimal("100.00"))));
|
||||
|
||||
assertThatThrownBy(() -> service.createAllocationGroup(41L, 9L, request))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("同时填写或同时留空");
|
||||
verify(repository, never()).insert(any());
|
||||
}
|
||||
|
||||
private void ownedPlan() {
|
||||
LoadingPlanEntity plan = new LoadingPlanEntity();
|
||||
plan.setId(41L);
|
||||
when(loading.plan(41L)).thenReturn(plan);
|
||||
LoadingPlanItemEntity item = new LoadingPlanItemEntity();
|
||||
item.setAccountId(10001L);
|
||||
item.setOrderId(501L);
|
||||
item.setOrderNo("FL-501");
|
||||
when(loading.items(41L)).thenReturn(List.of(item));
|
||||
}
|
||||
|
||||
private static LoadingPlanItemEntity loadingItem(long accountId, long orderId, String orderNo,
|
||||
Long inboundId, String inboundCode) {
|
||||
LoadingPlanItemEntity value = new LoadingPlanItemEntity();
|
||||
value.setAccountId(accountId); value.setOrderId(orderId); value.setOrderNo(orderNo);
|
||||
value.setInboundId(inboundId); value.setInboundCode(inboundCode);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static FeeItemEntity item() {
|
||||
FeeItemEntity value = new FeeItemEntity();
|
||||
value.setId(11L);
|
||||
|
||||
Reference in New Issue
Block a user