优化
This commit is contained in:
+1
-1
@@ -87,7 +87,7 @@
|
||||
|
||||
- 智能装柜全局工作台由服务端把业务渠道分组映射为实际渠道,并分别分页查询候选与 `WAIT_LOADING/EDITING/LOADING` 柜次;候选支持按创建日期和预计到货日期闭区间筛选,状态筛选使用最多四项的 `orderStatuses`,未传表示 `PENDING_REVIEW/WAIT_MEASURE/WAIT_SPLIT/LOADING` 全部未完成状态。状态只用于搜索和展示,能否加入以响应 `canAssign` 为准,由有效柜次占用、剩余箱数、容量、组锁和版本等事实决定;人工加入允许订单国内仓及渠道与柜次不同,智能推荐允许跨国内仓但仍只选择柜次渠道。容积和重量均允许达到柜型额定值的 150%,超过即拒绝。待审核事实必须经 `PendingReviewOrderClient` 重新核验 Order 权威 `SUBMITTED` 订单,待审核或待测量可先占位,但确认预装和启动 PDA 前仍必须进入 `WAIT_SPLIT`。计划明细通过 `totalPackageCount/remainingPackageCount` 返回可调总量和余量:测量前取 Order 原始申报箱数,进入 `WAIT_SPLIT/LOADING` 后取最新已审核测量箱数;只读补充失败时保守回退当前已分配数,调整写入仍重新查询并校验权威数量。同柜不可拆组只有全部成员命中状态集合时才返回,且必须成员完整、满箱并作为单个分页与分配单元,不能跨页或拆分。一次性移除未拣明细时,普通未拣项直接移除;同柜组仅全组未拣时整组移除,任一成员已拣则整组保留,单事务只递增一次计划版本。推荐顺序按入库时间先进先出。装柜计划状态为 `EDITING -> LOADING -> COMPLETED`,PDA 全部明细处理完成后自动完成。
|
||||
- 客户入库费用只在登录客户范围、测量审核通过、`feeStatus=SENT` 且存在已发布明细时可见;确认必须具备 `merchant:order:edit`,携带当前 `expectedFeeVersion` 与幂等键。成功后记录确认成员和时间;客户未确认或该票尚未装柜完成时,平台仍可修改箱规规则、生成新费用版本并重新发送,新版本必须清空旧版客户确认快照。仅当“客户已确认且该票已装柜完成”同时成立时最终冻结;装柜完成不得代替客户自动确认。旧版本、未发送或重复异参确认均返回冲突,不做兼容覆盖。
|
||||
- PDA 拣货以 `fl_inbound_carton_label.box_code` 对应的稳定箱唛箱码为扫描身份;审核建单、重复打印、手工测量和墨家测量复用同一映射,版本字段只用于并发控制,不进入扫码文本。一个有效且未重复的箱码扫描表示拣出一箱,不得以 SKU、商品件数或页面展示字段替代箱码。任务下发与提交契约见 `src/main/resources/openapi/phase3-pda-convergence-v1.yaml`。
|
||||
- PDA 拣货以 `fl_inbound_carton_label.box_code` 对应的稳定箱唛箱码为扫描身份;审核建单、重复打印、手工测量和墨家测量复用同一映射,版本字段只用于并发控制,不进入扫码文本。一个有效且未重复的箱码扫描表示拣出一箱,不得以 SKU、商品件数或页面展示字段替代箱码。装柜允许跨国内仓,仓库与渠道限制统一由上游装柜编排负责;PDA 只按已生成任务读取各入仓单自身的 `CONFIRMED` 库位资源,不得再用柜次仓库过滤任务明细、库存快照或拣货写入。任务下发与提交契约见 `src/main/resources/openapi/phase3-pda-convergence-v1.yaml`。
|
||||
- PDA 部分拣货生成二级子入库单时,数量必须守恒:`已拣数量 + 子单数量 = 拣货前主单可用数量`。子单只允许一层,不能继续拆分。
|
||||
- 首张称重图片确认时按 `max(已确认称重图片数, ceil(有效体积 / 默认托盘容量))` 尽力预留并确认整票库位块;有效体积先取入库单已落地的正数实测体积,否则对最新测量批次的正数 `totalVolumeCbm/originalVolumeCbm` 取较大值,再无有效值才查询 Order 最新客户申报体积,全部无效则拒绝。后续确认绑定最小空槽,所需槽数增长时通过 Warehouse `/blocks/expand` 原子追加,既有 `blockId/slotNo/reservationId/locationId/sharedOverflow` 不得变化。库位指引失败只记录警告,不阻断称重确认;测量提交后以最新已确认图片调用 `ensureForSubmission` 再次补齐和校验,失败同样不回滚已提交的测量主流程。Firstleg 必须显式保存 `weightResultId -> blockId/slotNo/reservationId/locationId/locationCode`,按 `confirmedAt + weightResultId` 稳定顺序绑定,禁止按列表下标推断;普通连续位不足的新增槽由 Warehouse 放入共享 A0,不阻断确认。
|
||||
- 删除称重图片只解除图片和托盘快照绑定,将槽位恢复为 `RESERVED_EMPTY`;槽位和整票块继续归当前入仓码。解绑失败时不得调用文件删除,文件删除失败时本地解绑和重量结果删除必须回滚。
|
||||
|
||||
@@ -139,15 +139,13 @@ public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity>
|
||||
JOIN fl_loading_plan_item plan_item ON plan_item.id = pick_item.loading_plan_item_id
|
||||
JOIN fl_loading_plan plan ON plan.id = plan_item.loading_plan_id
|
||||
AND plan.id = pick_task.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
JOIN fl_inbound_order inbound_order ON inbound_order.id = plan_item.inbound_id
|
||||
AND inbound_order.tenant_id = plan_item.account_id AND inbound_order.deleted = 0
|
||||
WHERE pick_item.pick_task_id = #{taskId} AND booking.warehouse_id = #{warehouseId}
|
||||
AND plan_item.deleted = 0
|
||||
WHERE pick_item.pick_task_id = #{taskId} AND plan_item.deleted = 0
|
||||
ORDER BY pick_item.id ASC
|
||||
""")
|
||||
java.util.List<LoadingPickItemEntity> selectTaskItemsWithAccount(@Param("taskId") long taskId,
|
||||
@Param("warehouseId") long warehouseId);
|
||||
@Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("""
|
||||
SELECT pick_item.*
|
||||
@@ -156,11 +154,10 @@ public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity>
|
||||
JOIN fl_loading_plan_item plan_item ON plan_item.id = pick_item.loading_plan_item_id
|
||||
JOIN fl_loading_plan plan ON plan.id = plan_item.loading_plan_id
|
||||
AND plan.id = pick_task.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
JOIN fl_inbound_order inbound_order ON inbound_order.id = plan_item.inbound_id
|
||||
AND inbound_order.tenant_id = plan_item.account_id AND inbound_order.deleted = 0
|
||||
WHERE pick_item.pick_task_id = #{taskId} AND pick_item.id = #{itemId}
|
||||
AND booking.warehouse_id = #{warehouseId} AND plan_item.deleted = 0
|
||||
AND plan_item.deleted = 0
|
||||
""")
|
||||
LoadingPickItemEntity selectTaskItemWithAccount(@Param("taskId") long taskId,
|
||||
@Param("itemId") long itemId,
|
||||
@@ -174,10 +171,8 @@ public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity>
|
||||
version = version + 1
|
||||
WHERE id = #{id} AND version = #{version}
|
||||
AND EXISTS (SELECT 1 FROM fl_loading_plan_item plan_item
|
||||
JOIN fl_loading_plan plan ON plan.id = plan_item.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
WHERE plan_item.id = fl_loading_pick_item.loading_plan_item_id
|
||||
AND booking.warehouse_id = #{warehouseId} AND plan_item.deleted = 0)
|
||||
AND plan_item.deleted = 0)
|
||||
AND picked_package_count + #{count} <= planned_package_count
|
||||
""")
|
||||
int pick(@Param("id") long id, @Param("count") int count,
|
||||
@@ -190,10 +185,8 @@ public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity>
|
||||
version = version + 1
|
||||
WHERE id = #{id} AND version = #{version}
|
||||
AND EXISTS (SELECT 1 FROM fl_loading_plan_item plan_item
|
||||
JOIN fl_loading_plan plan ON plan.id = plan_item.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
WHERE plan_item.id = fl_loading_pick_item.loading_plan_item_id
|
||||
AND booking.warehouse_id = #{warehouseId} AND plan_item.deleted = 0)
|
||||
AND plan_item.deleted = 0)
|
||||
AND item_status IN ('PENDING', 'PICKING')
|
||||
AND picked_package_count < #{finalCount}
|
||||
AND #{finalCount} < planned_package_count
|
||||
@@ -207,13 +200,12 @@ public interface LoadingPickItemMapper extends BaseMapper<LoadingPickItemEntity>
|
||||
SET item_status = 'SKIPPED', version = version + 1
|
||||
WHERE id = #{id} AND version = #{version}
|
||||
AND EXISTS (SELECT 1 FROM fl_loading_plan_item plan_item
|
||||
JOIN fl_loading_plan plan ON plan.id = plan_item.loading_plan_id
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
WHERE plan_item.id = fl_loading_pick_item.loading_plan_item_id
|
||||
AND booking.warehouse_id = #{warehouseId} AND plan_item.deleted = 0)
|
||||
AND plan_item.deleted = 0)
|
||||
AND item_status = 'PENDING' AND picked_package_count = 0
|
||||
""")
|
||||
int skip(@Param("id") long id, @Param("version") int version, @Param("warehouseId") long warehouseId);
|
||||
int skip(@Param("id") long id, @Param("version") int version,
|
||||
@Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(*)
|
||||
|
||||
@@ -50,7 +50,6 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
JOIN fl_container_booking booking ON booking.id = plan.container_booking_id
|
||||
WHERE booking.container_no = #{containerNo} AND booking.record_status = 'ACTIVE'
|
||||
AND task.task_status = 'LOADING'
|
||||
AND booking.warehouse_id = #{warehouseId}
|
||||
LIMIT 1
|
||||
""")
|
||||
LoadingPickTaskEntity selectLoadingByContainerAndAccount(@Param("containerNo") String containerNo,
|
||||
@@ -59,13 +58,13 @@ public interface LoadingPickTaskMapper extends BaseMapper<LoadingPickTaskEntity>
|
||||
@Select("SELECT task.* FROM fl_loading_pick_task task JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id "
|
||||
+ "JOIN fl_container_booking booking ON booking.id=plan.container_booking_id "
|
||||
+ "WHERE task.task_status='LOADING' AND booking.record_status='ACTIVE' "
|
||||
+ "AND booking.cabinet_no=#{cabinetNo} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||
+ "AND booking.cabinet_no=#{cabinetNo} LIMIT 1")
|
||||
LoadingPickTaskEntity selectLoadingByCabinet(@Param("cabinetNo") String cabinetNo, @Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("SELECT task.* FROM fl_loading_pick_task task JOIN fl_loading_plan plan ON plan.id=task.loading_plan_id "
|
||||
+ "JOIN fl_container_booking booking ON booking.id=plan.container_booking_id "
|
||||
+ "WHERE task.task_status='LOADING' AND booking.record_status='ACTIVE' "
|
||||
+ "AND task.loading_plan_id=#{loadingPlanId} AND booking.warehouse_id=#{warehouseId} LIMIT 1")
|
||||
+ "AND task.loading_plan_id=#{loadingPlanId} LIMIT 1")
|
||||
LoadingPickTaskEntity selectLoadingByPlan(@Param("loadingPlanId") long loadingPlanId, @Param("warehouseId") long warehouseId);
|
||||
|
||||
@Select("SELECT booking.* FROM fl_loading_pick_task task "
|
||||
|
||||
@@ -242,7 +242,7 @@ public class LoadingPickService {
|
||||
|
||||
public InventorySnapshot inventorySnapshot(long taskId, long warehouseId) {
|
||||
LoadingPickTaskEntity task = requiredTask(taskId);
|
||||
List<LoadingPickItemEntity> items = warehouseScopedItems(task, warehouseId);
|
||||
List<LoadingPickItemEntity> items = taskItems(task, warehouseId);
|
||||
return buildInventorySnapshot(task, warehouseId, items);
|
||||
}
|
||||
|
||||
@@ -322,10 +322,7 @@ public class LoadingPickService {
|
||||
assertReplay(replay, taskId, itemId, requestHash);
|
||||
return view(task, request.warehouseId());
|
||||
}
|
||||
LoadingPickItemEntity item = repository.item(taskId, itemId, request.warehouseId());
|
||||
if (item == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在");
|
||||
}
|
||||
LoadingPickItemEntity item = requireTaskItem(taskId, itemId, request.warehouseId());
|
||||
if (repository.pick(itemId, request.pickedPackageCount(), request.version(), request.warehouseId()) != 1) {
|
||||
throw conflict("拣货数量超过计划箱数或明细版本已变化");
|
||||
}
|
||||
@@ -364,7 +361,7 @@ public class LoadingPickService {
|
||||
assertReplay(replay, taskId, itemId, requestHash);
|
||||
return view(task, request.warehouseId());
|
||||
}
|
||||
requireWarehouseItem(taskId, itemId, request.warehouseId());
|
||||
requireTaskItem(taskId, itemId, request.warehouseId());
|
||||
if (repository.partial(itemId, request.finalPickedPackageCount(), request.version(),
|
||||
request.warehouseId()) != 1) {
|
||||
throw conflict("部分拣货箱数必须大于已拣箱数且小于计划箱数,或明细版本已变化");
|
||||
@@ -396,7 +393,7 @@ public class LoadingPickService {
|
||||
assertReplay(replay, taskId, itemId, requestHash);
|
||||
return view(task, request.warehouseId());
|
||||
}
|
||||
LoadingPickItemEntity item = requireWarehouseItem(taskId, itemId, request.warehouseId());
|
||||
LoadingPickItemEntity item = requireTaskItem(taskId, itemId, request.warehouseId());
|
||||
if (repository.hasPickedOrPartialItem(taskId, item.getInboundId())) {
|
||||
throw conflict("同一入库单已存在已拣或部分拣货明细,不能跳拣并恢复智能装柜候选");
|
||||
}
|
||||
@@ -897,10 +894,10 @@ public class LoadingPickService {
|
||||
return task;
|
||||
}
|
||||
|
||||
private LoadingPickItemEntity requireWarehouseItem(long taskId, long itemId, long warehouseId) {
|
||||
private LoadingPickItemEntity requireTaskItem(long taskId, long itemId, long warehouseId) {
|
||||
LoadingPickItemEntity item = repository.item(taskId, itemId, warehouseId);
|
||||
if (item == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在或不属于当前仓库");
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在或不属于当前任务");
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@@ -910,16 +907,12 @@ public class LoadingPickService {
|
||||
if (booking == null || !Objects.equals(task.getContainerBookingId(), booking.getId())) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货任务不存在或柜次已非有效存续记录");
|
||||
}
|
||||
if (warehouseId != null && !Objects.equals(warehouseId, booking.getWarehouseId())) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货任务不存在或不属于当前仓库");
|
||||
}
|
||||
List<LoadingPickItemEntity> source = warehouseId == null
|
||||
? repository.items(task.getId()) : repository.items(task.getId(), warehouseId);
|
||||
Map<Long, List<CartonLabelRow>> cartonsByItem = repository.cartonLabels(task.getId()).stream()
|
||||
.collect(Collectors.groupingBy(CartonLabelRow::getPickItemId));
|
||||
Map<Long, Long> tenantByInbound = tenantByInbound(task.getId());
|
||||
Map<Long, List<InventoryResource>> inventoryByInbound = inventoryByInbound(
|
||||
booking.getWarehouseId(), source, tenantByInbound);
|
||||
Map<Long, List<InventoryResource>> inventoryByInbound = inventoryByInbound(source, tenantByInbound);
|
||||
List<PickItem> items = source.stream().map(item -> {
|
||||
List<CartonLabelRow> cartons = cartonsByItem.getOrDefault(item.getId(), List.of());
|
||||
CartonLabelRow first = cartons.isEmpty() ? null : cartons.get(0);
|
||||
@@ -967,11 +960,11 @@ public class LoadingPickService {
|
||||
private List<BatchLine> validateBatchLines(LoadingPickTaskEntity task, BatchRequest request) {
|
||||
long taskId = task.getId();
|
||||
List<LoadingPickItemEntity> allItems = repository.items(taskId);
|
||||
List<LoadingPickItemEntity> scopedItems = repository.items(taskId, request.warehouseId());
|
||||
if (allItems.isEmpty() || scopedItems.size() != allItems.size()) {
|
||||
throw conflict("PDA任务柜次、仓库或客户归属不一致");
|
||||
List<LoadingPickItemEntity> taskItems = repository.items(taskId, request.warehouseId());
|
||||
if (allItems.isEmpty() || taskItems.size() != allItems.size()) {
|
||||
throw conflict("PDA任务柜次或客户归属不一致");
|
||||
}
|
||||
Map<Long, LoadingPickItemEntity> itemsById = scopedItems.stream().collect(Collectors.toMap(
|
||||
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());
|
||||
@@ -989,7 +982,7 @@ public class LoadingPickService {
|
||||
LoadingPickItemEntity item = itemsById.get(requestItem.itemId());
|
||||
if (item == null || item.getInboundId() == null || item.getLoadingPlanItemId() == null
|
||||
|| item.getInboundDetailId() == null) {
|
||||
throw conflict("PDA明细不属于当前任务、柜次、仓库或客户");
|
||||
throw conflict("PDA明细不属于当前任务、柜次或客户");
|
||||
}
|
||||
if (!"SKIP".equals(requestItem.action())) {
|
||||
inboundHasWork.put(item.getInboundId(), true);
|
||||
@@ -1041,19 +1034,19 @@ public class LoadingPickService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<LoadingPickItemEntity> warehouseScopedItems(LoadingPickTaskEntity task, long warehouseId) {
|
||||
private List<LoadingPickItemEntity> taskItems(LoadingPickTaskEntity task, long warehouseId) {
|
||||
List<LoadingPickItemEntity> allItems = repository.items(task.getId());
|
||||
List<LoadingPickItemEntity> scopedItems = repository.items(task.getId(), warehouseId);
|
||||
if (allItems.isEmpty() || scopedItems.size() != allItems.size()) {
|
||||
throw conflict("PDA任务柜次、仓库或客户归属不一致");
|
||||
List<LoadingPickItemEntity> taskItems = repository.items(task.getId(), warehouseId);
|
||||
if (allItems.isEmpty() || taskItems.size() != allItems.size()) {
|
||||
throw conflict("PDA任务柜次或客户归属不一致");
|
||||
}
|
||||
return scopedItems;
|
||||
return taskItems;
|
||||
}
|
||||
|
||||
private InventorySnapshot buildInventorySnapshot(LoadingPickTaskEntity task, long warehouseId,
|
||||
List<LoadingPickItemEntity> items) {
|
||||
Map<Long, List<InventoryResource>> inventoryByInbound = inventoryByInbound(
|
||||
warehouseId, items, tenantByInbound(task.getId()));
|
||||
items, tenantByInbound(task.getId()));
|
||||
List<InboundInventory> inbounds = items.stream().map(LoadingPickItemEntity::getInboundId)
|
||||
.filter(Objects::nonNull).distinct().sorted().map(inboundId -> {
|
||||
List<InventoryResource> resources = inventoryByInbound.getOrDefault(inboundId, List.of());
|
||||
@@ -1077,8 +1070,7 @@ public class LoadingPickService {
|
||||
}));
|
||||
}
|
||||
|
||||
private Map<Long, List<InventoryResource>> inventoryByInbound(long warehouseId,
|
||||
List<LoadingPickItemEntity> items,
|
||||
private Map<Long, List<InventoryResource>> inventoryByInbound(List<LoadingPickItemEntity> items,
|
||||
Map<Long, Long> tenantByInbound) {
|
||||
Map<Long, List<InventoryResource>> result = new HashMap<>();
|
||||
for (Long inboundId : items.stream().map(LoadingPickItemEntity::getInboundId)
|
||||
@@ -1086,7 +1078,6 @@ public class LoadingPickService {
|
||||
Long tenantId = tenantByInbound.get(inboundId);
|
||||
if (tenantId == null) throw conflict("PDA任务缺少入库单客户归属");
|
||||
List<InventoryResource> resources = palletAllocations.list(tenantId, inboundId).stream()
|
||||
.filter(resource -> Objects.equals(resource.getWarehouseId(), warehouseId))
|
||||
.filter(resource -> "CONFIRMED".equals(resource.getAllocationStatus()))
|
||||
.sorted(Comparator.comparing(InboundPalletAllocationEntity::getBlockId,
|
||||
Comparator.nullsLast(Long::compareTo))
|
||||
@@ -1134,7 +1125,7 @@ public class LoadingPickService {
|
||||
|
||||
private void validateInventorySnapshot(LoadingPickTaskEntity task, BatchRequest request,
|
||||
List<BatchLine> lines) {
|
||||
List<LoadingPickItemEntity> allItems = warehouseScopedItems(task, request.warehouseId());
|
||||
List<LoadingPickItemEntity> allItems = taskItems(task, request.warehouseId());
|
||||
InventorySnapshot current = buildInventorySnapshot(task, request.warehouseId(), allItems);
|
||||
if (!current.snapshotToken().equals(request.inventorySnapshotToken().trim())) {
|
||||
throw conflict("库存已变化,请刷新后重试");
|
||||
|
||||
+3
-3
@@ -89,16 +89,16 @@ class LoadingPickItemMapperContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaWritesAndReadsRequireWarehouseScope() throws Exception {
|
||||
void pdaWritesAndReadsRelyOnUpstreamTaskScopeInsteadOfWarehouseScope() throws Exception {
|
||||
Method list = LoadingPickItemMapper.class.getDeclaredMethod("selectTaskItemsWithAccount", long.class, long.class);
|
||||
Method pick = LoadingPickItemMapper.class.getDeclaredMethod("pick", long.class, int.class,
|
||||
int.class, long.class);
|
||||
|
||||
assertThat(list.getAnnotation(Select.class).value()[0])
|
||||
.contains("booking.warehouse_id = #{warehouseId}")
|
||||
.doesNotContain("booking.warehouse_id = #{warehouseId}")
|
||||
.contains("plan_item.deleted = 0");
|
||||
assertThat(pick.getAnnotation(Update.class).value()[0])
|
||||
.contains("booking.warehouse_id = #{warehouseId}")
|
||||
.doesNotContain("booking.warehouse_id = #{warehouseId}")
|
||||
.contains("plan_item.deleted = 0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,16 +511,22 @@ class LoadingPickServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void taskDetailRejectsWarehouseThatDoesNotOwnTheContainer() {
|
||||
void taskDetailDoesNotRecheckWarehouseOwnership() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.items(20L)).thenReturn(List.of(item()));
|
||||
when(repository.items(20L, 999L)).thenReturn(List.of(item()));
|
||||
when(repository.cartonLabels(20L)).thenReturn(List.of());
|
||||
when(repository.inboundOwners(20L)).thenReturn(List.of(
|
||||
new LoadingPickItemMapper.InboundOwner(101L, 50L)));
|
||||
when(palletAllocations.list(101L, 50L)).thenReturn(List.of(allocation(50L, 2, 202L)));
|
||||
|
||||
assertThatThrownBy(() -> service.task(20L, 999L))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("不属于当前仓库");
|
||||
var result = service.task(20L, 999L);
|
||||
|
||||
verify(repository, never()).items(20L, 999L);
|
||||
verify(repository, never()).cartonLabels(20L);
|
||||
verify(completionCommands, never()).latest(20L);
|
||||
assertThat(result.id()).isEqualTo(20L);
|
||||
assertThat(result.items()).hasSize(1);
|
||||
assertThat(result.items().get(0).inventoryResources()).singleElement()
|
||||
.extracting("availablePackageCount").isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1308,6 +1314,40 @@ class LoadingPickServiceTest {
|
||||
verify(settlementService, never()).settle(20L, 9L, "operator");
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchUsesConfirmedInboundInventoryWhenContainerLoadsAcrossWarehouses() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
LoadingPickItemEntity item = item();
|
||||
item.setPlannedPackageCount(250);
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
when(repository.assignment(20L)).thenReturn(assignment());
|
||||
when(repository.items(20L)).thenReturn(List.of(item));
|
||||
when(repository.items(20L, 101L)).thenReturn(List.of(item));
|
||||
when(repository.inboundOwners(20L)).thenReturn(List.of(
|
||||
new LoadingPickItemMapper.InboundOwner(1L, 50L)));
|
||||
when(palletAllocations.list(1L, 50L)).thenReturn(List.of(allocation(50L, 250, 202L)));
|
||||
when(repository.pick(30L, 250, 0, 101L)).thenReturn(1);
|
||||
when(repository.allTaskItemsResolved(20L)).thenReturn(true);
|
||||
when(repository.touchTask(20L, 0)).thenReturn(1);
|
||||
|
||||
var snapshot = service.inventorySnapshot(20L, 101L);
|
||||
assertThat(snapshot.warehouseId()).isEqualTo(101L);
|
||||
assertThat(snapshot.inbounds()).singleElement().satisfies(inbound -> {
|
||||
assertThat(inbound.availablePackageCount()).isEqualTo(250);
|
||||
assertThat(inbound.resources()).singleElement()
|
||||
.extracting("availablePackageCount")
|
||||
.isEqualTo(250);
|
||||
});
|
||||
|
||||
service.batch(20L, "batch-cross-warehouse", new BatchRequest(
|
||||
101L, 10L, 99L, 0, snapshot.snapshotToken(), List.of(
|
||||
new BatchItemRequest(30L, "PICK", 250, null, 0, null))), 9L, "operator");
|
||||
|
||||
verify(repository).pick(30L, 250, 0, 101L);
|
||||
verify(repository).touchTask(20L, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsBatchTaskVersionConflictBeforeWriting() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
@@ -1457,6 +1497,11 @@ class LoadingPickServiceTest {
|
||||
}
|
||||
|
||||
private static InboundPalletAllocationEntity allocation(long inboundId, int packageCount) {
|
||||
return allocation(inboundId, packageCount, 101L);
|
||||
}
|
||||
|
||||
private static InboundPalletAllocationEntity allocation(long inboundId, int packageCount,
|
||||
long warehouseId) {
|
||||
InboundPalletAllocationEntity allocation = new InboundPalletAllocationEntity();
|
||||
allocation.setId(72L);
|
||||
allocation.setTenantId(1L);
|
||||
@@ -1464,7 +1509,7 @@ class LoadingPickServiceTest {
|
||||
allocation.setBlockId(71L);
|
||||
allocation.setSlotNo(1);
|
||||
allocation.setAllocationNo(1);
|
||||
allocation.setWarehouseId(101L);
|
||||
allocation.setWarehouseId(warehouseId);
|
||||
allocation.setPalletId(73L);
|
||||
allocation.setLocationId(74L);
|
||||
allocation.setLocationCode("A-01");
|
||||
|
||||
Reference in New Issue
Block a user