fix(acceptance): reuse initialized pallet fixtures
This commit is contained in:
@@ -6988,7 +6988,8 @@ ON DUPLICATE KEY UPDATE
|
||||
capacity_cbm = VALUES(capacity_cbm),
|
||||
weight_kg = VALUES(weight_kg),
|
||||
color = VALUES(color),
|
||||
remark = VALUES(remark);
|
||||
remark = VALUES(remark),
|
||||
deleted = 0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_domestic_pallet_seed;
|
||||
DELIMITER //
|
||||
@@ -7010,6 +7011,14 @@ BEGIN
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '国内作业仓木托盘、塑料托盘初始化不完整';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*)
|
||||
FROM wh_pallet
|
||||
WHERE tenant_id = 0
|
||||
AND deleted = 0
|
||||
AND ((warehouse_id = 193000000000200001 AND pallet_code IN ('TP-001','YW-TP-002'))
|
||||
OR (warehouse_id = 193000000000200002 AND pallet_code IN ('TP-002','DG-TP-002')))) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '国内作业仓固定可复用托盘编码初始化不完整';
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
CALL assert_domestic_pallet_seed();
|
||||
|
||||
@@ -1317,7 +1317,7 @@ export class AcceptanceRun {
|
||||
const createdKeys = ['orders', 'ocrInbound', 'testInbound', 'inboundMeasurement']
|
||||
const contextKeys = ['inboundWeightOcr', 'ocrInbound', 'testInbound', 'createdInbound',
|
||||
'phase3LoadingRequireSharedOverflow', 'phase3LoadingIncrementalPackageCount',
|
||||
'phase3LoadingIncrementalDetailCount', 'orderProfile', 'measurementPalletOverride']
|
||||
'phase3LoadingIncrementalDetailCount', 'orderProfile']
|
||||
const createdSnapshot = new Map(createdKeys.map((key) => [key, {
|
||||
present: Object.hasOwn(this.createdResources, key), value: this.createdResources[key],
|
||||
}]))
|
||||
@@ -1362,9 +1362,6 @@ export class AcceptanceRun {
|
||||
: { forced: false, disabled: [] }
|
||||
this.context.phase3LoadingRequireSharedOverflow = requireSharedOverflow
|
||||
try {
|
||||
if (requireSharedOverflow) {
|
||||
this.context.measurementPalletOverride = await this.createPhase3FreshMeasurementPallet(warehouseId)
|
||||
}
|
||||
const measurement = await import(new URL('./admin-test-env-modules/inbound-measurement.mjs', import.meta.url))
|
||||
await this.executeModule('inbound-measurement-phase3-loading-fixture', () => measurement.run(this))
|
||||
} finally {
|
||||
@@ -1397,7 +1394,6 @@ export class AcceptanceRun {
|
||||
const incrementalMarker = `${originalMarker}-LOAD${suffix}-INC`
|
||||
this.marker = incrementalMarker
|
||||
this.context.phase3LoadingRequireSharedOverflow = false
|
||||
delete this.context.measurementPalletOverride
|
||||
this.context.phase3LoadingIncrementalPackageCount = 2
|
||||
this.context.phase3LoadingIncrementalDetailCount = 4
|
||||
// orders.mjs derives the actual SKU/package shape from waitSplitVariant. Override that
|
||||
@@ -1609,37 +1605,6 @@ export class AcceptanceRun {
|
||||
{ scenario: 'phase3-phase4-producer-fixture', deploymentBlocker: true })
|
||||
}
|
||||
|
||||
async createPhase3FreshMeasurementPallet(warehouseId) {
|
||||
const module = 'phase3-loading-fixture'
|
||||
const options = { scenario: 'phase3-loading-a0-fixture', deploymentBlocker: true }
|
||||
const payload = {
|
||||
warehouseId, palletType: 'STANDARD', capacityCbm: 1.5, weightKg: 25,
|
||||
color: 'ORANGE', remark: `${this.marker}-inventory-snapshot-a0`,
|
||||
}
|
||||
const createResponse = await this.request('POST', '/api/platform/v1/pallets?tenantId=0', payload)
|
||||
const palletId = String(this.apiSuccess(module, '创建本轮库存快照专用称重托盘', createResponse,
|
||||
`warehouseId=${warehouseId}`, options) || '')
|
||||
if (!palletId) return null
|
||||
|
||||
const detailResponse = await this.request('GET',
|
||||
`/api/platform/v1/pallets/${encodeURIComponent(palletId)}?tenantId=0&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const pallet = this.apiSuccess(module, '回读本轮库存快照专用称重托盘', detailResponse,
|
||||
`palletId=${palletId}`, options)
|
||||
const valid = String(pallet?.id || '') === palletId
|
||||
&& String(pallet?.warehouseId || '') === String(warehouseId)
|
||||
&& Boolean(String(pallet?.palletCode || '').trim())
|
||||
this.assert(module, '本轮专用称重托盘尚未复用历史占用身份', detailResponse, valid,
|
||||
`palletId=${palletId}, palletCode=${pallet?.palletCode || 'missing'}`, options)
|
||||
if (!valid) return null
|
||||
|
||||
const fixture = {
|
||||
id: palletId, code: String(pallet.palletCode), warehouseId: String(warehouseId),
|
||||
marker: this.marker, retainedReason: '测量审核后成为正式库存占用,保留为开发验收证据',
|
||||
}
|
||||
this.createdResources.phase3LoadingFreshMeasurementPallet = fixture
|
||||
return fixture
|
||||
}
|
||||
|
||||
async listAvailableLocationsForA0(warehouseId, module, options) {
|
||||
const locations = []
|
||||
const seen = new Set()
|
||||
|
||||
@@ -140,7 +140,7 @@ node --env-file="$env:USERPROFILE\.codex\chongchong-global-dev-acceptance.env" s
|
||||
|
||||
其中,无号 push 只冻结装柜计划、建立 bridge 并使预装可见,不得创建 PDA task;订舱放舱也不创建真实货柜或 allocation。港口取得真实柜号后,四字段 link 直接补全原模拟柜并创建 allocation,原 `containerBookingId/cabinetNo/loadingPlan/items` 不变;未关联、订舱未 `RELEASED` 或整份计划未确认时,PDA 查询必须返回业务码 `A0404`。唯一确认入口为 `POST /api/platform/v1/first-leg/loading-plans/{loadingPlanId}/confirm`,请求体只含当前计划 `expectedVersion` 并携带 `Idempotency-Key`;成功必须返回 `confirmationStatus=CONFIRMED`、`readiness.readyForPda=true` 和唯一 `readiness.pdaTaskId`。验收脚本禁止调用旧入库单级 `/preload/confirming`、`/preload/confirm`。
|
||||
|
||||
计划在 `EDITING/LOADING` 可通过正式 assignments/PATCH/DELETE 端点增量;计划、任务和明细版本必须递增,PDA 四元身份不变,旧 `inventorySnapshotToken` 必须返回 `A0409 库存已变化,请刷新后重试`。完整国内货柜履约装柜 fixture 通过平台托盘 API 创建本轮专用称重托盘,再交给通用测量模块;当普通位不足时由 Warehouse 自然分配到 A0,随后仅使用公开 A0 换绑接口制造库存版本变化,不直连数据库或调用内部 Warehouse 接口。主国内货柜履约链强制完成该专项,海外仓业务生产矩阵的附加柜只负责跨区生产,不重复要求 A0;附加柜若自然落入 A0 仍执行同一验证。批量待分柜入口仍只复用 `TP-001`/`YW-TP-002`,不创建托盘。已拣项不可删或减量,未拣项可删;增量会把全拣的 `AWAITING_REVIEW` 撤回为 `IN_PROGRESS`,刷新 task detail 与 inventory 后续拣才可再次等待最终确认。最终唯一完成入口仍为 `POST /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/complete`:保留同柜 `RELEASED` 订舱、bridge 与五类 ACTIVE 证据门禁;拖车状态和数量不是前置门禁。主事务提交后,零条有效拖车直接跳过,恰有一条时在独立事务尝试推进 `COMPLETED`,多条或并发冲突只记录拖车同步失败,不回滚已完成的 task/plan/inbound。PDA 结算可能先递增来源入库单版本或创建实装分片,完成动作必须在结算后重新锁定实际完成行并使用当前版本;HTTP finalizer 会回查 full/partial 履约分片,失败时保存 `requestId/traceId` 并断言 task/plan/inbound/operation 零漂移。后续模块必须复用 `phase3Loading` 发布的本轮 `taskId + inboundId + loadingPlanId + containerBookingId`,不得回退历史资源。
|
||||
计划在 `EDITING/LOADING` 可通过正式 assignments/PATCH/DELETE 端点增量;计划、任务和明细版本必须递增,PDA 四元身份不变,旧 `inventorySnapshotToken` 必须返回 `A0409 库存已变化,请刷新后重试`。完整国内货柜履约装柜 fixture 与批量待分柜入口都只随机复用初始化的 `TP-001`/`YW-TP-002`,任一缺失即报告部署阻断,不调用托盘新增接口;当普通位不足时由 Warehouse 自然分配到 A0,随后仅使用公开 A0 换绑接口制造库存版本变化,不直连数据库、创建库位或调用内部 Warehouse 接口。模拟柜的 `channelGroupCode` 统一由 `smart-loading-channel-group.mjs` 根据订单渠道映射。主国内货柜履约链强制完成该专项,海外仓业务生产矩阵的附加柜只负责跨区生产,不重复要求 A0;附加柜若自然落入 A0 仍执行同一验证。已拣项不可删或减量,未拣项可删;增量会把全拣的 `AWAITING_REVIEW` 撤回为 `IN_PROGRESS`,刷新 task detail 与 inventory 后续拣才可再次等待最终确认。最终唯一完成入口仍为 `POST /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/complete`:保留同柜 `RELEASED` 订舱、bridge 与五类 ACTIVE 证据门禁;拖车状态和数量不是前置门禁。主事务提交后,零条有效拖车直接跳过,恰有一条时在独立事务尝试推进 `COMPLETED`,多条或并发冲突只记录拖车同步失败,不回滚已完成的 task/plan/inbound。PDA 结算可能先递增来源入库单版本或创建实装分片,完成动作必须在结算后重新锁定实际完成行并使用当前版本;HTTP finalizer 会回查 full/partial 履约分片,失败时保存 `requestId/traceId` 并断言 task/plan/inbound/operation 零漂移。后续模块必须复用 `phase3Loading` 发布的本轮 `taskId + inboundId + loadingPlanId + containerBookingId`,不得回退历史资源。
|
||||
|
||||
动态增量还会读取计划明细中的服务端 `totalPackageCount/remainingPackageCount`,断言
|
||||
`remaining = total - assigned`;随后尝试把箱数调整为权威总数加一,必须得到 `A0400`,并回查
|
||||
|
||||
@@ -30,13 +30,6 @@ function validPdf(response) {
|
||||
|
||||
async function reusablePallets(context, warehouseId) {
|
||||
const cacheKey = asId(warehouseId)
|
||||
const override = context.context?.measurementPalletOverride
|
||||
if (asId(override?.warehouseId) === cacheKey && asId(override?.id)
|
||||
&& String(override?.code || '').trim()) {
|
||||
context.assert(MODULE, '使用编排层提供的本轮专用称重托盘', null, true,
|
||||
`warehouseId=${warehouseId}, palletId=${asId(override.id)}`, OPTIONS)
|
||||
return [{ id: asId(override.id), code: String(override.code) }]
|
||||
}
|
||||
const allowAnyExistingPallet = context.context?.allowAnyExistingPallet === true
|
||||
const scopedCacheKey = `${cacheKey}:${allowAnyExistingPallet ? 'any' : 'fixed'}`
|
||||
if (reusablePalletsByWarehouse.has(scopedCacheKey)) return reusablePalletsByWarehouse.get(scopedCacheKey)
|
||||
@@ -64,29 +57,11 @@ async function reusablePallets(context, warehouseId) {
|
||||
const exact = (page?.records || []).find((item) => asId(item?.warehouseId) === cacheKey
|
||||
&& String(item?.palletCode || '') === palletCode && asId(item?.id))
|
||||
context.assert(MODULE, `查询原有称重托盘 ${palletCode}`, response, true,
|
||||
`warehouseId=${warehouseId}, matched=${exact?.palletCode || 'missing'}, fallback=${exact ? 'not-needed' : 'create-marker-owned'}`, OPTIONS)
|
||||
`warehouseId=${warehouseId}, matched=${exact?.palletCode || 'missing'}, fallback=disabled`, OPTIONS)
|
||||
if (!exact) {
|
||||
// A newly selected development warehouse may not have the historical fixture pallets.
|
||||
// Create one marker-owned pallet through the public platform API rather than coupling
|
||||
// measurement acceptance to another warehouse's seed data.
|
||||
const createResponse = await context.request('POST', '/api/platform/v1/pallets?tenantId=0', {
|
||||
warehouseId, palletType: 'STANDARD', capacityCbm: 1.5, weightKg: 25,
|
||||
color: 'ORANGE', remark: `${context.marker}-measurement-fixture`,
|
||||
})
|
||||
const createdId = asId(context.apiSuccess(MODULE, '创建本轮称重托盘', createResponse,
|
||||
`warehouseId=${warehouseId}`, OPTIONS))
|
||||
if (!createdId) return null
|
||||
const detailResponse = await context.request('GET',
|
||||
`/api/platform/v1/pallets/${encodeURIComponent(createdId)}?tenantId=0&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const created = context.apiSuccess(MODULE, '回读本轮称重托盘', detailResponse,
|
||||
`warehouseId=${warehouseId}, palletId=${createdId}`, OPTIONS)
|
||||
const valid = asId(created?.id) === createdId && asId(created?.warehouseId) === cacheKey
|
||||
&& String(created?.palletCode || '').trim()
|
||||
context.assert(MODULE, '新建称重托盘归属当前仓库且编码完整', detailResponse, valid,
|
||||
`warehouseId=${warehouseId}, palletId=${createdId}, palletCode=${created?.palletCode || 'missing'}`, OPTIONS)
|
||||
if (!valid) return null
|
||||
pallets.push({ id: createdId, code: String(created.palletCode) })
|
||||
break
|
||||
context.assert(MODULE, '固定称重托盘主数据完整', response, false,
|
||||
`warehouseId=${warehouseId}, missing=${palletCode}`, OPTIONS)
|
||||
return null
|
||||
}
|
||||
pallets.push({ id: asId(exact.id), code: palletCode })
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ test('随机线路只固定义乌仓并从客户真实可计价候选选择', ()
|
||||
assert.doesNotMatch(runner, /channel-name|channel-code/)
|
||||
})
|
||||
|
||||
test('称重优先复用固定托盘并允许为无基线仓创建本轮托盘', () => {
|
||||
test('称重只随机复用固定托盘且不创建新托盘', () => {
|
||||
assert.match(measurement, /REUSABLE_PALLET_CODES = Object\.freeze\(\['TP-001', 'YW-TP-002'\]\)/)
|
||||
assert.match(measurement, /context\.request\('GET',[\s\S]*\/api\/platform\/v1\/pallets\?page=1&size=20/)
|
||||
assert.match(measurement, /randomlyReusePalletIds\(candidates, palletCount/)
|
||||
assert.match(measurement, /随机复用原有称重托盘/)
|
||||
assert.match(measurement, /context\.request\('POST', '\/api\/platform\/v1\/pallets\?tenantId=0'/)
|
||||
assert.match(measurement, /创建本轮称重托盘/)
|
||||
assert.match(measurement, /measurement-fixture/)
|
||||
assert.match(measurement, /固定称重托盘主数据完整/)
|
||||
assert.doesNotMatch(measurement, /context\.request\('POST', '\/api\/platform\/v1\/pallets/)
|
||||
assert.doesNotMatch(acceptance, /createPhase3FreshMeasurementPallet/)
|
||||
})
|
||||
|
||||
test('称重脚本不新增库位且容量不足由仓库服务复用 A0 公共位', () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { diffPushPreloadStability, projectPushPreloadStability } from './push-pr
|
||||
import { containerFieldGaps, loadingListFieldGaps, preloadFieldGaps } from './fulfillment-list-field-contract.mjs'
|
||||
import { ensureMatsonCarrierPartner } from './carrier-partner-fixture.mjs'
|
||||
import { iso6346ContainerNumber } from './iso6346-container-number.mjs'
|
||||
import { channelGroup } from './smart-loading-channel-group.mjs'
|
||||
|
||||
const MODULE = 'phase3-container-loading'
|
||||
const OPTIONS = { scenario: 'phase3-container-smart-loading-pda', deploymentBlocker: true }
|
||||
@@ -208,7 +209,7 @@ export async function run(context) {
|
||||
context.assert(MODULE, '订舱渠道与仓库快照完整', inboundResponse, Boolean(channelCode && channelName && warehouseName),
|
||||
`channelCode=${channelCode || 'missing'}, warehouseId=${warehouseId}, warehouseName=${warehouseName || 'missing'}`, OPTIONS)
|
||||
const simulationResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode, warehouseId, containerType: primaryRequirement.containerType,
|
||||
channelGroupCode: channelGroup(channelCode), warehouseId, containerType: primaryRequirement.containerType,
|
||||
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg,
|
||||
})
|
||||
const simulation = context.apiSuccess(MODULE, '新增内部模拟柜', simulationResponse,
|
||||
@@ -216,7 +217,8 @@ export async function run(context) {
|
||||
const containerId = id(simulation?.id)
|
||||
context.assert(MODULE, '模拟柜由服务端生成稳定身份且不伪造真实柜号或订舱', simulationResponse,
|
||||
Boolean(containerId) && Boolean(simulation?.cabinetNo) && simulation?.containerNo == null
|
||||
&& simulation?.oceanBookingId == null && simulation?.channelCode === channelCode
|
||||
&& simulation?.oceanBookingId == null && simulation?.channelGroupCode === channelGroup(channelCode)
|
||||
&& simulation?.channelCode == null
|
||||
&& id(simulation?.warehouseId) === warehouseId,
|
||||
`containerBookingId=${containerId || 'missing'}, cabinetNo=${simulation?.cabinetNo || 'missing'}, oceanBookingId=${id(simulation?.oceanBookingId) || 'null'}`, OPTIONS)
|
||||
if (!containerId) return
|
||||
@@ -240,7 +242,8 @@ export async function run(context) {
|
||||
} else {
|
||||
const foreignChannel = channelIsolation.pair.foreign
|
||||
const foreignContainerResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode: foreignChannel.channelCode, warehouseId, containerType: primaryRequirement.containerType,
|
||||
channelGroupCode: channelGroup(foreignChannel.channelCode), warehouseId,
|
||||
containerType: primaryRequirement.containerType,
|
||||
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg,
|
||||
})
|
||||
const foreignContainer = context.apiSuccess(MODULE, '新增真实异渠道模拟柜', foreignContainerResponse, '', OPTIONS)
|
||||
@@ -288,9 +291,10 @@ export async function run(context) {
|
||||
context.assert(MODULE, '货柜详情保留候选货物路线约束', containerDetailResponse,
|
||||
(!containerDetail?.destinationWarehouseCode || !candidate.destinationWarehouseCode
|
||||
|| containerDetail.destinationWarehouseCode === candidate.destinationWarehouseCode)
|
||||
&& containerDetail?.channelCode === channelCode
|
||||
&& containerDetail?.channelGroupCode === channelGroup(channelCode)
|
||||
&& containerDetail?.channelCode == null
|
||||
&& ['EDITING', 'WAIT_LOADING'].includes(containerDetail?.planStatus),
|
||||
`planStatus=${containerDetail?.planStatus || 'missing'}, channel=${containerDetail?.channelCode || 'missing'}`, OPTIONS)
|
||||
`planStatus=${containerDetail?.planStatus || 'missing'}, channelGroup=${containerDetail?.channelGroupCode || 'missing'}`, OPTIONS)
|
||||
|
||||
const containerNo = id(containerDetail?.containerNo || containerDetail?.cabinetNo)
|
||||
const planResponse = await request(context, 'GET', `${SMART}/plans/by-booking/${encodeURIComponent(containerId)}`)
|
||||
@@ -517,7 +521,7 @@ export async function run(context) {
|
||||
`containerBookingId=${id(mergedContainer?.containerBookingId) || 'missing'}, requirementId=${id(mergedContainer?.requirementId) || 'missing'}, allocationId=${id(mergedContainer?.allocationId) || 'missing'}`, OPTIONS)
|
||||
|
||||
const duplicateSimulationResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode, warehouseId, containerType: primaryRequirement.containerType,
|
||||
channelGroupCode: channelGroup(channelCode), warehouseId, containerType: primaryRequirement.containerType,
|
||||
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg,
|
||||
})
|
||||
const duplicateSimulation = context.apiSuccess(MODULE, '新增重复容量占用负向模拟柜', duplicateSimulationResponse, '', OPTIONS)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { diffPushPreloadStability, projectPushPreloadStability } from './push-preload-stability.mjs'
|
||||
import { isPureStatusNoop, visibleLoadingHistories } from './loading-history-noop.mjs'
|
||||
import { ensureMatsonCarrierPartner } from './carrier-partner-fixture.mjs'
|
||||
import { channelGroup } from './smart-loading-channel-group.mjs'
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
const MODULE = 'phase3-loading'
|
||||
@@ -135,20 +136,6 @@ function inventorySignature(snapshot) {
|
||||
})))
|
||||
}
|
||||
|
||||
function channelGroup(channelCode) {
|
||||
const value = String(channelCode || '').toUpperCase()
|
||||
if (value.startsWith('EXX_')) return 'EXX'
|
||||
if (value.startsWith('MATSON_CLX_')) return 'MATSON_CLX'
|
||||
if (value.startsWith('MATSON_MAX_')) return 'MATSON_MAX'
|
||||
if (value.startsWith('HEDE_')) return 'HEDE'
|
||||
if (value.startsWith('OA_LA_') || value === 'OA_PARCEL') return 'OA_LA'
|
||||
if (value.startsWith('OA_CHICAGO_')) return 'OA_CHICAGO'
|
||||
if (value.startsWith('OA_HOUSTON_')) return 'OA_HOUSTON'
|
||||
if (value.startsWith('OA_SAVANNAH_')) return 'OA_SAVANNAH'
|
||||
if (value === 'OA_NEW_YORK_TRUCK') return value
|
||||
throw new Error(`unsupported smart-loading channel group: ${channelCode}`)
|
||||
}
|
||||
|
||||
function operationsSignature(operations) {
|
||||
return JSON.stringify((operations || []).map((operation) => ({
|
||||
id: id(operation?.id), operationId: operation?.operationId || null,
|
||||
@@ -226,7 +213,7 @@ export async function run(context) {
|
||||
maxWeightKg: 28000 }],
|
||||
}
|
||||
const simulationResponse = await request(context, 'POST', '/api/platform/v1/containers', {
|
||||
channelCode: initial?.summary?.channelCode, warehouseId, containerType: '40HC',
|
||||
channelGroupCode: channelGroup(initial?.summary?.channelCode), warehouseId, containerType: '40HC',
|
||||
capacityCbm: 76, maxWeightKg: 28000,
|
||||
})
|
||||
const simulation = context.apiSuccess(MODULE, '新增三期内部模拟柜', simulationResponse, '', OPTIONS)
|
||||
|
||||
@@ -10,6 +10,7 @@ const PDA = '/api/platform/v1/first-leg/pda-loading'
|
||||
import { oceanBookingDraft } from './ocean-booking-fixture.mjs'
|
||||
import { iso6346ContainerNumber } from './iso6346-container-number.mjs'
|
||||
import { ensureInitialCalculated } from './inbound-fees.mjs'
|
||||
import { channelGroup } from './smart-loading-channel-group.mjs'
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
|
||||
@@ -186,7 +187,8 @@ async function loadOneFragment(context, state) {
|
||||
const carrier = await ensureMatsonCarrierPartner(context, MODULE, OPTIONS)
|
||||
if (!carrier?.id) return null
|
||||
const simulationResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode: state.channelCode, warehouseId, containerType: '40HC', capacityCbm: 76, maxWeightKg: 28000,
|
||||
channelGroupCode: channelGroup(state.channelCode), warehouseId,
|
||||
containerType: '40HC', capacityCbm: 76, maxWeightKg: 28000,
|
||||
})
|
||||
const simulation = context.apiSuccess(MODULE, `为 ${sourceOrderNo} 新增内部模拟柜`, simulationResponse, '', OPTIONS)
|
||||
const containerId = id(simulation?.id)
|
||||
|
||||
@@ -434,7 +434,9 @@ test('container loading permits manual cross-channel allocation and keeps stable
|
||||
'换柜号后按稳定计划身份回查 PDA 任务',
|
||||
'换柜号不改变任务计划柜次柜次号和库存快照身份',
|
||||
])
|
||||
assert.match(value, /containerDetail\?\.channelCode === channelCode/)
|
||||
assert.match(value, /simulation\?\.channelGroupCode === channelGroup\(channelCode\)/)
|
||||
assert.match(value, /simulation\?\.channelCode == null/)
|
||||
assert.match(value, /containerDetail\?\.channelGroupCode === channelGroup\(channelCode\)/)
|
||||
assert.match(value, /id\(replacedTask\?\.cabinetNo\) === stableIdentity\.cabinetNo/)
|
||||
assert.match(value, /afterReplacementSnapshot\?\.snapshotToken === beforeReplacementSnapshot\?\.snapshotToken/)
|
||||
assert.match(value, /pdaIdentityStable: true/)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export function channelGroup(channelCode) {
|
||||
const value = String(channelCode || '').trim().toUpperCase()
|
||||
if (value.startsWith('EXX_') || ['EXXHK', 'EXXHWK', 'NYEXX'].includes(value)) return 'EXX'
|
||||
if (value.startsWith('MATSON_CLX_') || ['ZBYHK', 'ZBYHWK', 'ZBYHP'].includes(value)) return 'MATSON_CLX'
|
||||
if (value.startsWith('MATSON_MAX_') || ['BYHK', 'BYHWK', 'BYHP'].includes(value)) return 'MATSON_MAX'
|
||||
if (value.startsWith('HEDE_') || ['HDHK', 'HDHWK', 'HDHP'].includes(value)) return 'HEDE'
|
||||
if (value.startsWith('OA_LA_') || value === 'OA_PARCEL'
|
||||
|| ['PCHK', 'PCHWK', 'PCKK', 'PCHP'].includes(value)) return 'OA_LA'
|
||||
if (value.startsWith('OA_CHICAGO_') || value === 'CPHT') return 'OA_CHICAGO'
|
||||
if (value.startsWith('OA_HOUSTON_') || value === 'HPHK') return 'OA_HOUSTON'
|
||||
if (value.startsWith('OA_SAVANNAH_') || value === 'SPHK') return 'OA_SAVANNAH'
|
||||
if (['OA_NEW_YORK_TRUCK', 'NPHK', 'NPHWK'].includes(value)) return 'OA_NEW_YORK_TRUCK'
|
||||
throw new Error(`unsupported smart-loading channel group: ${channelCode}`)
|
||||
}
|
||||
@@ -16,7 +16,8 @@ ON DUPLICATE KEY UPDATE
|
||||
capacity_cbm=VALUES(capacity_cbm),
|
||||
weight_kg=VALUES(weight_kg),
|
||||
color=VALUES(color),
|
||||
remark=VALUES(remark);
|
||||
remark=VALUES(remark),
|
||||
deleted=0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_domestic_pallet_seed;
|
||||
DELIMITER //
|
||||
@@ -38,6 +39,14 @@ BEGIN
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='国内作业仓木托盘、塑料托盘初始化不完整';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*)
|
||||
FROM wh_pallet
|
||||
WHERE tenant_id=0
|
||||
AND deleted=0
|
||||
AND ((warehouse_id=190000000000000001 AND pallet_code IN ('TP-001','YW-TP-002'))
|
||||
OR (warehouse_id=190000000000000002 AND pallet_code IN ('TP-002','DG-TP-002')))) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='国内作业仓固定可复用托盘编码初始化不完整';
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
CALL assert_domestic_pallet_seed();
|
||||
|
||||
+3
@@ -27,6 +27,7 @@ class InitialWarehouseResourceSeedSqlContractTest {
|
||||
}
|
||||
|
||||
private static void assertSeed(String sql, String yiwuId, String dongguanId, String idPrefix) {
|
||||
String compactSql = sql.replaceAll("\\s+", "");
|
||||
int locationSeedStart = sql.indexOf("WHEN zones.zone_index = 0 AND slots.slot_number = 1 THEN 201");
|
||||
int locationSeedEnd = sql.indexOf("DROP PROCEDURE IF EXISTS assert_yiwu_location_seed", locationSeedStart);
|
||||
String locationSeed = sql.substring(locationSeedStart, locationSeedEnd);
|
||||
@@ -37,6 +38,7 @@ class InitialWarehouseResourceSeedSqlContractTest {
|
||||
.contains("0," + dongguanId + ",'DG-TP-002','木托盘'")
|
||||
.contains("COUNT(DISTINCT pallet.pallet_type)")
|
||||
.contains("国内作业仓木托盘、塑料托盘初始化不完整")
|
||||
.contains("固定可复用托盘编码初始化不完整")
|
||||
.doesNotContain("'胶托盘'")
|
||||
.contains("WHEN zones.zone_index = 0 AND slots.slot_number = 1 THEN 201")
|
||||
.contains("ELSE 203 + zones.zone_index * 15 + slots.slot_number")
|
||||
@@ -55,6 +57,7 @@ class InitialWarehouseResourceSeedSqlContractTest {
|
||||
.contains("'AVAILABLE'")
|
||||
.doesNotContain("INSERT INTO wh_location_reservation")
|
||||
.doesNotContain("INSERT INTO wh_location_occupancy");
|
||||
assertThat(compactSql).contains("remark=VALUES(remark),deleted=0;");
|
||||
assertThat(locationSeed)
|
||||
.doesNotContain("status='AVAILABLE'")
|
||||
.doesNotContain("status = 'AVAILABLE'")
|
||||
|
||||
Reference in New Issue
Block a user