From cd749733deb510242caa5e7f4483e0a62836ffd9 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 00:01:39 +0800 Subject: [PATCH] fix: preserve dropship sku loading details --- .../mapper/SmartLoadingCandidateMapper.java | 66 +++++++++++++++---- ...artLoadingCandidateMapperContractTest.java | 7 ++ .../admin-test-env-modules/phase3-loading.mjs | 46 ++++++++----- ...-phase4-producer-fixture-contract.test.mjs | 4 +- 4 files changed, 92 insertions(+), 31 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/mapper/SmartLoadingCandidateMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/mapper/SmartLoadingCandidateMapper.java index 70f787c00..11796953d 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/mapper/SmartLoadingCandidateMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/mapper/SmartLoadingCandidateMapper.java @@ -39,13 +39,34 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba AND m.id=(SELECT MAX(m2.id) FROM fl_inbound_measurement m2 WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id AND m2.status='APPROVED') JOIN ( - SELECT tenant_id,measurement_id,inbound_detail_id,SUM(package_count) package_count, - COALESCE(SUM(COALESCE(total_volume_cbm,COALESCE(unit_volume_cbm,0)*package_count)) + SELECT measured.tenant_id,batch.inbound_id,measured.measurement_id,measured.inbound_detail_id, + measured.package_count,measured.unit_volume_cbm + FROM ( + SELECT tenant_id,measurement_id,inbound_detail_id,SUM(package_count) package_count, + COALESCE(SUM(COALESCE(total_volume_cbm,COALESCE(unit_volume_cbm,0)*package_count)) / NULLIF(SUM(package_count),0),0) unit_volume_cbm - FROM fl_inbound_measurement_item - WHERE inbound_detail_id IS NOT NULL AND package_count>0 - GROUP BY tenant_id,measurement_id,inbound_detail_id - ) i ON i.tenant_id=o.tenant_id AND i.measurement_id=m.id + FROM fl_inbound_measurement_item + WHERE inbound_detail_id IS NOT NULL AND package_count>0 + GROUP BY tenant_id,measurement_id,inbound_detail_id + ) measured + JOIN fl_inbound_measurement batch ON batch.id=measured.measurement_id + AND batch.tenant_id=measured.tenant_id + JOIN fl_inbound_order measured_order ON measured_order.id=batch.inbound_id + AND measured_order.tenant_id=batch.tenant_id + WHERE COALESCE(measured_order.fulfillment_type,'STANDARD')<>'ONE_CLICK_DROPSHIP' + UNION ALL + SELECT carton.tenant_id,carton.inbound_id,batch.id,carton.inbound_detail_id,COUNT(*), + COALESCE(batch.total_volume_cbm/NULLIF(source.total_package_count,0),0) + FROM fl_dropship_pallet_carton carton + JOIN fl_dropship_pallet pallet ON pallet.id=carton.pallet_id AND pallet.pallet_status='COMPLETED' + JOIN fl_inbound_order source ON source.id=carton.inbound_id AND source.tenant_id=carton.tenant_id + AND source.fulfillment_type='ONE_CLICK_DROPSHIP' + JOIN fl_inbound_measurement batch ON batch.inbound_id=source.id AND batch.tenant_id=source.tenant_id + AND batch.status='APPROVED' AND batch.id=(SELECT MAX(latest.id) FROM fl_inbound_measurement latest + WHERE latest.tenant_id=source.tenant_id AND latest.inbound_id=source.id AND latest.status='APPROVED') + GROUP BY carton.tenant_id,carton.inbound_id,batch.id,carton.inbound_detail_id, + batch.total_volume_cbm,source.total_package_count + ) i ON i.tenant_id=o.tenant_id AND i.inbound_id=o.id AND i.measurement_id=m.id WHERE o.warehouse_id=#{warehouseId} AND o.process_status='WAIT_SPLIT' AND o.actual_gross_weight_kg IS NOT NULL AND o.actual_net_weight_kg IS NOT NULL AND (o.inbound_role='MASTER' OR (o.split_purpose='REMAINDER' AND o.split_status='NOT_SPLIT')) @@ -89,13 +110,34 @@ import com.baiyue.firstleg.dto.SmartLoadingDtos.Candidate; import org.apache.iba AND m.id=(SELECT MAX(m2.id) FROM fl_inbound_measurement m2 WHERE m2.tenant_id=o.tenant_id AND m2.inbound_id=o.id AND m2.status='APPROVED') JOIN ( - SELECT tenant_id,measurement_id,inbound_detail_id,SUM(package_count) package_count, - COALESCE(SUM(COALESCE(total_volume_cbm,COALESCE(unit_volume_cbm,0)*package_count)) + SELECT measured.tenant_id,batch.inbound_id,measured.measurement_id,measured.inbound_detail_id, + measured.package_count,measured.unit_volume_cbm + FROM ( + SELECT tenant_id,measurement_id,inbound_detail_id,SUM(package_count) package_count, + COALESCE(SUM(COALESCE(total_volume_cbm,COALESCE(unit_volume_cbm,0)*package_count)) / NULLIF(SUM(package_count),0),0) unit_volume_cbm - FROM fl_inbound_measurement_item - WHERE inbound_detail_id IS NOT NULL AND package_count>0 - GROUP BY tenant_id,measurement_id,inbound_detail_id - ) i ON i.tenant_id=o.tenant_id AND i.measurement_id=m.id + FROM fl_inbound_measurement_item + WHERE inbound_detail_id IS NOT NULL AND package_count>0 + GROUP BY tenant_id,measurement_id,inbound_detail_id + ) measured + JOIN fl_inbound_measurement batch ON batch.id=measured.measurement_id + AND batch.tenant_id=measured.tenant_id + JOIN fl_inbound_order measured_order ON measured_order.id=batch.inbound_id + AND measured_order.tenant_id=batch.tenant_id + WHERE COALESCE(measured_order.fulfillment_type,'STANDARD')<>'ONE_CLICK_DROPSHIP' + UNION ALL + SELECT carton.tenant_id,carton.inbound_id,batch.id,carton.inbound_detail_id,COUNT(*), + COALESCE(batch.total_volume_cbm/NULLIF(source.total_package_count,0),0) + FROM fl_dropship_pallet_carton carton + JOIN fl_dropship_pallet pallet ON pallet.id=carton.pallet_id AND pallet.pallet_status='COMPLETED' + JOIN fl_inbound_order source ON source.id=carton.inbound_id AND source.tenant_id=carton.tenant_id + AND source.fulfillment_type='ONE_CLICK_DROPSHIP' + JOIN fl_inbound_measurement batch ON batch.inbound_id=source.id AND batch.tenant_id=source.tenant_id + AND batch.status='APPROVED' AND batch.id=(SELECT MAX(latest.id) FROM fl_inbound_measurement latest + WHERE latest.tenant_id=source.tenant_id AND latest.inbound_id=source.id AND latest.status='APPROVED') + GROUP BY carton.tenant_id,carton.inbound_id,batch.id,carton.inbound_detail_id, + batch.total_volume_cbm,source.total_package_count + ) i ON i.tenant_id=o.tenant_id AND i.inbound_id=o.id AND i.measurement_id=m.id WHERE o.warehouse_id=#{warehouseId} AND o.id=#{inboundId} AND (o.process_status='WAIT_SPLIT' OR (o.process_status='LOADING' AND EXISTS ( SELECT 1 FROM fl_loading_plan_item same_plan_item diff --git a/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java index 543c01425..38c98417a 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/repository/SmartLoadingCandidateMapperContractTest.java @@ -42,6 +42,10 @@ class SmartLoadingCandidateMapperContractTest { .contains("WHERE inbound_detail_id IS NOT NULL AND package_count>0") .contains("SUM(package_count) package_count") .contains("GROUP BY tenant_id,measurement_id,inbound_detail_id") + .contains("fl_dropship_pallet_carton carton") + .contains("pallet.pallet_status='COMPLETED'") + .contains("source.fulfillment_type='ONE_CLICK_DROPSHIP'") + .contains("i.inbound_id=o.id") .contains("o.warehouse_id=#{warehouseId}") .contains("o.process_status='WAIT_SPLIT'") .contains("unitGrossWeightKg") @@ -82,6 +86,9 @@ class SmartLoadingCandidateMapperContractTest { .contains("SELECT SUM(li.package_count)") .contains("SUM(package_count) package_count") .contains("GROUP BY tenant_id,measurement_id,inbound_detail_id") + .contains("fl_dropship_pallet_carton carton") + .contains("source.fulfillment_type='ONE_CLICK_DROPSHIP'") + .contains("i.inbound_id=o.id") .contains("FOR UPDATE"); } diff --git a/scripts/admin-test-env-modules/phase3-loading.mjs b/scripts/admin-test-env-modules/phase3-loading.mjs index 04eede819..d19eb53c4 100644 --- a/scripts/admin-test-env-modules/phase3-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-loading.mjs @@ -212,8 +212,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, @@ -226,17 +227,23 @@ export async function run(context) { const loadingPlan = context.apiSuccess(MODULE, '读取柜次创建时原子生成的智能装柜计划', loadingPlanResponse, '', OPTIONS) const loadingPlanId = id(loadingPlan?.id) if (!loadingPlanId) return - const initialAssignmentCount = source?.fulfillmentType === 'ONE_CLICK_DROPSHIP' - ? Number(candidate.availablePackageCount) : 1 - const assignmentResponse = await request(context, 'POST', `${smartLoadingBase}/plans/${loadingPlanId}/assignments`, { - inboundId, inboundDetailId: id(candidate.inboundDetailId), packageCount: initialAssignmentCount, - expectedVersion: Number(loadingPlan.version), operationId: `${context.marker}-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}-bridge-assign-inbound`.slice(0, 128) } + : { inboundId, inboundDetailId: id(candidate.inboundDetailId), packageCount: initialAssignmentCount, + expectedVersion: Number(loadingPlan.version), operationId: `${context.marker}-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) === initialAssignmentCount) - && Number(assignedLoadingPlan?.version) === 1, + 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) @@ -499,12 +506,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) @@ -570,10 +580,10 @@ export async function run(context) { warehouseId, loadingPlanId, containerBookingId, expectedVersion: Number(claimed?.version), inventorySnapshotToken: inventory.snapshotToken, - items: [{ - itemId: id(canonicalItem.id), action: 'PICK', pickedPackageCount: initialAssignmentCount, - releaseMode: 'MANUAL', locationIds: [], version: Number(canonicalItem.version), - }], + items: canonicalPickItems.map((item) => ({ + itemId: id(item.id), action: 'PICK', pickedPackageCount: Number(item.plannedPackageCount), + releaseMode: 'MANUAL', locationIds: [], version: Number(item.version), + })), } const staleBatch = await request(context, 'POST', batchPath, diff --git a/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs b/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs index ba73a0b32..addce3075 100644 --- a/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-phase4-producer-fixture-contract.test.mjs @@ -88,7 +88,9 @@ test('measurement and loading pass destination identity into the LOADED producer assert.match(entry, /skuCodes: Array\.isArray\(fixtureOrder\.skuCodes\) \? fixtureOrder\.skuCodes : \[\]/) assert.match(entry, /skuLines: Array\.isArray\(fixtureOrder\.skuLines\) \? fixtureOrder\.skuLines : \[\]/) const pda = read('phase4-overseas-pda.mjs') - assert.match(loading, /pickedPackageCount: initialAssignmentCount/) + assert.match(loading, /inbound-assignments/) + assert.match(loading, /canonicalPickItems\.map/) + assert.match(loading, /pickedPackageCount: Number\(item\.plannedPackageCount\)/) assert.match(pda, /find\(\(item\) => item\?\.fulfillmentType === 'ONE_CLICK_DROPSHIP'\)/) assert.match(pda, /resolveCartonFixtures\(context, token, current\.data\.inbounds, producerCartonMarks\)/) assert.match(pda, /FBA与OVERSEAS箱码resolve均为纯查询且零状态写入/)