test: stabilize full development acceptance
This commit is contained in:
@@ -6,10 +6,10 @@ import { buildDirectPaidBody, findDirectPaidCost, findOpenSettlement } from './f
|
||||
const source = await readFile(new URL('./finance-direct-paid-isolation.mjs', import.meta.url), 'utf8')
|
||||
const acceptance = await readFile(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8')
|
||||
|
||||
test('直接收付款幂等在全量结清模块消费 AR/AP 之前执行', () => {
|
||||
test('直接收付款幂等在全量结算模块产生收付款结果之后执行', () => {
|
||||
const modules = acceptance.match(/const CONFIGURED_MODULES = \[([^\n]+)\]/)?.[1] || ''
|
||||
assert.ok(modules.indexOf("'finance-reconciliation-full'") < modules.indexOf("'finance-direct-paid-isolation'"))
|
||||
assert.ok(modules.indexOf("'finance-direct-paid-isolation'") < modules.indexOf("'finance-settlement-full'"))
|
||||
assert.ok(modules.indexOf("'finance-reconciliation-full'") < modules.indexOf("'finance-settlement-full'"))
|
||||
assert.ok(modules.indexOf("'finance-settlement-full'") < modules.indexOf("'finance-direct-paid-isolation'"))
|
||||
})
|
||||
|
||||
test('DIRECT_PAID 请求只绑定客户、仓库、供应商且不伪造业务关联', () => {
|
||||
|
||||
@@ -23,8 +23,9 @@ test('八类 Finance XLSX 契约均有正式表头并在完整链末尾执行',
|
||||
])
|
||||
assert.ok(Object.values(EXPORT_SPECS).every((headers) => headers.length >= 9))
|
||||
const order = normalizeModuleOrder(['finance-downloads', 'finance-settlement-full'])
|
||||
assert.deepEqual(order.slice(-4), [
|
||||
'finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full', 'finance-downloads',
|
||||
assert.deepEqual(order.slice(-5), [
|
||||
'finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full',
|
||||
'finance-direct-paid-isolation', 'finance-downloads',
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ test('字段断言接入正式生产模块和默认全量入口', () => {
|
||||
})
|
||||
|
||||
test('矩阵登记 LANDED 与 UNKNOWN 且禁止 fixture 伪造未知字段', () => {
|
||||
const matrix = read(workspace, 'docs', '三期', '货柜履约原型字段验收矩阵-2026-08-17.md')
|
||||
const matrix = read(workspace, 'docs', '国内货柜履约', '货柜履约原型字段验收矩阵-2026-08-17.md')
|
||||
assert.match(matrix, /LANDED/)
|
||||
assert.match(matrix, /UNKNOWN/)
|
||||
assert.match(matrix, /不得由 fixture 伪造|不生成假值/)
|
||||
|
||||
@@ -28,13 +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.doesNotMatch(measurement, /context\.request\('POST', '\/api\/platform\/v1\/pallets/)
|
||||
assert.doesNotMatch(measurement, /创建称重托盘主数据/)
|
||||
assert.match(measurement, /context\.request\('POST', '\/api\/platform\/v1\/pallets\?tenantId=0'/)
|
||||
assert.match(measurement, /创建本轮称重托盘/)
|
||||
assert.match(measurement, /measurement-fixture/)
|
||||
})
|
||||
|
||||
test('称重脚本不新增库位且容量不足由仓库服务复用 A0 公共位', () => {
|
||||
|
||||
@@ -31,9 +31,9 @@ test('DRAFT can be partial but release validates and freezes the five transport
|
||||
assert.match(fixture, new RegExp(`${field}:`), field)
|
||||
}
|
||||
assert.match(booking, /const frozen = transportSnapshot\(completedDraft\?\.oceanBooking \|\| data\)/)
|
||||
assert.match(booking, /documentType', 'BOOKING_CONFIRMATION'/)
|
||||
assert.match(booking, /documentType', 'BILL_OF_LADING'/)
|
||||
assert.match(booking, /\/documents\/upload/)
|
||||
assert.match(booking, /expectedVersion: documentVersion/)
|
||||
assert.match(booking, /expectedVersion: Number\(uploaded\.bookingVersion\)/)
|
||||
assert.match(booking, /完整 DRAFT 放舱为 RELEASED 且冻结五项运输事实/)
|
||||
assert.match(booking, /Object\.entries\(frozen\)\.every/)
|
||||
assert.match(booking, /RELEASED 编辑拒绝且零写入/)
|
||||
|
||||
@@ -223,8 +223,9 @@ export async function run(context) {
|
||||
if (!containerBookingId) return
|
||||
const candidateResponse = await context.request('GET', `${smartLoadingBase}/candidates?bookingId=${encodeURIComponent(containerBookingId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const candidates = context.apiSuccess(MODULE, '查询本轮入库单柜次装柜候选', candidateResponse, '', OPTIONS)
|
||||
const candidate = Array.isArray(candidates) ? candidates.find((item) => id(item.inboundId) === inboundId
|
||||
&& Number(item.availablePackageCount) > 0) : null
|
||||
const inboundCandidates = Array.isArray(candidates) ? candidates.filter((item) => id(item.inboundId) === inboundId
|
||||
&& Number(item.availablePackageCount) > 0) : []
|
||||
const candidate = inboundCandidates[0]
|
||||
context.assert(MODULE, '柜次装柜候选与入库单、客户和仓库一致', candidateResponse,
|
||||
Boolean(candidate?.inboundDetailId) && id(candidate?.accountId) === tenantId
|
||||
&& id(candidate?.warehouseId) === warehouseId,
|
||||
@@ -237,16 +238,24 @@ export async function run(context) {
|
||||
const loadingPlan = context.apiSuccess(MODULE, '读取柜次自动创建的智能装柜计划', loadingPlanResponse, '', OPTIONS)
|
||||
const loadingPlanId = id(loadingPlan?.id)
|
||||
if (!loadingPlanId) return
|
||||
const assignmentResponse = await request(context, 'POST', `${smartLoadingBase}/plans/${loadingPlanId}/assignments`, {
|
||||
inboundId, inboundDetailId: id(candidate.inboundDetailId), packageCount: 1,
|
||||
expectedVersion: Number(loadingPlan.version),
|
||||
operationId: `${context.marker}-${loadingPlanId}-bridge-assign`.slice(0, 128),
|
||||
})
|
||||
const dropshipAssignment = source?.fulfillmentType === 'ONE_CLICK_DROPSHIP'
|
||||
const initialAssignmentCount = dropshipAssignment
|
||||
? inboundCandidates.reduce((total, item) => total + Number(item.availablePackageCount || 0), 0) : 1
|
||||
const assignmentPath = dropshipAssignment
|
||||
? `${smartLoadingBase}/plans/${loadingPlanId}/inbound-assignments`
|
||||
: `${smartLoadingBase}/plans/${loadingPlanId}/assignments`
|
||||
const assignmentBody = dropshipAssignment
|
||||
? { inboundId, packageCount: initialAssignmentCount, expectedVersion: Number(loadingPlan.version),
|
||||
operationId: `${context.marker}-${loadingPlanId}-bridge-assign-inbound`.slice(0, 128) }
|
||||
: { inboundId, inboundDetailId: id(candidate.inboundDetailId), packageCount: initialAssignmentCount,
|
||||
expectedVersion: Number(loadingPlan.version), operationId: `${context.marker}-${loadingPlanId}-bridge-assign`.slice(0, 128) }
|
||||
const assignmentResponse = await request(context, 'POST', assignmentPath, assignmentBody)
|
||||
const assignedLoadingPlan = context.apiSuccess(MODULE, '将本轮入库货物分配到柜次计划', assignmentResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '柜次计划明细和版本真实持久化', assignmentResponse,
|
||||
assignedLoadingPlan?.items?.some?.((item) => id(item.inboundId) === inboundId && Number(item.packageCount) === 1)
|
||||
&& Number(assignedLoadingPlan?.version) === 1,
|
||||
`loadingPlanId=${loadingPlanId}, version=${assignedLoadingPlan?.version ?? 'missing'}`, OPTIONS)
|
||||
assignedLoadingPlan?.items?.filter?.((item) => id(item.inboundId) === inboundId)
|
||||
.reduce((total, item) => total + Number(item.packageCount || 0), 0) === initialAssignmentCount
|
||||
&& Number(assignedLoadingPlan?.version) >= 1,
|
||||
`loadingPlanId=${loadingPlanId}, assignedPackages=${initialAssignmentCount}, version=${assignedLoadingPlan?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const assignedItem = assignedLoadingPlan?.items?.find?.((item) => id(item.inboundId) === inboundId)
|
||||
const orderRemarks = source || {}
|
||||
@@ -584,12 +593,15 @@ export async function run(context) {
|
||||
const canonicalTaskId = id(canonicalTask?.id)
|
||||
const taskDetailPath = `${pdaBase}/tasks/${canonicalTaskId}?warehouseId=${encodeURIComponent(warehouseId)}`
|
||||
const canonicalItem = canonicalTask?.items?.find?.((item) => id(item.inboundId) === inboundId)
|
||||
const canonicalPickItems = dropshipAssignment
|
||||
? canonicalTask?.items?.filter?.((item) => id(item.inboundId) === inboundId) || []
|
||||
: canonicalItem ? [canonicalItem] : []
|
||||
context.assert(MODULE, '柜次 PDA 任务与桥接入库明细一致', canonicalTaskResponse,
|
||||
Boolean(canonicalTaskId) && canonicalTaskId === confirmedTaskId && id(canonicalTask?.loadingPlanId) === loadingPlanId
|
||||
&& id(canonicalTask?.containerBookingId) === containerBookingId
|
||||
&& canonicalTask?.workflowStatus === 'UNASSIGNED' && Boolean(canonicalItem?.id),
|
||||
`taskId=${canonicalTaskId || 'missing'}, itemId=${id(canonicalItem?.id) || 'missing'}`, OPTIONS)
|
||||
if (!canonicalTaskId || !canonicalItem?.id) return
|
||||
&& canonicalTask?.workflowStatus === 'UNASSIGNED' && canonicalPickItems.length > 0,
|
||||
`taskId=${canonicalTaskId || 'missing'}, itemCount=${canonicalPickItems.length}`, OPTIONS)
|
||||
if (!canonicalTaskId || !canonicalPickItems.length) return
|
||||
|
||||
const beforeClaimPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
|
||||
const beforeClaimPlan = context.apiSuccess(MODULE, '领取前读取柜次计划即时版本', beforeClaimPlanResponse, '', OPTIONS)
|
||||
@@ -665,15 +677,18 @@ export async function run(context) {
|
||||
const skipBatchKey = `${context.marker}-exception-skip-batch`.slice(0, 128)
|
||||
const partialBatchKey = `${context.marker}-exception-partial-batch`.slice(0, 128)
|
||||
const dynamicBatchKey = `${context.marker}-dynamic-pick`.slice(0, 128)
|
||||
const expectedBatchOperationIds = [batchKey, skipBatchKey, partialBatchKey, dynamicBatchKey]
|
||||
const dropshipWholePallet = source?.fulfillmentType === 'ONE_CLICK_DROPSHIP'
|
||||
const expectedBatchOperationIds = dropshipWholePallet
|
||||
? [batchKey]
|
||||
: [batchKey, skipBatchKey, partialBatchKey, dynamicBatchKey]
|
||||
const batchPayload = {
|
||||
warehouseId, loadingPlanId, containerBookingId,
|
||||
expectedVersion: Number(claimed?.version),
|
||||
inventorySnapshotToken: inventory.snapshotToken,
|
||||
items: [{
|
||||
itemId: id(canonicalItem.id), action: 'PICK', pickedPackageCount: 1,
|
||||
version: Number(canonicalItem.version),
|
||||
}],
|
||||
items: canonicalPickItems.map((item) => ({
|
||||
itemId: id(item.id), action: 'PICK', pickedPackageCount: Number(item.plannedPackageCount),
|
||||
version: Number(item.version),
|
||||
})),
|
||||
}
|
||||
|
||||
const staleBatch = await request(context, 'POST', batchPath,
|
||||
@@ -1095,7 +1110,10 @@ export async function run(context) {
|
||||
let partialBatchVerified = false
|
||||
let partialBlockSnapshot = null
|
||||
let dynamicIncrementVerified = false
|
||||
if (!incrementalInboundId || incrementalFixture?.marker !== context.marker || incrementalInboundId === inboundId) {
|
||||
if (dropshipWholePallet) {
|
||||
context.addStep(MODULE, '一件代发整托生产者不执行可拆分动态增量场景', null, true,
|
||||
'逐箱封托事实必须整托守恒;SKIP、PARTIAL 和动态增删改由 STANDARD 生产者覆盖', OPTIONS)
|
||||
} else if (!incrementalInboundId || incrementalFixture?.marker !== context.marker || incrementalInboundId === inboundId) {
|
||||
context.addStep(MODULE, '取得本轮独立动态增量入库单', null, false,
|
||||
`inboundId=${incrementalInboundId || 'missing'}, primaryInboundId=${inboundId}, marker=${incrementalFixture?.marker || 'missing'}`, OPTIONS)
|
||||
} else {
|
||||
@@ -1507,9 +1525,9 @@ export async function run(context) {
|
||||
cartonMarks: stableLoadedCartonMarks(stagedTask?.items),
|
||||
fulfillmentType: source?.fulfillmentType || 'STANDARD',
|
||||
skuCodes: [...new Set([...(Array.isArray(source?.skuCodes) ? source.skuCodes : []),
|
||||
...(Array.isArray(incrementalFixture?.skuCodes) ? incrementalFixture.skuCodes : [])])],
|
||||
...(!dropshipWholePallet && Array.isArray(incrementalFixture?.skuCodes) ? incrementalFixture.skuCodes : [])])],
|
||||
skuLines: [...(Array.isArray(source?.skuLines) ? source.skuLines : []),
|
||||
...(Array.isArray(incrementalFixture?.skuLines) ? incrementalFixture.skuLines : [])],
|
||||
...(!dropshipWholePallet && Array.isArray(incrementalFixture?.skuLines) ? incrementalFixture.skuLines : [])],
|
||||
destinationType: initial?.summary?.destinationType || source?.destinationType || null,
|
||||
destinationWarehouseId: id(initial?.summary?.destinationWarehouseId || source?.destinationWarehouseId),
|
||||
destinationWarehouseCode: initial?.summary?.destinationCode || source?.destinationWarehouseCode || null,
|
||||
|
||||
@@ -27,10 +27,11 @@ test('AWAITING_REVIEW derives zero client actions and never needs a repeated bat
|
||||
assert.match(convergence, /\$\{taskPath\}\/evidence\?warehouseId=\$\{encodeURIComponent\(warehouseId\)\}/)
|
||||
assert.doesNotMatch(convergence, /api\/platform\/v1\/inbounds\/\$\{inboundId\}\/loading\/evidence/)
|
||||
assert.match(convergence, /filter\(\(item\) => item\.actionType === 'BATCH'/)
|
||||
assert.match(convergence, /expectedBatchOperationIds\.length === 4/)
|
||||
assert.match(convergence, /actualBatchOperationIds\.length === 4/)
|
||||
assert.match(convergence, /new Set\(expectedBatchOperationIds\)\.size === 4/)
|
||||
assert.match(convergence, /new Set\(actualBatchOperationIds\)\.size === 4/)
|
||||
assert.match(convergence, /expectedBatchCount = fixture\?\.fulfillmentType === 'ONE_CLICK_DROPSHIP' \? 1 : 4/)
|
||||
assert.match(convergence, /expectedBatchOperationIds\.length === expectedBatchCount/)
|
||||
assert.match(convergence, /actualBatchOperationIds\.length === expectedBatchCount/)
|
||||
assert.match(convergence, /new Set\(expectedBatchOperationIds\)\.size === expectedBatchCount/)
|
||||
assert.match(convergence, /new Set\(actualBatchOperationIds\)\.size === expectedBatchCount/)
|
||||
assert.match(convergence, /expectedBatchOperationIds\.every[\s\S]*actualBatchOperationIds\.every/)
|
||||
assert.doesNotMatch(convergence, /dynamicIncrementVerified === true \? 2 : 1/)
|
||||
assert.match(convergence, /LOADING 增量重开、刷新续拣与已拣不可删减/)
|
||||
|
||||
@@ -500,7 +500,7 @@ test('PDA completion acceptance verifies immutable BATCH and COMPLETE_LOADING au
|
||||
for (const key of ['bridge-batch', 'exception-skip-batch', 'exception-partial-batch', 'dynamic-pick']) {
|
||||
assert.match(loading, new RegExp(key))
|
||||
}
|
||||
assert.match(loading, /expectedBatchOperationIds = \[batchKey, skipBatchKey, partialBatchKey, dynamicBatchKey\]/)
|
||||
assert.match(loading, /expectedBatchOperationIds = dropshipWholePallet[\s\S]*\? \[batchKey\][\s\S]*: \[batchKey, skipBatchKey, partialBatchKey, dynamicBatchKey\]/)
|
||||
assert.match(loading, /skipBatchVerified, partialBatchVerified, dynamicIncrementVerified/)
|
||||
assert.match(convergence, /expectedBatchCount = fixture\?\.fulfillmentType === 'ONE_CLICK_DROPSHIP' \? 1 : 4/)
|
||||
assert.match(convergence, /expectedBatchOperationIds\.length === expectedBatchCount/)
|
||||
|
||||
@@ -67,7 +67,7 @@ test('domestic and overseas PDA operators use isolated authorities', () => {
|
||||
test('unified migration creates the phase-four role matrix in the IAM schema used by its assertion routine', () => {
|
||||
assert.match(
|
||||
unifiedSql,
|
||||
/USE baiyue_iam;\s*DROP TEMPORARY TABLE IF EXISTS tmp_phase4_warehouse_role_permission;\s*CREATE TEMPORARY TABLE tmp_phase4_warehouse_role_permission/,
|
||||
/USE baiyue_iam;[\s\S]*?DROP TEMPORARY TABLE IF EXISTS tmp_phase4_warehouse_role_permission;\s*CREATE TEMPORARY TABLE tmp_phase4_warehouse_role_permission/,
|
||||
)
|
||||
assert.match(unifiedSql, /CREATE PROCEDURE baiyue_iam\.assert_phase4_warehouse_role_minimum\(\)/)
|
||||
assert.match(unifiedSql, /CREATE PROCEDURE assert_phase4_warehouse_role_minimum\(\)/)
|
||||
})
|
||||
|
||||
@@ -76,16 +76,16 @@ test('new tailleg number is Shanghai daily LMO-yyMMdd-#### with concurrency and
|
||||
assert.match(generatorTests, /LMO-260828-0001/)
|
||||
})
|
||||
|
||||
test('one container with 15 inbound rows shares one new LMO number', () => {
|
||||
test('one container with 15 inbound rows shares one stable container order number', () => {
|
||||
const rows = Array.from({ length: 15 }, (_, index) => ({
|
||||
containerNo: 'CONT-15', orderNo: 'LMO-260827-0042', sourceInboundId: `inbound-${index + 1}`,
|
||||
}))
|
||||
assert.equal(validContainerOrderGroup(rows), true)
|
||||
assert.equal(validContainerOrderGroup(rows.map((row, index) => index === 14
|
||||
? { ...row, orderNo: 'LMO-260827-0043' } : row)), false)
|
||||
assert.match(consumer, /String orderNo\s*=\s*orderNoGenerator\.next\(\);[\s\S]*for \(int index/)
|
||||
assert.match(consumer, /String orderNo\s*=\s*orderNoGenerator\.forContainerNo\(payload\.containerNo\(\)\);[\s\S]*for \(int index/)
|
||||
assert.match(consumer, /createOrder\(envelope, payload, source, orderNo, rawSource, containerNo/)
|
||||
assert.match(consumerTests, /oneContainerWith15InboundRowsSharesOneNewLmoNumber/)
|
||||
assert.match(consumerTests, /oneContainerWith15InboundRowsSharesOneStableContainerOrderNumber/)
|
||||
})
|
||||
|
||||
test('same event replay adds nothing and preserves the allocated number', () => {
|
||||
|
||||
Reference in New Issue
Block a user