优化
This commit is contained in:
@@ -9,21 +9,21 @@ test('同柜不可拆组HTTP验收锁定公开接口与防绕过矩阵', async (
|
||||
'/api/platform/v1/first-leg/coload-groups/batch-query',
|
||||
'/group-assignments',
|
||||
'/inbound-assignments',
|
||||
'/recommend',
|
||||
'/push-preload',
|
||||
'创建同键同载荷精确重放',
|
||||
'创建同键异载荷返回并发冲突',
|
||||
'组成员不能单独调整箱数',
|
||||
'组成员不能单独删除',
|
||||
'整组原子移除',
|
||||
'智能推荐对整组全取或全不取',
|
||||
'目标同柜组整体超容时智能推荐明确拒绝',
|
||||
'整组容量不足时推荐事务零漂移',
|
||||
'推荐候选仅含当前目标同柜组且成员身份精确',
|
||||
'ACTIVE同柜组整组取消成功',
|
||||
'同柜组PDA原子批次记录部分拣货异常',
|
||||
'同柜组任一成员短拣时完成被拒绝',
|
||||
'短拣完成拒绝后任务、父子树和组锁零漂移',
|
||||
'同柜组足量完成命令可靠受理',
|
||||
'足量完成整组已消费、全部入库完成且不生成子单',
|
||||
'足量完成整组已消费、全部入库完成且不生成余量子单',
|
||||
'未成组普通单推送预装不回归',
|
||||
'未成组普通单仍按既有规则生成唯一余量子单',
|
||||
'已有父子拆分订单创建同柜组被拒绝',
|
||||
|
||||
@@ -79,8 +79,8 @@ async function createSecondGroup(context, third, fourth) {
|
||||
|
||||
async function createAssignedPushedPlan(context, fixture, groupId, suffix) {
|
||||
const simulationResponse = await context.request('POST', CONTAINERS, {
|
||||
channelCode: fixture.channelCode, warehouseId: fixture.warehouseId, containerType: '20GP',
|
||||
capacityCbm: 33, maxWeightKg: 28000,
|
||||
channelCode: fixture.channelCode, warehouseId: fixture.warehouseId, containerType: '40HC',
|
||||
capacityCbm: 76, maxWeightKg: 28000,
|
||||
})
|
||||
const simulation = context.apiSuccess(MODULE, `${suffix} 创建组装柜模拟柜`, simulationResponse,
|
||||
evidence(simulationResponse), OPTIONS)
|
||||
@@ -109,8 +109,8 @@ async function createAssignedPushedPlan(context, fixture, groupId, suffix) {
|
||||
|
||||
async function createNormalPushedPlan(context, fixture, suffix) {
|
||||
const simulationResponse = await context.request('POST', CONTAINERS, {
|
||||
channelCode: fixture.channelCode, warehouseId: fixture.warehouseId, containerType: '20GP',
|
||||
capacityCbm: 33, maxWeightKg: 28000,
|
||||
channelCode: fixture.channelCode, warehouseId: fixture.warehouseId, containerType: '40HC',
|
||||
capacityCbm: 76, maxWeightKg: 28000,
|
||||
})
|
||||
const simulation = context.apiSuccess(MODULE, `${suffix} 创建普通单模拟柜`, simulationResponse,
|
||||
evidence(simulationResponse), OPTIONS)
|
||||
@@ -168,8 +168,8 @@ async function createReleasedBookingAndLink(context, fixture, chains) {
|
||||
const draft = oceanBookingDraft(context, suffix, master, {
|
||||
bookingAgentId: id(agent.id), bookingAgentName: agent.name,
|
||||
shippingCompanyId: id(carrier.id), shippingCompanyName: carrier.name, carrierCode: 'MATSON',
|
||||
containerRequirements: [{ containerType: '20GP', containerKind: 'FCL', quantity: chains.length,
|
||||
capacityCbm: 33, maxWeightKg: 28000 }],
|
||||
containerRequirements: [{ containerType: '40HC', containerKind: 'FCL', quantity: chains.length,
|
||||
capacityCbm: 76, maxWeightKg: 28000 }],
|
||||
})
|
||||
const createResponse = await context.request('POST', '/api/platform/v1/ocean-bookings',
|
||||
{ expectedVersion: 0, data: draft }, undefined,
|
||||
@@ -183,7 +183,7 @@ async function createReleasedBookingAndLink(context, fixture, chains) {
|
||||
}, undefined, { 'Idempotency-Key': `${context.marker}-coload-pda-booking-release`.slice(0, 128) })
|
||||
const released = context.apiSuccess(MODULE, 'PDA链放舱并冻结两柜需求', releaseResponse,
|
||||
evidence(releaseResponse), OPTIONS)
|
||||
const requirement = (released?.containerRequirements || []).find((item) => item?.containerType === '20GP')
|
||||
const requirement = (released?.containerRequirements || []).find((item) => item?.containerType === '40HC')
|
||||
context.assert(MODULE, '放舱需求数量覆盖短拣与足量两柜', releaseResponse,
|
||||
released?.oceanBooking?.status === 'RELEASED' && Number(requirement?.quantity) === chains.length,
|
||||
`status=${released?.oceanBooking?.status || 'missing'}, quantity=${requirement?.quantity ?? 'missing'}`, OPTIONS)
|
||||
@@ -286,7 +286,7 @@ async function uploadCompletionEvidence(context, fixture, label) {
|
||||
const bindResponse = await context.request('POST',
|
||||
`/api/platform/v1/inbounds/${fixture.inboundId}/loading/evidence?tenantId=${encodeURIComponent(fixture.tenantId)}`, {
|
||||
expectedVersion: 0, evidenceType, fileId,
|
||||
sealNo: evidenceType === 'SEAL' ? `SEAL-${context.marker}`.slice(0, 64) : null,
|
||||
sealNo: evidenceType === 'SEAL' ? `SEAL-${context.marker}-${label}`.slice(0, 64) : null,
|
||||
remark: context.marker,
|
||||
}, undefined, { 'Idempotency-Key': `${context.marker}-${label}-${evidenceType}`.slice(0, 128) })
|
||||
const bound = context.apiSuccess(MODULE, `${label} 绑定 ${evidenceType} 柜级证据`, bindResponse,
|
||||
@@ -466,16 +466,16 @@ export async function run(context) {
|
||||
channelCode: first.channelCode, warehouseId: first.warehouseId, containerType: '20GP',
|
||||
capacityCbm: 33, maxWeightKg: 28000,
|
||||
})
|
||||
const simulation = context.apiSuccess(MODULE, '创建本轮组装柜模拟柜', simulationResponse,
|
||||
const simulation = context.apiSuccess(MODULE, '创建容量拒绝专用20GP模拟柜', simulationResponse,
|
||||
evidence(simulationResponse), OPTIONS)
|
||||
const bookingId = id(simulation?.id)
|
||||
let bookingId = id(simulation?.id)
|
||||
if (!bookingId) return
|
||||
const planResponse = await context.request('POST', '/api/platform/v1/first-leg/smart-loading/plans', {
|
||||
bookingId, remark: `${context.marker} 同柜不可拆组验收`,
|
||||
})
|
||||
let plan = context.apiSuccess(MODULE, '创建同柜组装柜计划', planResponse,
|
||||
let plan = context.apiSuccess(MODULE, '创建容量拒绝专用空计划', planResponse,
|
||||
evidence(planResponse, `bookingId=${bookingId}`), OPTIONS)
|
||||
const planId = id(plan?.id)
|
||||
let planId = id(plan?.id)
|
||||
if (!planId) return
|
||||
|
||||
const candidateResponse = await context.request('GET',
|
||||
@@ -488,6 +488,78 @@ export async function run(context) {
|
||||
&& Number(item?.groupTotalBoxes) === first.packages + second.packages && item?.locked !== true),
|
||||
`count=${groupedCandidates.length}, groupTotal=${first.packages + second.packages}`, OPTIONS)
|
||||
|
||||
let isolatedCandidates = candidates
|
||||
const competing = [...new Set(isolatedCandidates.filter((item) => item?.locked !== true)
|
||||
.map((item) => id(item?.coLoadGroupId)).filter((value) => value && value !== groupId))]
|
||||
for (const competingGroupId of competing) {
|
||||
const competingOrders = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === competingGroupId)
|
||||
.map((item) => id(item?.orderId)).filter(Boolean)
|
||||
const competingQueryResponse = await context.request('POST', '/api/platform/v1/first-leg/coload-groups/batch-query',
|
||||
{ orderIds: competingOrders })
|
||||
const competingQuery = context.apiSuccess(MODULE, '清理前回读旧验收活动组', competingQueryResponse,
|
||||
evidence(competingQueryResponse, `groupId=${competingGroupId}`), OPTIONS) || []
|
||||
const competingGroup = competingQuery.find((item) => id(item?.group?.groupId) === competingGroupId)?.group
|
||||
if (competingGroup?.status === 'ACTIVE') {
|
||||
const cleanupResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/coload-groups/${encodeURIComponent(competingGroupId)}/cancel`, {
|
||||
expectedVersion: Number(competingGroup.version), reason: '清理旧HTTP验收活动组以隔离容量候选',
|
||||
}, undefined, { 'Idempotency-Key': `${context.marker}-cleanup-${competingGroupId}`.slice(0, 128) })
|
||||
context.apiSuccess(MODULE, '通过公开接口取消旧验收活动组', cleanupResponse,
|
||||
evidence(cleanupResponse, `groupId=${competingGroupId}`), OPTIONS)
|
||||
}
|
||||
}
|
||||
let isolatedCandidateResponse = candidateResponse
|
||||
if (competing.length > 0) {
|
||||
isolatedCandidateResponse = await context.request('GET',
|
||||
`${SMART}/inbound-candidates?bookingId=${encodeURIComponent(bookingId)}&warehouseId=${encodeURIComponent(first.warehouseId)}`)
|
||||
isolatedCandidates = context.apiSuccess(MODULE, '清理后重新隔离读取目标组候选', isolatedCandidateResponse,
|
||||
evidence(isolatedCandidateResponse), OPTIONS) || []
|
||||
}
|
||||
const candidateGroupIds = [...new Set(isolatedCandidates.filter((item) => item?.locked !== true)
|
||||
.map((item) => id(item?.coLoadGroupId)).filter(Boolean))]
|
||||
const targetCandidateInbounds = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === groupId)
|
||||
.map((item) => id(item?.inboundId)).sort()
|
||||
context.assert(MODULE, '推荐候选仅含当前目标同柜组且成员身份精确', isolatedCandidateResponse,
|
||||
candidateGroupIds.length === 1 && candidateGroupIds[0] === groupId
|
||||
&& JSON.stringify(targetCandidateInbounds) === JSON.stringify([first.inboundId, second.inboundId].sort()),
|
||||
`groupId=${groupId}, candidateGroupIds=${candidateGroupIds.join('|') || 'none'}, members=${targetCandidateInbounds.join('|')}, capacity=33cbm/28000kg`, OPTIONS)
|
||||
const targetCandidateVolume = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === groupId)
|
||||
.reduce((sum, item) => sum + Number(item?.availableVolumeCbm || 0), 0)
|
||||
context.assert(MODULE, '目标组自身总体积明确超过20GP容量容差', isolatedCandidateResponse,
|
||||
targetCandidateVolume > 33 * 1.1,
|
||||
`targetGroupId=${groupId}, volume=${targetCandidateVolume}, limit=${33 * 1.1}`, OPTIONS)
|
||||
const beforeRecommend = plan
|
||||
const recommendResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(planId)}/group-assignments`, {
|
||||
inboundId: first.inboundId, expectedVersion: Number(plan?.version),
|
||||
operationId: `${context.marker}-coload-capacity-assign`.slice(0, 128),
|
||||
})
|
||||
const capacityRejected = recommendResponse?.http === 200 && code(recommendResponse) === 'A0422'
|
||||
&& String(recommendResponse?.body?.message || '').includes('同柜不可拆组整体超过货柜')
|
||||
context.assert(MODULE, '目标同柜组整体超容时智能推荐明确拒绝', recommendResponse,
|
||||
capacityRejected,
|
||||
evidence(recommendResponse, `targetGroupId=${groupId}, members=${[first.inboundId, second.inboundId].join('|')}, message=${recommendResponse?.body?.message || 'none'}`), OPTIONS)
|
||||
const afterRecommend = await readPlan(context, planId, '回读整组容量拒绝后计划')
|
||||
context.assert(MODULE, '整组容量不足时推荐事务零漂移', afterRecommend.response,
|
||||
planSignature(beforeRecommend) === planSignature(afterRecommend.data),
|
||||
`before=${planSignature(beforeRecommend)}, after=${planSignature(afterRecommend.data)}`, OPTIONS)
|
||||
|
||||
const workingSimulationResponse = await context.request('POST', '/api/platform/v1/containers', {
|
||||
channelCode: first.channelCode, warehouseId: first.warehouseId, containerType: '40HC',
|
||||
capacityCbm: 76, maxWeightKg: 28000,
|
||||
})
|
||||
const workingSimulation = context.apiSuccess(MODULE, '创建PDA主链40HC模拟柜', workingSimulationResponse,
|
||||
evidence(workingSimulationResponse), OPTIONS)
|
||||
bookingId = id(workingSimulation?.id)
|
||||
if (!bookingId) return
|
||||
const workingPlanResponse = await context.request('POST', `${SMART}/plans`, {
|
||||
bookingId, remark: `${context.marker} 同柜不可拆组PDA主链`,
|
||||
})
|
||||
plan = context.apiSuccess(MODULE, '创建PDA主链同柜装柜计划', workingPlanResponse,
|
||||
evidence(workingPlanResponse, `bookingId=${bookingId}`), OPTIONS)
|
||||
planId = id(plan?.id)
|
||||
if (!planId) return
|
||||
|
||||
const beforeIndividual = await readPlan(context, planId, '读取单票绕过前计划快照')
|
||||
const individualResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(planId)}/inbound-assignments`, {
|
||||
@@ -569,84 +641,13 @@ export async function run(context) {
|
||||
activeQuery.length === 2 && activeQuery.every((item) => item?.group?.status === 'ACTIVE'
|
||||
&& item?.group?.lockedPlanId == null),
|
||||
`status=${activeQuery[0]?.group?.status || 'missing'}, lockedPlanId=${id(activeQuery[0]?.group?.lockedPlanId) || 'null'}`, OPTIONS)
|
||||
|
||||
let isolatedCandidateResponse = await context.request('GET',
|
||||
`${SMART}/inbound-candidates?bookingId=${encodeURIComponent(bookingId)}&warehouseId=${encodeURIComponent(first.warehouseId)}`)
|
||||
let isolatedCandidates = context.apiSuccess(MODULE, '推荐前隔离读取目标组候选', isolatedCandidateResponse,
|
||||
evidence(isolatedCandidateResponse), OPTIONS) || []
|
||||
const competing = [...new Set(isolatedCandidates.filter((item) => item?.locked !== true)
|
||||
.map((item) => id(item?.coLoadGroupId)).filter((value) => value && value !== groupId))]
|
||||
for (const competingGroupId of competing) {
|
||||
const competingOrders = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === competingGroupId)
|
||||
.map((item) => id(item?.orderId)).filter(Boolean)
|
||||
const competingQueryResponse = await context.request('POST', '/api/platform/v1/first-leg/coload-groups/batch-query',
|
||||
{ orderIds: competingOrders })
|
||||
const competingQuery = context.apiSuccess(MODULE, '清理前回读旧验收活动组', competingQueryResponse,
|
||||
evidence(competingQueryResponse, `groupId=${competingGroupId}`), OPTIONS) || []
|
||||
const competingGroup = competingQuery.find((item) => id(item?.group?.groupId) === competingGroupId)?.group
|
||||
if (competingGroup?.status === 'ACTIVE') {
|
||||
const cleanupResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/coload-groups/${encodeURIComponent(competingGroupId)}/cancel`, {
|
||||
expectedVersion: Number(competingGroup.version), reason: '清理旧HTTP验收活动组以隔离容量候选',
|
||||
}, undefined, { 'Idempotency-Key': `${context.marker}-cleanup-${competingGroupId}`.slice(0, 128) })
|
||||
context.apiSuccess(MODULE, '通过公开接口取消旧验收活动组', cleanupResponse,
|
||||
evidence(cleanupResponse, `groupId=${competingGroupId}`), OPTIONS)
|
||||
}
|
||||
}
|
||||
if (competing.length > 0) {
|
||||
isolatedCandidateResponse = await context.request('GET',
|
||||
`${SMART}/inbound-candidates?bookingId=${encodeURIComponent(bookingId)}&warehouseId=${encodeURIComponent(first.warehouseId)}`)
|
||||
isolatedCandidates = context.apiSuccess(MODULE, '清理后重新隔离读取目标组候选', isolatedCandidateResponse,
|
||||
evidence(isolatedCandidateResponse), OPTIONS) || []
|
||||
}
|
||||
const candidateGroupIds = [...new Set(isolatedCandidates.filter((item) => item?.locked !== true)
|
||||
.map((item) => id(item?.coLoadGroupId)).filter(Boolean))]
|
||||
const targetCandidateInbounds = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === groupId)
|
||||
.map((item) => id(item?.inboundId)).sort()
|
||||
context.assert(MODULE, '推荐候选仅含当前目标同柜组且成员身份精确', isolatedCandidateResponse,
|
||||
candidateGroupIds.length === 1 && candidateGroupIds[0] === groupId
|
||||
&& JSON.stringify(targetCandidateInbounds) === JSON.stringify([first.inboundId, second.inboundId].sort()),
|
||||
`groupId=${groupId}, candidateGroupIds=${candidateGroupIds.join('|') || 'none'}, members=${targetCandidateInbounds.join('|')}, capacity=33cbm/28000kg`, OPTIONS)
|
||||
|
||||
const targetCandidateVolume = isolatedCandidates.filter((item) => id(item?.coLoadGroupId) === groupId)
|
||||
.reduce((sum, item) => sum + Number(item?.availableVolumeCbm || 0), 0)
|
||||
context.assert(MODULE, '目标组自身总体积明确超过20GP容量容差', isolatedCandidateResponse,
|
||||
targetCandidateVolume > 33 * 1.1,
|
||||
`targetGroupId=${groupId}, volume=${targetCandidateVolume}, limit=${33 * 1.1}`, OPTIONS)
|
||||
|
||||
const beforeRecommend = plan
|
||||
const recommendResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(planId)}/recommend`, {
|
||||
expectedVersion: Number(plan?.version), operationId: `${context.marker}-coload-recommend`.slice(0, 128),
|
||||
const manualGroupResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(planId)}/group-assignments`, {
|
||||
inboundId: first.inboundId, expectedVersion: Number(plan?.version),
|
||||
operationId: `${context.marker}-coload-reassign`.slice(0, 128),
|
||||
})
|
||||
const recommendSucceeded = recommendResponse?.http === 200 && code(recommendResponse) === '200'
|
||||
const capacityRejected = recommendResponse?.http === 200 && code(recommendResponse) === 'A0422'
|
||||
&& String(recommendResponse?.body?.message || '').includes('同柜不可拆组整体超过货柜')
|
||||
context.assert(MODULE, '目标同柜组整体超容时智能推荐明确拒绝', recommendResponse,
|
||||
capacityRejected,
|
||||
evidence(recommendResponse, `targetGroupId=${groupId}, members=${[first.inboundId, second.inboundId].join('|')}, message=${recommendResponse?.body?.message || 'none'}`), OPTIONS)
|
||||
plan = recommendSucceeded ? recommendResponse.body.data : beforeRecommend
|
||||
const recommendedIds = new Set((plan?.items || []).map((item) => id(item?.inboundId)))
|
||||
const recommendedMembers = [first.inboundId, second.inboundId].filter((value) => recommendedIds.has(value))
|
||||
context.assert(MODULE, '智能推荐对整组全取或全不取', recommendResponse,
|
||||
recommendedMembers.length === 0 || recommendedMembers.length === 2,
|
||||
`recommendedMembers=${recommendedMembers.join(',') || 'none'}`, OPTIONS)
|
||||
if (capacityRejected) {
|
||||
const afterRecommend = await readPlan(context, planId, '回读整组容量拒绝后计划')
|
||||
context.assert(MODULE, '整组容量不足时推荐事务零漂移', afterRecommend.response,
|
||||
planSignature(beforeRecommend) === planSignature(afterRecommend.data),
|
||||
`before=${planSignature(beforeRecommend)}, after=${planSignature(afterRecommend.data)}`, OPTIONS)
|
||||
plan = afterRecommend.data
|
||||
}
|
||||
if (recommendedMembers.length === 0) {
|
||||
const manualGroupResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(planId)}/group-assignments`, {
|
||||
inboundId: first.inboundId, expectedVersion: Number(plan?.version),
|
||||
operationId: `${context.marker}-coload-reassign`.slice(0, 128),
|
||||
})
|
||||
plan = context.apiSuccess(MODULE, '推荐容量未纳入时整组手工回配', manualGroupResponse,
|
||||
evidence(manualGroupResponse), OPTIONS)
|
||||
}
|
||||
plan = context.apiSuccess(MODULE, '整组移除后再次完整回配', manualGroupResponse,
|
||||
evidence(manualGroupResponse), OPTIONS)
|
||||
if (!plan) return
|
||||
|
||||
const pushKey = `${context.marker}-coload-push`.slice(0, 128)
|
||||
@@ -720,7 +721,7 @@ export async function run(context) {
|
||||
const partialBatchItems = (partialPda.task.items || []).map((item) => id(item.id) === id(partialTarget.id)
|
||||
? { itemId: id(item.id), action: 'PARTIAL', pickedPackageCount: null,
|
||||
finalPickedPackageCount: Number(item.plannedPackageCount) - 1, releaseMode: 'MANUAL',
|
||||
locationIds: [], version: Number(item.version), reason: '同柜不可拆组短拣异常验收' }
|
||||
locationIds: [], version: Number(item.version), reason: null }
|
||||
: { itemId: id(item.id), action: 'PICK', pickedPackageCount: Number(item.plannedPackageCount),
|
||||
finalPickedPackageCount: null, releaseMode: 'MANUAL', locationIds: [],
|
||||
version: Number(item.version), reason: null })
|
||||
@@ -807,12 +808,13 @@ export async function run(context) {
|
||||
evidence(response), OPTIONS)
|
||||
completedInboundStates.push(inbound?.summary?.processStatus)
|
||||
}
|
||||
context.assert(MODULE, '足量完成整组已消费、全部入库完成且不生成子单', consumedGroupResponse,
|
||||
const fullRemainders = fullTreesAfter.flatMap((item) => item?.tree?.children || [])
|
||||
.filter((item) => item?.splitRole === 'REMAINDER')
|
||||
context.assert(MODULE, '足量完成整组已消费、全部入库完成且不生成余量子单', consumedGroupResponse,
|
||||
consumedGroup.length === 2 && consumedGroup.every((item) => item?.group?.status === 'CONSUMED')
|
||||
&& completedInboundStates.every((status) => status === 'LOADED')
|
||||
&& fullTreesBefore.every((before, index) => (fullTreesAfter[index]?.tree?.children || []).length === 0
|
||||
&& before.signature === fullTreesAfter[index]?.signature),
|
||||
`groupStatus=${consumedGroup[0]?.group?.status || 'missing'}, inboundStates=${completedInboundStates.join('|')}, children=${fullTreesAfter.map((item) => item.tree?.children?.length ?? -1).join('|')}`, OPTIONS)
|
||||
&& fullRemainders.length === 0,
|
||||
`groupStatus=${consumedGroup[0]?.group?.status || 'missing'}, inboundStates=${completedInboundStates.join('|')}, remainderChildren=${fullRemainders.length}, childRoles=${fullTreesAfter.flatMap((item) => item?.tree?.children || []).map((item) => item?.splitRole).join('|') || 'none'}`, OPTIONS)
|
||||
|
||||
const normalTreeBefore = await fulfillmentTrees(context, normalChain.fixtures, '普通单完成前')
|
||||
const normalBatchItems = (normalPda.task?.items || []).map((item) => ({
|
||||
|
||||
Reference in New Issue
Block a user