feat: add dropship fulfillment producer chain
This commit is contained in:
@@ -921,10 +921,10 @@ export class AcceptanceRun {
|
||||
}
|
||||
}
|
||||
|
||||
async runPhase4ProducerVariant(label, destinationType) {
|
||||
async runPhase4ProducerVariant(label, destinationType, fulfillmentType = 'STANDARD') {
|
||||
const matrixMarker = this.marker
|
||||
const variantMarker = `${matrixMarker}-P4-${label}`
|
||||
const contextKeys = ['orderDestinationType', 'phase4ProducerMatrixMarker']
|
||||
const contextKeys = ['orderDestinationType', 'orderFulfillmentType', 'phase4ProducerMatrixMarker']
|
||||
const createdKeys = ['phase3Loading', 'phase3LoadingCompletedProducer', 'orchestrationFixtures', 'oceanBooking']
|
||||
const contextSnapshot = new Map(contextKeys.map((key) => [key, {
|
||||
present: Object.hasOwn(this.context, key), value: this.context[key],
|
||||
@@ -935,6 +935,7 @@ export class AcceptanceRun {
|
||||
let restoreFixture = null
|
||||
this.marker = variantMarker
|
||||
this.context.orderDestinationType = destinationType
|
||||
this.context.orderFulfillmentType = fulfillmentType
|
||||
this.context.phase4ProducerMatrixMarker = matrixMarker
|
||||
try {
|
||||
restoreFixture = await this.preparePhase3LoadingFixture()
|
||||
@@ -958,19 +959,22 @@ export class AcceptanceRun {
|
||||
|
||||
async ensurePhase4PdaProducerMatrix() {
|
||||
const readiness = () => this.createdResources.phase4InboundProducerFixtures?.readiness || {}
|
||||
if (Number(readiness().fbaContainerCount || 0) < 1) {
|
||||
await this.runPhase4ProducerVariant('FBA-PRIMARY', 'FBA')
|
||||
if (Number(readiness().dropshipContainerCount || 0) < 1) {
|
||||
await this.runPhase4ProducerVariant('DROPSHIP-PRIMARY', 'FBA', 'ONE_CLICK_DROPSHIP')
|
||||
}
|
||||
if (Number(readiness().fbaContainerCount || 0) < 2) {
|
||||
await this.runPhase4ProducerVariant('FBA-STANDARD', 'FBA')
|
||||
}
|
||||
if (Number(readiness().overseasContainerCount || 0) < 1) {
|
||||
await this.runPhase4ProducerVariant('OVERSEAS-PRIMARY', 'OVERSEAS')
|
||||
}
|
||||
if (Number(readiness().distinctContainerCount || 0) < 3) {
|
||||
if (Number(readiness().distinctContainerCount || 0) < 4) {
|
||||
await this.runPhase4ProducerVariant('SECONDARY', 'FBA')
|
||||
}
|
||||
const matrix = this.createdResources.phase4InboundProducerFixtures
|
||||
this.assert('phase4-overseas-pda-fixture', '本轮三期跨区生产矩阵已就绪', null,
|
||||
matrix?.marker === this.marker && matrix?.readiness?.readyForPhase4Pda === true,
|
||||
`marker=${matrix?.marker || 'missing'}, FBA=${matrix?.readiness?.fbaContainerCount ?? 0}, OVERSEAS=${matrix?.readiness?.overseasContainerCount ?? 0}, containers=${matrix?.readiness?.distinctContainerCount ?? 0}`,
|
||||
`marker=${matrix?.marker || 'missing'}, FBA=${matrix?.readiness?.fbaContainerCount ?? 0}, OVERSEAS=${matrix?.readiness?.overseasContainerCount ?? 0}, DROPSHIP=${matrix?.readiness?.dropshipContainerCount ?? 0}, containers=${matrix?.readiness?.distinctContainerCount ?? 0}`,
|
||||
{ scenario: 'phase3-phase4-producer-fixture', deploymentBlocker: true })
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +250,7 @@ function buildPayload(context, profile, master, productImageFileId) {
|
||||
const referenceId = `REF-${context.marker}`.slice(0, 64)
|
||||
const companyName = profile.tenantName || `订单验收客户-${context.marker}`
|
||||
return {
|
||||
fulfillmentType: String(context.context?.orderFulfillmentType || 'STANDARD').trim().toUpperCase(),
|
||||
customerId: master.partner?.id ?? null,
|
||||
customerName: master.partner?.partnerName ?? companyName,
|
||||
transportMode: 'SEA', tradeTerm: 'FOB',
|
||||
@@ -397,6 +398,7 @@ export async function run(context) {
|
||||
if (!orderId) throw new Error('Client 订单创建失败,后续 Admin 链路停止')
|
||||
context.createdResources.orders = {
|
||||
marker: context.marker, tenantId, orderId, productImageFileId,
|
||||
fulfillmentType: payload.fulfillmentType,
|
||||
boxTypes: BOXES.map((box) => box.boxType), inboundId: null,
|
||||
receivingWarehouseId: id(master.receiving.id),
|
||||
destinationType: master.destinationType,
|
||||
|
||||
@@ -35,6 +35,7 @@ export function collectPhase4InboundProducerFixture(previous, producer) {
|
||||
destinationType,
|
||||
containers.filter((item) => item.destinationType === destinationType),
|
||||
]))
|
||||
const dropshipContainers = containers.filter((item) => item.fulfillmentType === 'ONE_CLICK_DROPSHIP')
|
||||
const distinctContainerCount = new Set(containers.map((item) => item.containerNo).filter(Boolean)).size
|
||||
return {
|
||||
marker: producer.marker,
|
||||
@@ -43,12 +44,15 @@ export function collectPhase4InboundProducerFixture(previous, producer) {
|
||||
readiness: {
|
||||
fbaContainerCount: byDestinationType.FBA.length,
|
||||
overseasContainerCount: byDestinationType.OVERSEAS.length,
|
||||
dropshipContainerCount: dropshipContainers.length,
|
||||
distinctContainerCount,
|
||||
readyForPhase4Pda: byDestinationType.FBA.length >= 1
|
||||
readyForPhase4Pda: byDestinationType.FBA.length >= 2
|
||||
&& byDestinationType.OVERSEAS.length >= 1
|
||||
&& distinctContainerCount >= 3,
|
||||
&& dropshipContainers.length >= 1
|
||||
&& distinctContainerCount >= 4,
|
||||
},
|
||||
requiredEntryOrchestration: [
|
||||
'DROPSHIP-FBA: public order fulfillmentType -> inbound-measurement -> phase3-loading -> phase3-loading-finalize -> overseas PDA',
|
||||
'FBA: orders -> inbound-measurement -> phase3-loading -> phase3-customs -> phase3-loading-finalize',
|
||||
'OVERSEAS-PRIMARY: orders -> inbound-measurement -> phase3-loading -> phase3-customs -> phase3-loading-finalize',
|
||||
'SECONDARY(FBA|OVERSEAS): orders -> inbound-measurement -> phase3-loading -> phase3-customs -> phase3-loading-finalize',
|
||||
@@ -367,6 +371,7 @@ export async function run(context) {
|
||||
sourceInboundId: inboundId,
|
||||
cartonMark: fixture.cartonMark || null,
|
||||
cartonMarks: Array.isArray(fixture.cartonMarks) ? fixture.cartonMarks : [],
|
||||
fulfillmentType: fixture.fulfillmentType || 'STANDARD',
|
||||
destinationType: fixture.destinationType || null,
|
||||
destinationWarehouseId: id(fixture.destinationWarehouseId),
|
||||
destinationWarehouseCode: fixture.destinationWarehouseCode || null,
|
||||
|
||||
@@ -1215,6 +1215,7 @@ export async function run(context) {
|
||||
cartonMark: source?.boxCode || source?.cartonMark || null,
|
||||
cartonMarks: [...new Set((stagedTask?.items || []).flatMap((item) => item?.boxCodes || [])
|
||||
.map((value) => String(value || '').trim()).filter(Boolean))],
|
||||
fulfillmentType: source?.fulfillmentType || 'STANDARD',
|
||||
destinationType: initial?.summary?.destinationType || source?.destinationType || null,
|
||||
destinationWarehouseId: id(initial?.summary?.destinationWarehouseId || source?.destinationWarehouseId),
|
||||
destinationWarehouseCode: initial?.summary?.destinationCode || source?.destinationWarehouseCode || null,
|
||||
|
||||
@@ -17,25 +17,32 @@ test('order fixture defaults to FBA and accepts only the explicit OVERSEAS varia
|
||||
const orders = read('orders.mjs')
|
||||
assert.match(orders, /warehouseType=\$\{encodeURIComponent\(destinationType\)\}/)
|
||||
assert.match(orders, /destinationType: master\.destinationType/)
|
||||
assert.match(orders, /fulfillmentType: String\(context\.context\?\.orderFulfillmentType \|\| 'STANDARD'\)/)
|
||||
assert.match(orders, /destinationType,\s*\n\s*\}\)/)
|
||||
assert.doesNotMatch(orders, /const fbaResponse =/)
|
||||
})
|
||||
|
||||
test('producer collector requires one FBA, one OVERSEAS and a third distinct container', () => {
|
||||
test('producer collector requires dropship FBA, standard FBA, OVERSEAS and a fourth distinct container', () => {
|
||||
const common = { marker: 'RUN-1', eventType: 'FIRST_LEG_LOADING_PLAN_COMPLETED', sourceStatus: 'LOADED' }
|
||||
const fba = { ...common, eventKey: 'FBA-EVENT', destinationType: 'FBA', containerNo: 'FBA-001' }
|
||||
const dropship = { ...common, eventKey: 'DROP-EVENT', destinationType: 'FBA',
|
||||
fulfillmentType: 'ONE_CLICK_DROPSHIP', containerNo: 'DROP-001' }
|
||||
const fba = { ...common, eventKey: 'FBA-EVENT', destinationType: 'FBA',
|
||||
fulfillmentType: 'STANDARD', containerNo: 'FBA-001' }
|
||||
const overseasPrimary = { ...common, eventKey: 'OS-EVENT-1', destinationType: 'OVERSEAS', containerNo: 'OS-001' }
|
||||
const secondary = { ...common, eventKey: 'FBA-EVENT-2', destinationType: 'FBA', containerNo: 'FBA-002' }
|
||||
const one = collectPhase4InboundProducerFixture(null, fba)
|
||||
const two = collectPhase4InboundProducerFixture(one, overseasPrimary)
|
||||
const ready = collectPhase4InboundProducerFixture(two, secondary)
|
||||
const one = collectPhase4InboundProducerFixture(null, dropship)
|
||||
const two = collectPhase4InboundProducerFixture(one, fba)
|
||||
const three = collectPhase4InboundProducerFixture(two, overseasPrimary)
|
||||
const ready = collectPhase4InboundProducerFixture(three, secondary)
|
||||
assert.equal(one.readiness.readyForPhase4Pda, false)
|
||||
assert.equal(two.readiness.readyForPhase4Pda, false)
|
||||
assert.equal(three.readiness.readyForPhase4Pda, false)
|
||||
assert.equal(ready.readiness.readyForPhase4Pda, true)
|
||||
assert.equal(ready.readiness.fbaContainerCount, 2)
|
||||
assert.equal(ready.readiness.fbaContainerCount, 3)
|
||||
assert.equal(ready.readiness.overseasContainerCount, 1)
|
||||
assert.equal(ready.readiness.distinctContainerCount, 3)
|
||||
assert.equal(ready.byDestinationType.FBA[1].eventKey, 'FBA-EVENT-2')
|
||||
assert.equal(ready.readiness.dropshipContainerCount, 1)
|
||||
assert.equal(ready.readiness.distinctContainerCount, 4)
|
||||
assert.equal(ready.byDestinationType.FBA[2].eventKey, 'FBA-EVENT-2')
|
||||
})
|
||||
|
||||
test('same event key is idempotently replaced and another marker starts a new matrix', () => {
|
||||
@@ -49,6 +56,7 @@ test('same event key is idempotently replaced and another marker starts a new ma
|
||||
assert.equal(nextRun.containers.length, 1)
|
||||
assert.equal(nextRun.marker, 'RUN-2')
|
||||
assert.deepEqual(nextRun.requiredEntryOrchestration, [
|
||||
'DROPSHIP-FBA: public order fulfillmentType -> inbound-measurement -> phase3-loading -> phase3-loading-finalize -> overseas PDA',
|
||||
'FBA: orders -> inbound-measurement -> phase3-loading -> phase3-customs -> phase3-loading-finalize',
|
||||
'OVERSEAS-PRIMARY: orders -> inbound-measurement -> phase3-loading -> phase3-customs -> phase3-loading-finalize',
|
||||
'SECONDARY(FBA|OVERSEAS): orders -> inbound-measurement -> phase3-loading -> phase3-customs -> phase3-loading-finalize',
|
||||
@@ -61,7 +69,14 @@ 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, /fulfillmentType: source\?\.fulfillmentType \|\| 'STANDARD'/)
|
||||
assert.match(finalize, /destinationType: fixture\.destinationType \|\| null/)
|
||||
assert.match(finalize, /fulfillmentType: fixture\.fulfillmentType \|\| 'STANDARD'/)
|
||||
assert.match(finalize, /resourceKey: `\$\{context\.marker\}:\$\{fixture\.destinationType \|\| 'UNKNOWN'\}:\$\{fixture\.containerNo \|\| containerBookingId\}`/)
|
||||
assert.match(finalize, /phase4InboundProducerFixtures = collectPhase4InboundProducerFixture/)
|
||||
const entry = read('../admin-test-env-acceptance.mjs')
|
||||
assert.match(entry, /runPhase4ProducerVariant\('DROPSHIP-PRIMARY', 'FBA', 'ONE_CLICK_DROPSHIP'\)/)
|
||||
assert.match(entry, /orderFulfillmentType/)
|
||||
const pda = read('phase4-overseas-pda.mjs')
|
||||
assert.match(pda, /find\(\(item\) => item\?\.fulfillmentType === 'ONE_CLICK_DROPSHIP'\)/)
|
||||
})
|
||||
|
||||
@@ -413,7 +413,8 @@ export async function run(context) {
|
||||
return
|
||||
}
|
||||
const matrix = context.createdResources?.phase4InboundProducerFixtures
|
||||
const producers = [matrix?.byDestinationType?.FBA?.[0], matrix?.byDestinationType?.OVERSEAS?.[0]]
|
||||
const dropship = matrix?.containers?.find((item) => item?.fulfillmentType === 'ONE_CLICK_DROPSHIP')
|
||||
const producers = [dropship || matrix?.byDestinationType?.FBA?.[0], matrix?.byDestinationType?.OVERSEAS?.[0]]
|
||||
.filter((item) => nonBlank(item?.containerNo))
|
||||
const allProducers = Array.isArray(matrix?.containers) ? matrix.containers : producers
|
||||
if (allProducers.length && !(await awaitProducerCandidates(context, primary.token, allProducers))) {
|
||||
|
||||
Reference in New Issue
Block a user