fix(lastleg): accept cabinet pallet identities
This commit is contained in:
@@ -30,7 +30,7 @@ export function containerFieldGaps(record, { requireContainerNo = false } = {})
|
||||
cabinetNo: isNonBlank,
|
||||
warehouseId: isPositiveId,
|
||||
warehouseName: isNonBlank,
|
||||
channelCode: isNonBlank,
|
||||
channelGroupCode: isNonBlank,
|
||||
containerType: isNonBlank,
|
||||
capacityCbm: (value) => Number(value) > 0,
|
||||
maxWeightKg: (value) => Number(value) > 0,
|
||||
|
||||
@@ -82,7 +82,7 @@ test('正式 DTO/OpenAPI 暴露矩阵使用的稳定字段', () => {
|
||||
|
||||
test('字段校验器接受正式完整投影并精确暴露缺字段', () => {
|
||||
const samples = [
|
||||
[containerFieldGaps, { id: ids.container, cabinetNo: 'CAB-1', containerNo: 'MSCU1234567', warehouseId: ids.id, warehouseName: '宁波仓', channelCode: 'EXX', containerType: '40HQ', capacityCbm: 68, maxWeightKg: 19500, planStatus: 'EDITING' }, { requireContainerNo: true }, 'warehouseName'],
|
||||
[containerFieldGaps, { id: ids.container, cabinetNo: 'CAB-1', containerNo: 'MSCU1234567', warehouseId: ids.id, warehouseName: '宁波仓', channelGroupCode: 'EXX', containerType: '40HQ', capacityCbm: 68, maxWeightKg: 19500, planStatus: 'EDITING' }, { requireContainerNo: true }, 'warehouseName'],
|
||||
[preloadFieldGaps, { planId: ids.plan, containerBookingId: ids.container, preloadNo: 'PL-1', cabinetNo: 'CAB-1', customers: [{ accountId: ids.id, customerName: '测试客户' }], warehouseName: '宁波仓', containerType: '40HQ', status: 'EDITING', skuCount: 1, totalPackageCount: 1, totalVolumeCbm: 1, totalGrossWeightKg: 10, createdAt: at }, undefined, 'customers'],
|
||||
[loadingListFieldGaps, { planId: ids.plan, containerBookingId: ids.container, loadingListNo: 'LL-1', containerNo: 'MSCU1234567', cabinetNo: 'CAB-1', warehouseName: '宁波仓', status: 'COMPLETED', totalPackageCount: 1, totalGrossWeightKg: 10, totalNetWeightKg: 9, totalVolumeCbm: 1, totalQuantity: 2, createdAt: at }, undefined, 'containerNo'],
|
||||
[oceanBookingFieldGaps, { id: ids.id, bookingNo: 'BK-1', status: 'RELEASED', vesselName: 'VESSEL', voyageNo: 'V1', estimatedSailingAt: at, estimatedArrivalAt: at, customerId: ids.id, customerName: '测试客户', shippingCompanyId: null, shippingCompanyName: null, bookingAgentId: ids.id, bookingAgentName: '代理' }, undefined, 'voyageNo'],
|
||||
|
||||
@@ -110,7 +110,7 @@ async function verifyPalletLabels(context, fixture, loadingPlanId) {
|
||||
const index = Number(label?.palletIndex)
|
||||
return id(label?.containerNo) === id(fixture.containerNo)
|
||||
&& id(label?.destinationWarehouseInternalCode)
|
||||
&& id(label?.palletCode) === `${fixture.containerNo}-${label.destinationWarehouseInternalCode}-${String(index).padStart(3, '0')}`
|
||||
&& id(label?.palletCode) === `${fixture.cabinetNo}-${label.destinationWarehouseInternalCode}-${String(index).padStart(3, '0')}`
|
||||
})
|
||||
const indexesValid = (manifest?.groups || []).every((group) =>
|
||||
(group?.labels || []).every((label, index) => Number(label?.palletIndex) === index + 1))
|
||||
@@ -580,6 +580,7 @@ export async function run(context) {
|
||||
destinationWarehouseId: id(fixture.destinationWarehouseId),
|
||||
destinationWarehouseCode: fixture.destinationWarehouseCode || null,
|
||||
palletLabelManifest,
|
||||
cabinetNo: fixture.cabinetNo || null,
|
||||
containerNo: fixture.containerNo || null,
|
||||
sourceStatus: 'LOADED',
|
||||
verification: 'PRODUCED_IN_COMPLETE_TRANSACTION_PENDING_BRIDGE_CONSUMER_HTTP',
|
||||
|
||||
@@ -1715,7 +1715,8 @@ export async function run(context) {
|
||||
missingTypeVoidedEvidenceId: id(missingTypeTarget?.id), replacementEvidenceFileId: replacementFileId,
|
||||
immutableEvidenceId: id(immutablePhoto?.id), immutableEvidenceVersion: Number(immutablePhoto?.version),
|
||||
postLoadedFileId,
|
||||
oceanBookingId: bookingId, loadingPlanId, containerBookingId, containerNo, warehouseId,
|
||||
oceanBookingId: bookingId, loadingPlanId, containerBookingId,
|
||||
cabinetNo: simulation.cabinetNo, containerNo, warehouseId,
|
||||
canonicalPdaTaskId: canonicalTaskId,
|
||||
pickingTaskId: canonicalTaskId, inboundVersion: loadingVersion,
|
||||
atomicBatchVerified: awaitingReview && atomicBatchReplayVerified,
|
||||
|
||||
@@ -81,10 +81,13 @@ test('measurement and loading pass destination identity into the LOADED producer
|
||||
const finalize = read('phase3-loading-finalize.mjs')
|
||||
assert.match(measurement, /destinationType: context\.createdResources\?\.orders\?\.destinationType/)
|
||||
assert.match(loading, /destinationType: initial\?\.summary\?\.destinationType \|\| source\?\.destinationType/)
|
||||
assert.match(loading, /cabinetNo: simulation\.cabinetNo, containerNo/)
|
||||
assert.match(loading, /fulfillmentType: source\?\.fulfillmentType \|\| 'STANDARD'/)
|
||||
assert.match(loading, /Array\.isArray\(incrementalFixture\?\.skuCodes\)/)
|
||||
assert.match(loading, /Array\.isArray\(incrementalFixture\?\.skuLines\)/)
|
||||
assert.match(finalize, /destinationType: fixture\.destinationType \|\| null/)
|
||||
assert.match(finalize, /cabinetNo: fixture\.cabinetNo \|\| null/)
|
||||
assert.match(finalize, /`\$\{fixture\.cabinetNo\}-\$\{label\.destinationWarehouseInternalCode\}-/)
|
||||
assert.match(finalize, /fulfillmentType: fixture\.fulfillmentType \|\| 'STANDARD'/)
|
||||
assert.match(finalize, /skuCodes: Array\.isArray\(fixture\.skuCodes\) \? fixture\.skuCodes : \[\]/)
|
||||
assert.match(finalize, /skuLines: Array\.isArray\(fixture\.skuLines\) \? fixture\.skuLines : \[\]/)
|
||||
|
||||
@@ -103,6 +103,9 @@ test('PDA module covers the container-number receiving contract and excludes out
|
||||
for (const forbidden of ['/outbound', '/dispatch', '/delivery', '/internal/']) assert.equal(source.includes(forbidden), false, forbidden)
|
||||
assert.match(source, /同键同载荷重放/)
|
||||
assert.match(source, /inbound-plans\/container-preview/)
|
||||
assert.match(source, /orders\/container-arrival-times\/query/)
|
||||
assert.match(source, /container-summaries\/\$\{encodeURIComponent\(bookingId\)\}\/expected-arrival/)
|
||||
assert.match(source, /estimatedWarehouseArrivalAt, expectedVersion: Number\(arrivalTime\.version\)/)
|
||||
assert.match(source, /snapshotVersion: Number\(preview\.snapshotVersion\)/)
|
||||
assert.match(source, /snapshotHash: String\(preview\.snapshotHash\)/)
|
||||
assert.match(source, /rememberGeneratedPlans\(context, plans\)/)
|
||||
|
||||
@@ -336,6 +336,33 @@ async function convertReceivingContainers(context, producers) {
|
||||
`sourceContainerBookingId=${id(summary?.sourceContainerBookingId) || 'missing'}, allPlansReady=${summary?.allPlansReady === true}, orders=${totalOrders}, inboundCodes=${inboundCodes}, plans=${existingPlans}, pending=${pendingPlans}; ${trace(summaryResponse)}`, OPTIONS)
|
||||
if (!convertible) return false
|
||||
const bookingId = id(summary.sourceContainerBookingId)
|
||||
const arrivalQueryResponse = await context.requestOverseas('POST',
|
||||
'/api/platform/v1/last-leg/orders/container-arrival-times/query',
|
||||
{ sourceContainerBookingIds: [bookingId] }, {}, context.overseasToken)
|
||||
const arrivalTimes = success(arrivalQueryResponse) && Array.isArray(arrivalQueryResponse.body?.data)
|
||||
? arrivalQueryResponse.body.data : []
|
||||
let arrivalTime = arrivalTimes.find((item) => id(item?.sourceContainerBookingId) === bookingId)
|
||||
context.addStep(MODULE, `读取整柜预计到仓时间 ${containerNo}`, arrivalQueryResponse,
|
||||
Boolean(arrivalTime) && Number.isInteger(Number(arrivalTime?.version)),
|
||||
`sourceContainerBookingId=${bookingId}, estimatedWarehouseArrivalAt=${arrivalTime?.estimatedWarehouseArrivalAt || 'missing'}, version=${arrivalTime?.version ?? 'missing'}; ${trace(arrivalQueryResponse)}`,
|
||||
OPTIONS)
|
||||
if (!arrivalTime) return false
|
||||
if (!nonBlank(arrivalTime.estimatedWarehouseArrivalAt)) {
|
||||
const estimatedWarehouseArrivalAt = new Date(Date.now() + 2 * 86400000).toISOString().slice(0, 19)
|
||||
const arrivalUpdateResponse = await context.requestOverseas('PUT',
|
||||
`/api/platform/v1/last-leg/orders/container-summaries/${encodeURIComponent(bookingId)}/expected-arrival`,
|
||||
{ estimatedWarehouseArrivalAt, expectedVersion: Number(arrivalTime.version),
|
||||
remark: `${context.marker}-overseas-inbound-plan-prerequisite` },
|
||||
{ 'Idempotency-Key': `${context.marker}-expected-arrival-${containerNo}`.slice(0, 128) },
|
||||
context.overseasToken)
|
||||
arrivalTime = success(arrivalUpdateResponse) ? arrivalUpdateResponse.body?.data : null
|
||||
context.addStep(MODULE, `维护整柜预计到仓时间 ${containerNo}`, arrivalUpdateResponse,
|
||||
id(arrivalTime?.sourceContainerBookingId) === bookingId
|
||||
&& nonBlank(arrivalTime?.estimatedWarehouseArrivalAt),
|
||||
`sourceContainerBookingId=${bookingId}, estimatedWarehouseArrivalAt=${arrivalTime?.estimatedWarehouseArrivalAt || 'missing'}, version=${arrivalTime?.version ?? 'missing'}; ${trace(arrivalUpdateResponse)}`,
|
||||
OPTIONS)
|
||||
if (!arrivalTime || !nonBlank(arrivalTime.estimatedWarehouseArrivalAt)) return false
|
||||
}
|
||||
const previewPath = `/api/platform/v1/last-leg/inbound-plans/container-preview?sourceContainerBookingId=${encodeURIComponent(bookingId)}`
|
||||
const previewResponse = await context.requestOverseas('GET', previewPath,
|
||||
undefined, {}, context.overseasToken)
|
||||
|
||||
Reference in New Issue
Block a user