fix phase3 phase4 development acceptance blockers

This commit is contained in:
2026-08-24 10:33:05 +08:00
parent 81899015cf
commit ccfd024aef
13 changed files with 112 additions and 28 deletions
@@ -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=/)
})
@@ -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',
@@ -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)
@@ -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('入库不可独立保留状态显式阻断', () => {
@@ -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`)
+16 -1
View File
@@ -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