From ccfd024aeffa0fb760ac7cd13cad11cfad12a097 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 24 Aug 2026 10:33:05 +0800 Subject: [PATCH] fix phase3 phase4 development acceptance blockers --- deploy/sql/头程数据库统一增量-MySQL8.sql | 31 +++++++++++++++++++ deploy/sql/海外区域统一增量-MySQL8.sql | 17 ++++++++++ .../baiyue/firstleg/dto/SmartLoadingDtos.java | 6 ++-- .../CarrierScheduleQuerySqlContractTest.java | 9 ++++++ ...artLoadingCandidateMapperContractTest.java | 14 +++++++++ .../src/main/resources/db/lastleg.sql | 3 +- .../LocalLooseboardSchemaContractTest.java | 2 ++ .../booking-supplier-scope-contract.test.mjs | 5 +-- .../inbound-weight-ocr.mjs | 4 +-- .../admin-test-env-modules/ocean-bookings.mjs | 5 ++- ...-inbound-status-fixtures-contract.test.mjs | 10 +++--- .../order-inbound-status-fixtures.mjs | 17 +++------- scripts/db/incremental.sql | 17 +++++++++- 13 files changed, 112 insertions(+), 28 deletions(-) diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index 2bdf80d11..4570d9bdc 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -10626,6 +10626,37 @@ DELIMITER ; CALL migrate_carrier_schedule_retry_columns(); DROP PROCEDURE IF EXISTS migrate_carrier_schedule_retry_columns; +-- CREATE TABLE IF NOT EXISTS 不会替换历史 CHECK 与唯一索引。统一收敛到当前 +-- READY/MANUAL_REQUIRED/STOPPED、五种访问方式、十个实际节点和每业务范围唯一任务。 +ALTER TABLE fl_carrier_schedule_query + DROP CHECK chk_carrier_schedule_query_access, + DROP CHECK chk_carrier_schedule_query_status; +DROP PROCEDURE IF EXISTS drop_legacy_carrier_schedule_node_check; +DELIMITER $$ +CREATE PROCEDURE drop_legacy_carrier_schedule_node_check() +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.table_constraints + WHERE constraint_schema=DATABASE() AND table_name='fl_carrier_schedule_query' + AND constraint_name='chk_carrier_schedule_query_node' AND constraint_type='CHECK') THEN + ALTER TABLE fl_carrier_schedule_query DROP CHECK chk_carrier_schedule_query_node; + END IF; +END$$ +DELIMITER ; +CALL drop_legacy_carrier_schedule_node_check(); +DROP PROCEDURE IF EXISTS drop_legacy_carrier_schedule_node_check; +ALTER TABLE fl_carrier_schedule_query + ADD CONSTRAINT chk_carrier_schedule_query_access CHECK + (access_mode IN ('PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API')), + ADD CONSTRAINT chk_carrier_schedule_query_status CHECK + (status IN ('READY','MANUAL_REQUIRED','STOPPED')), + ADD CONSTRAINT chk_carrier_schedule_query_node CHECK + (latest_standard_node IS NULL OR latest_standard_node IN + ('DEPARTED_WAREHOUSE','AT_ORIGIN_PORT','LOADED_ON_VESSEL','DEPARTED_ORIGIN_PORT','IN_TRANSIT', + 'ARRIVED_DESTINATION_PORT','DISCHARGED_FROM_VESSEL','PICKUP_READY','PICKED_UP','EMPTY_RETURNED')), + DROP INDEX uk_carrier_schedule_query_scope, + ADD UNIQUE KEY uk_carrier_schedule_query_scope + (customer_id,ocean_booking_id,container_scope_id); + SET @carrier_schedule_now = CONVERT_TZ(UTC_TIMESTAMP(3),'+00:00','+08:00'); SET @carrier_schedule_next = CASE WHEN TIME(@carrier_schedule_now) < '09:00:00' THEN DATE(@carrier_schedule_now) + INTERVAL 9 HOUR diff --git a/deploy/sql/海外区域统一增量-MySQL8.sql b/deploy/sql/海外区域统一增量-MySQL8.sql index c0593faa6..6b02596d9 100644 --- a/deploy/sql/海外区域统一增量-MySQL8.sql +++ b/deploy/sql/海外区域统一增量-MySQL8.sql @@ -187,6 +187,23 @@ CREATE TABLE IF NOT EXISTS ll_local_looseboard_history ( KEY idx_ll_local_looseboard_history(customer_id,order_id,created_at) ) ENGINE=InnoDB COMMENT='本土散板操作流水'; +-- 尾程主订单状态历史补齐客户隔离键。代码写入 customer_id,已有表需显式升级; +-- 先允许 NULL 以便从主订单回填,再收紧为 NOT NULL,脚本可重复执行。 +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() + AND table_name='ll_loading_order_status_history' AND column_name='customer_id'), 'SELECT 1', + 'ALTER TABLE ll_loading_order_status_history ADD COLUMN customer_id VARCHAR(128) NULL COMMENT ''客户数据隔离键'' AFTER id')); +PREPARE phase4_loading_history_customer_stmt FROM @ddl; EXECUTE phase4_loading_history_customer_stmt; DEALLOCATE PREPARE phase4_loading_history_customer_stmt; +UPDATE ll_loading_order_status_history history +JOIN ll_order orders ON orders.id=history.order_id +SET history.customer_id=orders.customer_id +WHERE history.customer_id IS NULL; +ALTER TABLE ll_loading_order_status_history + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() + AND table_name='ll_loading_order_status_history' AND index_name='idx_ll_loading_history_customer_order'), 'SELECT 1', + 'ALTER TABLE ll_loading_order_status_history ADD KEY idx_ll_loading_history_customer_order(customer_id,order_id,created_at)')); +PREPARE phase4_loading_history_customer_stmt FROM @ddl; EXECUTE phase4_loading_history_customer_stmt; DEALLOCATE PREPARE phase4_loading_history_customer_stmt; + -- 四期平台排车查询、智能预排与草稿锁;不开放编辑、发车、PDA 出库或派送。 CREATE TABLE IF NOT EXISTS ll_dispatch_order ( id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', diff --git a/firstleg/src/main/java/com/baiyue/firstleg/dto/SmartLoadingDtos.java b/firstleg/src/main/java/com/baiyue/firstleg/dto/SmartLoadingDtos.java index 68961190b..4bc1efeea 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/dto/SmartLoadingDtos.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/dto/SmartLoadingDtos.java @@ -1,5 +1,5 @@ package com.baiyue.firstleg.dto; -import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import jakarta.validation.constraints.*; import java.math.BigDecimal; import java.util.List; +import com.baiyue.firstleg.entity.LoadingPlanItemEntity; import jakarta.validation.constraints.*; import org.apache.ibatis.annotations.AutomapConstructor; import java.math.BigDecimal; import java.util.List; public final class SmartLoadingDtos { private SmartLoadingDtos(){} public record InboundCandidate(Long accountId, Long inboundId, Long orderId, String orderNo, String inboundCode, Long warehouseId, String channelCode, String channelName, Long destinationWarehouseId, @@ -7,8 +7,10 @@ public final class SmartLoadingDtos { private SmartLoadingDtos(){} Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg, BigDecimal averageUnitVolumeCbm, BigDecimal availableVolumeCbm) {} public record Candidate(Long accountId, Long inboundId, Long inboundDetailId, Long orderId, String orderNo, String inboundCode, - Long warehouseId, String channelCode, String channelName, Long destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName, String customerName, String goodsName, Integer availablePackageCount, + Long warehouseId, String channelCode, String channelName, Long destinationWarehouseId, String destinationWarehouseCode, String destinationWarehouseName, String customerName, String goodsName, Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg, BigDecimal unitVolumeCbm) { + @AutomapConstructor + public Candidate { } public Candidate(Long accountId, Long inboundId, Long inboundDetailId, Long orderId, String orderNo, String inboundCode, Long warehouseId, String destinationWarehouseCode, String customerName, String goodsName, Integer availablePackageCount, BigDecimal unitGrossWeightKg, BigDecimal unitNetWeightKg, BigDecimal unitVolumeCbm) { diff --git a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java index 4ee740534..ba178fd07 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/repository/CarrierScheduleQuerySqlContractTest.java @@ -74,6 +74,15 @@ class CarrierScheduleQuerySqlContractTest { .contains("ADD COLUMN attempt_count INT UNSIGNED NOT NULL DEFAULT 0") .contains("ADD COLUMN next_allowed_at DATETIME(3) NULL") .contains("ADD KEY idx_carrier_schedule_query_due (status,next_allowed_at,id)") + .contains("DROP CHECK chk_carrier_schedule_query_access") + .contains("DROP CHECK chk_carrier_schedule_query_status") + .contains("DROP INDEX uk_carrier_schedule_query_scope") + .contains("ADD UNIQUE KEY uk_carrier_schedule_query_scope") + .contains("(customer_id,ocean_booking_id,container_scope_id)") + .contains("'PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API'") + .contains("status IN ('READY','MANUAL_REQUIRED','STOPPED')") + .contains("ARRIVED_DESTINATION_PORT") + .contains("EMPTY_RETURNED") .contains("SET @carrier_schedule_now = CONVERT_TZ(UTC_TIMESTAMP(3),'+00:00','+08:00')") .contains("SET @carrier_schedule_next = CASE") .contains("CREATE PROCEDURE ensure_carrier_schedule_retry_check()") diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java index b74c68455..114087cce 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java @@ -1,6 +1,8 @@ package com.baiyue.firstleg.repository; import com.baiyue.firstleg.mapper.SmartLoadingCandidateMapper; +import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; +import org.apache.ibatis.annotations.AutomapConstructor; import org.apache.ibatis.annotations.Select; import org.junit.jupiter.api.Test; @@ -9,6 +11,18 @@ import java.lang.reflect.Method; import static org.assertj.core.api.Assertions.assertThat; class SmartLoadingCandidateMapperContractTest { + @Test + void canonicalCandidateConstructorIsTheUniqueMybatisAutomapTarget() { + long annotated = java.util.Arrays.stream(Candidate.class.getDeclaredConstructors()) + .filter(constructor -> constructor.isAnnotationPresent(AutomapConstructor.class)) + .count(); + + assertThat(annotated).isEqualTo(1); + assertThat(java.util.Arrays.stream(Candidate.class.getDeclaredConstructors()) + .filter(constructor -> constructor.isAnnotationPresent(AutomapConstructor.class)) + .findFirst().orElseThrow().getParameterCount()).isEqualTo(18); + } + @Test void selectsLatestApprovedBatch() throws Exception { Method list = SmartLoadingCandidateMapper.class.getDeclaredMethod("candidates", long.class); diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index 941b7842d..5da318484 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -104,6 +104,7 @@ CREATE TABLE IF NOT EXISTS ll_order_detail ( CREATE TABLE IF NOT EXISTS ll_loading_order_status_history ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '状态历史主键', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', order_id BIGINT NOT NULL COMMENT '尾程订单 ID', from_status VARCHAR(32) COMMENT '变更前状态', to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', @@ -111,7 +112,7 @@ CREATE TABLE IF NOT EXISTS ll_loading_order_status_history ( remark VARCHAR(500) COMMENT '状态变更备注', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录时间', PRIMARY KEY(id), - KEY idx_ll_loading_history_order(order_id,created_at) + KEY idx_ll_loading_history_customer_order(customer_id,order_id,created_at) ) ENGINE=InnoDB COMMENT='尾程订单状态变更历史'; CREATE TABLE IF NOT EXISTS ll_event_conflict ( diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java index 8aef0fe85..d0b305fce 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java @@ -19,5 +19,7 @@ class LocalLooseboardSchemaContractTest { assertTrue(sql.contains("uk_ll_local_looseboard_no(customer_id,order_no)")); assertTrue(sql.contains("lastleg_order_id BIGINT NULL")); assertTrue(sql.contains("uk_ll_local_looseboard_lastleg(lastleg_order_id)")); + assertTrue(sql.contains("customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键'")); + assertTrue(sql.contains("idx_ll_loading_history_customer_order(customer_id,order_id,created_at)")); } } diff --git a/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs b/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs index e7266b5c6..c5260832b 100644 --- a/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs +++ b/scripts/admin-test-env-modules/booking-supplier-scope-contract.test.mjs @@ -38,7 +38,8 @@ test('Firstleg booking supplier snapshot requires ACTIVE BOOKING and has no dome test('HTTP ocean booking acceptance selects an ACTIVE BOOKING supplier without a geographic scope', () => { assert.match(oceanBookingFixture, /category=BOOKING&status=ACTIVE/) - assert.match(oceanBookingFixture, /bookingAgentId: bookingAgent\.supplierId/) - assert.match(oceanBookingFixture, /订舱持久化启用订舱供应商快照/) + assert.match(oceanBookingFixture, /bookingAgentId: id\(agent\.id\), bookingAgentName: agent\.name/) + assert.match(oceanBookingFixture, /shippingCompanyId: id\(agent\.id\), shippingCompanyName: agent\.name/) + assert.match(oceanBookingFixture, /使用 ACTIVE BOOKING 订舱代理事实源/) assert.doesNotMatch(oceanBookingFixture, /category=BOOKING[^'\n]*scope=/) }) diff --git a/scripts/admin-test-env-modules/inbound-weight-ocr.mjs b/scripts/admin-test-env-modules/inbound-weight-ocr.mjs index a0962a2d1..d26bef16d 100644 --- a/scripts/admin-test-env-modules/inbound-weight-ocr.mjs +++ b/scripts/admin-test-env-modules/inbound-weight-ocr.mjs @@ -475,8 +475,8 @@ export async function run(context) { const confirmedSummary = confirmedNetSummary(afterConfirm.workspace) context.assert(MODULE, '货物总重量按全部已确认净重由服务端汇总', afterConfirm.response, confirmedSummary.complete && confirmedSummary.count === baselineImageSummary.count + 1 - && closeNumber(afterConfirm.workspace?.actualGrossWeightKg, confirmedSummary.total, 0.0011), - `confirmedCount=${confirmedSummary.count}, confirmedNetTotal=${confirmedSummary.total}, actual=${afterConfirm.workspace?.actualGrossWeightKg ?? 'missing'}`, + && closeNumber(afterConfirm.workspace?.actualNetWeightKg, confirmedSummary.total, 0.0011), + `confirmedCount=${confirmedSummary.count}, confirmedNetTotal=${confirmedSummary.total}, actual=${afterConfirm.workspace?.actualNetWeightKg ?? 'missing'}`, NEW_CONTRACT_OPTIONS) const staleDelete = await context.request('DELETE', diff --git a/scripts/admin-test-env-modules/ocean-bookings.mjs b/scripts/admin-test-env-modules/ocean-bookings.mjs index 7c17d5d9d..96aed8769 100644 --- a/scripts/admin-test-env-modules/ocean-bookings.mjs +++ b/scripts/admin-test-env-modules/ocean-bookings.mjs @@ -47,7 +47,10 @@ export async function run(context) { step(context, '使用 ACTIVE BOOKING 订舱代理事实源', agentResponse, Boolean(agent?.id && agent?.name), `supplierId=${id(agent?.id) || 'missing'}, scope=${agent?.scope || 'not-exposed'}`) if (!agent?.id || !agent?.name) return - const data = fullDraft(context, suffix, master, { bookingAgentId: id(agent.id), bookingAgentName: agent.name }) + const data = fullDraft(context, suffix, master, { + bookingAgentId: id(agent.id), bookingAgentName: agent.name, + shippingCompanyId: id(agent.id), shippingCompanyName: agent.name, + }) const create = await request(context, 'POST', BASE, { expectedVersion: 0, data }, { 'Idempotency-Key': `${context.marker}-ocean-draft-create`.slice(0, 128) }) const created = payload(create); const bookingId = id(created?.oceanBooking?.id) diff --git a/scripts/admin-test-env-modules/order-inbound-status-fixtures-contract.test.mjs b/scripts/admin-test-env-modules/order-inbound-status-fixtures-contract.test.mjs index 45a686dd1..5602f6540 100644 --- a/scripts/admin-test-env-modules/order-inbound-status-fixtures-contract.test.mjs +++ b/scripts/admin-test-env-modules/order-inbound-status-fixtures-contract.test.mjs @@ -36,12 +36,12 @@ test('状态矩阵复用订单共享的逐仓真实报价选择,不假设首 test('入库通过真实订单测量链保留测量态并最终进入 WAIT_SPLIT', () => { assert.match(source, /result\.inbounds\.WAIT_MEASURE/) assert.match(source, /processStatus: 'WAIT_MEASURE'/) - for (const status of ['MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED', 'WAIT_SPLIT']) { + for (const status of ['MEASURING', 'MEASURE_PENDING', 'WAIT_SPLIT']) { assert.match(source, new RegExp(`'${status}'`)) } assert.match(source, /\.\/inbound-measurement\.mjs/) - assert.match(source, /`\/api\/platform\/v1\/inbounds\/\$\{query\(inboundId\)\}\/measurements\/reject\?tenantId=\$\{query\(tenantId\)\}`/) - assert.doesNotMatch(source, /path\.replace\('\/measurement\/submit', '\/measurements\/reject'\)/) + assert.match(source, /入库列表保留 MEASURE_REJECTED/) + assert.match(source, /稳定 Admin 契约未开放 measurements\/reject/) }) test('FBA 状态样本为订单头和每条货物行提供四个必填标识', () => { @@ -60,9 +60,7 @@ test('测量状态截停发生在正确的 HTTP 边界', () => { assert.match(source, /target !== 'MEASURING'/) assert.doesNotMatch(source, /target === 'MEASURING' && ok\(response\)/) assert.match(source, /target === 'MEASURE_PENDING' && isSubmit/) - assert.match(source, /target === 'MEASURE_REJECTED' && isSubmit/) - assert.match(source, /measurements\/reject/) - assert.match(source, /expectedVersion: version/) + assert.doesNotMatch(source, /target === 'MEASURE_REJECTED' && isSubmit/) }) test('入库不可独立保留状态显式阻断', () => { diff --git a/scripts/admin-test-env-modules/order-inbound-status-fixtures.mjs b/scripts/admin-test-env-modules/order-inbound-status-fixtures.mjs index cfd20c090..86e269a01 100644 --- a/scripts/admin-test-env-modules/order-inbound-status-fixtures.mjs +++ b/scripts/admin-test-env-modules/order-inbound-status-fixtures.mjs @@ -191,18 +191,6 @@ async function prepareInboundStatus(context, target) { if (!ok(response)) throw new Error(`MEASURE_PENDING 提交失败: HTTP=${response?.http}, code=${response?.body?.code}`) throw stop } - if (target === 'MEASURE_REJECTED' && isSubmit) { - const submitted = await originalRequest.call(context, method, path, body, token, headers) - if (!ok(submitted)) throw new Error(`MEASURE_REJECTED 提交失败: HTTP=${submitted?.http}, code=${submitted?.body?.code}`) - const latest = await originalRequest.call(context, 'GET', detailPath, undefined, token) - const version = Number(data(latest)?.summary?.version) - const rejectPath = `/api/platform/v1/inbounds/${query(inboundId)}/measurements/reject?tenantId=${query(tenantId)}` - const rejected = await originalRequest.call(context, 'POST', rejectPath, - { measurementId: body?.measurementId, expectedVersion: version, reason: `${fixtureMarker} 保留审核驳回样本` }, - token, { 'Idempotency-Key': `${fixtureMarker}-reject`.slice(0, 128) }) - if (!ok(rejected)) throw new Error(`MEASURE_REJECTED 审核驳回失败: HTTP=${rejected?.http}, code=${rejected?.body?.code}, requestId=${rejected?.requestId || 'missing'}, traceId=${rejected?.traceId || 'missing'}`) - throw stop - } return originalRequest.call(context, method, path, body, token, headers) } if (target !== 'MEASURING') { @@ -303,7 +291,7 @@ export async function run(context, matrix = context.createdResources.manualState id: waitMeasure.orderId, status: 'WAIT_MEASURE', detailPath: `/orders/${waitMeasure.orderId}`, } - for (const status of ['MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED', 'WAIT_SPLIT']) { + for (const status of ['MEASURING', 'MEASURE_PENDING', 'WAIT_SPLIT']) { const sample = await prepareInboundStatus(context, status) result.inbounds[status] = sample matrix.pages.inbounds.fixtures[status] = { @@ -317,6 +305,9 @@ export async function run(context, matrix = context.createdResources.manualState } } + recordCoverageGap(context, '入库列表保留 MEASURE_REJECTED', + '当前稳定 Admin 契约未开放 measurements/reject,不能通过正式 HTTP 构造审核驳回样本') + if (!result.inbounds.WAIT_SPLIT) { const legacyResponse = await context.request('GET', `/api/platform/v1/inbounds?tenantId=${query(fixture.tenantId)}&processStatus=WAIT_SPLIT&page=1&size=20`) diff --git a/scripts/db/incremental.sql b/scripts/db/incremental.sql index ab75274ca..3a9bf24a9 100644 --- a/scripts/db/incremental.sql +++ b/scripts/db/incremental.sql @@ -484,6 +484,7 @@ CREATE TABLE IF NOT EXISTS ll_order_detail ( CREATE TABLE IF NOT EXISTS ll_loading_order_status_history ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '状态历史主键', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', order_id BIGINT NOT NULL COMMENT '尾程订单ID', from_status VARCHAR(32) NULL COMMENT '变更前状态', to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', @@ -491,7 +492,7 @@ CREATE TABLE IF NOT EXISTS ll_loading_order_status_history ( remark VARCHAR(500) NULL COMMENT '状态变更备注', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录时间', PRIMARY KEY (id), - KEY idx_ll_loading_history_order (order_id,created_at) + KEY idx_ll_loading_history_customer_order (customer_id,order_id,created_at) ) ENGINE=InnoDB COMMENT='尾程订单状态变更历史'; -- Existing order installations: add transfer fields without replacing row data. @@ -3685,6 +3686,20 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE AND table_name='ll_local_looseboard_order' AND index_name='uk_ll_local_looseboard_lastleg'), 'SELECT 1', 'ALTER TABLE ll_local_looseboard_order ADD UNIQUE KEY uk_ll_local_looseboard_lastleg(lastleg_order_id)')); PREPARE phase4_looseboard_tail_030 FROM @ddl; EXECUTE phase4_looseboard_tail_030; DEALLOCATE PREPARE phase4_looseboard_tail_030; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() + AND table_name='ll_loading_order_status_history' AND column_name='customer_id'), 'SELECT 1', + 'ALTER TABLE ll_loading_order_status_history ADD COLUMN customer_id VARCHAR(128) NULL COMMENT ''客户数据隔离键'' AFTER id')); +PREPARE phase4_loading_history_customer_030 FROM @ddl; EXECUTE phase4_loading_history_customer_030; DEALLOCATE PREPARE phase4_loading_history_customer_030; +UPDATE ll_loading_order_status_history history +JOIN ll_order orders ON orders.id=history.order_id +SET history.customer_id=orders.customer_id +WHERE history.customer_id IS NULL; +ALTER TABLE ll_loading_order_status_history + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() + AND table_name='ll_loading_order_status_history' AND index_name='idx_ll_loading_history_customer_order'), 'SELECT 1', + 'ALTER TABLE ll_loading_order_status_history ADD KEY idx_ll_loading_history_customer_order(customer_id,order_id,created_at)')); +PREPARE phase4_loading_history_customer_030 FROM @ddl; EXECUTE phase4_loading_history_customer_030; DEALLOCATE PREPARE phase4_loading_history_customer_030; -- ============================================================================ -- 2026-08-20 / 029