fix: 支持多SKU共箱整托装柜
This commit is contained in:
@@ -1068,19 +1068,48 @@ public class LoadingPickService {
|
||||
}
|
||||
Map<Long, LoadingPickItemEntity> itemsById = taskItems.stream().collect(Collectors.toMap(
|
||||
LoadingPickItemEntity::getId, item -> item));
|
||||
Set<Long> unresolvedIds = allItems.stream().filter(item -> !isResolved(item))
|
||||
.map(LoadingPickItemEntity::getId).collect(Collectors.toSet());
|
||||
Set<Long> requestIds = request.items().stream().map(BatchItemRequest::itemId)
|
||||
.collect(Collectors.toSet());
|
||||
if (requestIds.size() != request.items().size()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "整批明细不能包含重复itemId");
|
||||
}
|
||||
if (!requestIds.equals(unresolvedIds)) {
|
||||
Set<String> unresolvedUnits = allItems.stream().filter(item -> !isResolved(item))
|
||||
.map(LoadingPickService::batchUnitKey).collect(Collectors.toSet());
|
||||
Set<String> requestUnits = request.items().stream().map(requestItem -> {
|
||||
LoadingPickItemEntity item = itemsById.get(requestItem.itemId());
|
||||
if (item == null || isResolved(item)) throw conflict("PDA明细不属于当前任务或已完成");
|
||||
return batchUnitKey(item);
|
||||
}).collect(Collectors.toSet());
|
||||
if (requestUnits.size() != request.items().size()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "同一整托只能提交一次");
|
||||
}
|
||||
if (!requestUnits.equals(unresolvedUnits)) {
|
||||
throw conflict("整批提交必须且只能包含当前任务全部待拣明细");
|
||||
}
|
||||
|
||||
Map<Long, Boolean> inboundHasWork = new HashMap<>();
|
||||
List<BatchItemRequest> expandedRequests = new ArrayList<>();
|
||||
for (BatchItemRequest requestItem : request.items()) {
|
||||
LoadingPickItemEntity representative = itemsById.get(requestItem.itemId());
|
||||
if (representative.getPalletId() == null) {
|
||||
expandedRequests.add(requestItem);
|
||||
continue;
|
||||
}
|
||||
List<LoadingPickItemEntity> members = allItems.stream().filter(item -> !isResolved(item)
|
||||
&& representative.getPalletId().equals(item.getPalletId())).toList();
|
||||
int aggregateVersion = members.stream().map(LoadingPickItemEntity::getVersion)
|
||||
.filter(Objects::nonNull).max(Integer::compareTo).orElse(0);
|
||||
if (!Objects.equals(requestItem.version(), aggregateVersion)) {
|
||||
throw conflict("PDA明细版本已变化,请刷新后重试");
|
||||
}
|
||||
for (LoadingPickItemEntity member : members) {
|
||||
expandedRequests.add(new BatchItemRequest(member.getId(), requestItem.action(),
|
||||
requestItem.pickedPackageCount(), requestItem.finalPickedPackageCount(),
|
||||
member.getVersion(), requestItem.reason(), requestItem.palletNo()));
|
||||
}
|
||||
}
|
||||
|
||||
Map<Long, Boolean> inboundHasWork = new HashMap<>();
|
||||
for (BatchItemRequest requestItem : expandedRequests) {
|
||||
LoadingPickItemEntity item = itemsById.get(requestItem.itemId());
|
||||
if (item == null || item.getInboundId() == null || item.getLoadingPlanItemId() == null
|
||||
|| item.getInboundDetailId() == null) {
|
||||
@@ -1092,7 +1121,7 @@ public class LoadingPickService {
|
||||
}
|
||||
|
||||
List<BatchLine> result = new ArrayList<>();
|
||||
for (BatchItemRequest requestItem : request.items()) {
|
||||
for (BatchItemRequest requestItem : expandedRequests) {
|
||||
LoadingPickItemEntity item = itemsById.get(requestItem.itemId());
|
||||
if (!Objects.equals(requestItem.version(), item.getVersion())) {
|
||||
throw conflict("PDA明细版本已变化,请刷新后重试");
|
||||
@@ -1152,6 +1181,10 @@ public class LoadingPickService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String batchUnitKey(LoadingPickItemEntity item) {
|
||||
return item.getPalletId() == null ? "ITEM:" + item.getId() : "PALLET:" + item.getPalletId();
|
||||
}
|
||||
|
||||
private void validateWholePallets(List<BatchLine> lines, List<LoadingPickItemEntity> allItems, long warehouseId) {
|
||||
Map<Long, List<BatchLine>> groups = lines.stream().filter(line -> "PICK_PALLET".equals(line.action()))
|
||||
.collect(Collectors.groupingBy(line -> line.item().getPalletId()));
|
||||
@@ -1281,6 +1314,7 @@ public class LoadingPickService {
|
||||
Map<Long, Integer> requestedByInbound = new HashMap<>();
|
||||
Map<Long, BatchLine> lineByItem = lines.stream().collect(Collectors.toMap(
|
||||
line -> line.item().getId(), line -> line));
|
||||
Set<String> countedPalletInbounds = new HashSet<>();
|
||||
for (LoadingPickItemEntity item : allItems) {
|
||||
BatchLine line = lineByItem.get(item.getId());
|
||||
int picked = line == null ? count(item.getPickedPackageCount())
|
||||
@@ -1288,6 +1322,10 @@ public class LoadingPickService {
|
||||
? count(item.getPickedPackageCount()) + line.request().pickedPackageCount()
|
||||
: "PICK_PALLET".equals(line.action()) ? count(item.getPlannedPackageCount())
|
||||
: "PARTIAL".equals(line.action()) ? line.request().finalPickedPackageCount() : 0;
|
||||
if (line != null && "PICK_PALLET".equals(line.action())) {
|
||||
String physicalUnit = item.getPalletId() + ":" + item.getInboundId();
|
||||
if (!countedPalletInbounds.add(physicalUnit)) continue;
|
||||
}
|
||||
requestedByInbound.merge(item.getInboundId(), picked, Integer::sum);
|
||||
}
|
||||
requestedByInbound.forEach((inboundId, requested) -> {
|
||||
|
||||
@@ -296,7 +296,7 @@ components:
|
||||
items: {$ref: '#/components/schemas/BatchItemRequest'}
|
||||
BatchItemRequest:
|
||||
type: object
|
||||
description: PICK_PALLET submits every pickItem of the scanned DP pallet with its own version in the same atomic batch. No carton quantities or skip reason are accepted; the server verifies the complete SEALED pallet. Scanning only changes the device draft.
|
||||
description: PICK_PALLET submits one representative pickItem for each scanned DP pallet. The server expands that physical pallet to every SKU line and verifies them atomically. No carton quantities or skip reason are accepted. Scanning only changes the device draft.
|
||||
required: [itemId, action, version]
|
||||
properties:
|
||||
itemId: {type: integer, format: int64, minimum: 1}
|
||||
|
||||
@@ -1368,8 +1368,7 @@ class LoadingPickServiceTest {
|
||||
var snapshot = service.inventorySnapshot(20L, 101L);
|
||||
assertThat(snapshot.units()).hasSize(2).allSatisfy(unit -> assertThat(unit.palletNo()).isEqualTo("DP-260909-0001"));
|
||||
var request = new BatchRequest(101L, 10L, 99L, 0, snapshot.snapshotToken(), List.of(
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001"),
|
||||
new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
service.batch(20L, "batch-pallet", request, 9L, "operator");
|
||||
service.batch(20L, "batch-pallet", request, 9L, "operator");
|
||||
verify(repository, times(1)).pick(30L, 2, 0, 101L);
|
||||
@@ -1382,20 +1381,20 @@ class LoadingPickServiceTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"missing-row", "ordinary", "partial", "skip", "quantity", "wrong-code", "unsealed", "wrong-warehouse", "physical-short", "stale-item", "stale-task", "stale-snapshot"})
|
||||
@ValueSource(strings = {"missing-unit", "duplicate-pallet", "ordinary", "partial", "skip", "quantity", "wrong-code", "unsealed", "wrong-warehouse", "physical-short", "stale-item", "stale-task", "stale-snapshot"})
|
||||
void rejectsInvalidWholePalletBatchBeforeAnyWrite(String scenario) {
|
||||
var items = prepareWholePalletBatch();
|
||||
String snapshot = service.inventorySnapshot(20L, 101L).snapshotToken();
|
||||
var lines = new java.util.ArrayList<>(List.of(
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001"),
|
||||
new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001")));
|
||||
switch (scenario) {
|
||||
case "missing-row" -> lines.remove(1);
|
||||
case "ordinary" -> lines.set(1, new BatchItemRequest(31L, "PICK", 2, null, 0, null, null));
|
||||
case "partial" -> lines.set(1, new BatchItemRequest(31L, "PARTIAL", null, 1, 0, null, null));
|
||||
case "skip" -> lines.set(1, new BatchItemRequest(31L, "SKIP", null, null, 0, "skip", null));
|
||||
case "quantity" -> lines.set(1, new BatchItemRequest(31L, "PICK_PALLET", 2, null, 0, null, "DP-260909-0001"));
|
||||
case "wrong-code" -> lines.set(1, new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0999"));
|
||||
case "missing-unit" -> lines.clear();
|
||||
case "duplicate-pallet" -> lines.add(new BatchItemRequest(31L, "PICK_PALLET", null, null, 0, null, "DP-260909-0001"));
|
||||
case "ordinary" -> lines.set(0, new BatchItemRequest(30L, "PICK", 2, null, 0, null, null));
|
||||
case "partial" -> lines.set(0, new BatchItemRequest(30L, "PARTIAL", null, 1, 0, null, null));
|
||||
case "skip" -> lines.set(0, new BatchItemRequest(30L, "SKIP", null, null, 0, "skip", null));
|
||||
case "quantity" -> lines.set(0, new BatchItemRequest(30L, "PICK_PALLET", 2, null, 0, null, "DP-260909-0001"));
|
||||
case "wrong-code" -> lines.set(0, new BatchItemRequest(30L, "PICK_PALLET", null, null, 0, null, "DP-260909-0999"));
|
||||
case "unsealed" -> dropshipPallets.sealedPalletLines(801L).get(0).setPalletStatus("OPEN");
|
||||
case "wrong-warehouse" -> dropshipPallets.sealedPalletLines(801L).get(0).setWarehouseId(999L);
|
||||
case "physical-short" -> dropshipPallets.sealedPalletLines(801L).get(0).setCartonCount(1);
|
||||
|
||||
@@ -89,6 +89,7 @@ Client 全量、Client 入库测量与计费、待审核订单和批量待分柜
|
||||
| 本地质量 | `java-quality-summary.mjs` | 聚合 Maven 拓扑、OpenAPI、JaCoCo 和 SpotBugs 已有报告,不启动服务 |
|
||||
| 本地质量 | `agents-index-contract.ps1` | 离线检查 AGENTS 快速索引与关键入口一致性 |
|
||||
| 契约数据 | `synchronous-dependency-baseline.mjs` | 同步服务依赖合同的数据源,只由相邻合同测试导入,不是 CLI |
|
||||
| SQL 合同辅助 | `sql-baseline-contract-helpers.mjs` | 只供离线 Node 合同读取统一增量 SQL、拆分表定义和种子数据;不连接数据库,也不是 CLI |
|
||||
| OpenAPI | `openapi-validation/validate-openapi.mjs` | 发现并解析各服务 OpenAPI,检查跨文件引用;只读源码 |
|
||||
| 本地环境 | `local-env.ps1` | 本地开发环境公共参数/辅助函数,由其他 PowerShell 脚本加载 |
|
||||
| 本地环境 | `start-gateway-overseas-local.ps1` | 使用本地 Maven 启动海外 Gateway,并将海外安全投影地址指向本机;会启动本地 Java 进程,不访问或修改共享环境 |
|
||||
|
||||
@@ -11,7 +11,7 @@ const OPTIONAL_MODULES = ['phase4-overseas-return-followups', 'manual-order-spli
|
||||
'manual-pda-ready-fixture', 'manual-pda-ready-verify',
|
||||
'manual-pda-batch-fixture', 'manual-pda-batch-verify',
|
||||
'manual-loading-complete-fixture', 'manual-loading-complete-verify',
|
||||
'coload-groups', 'dropship-pallet-matrix', 'dropship-browser-fixture', 'mixed-container-receiving', 'pda-loading-link-regression']
|
||||
'coload-groups', 'dropship-pallet-matrix', 'dropship-physical-carton-chain', 'dropship-browser-fixture', 'mixed-container-receiving', 'pda-loading-link-regression']
|
||||
const AVAILABLE_MODULES = new Set([...CONFIGURED_MODULES, ...OPTIONAL_MODULES])
|
||||
const A0_LOCATION_PAGE_SIZE = 200
|
||||
const A0_MAX_LOCATION_MUTATIONS = 8
|
||||
@@ -45,6 +45,7 @@ const MODULE_DEPENDENCIES = new Map([
|
||||
['pda-loading-link-regression', ['merchant-qualification-fixture']],
|
||||
['phase4-overseas-return-followups', ['phase4-overseas-return-receipt']],
|
||||
['dropship-pallet-matrix', ['merchant-qualification-fixture']],
|
||||
['dropship-physical-carton-chain', ['merchant-qualification-fixture']],
|
||||
['dropship-browser-fixture', ['merchant-qualification-fixture']],
|
||||
['mixed-container-receiving', ['merchant-qualification-fixture']],
|
||||
['suppliers', ['merchant-qualification-fixture']],
|
||||
|
||||
@@ -76,6 +76,8 @@ export async function run(context) {
|
||||
|
||||
`dropship-pallet-matrix.mjs` 验证下述四场景整托手工加入、整托移除、智能推荐、独立订舱与文件放舱、真实柜号关联、预装确认、PDA 整托拣货和五类证据上传;异步装柜完成必须等到任务 `COMPLETED` 并逐票回查 `LOADED`。智能装柜的托盘汇总与装柜详情逐票组成分别核对,不能把共托汇总行的首票编号当作全托归属。随后经正式报关、国内离仓及人工船节点接口推进至 `PICKUP_READY`,再按柜生成逐票海外入库计划,检查来源托盘完整绑定、共享托盘身份一致和同键重放。海外收货使用本轮创建并绑定接柜仓的 PDA 操作员,上传三类照片、签收、逐实物托盘收货、逐票箱数核平和完成入库,同键重放及同托新键扫码均不得重复入账。随后从本轮逐票库存候选创建一件代发专用出库,携带原始箱唛由 PDA 拣货、完成,核对终态、幂等和可用库存扣减;finally 停用本轮操作员。出库重放按业务身份和数据库毫秒时间精度核对。离线合同为 `dropship-pallet-matrix-contract.test.mjs`;脚本覆盖不等于真实 HTTP 已通过,须检查当轮报告和库存审计。
|
||||
|
||||
`dropship-physical-carton-chain.mjs` 复用同一条正式链路,专门覆盖多 SKU 共用物理箱:20 箱×2 SKU/2 托、5 箱×3 SKU/1 托、7 箱×2 SKU/预计 2 托后合为 1 托。每个场景从新订单、测量、箱唛冻结和 PDA 清点开始,按去重箱唛核对预装、装柜、尾程计划、海外整托收货、库存候选和一件代发出库;每箱必须保留全部 SKU 明细,但任何汇总不得按 SKU 行重复累计箱数。使用 `--modules dropship-physical-carton-chain`,会保留本轮业务数据和 JSON 报告,不自动清理。
|
||||
|
||||
- `dropship-pallet-matrix` 通过总入口 `--modules dropship-pallet-matrix` 执行,自动依赖客户资质准备。通过正式 HTTP 创建五票订单,覆盖单票多托、单票单托、单票移箱合托、多票共享开放托盘;会创建并保留测试数据,不自动清理。报告 `createdResources.dropshipPalletMatrix` 保存逐票箱唛、托盘及各阶段证据,失败即停止并保留阻断;重跑创建新批次,不借用历史资源。`fullFlowVerified=false`,不能代替海外收货及一件代发专用出库的完整验收。
|
||||
|
||||
- 一件代发任务 `Task.pallets` 只列实际参与绑箱的托盘;未使用的预生成托盘通过 `/api/platform/v1/first-leg/pda-dropship/inbound-orders/{inboundId}/pallets?accountId=...` 读取,客户范围取本轮任务。脚本同时核对 `generatedPalletCount/palletSetupReady`,不因空的任务托盘列表新增资源。
|
||||
|
||||
@@ -176,6 +176,18 @@ test('dropship matrix has a real customer prerequisite without generic dispatch
|
||||
assert.ok(!modules.includes('phase5-overseas-fulfillment'))
|
||||
})
|
||||
|
||||
test('physical-carton chain covers mixed SKU cartons without multiplying carton totals', () => {
|
||||
const modules = normalizeModuleOrder(['dropship-physical-carton-chain'])
|
||||
assert.deepEqual(modules, ['merchant-qualification-fixture', 'dropship-physical-carton-chain'])
|
||||
assert.match(source, /prepare\('PHYSICAL-20X2', 2[\s\S]*totalPackageCount: 20, skuCount: 2/)
|
||||
assert.match(source, /prepare\('PHYSICAL-5X3', 1[\s\S]*totalPackageCount: 5, skuCount: 3/)
|
||||
assert.match(source, /prepare\('PHYSICAL-7X2-MERGED', 2, \{ mergeToFirstPallet: true \}/)
|
||||
assert.match(source, /carton\.skuLines\.length === \(record\.expectedSkuCount \|\| 1\)/)
|
||||
assert.match(source, /packagesByPhysicalUnit/)
|
||||
assert.match(source, /new Set\(actual\.map\(\(item\) => item\.skuCode\)/)
|
||||
assert.match(source, /PHYSICAL_CARTON_OUTBOUND_COMPLETED/)
|
||||
})
|
||||
|
||||
test('all four physical pallet shapes are explicitly produced', () => {
|
||||
assert.match(source, /prepare\('SINGLE-MULTI', 2\)/)
|
||||
assert.match(source, /prepare\('SINGLE-SINGLE', 1\)/)
|
||||
@@ -190,7 +202,7 @@ test('allocation checks both physical summaries and uncollapsed per-inbound quan
|
||||
assert.match(source, /first-leg\/loading-plans\/\$\{plan.id\}/)
|
||||
assert.match(source, /const actual = detail.items.filter/)
|
||||
assert.match(source, /plan.items\?\.length === pallets.length/)
|
||||
assert.match(source, /actual.reduce\([\s\S]*?record.expectedCartonMarks.length/)
|
||||
assert.match(source, /packagesByPhysicalUnit\.values\(\)[\s\S]*?record.expectedCartonMarks.length/)
|
||||
assert.match(source, /await conserve\('手工加入后'\)/)
|
||||
assert.match(source, /await conserve\('智能推荐后'\)/)
|
||||
assert.match(source, /fullFlowVerified: false/)
|
||||
|
||||
@@ -47,7 +47,12 @@ export async function runDomesticLinkRegression(context) {
|
||||
return runWorkflow(context, false, false, true)
|
||||
}
|
||||
|
||||
async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = false, linkRegression = false) {
|
||||
export async function runPhysicalCartonRegression(context) {
|
||||
return runWorkflow(context, false, false, false, true)
|
||||
}
|
||||
|
||||
async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = false, linkRegression = false,
|
||||
physicalCartonRegression = false) {
|
||||
const marker = context.marker
|
||||
const savedOptions = context.options.prepareDropshipManualCount
|
||||
const savedContext = { ...context.context }
|
||||
@@ -58,7 +63,7 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
context.assert(MODULE, name, response, passed, details, OPTIONS)
|
||||
if (!passed) throw new Error(name)
|
||||
}
|
||||
async function prepare(label, palletCount, mode = {}, destinationType = null) {
|
||||
async function prepare(label, palletCount, mode = {}, destinationType = null, variant = {}) {
|
||||
context.marker = `${marker}-${label}`
|
||||
for (const key of ['orders', 'inboundMeasurement', 'ocrInbound', 'testInbound', 'dropshipManualCount']) {
|
||||
delete context.createdResources[key]
|
||||
@@ -67,7 +72,13 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
context.context.orderDestinationType = destinationType || 'OVERSEAS'
|
||||
context.context.orderFulfillmentType = destinationType ? 'STANDARD' : 'ONE_CLICK_DROPSHIP'
|
||||
context.context.orderProfile = { taxMode: 'BUY_DECLARATION', pickupMethod: 'SELF_EXPRESS', containerMode: 'LCL' }
|
||||
context.context.waitSplitVariant = { skuCount: 1, packageCountPerSku: 4, packageCounts: [4], palletCount }
|
||||
const totalPackageCount = Number(variant.totalPackageCount || 4)
|
||||
const skuCount = Number(variant.skuCount || 1)
|
||||
context.context.waitSplitVariant = {
|
||||
skuCount, packageCountPerSku: totalPackageCount,
|
||||
packageCounts: Array.from({ length: skuCount }, () => totalPackageCount),
|
||||
totalPackageCount, palletCount,
|
||||
}
|
||||
if (label.startsWith('MULTI-MERGE-')) {
|
||||
context.context.waitSplitVariant.skuCodes = [`SKU-SHARED-${marker}`.slice(0, 64)]
|
||||
}
|
||||
@@ -101,6 +112,7 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
Boolean(task) && !context.steps.slice(start).some((step) => !step.passed))
|
||||
const record = { label, order: { ...context.createdResources.orders },
|
||||
measurement: { ...context.createdResources.inboundMeasurement }, source: { ...source }, task,
|
||||
expectedSkuCount: skuCount,
|
||||
expectedCartonMarks: Array.from({ length: source.totalCartons }, (_, index) =>
|
||||
`${source.inboundCode}#${source.totalCartons}-${index + 1}`) }
|
||||
records.push(record)
|
||||
@@ -116,6 +128,8 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
task?.taskStatus === 'COMPLETED' && task.pallets?.length === expectedPallets
|
||||
&& task.pallets.every((pallet) => pallet.status === 'SEALED')
|
||||
&& cartons.length === record.expectedCartonMarks.length
|
||||
&& cartons.every((carton) => Array.isArray(carton.skuLines)
|
||||
&& carton.skuLines.length === (record.expectedSkuCount || 1))
|
||||
&& JSON.stringify(cartons.map((carton) => carton.boxCode).sort())
|
||||
=== JSON.stringify([...record.expectedCartonMarks].sort()))
|
||||
record.task = task
|
||||
@@ -177,9 +191,18 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
=== group.reduce((sum, record) => sum + record.expectedCartonMarks.length, 0))
|
||||
for (const record of group) {
|
||||
const actual = detail.items.filter((item) => String(item.inboundId) === record.source.inboundId)
|
||||
const packagesByPhysicalUnit = new Map()
|
||||
for (const item of actual) {
|
||||
const key = item.palletId == null ? `ITEM:${item.id}` : `PALLET:${item.palletId}`
|
||||
packagesByPhysicalUnit.set(key, Math.max(packagesByPhysicalUnit.get(key) || 0,
|
||||
Number(item.packageCount)))
|
||||
}
|
||||
check(`${record.label}${name}分配箱数与托盘身份正确`, null,
|
||||
actual.reduce((sum, item) => sum + Number(item.packageCount), 0) === record.expectedCartonMarks.length
|
||||
&& (!record.task || actual.every((item) => record.task.pallets.some((pallet) => String(pallet.palletId) === String(item.palletId)))))
|
||||
[...packagesByPhysicalUnit.values()].reduce((sum, count) => sum + count, 0)
|
||||
=== record.expectedCartonMarks.length
|
||||
&& new Set(actual.map((item) => item.skuCode).filter(Boolean)).size === (record.expectedSkuCount || 1)
|
||||
&& (!record.task || actual.every((item) => record.task.pallets.some((pallet) =>
|
||||
String(pallet.palletId) === String(item.palletId)))))
|
||||
}
|
||||
}
|
||||
for (const pallet of pallets) {
|
||||
@@ -603,6 +626,26 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
for (const record of group) record.outbound = { task: completed, selected, detail: outbound, completed: true }
|
||||
}
|
||||
try {
|
||||
if (physicalCartonRegression) {
|
||||
const scenarios = [
|
||||
await prepare('PHYSICAL-20X2', 2, {}, null, { totalPackageCount: 20, skuCount: 2 }),
|
||||
await prepare('PHYSICAL-5X3', 1, {}, null, { totalPackageCount: 5, skuCount: 3 }),
|
||||
await prepare('PHYSICAL-7X2-MERGED', 2, { mergeToFirstPallet: true }, null,
|
||||
{ totalPackageCount: 7, skuCount: 2 }),
|
||||
]
|
||||
for (const [index, record] of scenarios.entries()) await verify(record, index === 2 ? 1 : index === 0 ? 2 : 1)
|
||||
const groups = scenarios.map((record) => [record])
|
||||
for (const group of groups) await allocate(group)
|
||||
context.createdResources.dropshipPalletMatrix.stage = 'PHYSICAL_CARTON_LOADING_COMPLETED'
|
||||
for (const group of groups) await progressShipping(group)
|
||||
context.createdResources.dropshipPalletMatrix.stage = 'PHYSICAL_CARTON_PICKUP_READY'
|
||||
for (const group of groups) {
|
||||
await convertOverseas(group)
|
||||
await receiveOverseas(group)
|
||||
}
|
||||
context.createdResources.dropshipPalletMatrix.stage = 'PHYSICAL_CARTON_OUTBOUND_COMPLETED'
|
||||
return
|
||||
}
|
||||
if (mixedContainer) {
|
||||
const dropship = await prepare('MIXED-DROPSHIP', 2)
|
||||
await verify(dropship, 2)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { runPhysicalCartonRegression } from './dropship-pallet-matrix.mjs'
|
||||
|
||||
export async function run(context) {
|
||||
return runPhysicalCartonRegression(context)
|
||||
}
|
||||
Reference in New Issue
Block a user