491 lines
25 KiB
Java
491 lines
25 KiB
Java
package com.baiyue.firstleg.service;
|
|
|
|
import com.baiyue.common.core.api.ResultCode;
|
|
import com.baiyue.common.core.exception.BizException;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.PickItem;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.PickRequest;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.PartialPickRequest;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.SkipRequest;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.Task;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.Assignment;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.Review;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.ClaimRequest;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.AssignRequest;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.ReviewRequest;
|
|
import com.baiyue.firstleg.dto.LoadingPickDtos.Operation;
|
|
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
|
import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
|
import com.baiyue.firstleg.entity.LoadingPickItemEntity;
|
|
import com.baiyue.firstleg.entity.LoadingPickOperationEntity;
|
|
import com.baiyue.firstleg.entity.LoadingPickReviewEntity;
|
|
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
|
|
import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
|
|
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
|
import jakarta.annotation.Resource;
|
|
import org.springframework.dao.DuplicateKeyException;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.util.StringUtils;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.List;
|
|
import java.util.HashSet;
|
|
|
|
/** 装柜中 PDA 拣货任务及明细同步。 */
|
|
@Service
|
|
public class LoadingPickService {
|
|
@Resource
|
|
private LoadingPickRepository repository;
|
|
@Resource
|
|
private LoadingListService loadingListService;
|
|
@Resource
|
|
private PdaPickSettlementService settlementService;
|
|
|
|
@Transactional
|
|
public void begin(long planId, String containerNo, long operatorId, String operatorName,
|
|
List<LoadingPlanItemEntity> planItems) {
|
|
LoadingPickTaskEntity task = repository.taskByPlan(planId);
|
|
if (task == null) {
|
|
task = new LoadingPickTaskEntity();
|
|
task.setLoadingPlanId(planId);
|
|
task.setContainerNo(containerNo);
|
|
task.setTaskStatus("LOADING");
|
|
task.setStartedBy(operatorId);
|
|
task.setStartedByName(operatorName);
|
|
task.setVersion(0);
|
|
try {
|
|
repository.insert(task);
|
|
} catch (DuplicateKeyException exception) {
|
|
task = repository.taskByPlan(planId);
|
|
}
|
|
}
|
|
if (task == null || !"LOADING".equals(task.getTaskStatus())) {
|
|
throw conflict("装柜任务已完成,不能继续同步明细");
|
|
}
|
|
for (LoadingPlanItemEntity planItem : planItems) {
|
|
syncItem(task.getId(), planItem);
|
|
}
|
|
repository.syncInboundProjections(task.getId(), operatorId, operatorName);
|
|
}
|
|
|
|
@Transactional
|
|
public void sync(long planId, LoadingPlanItemEntity planItem) {
|
|
LoadingPickTaskEntity task = repository.taskByPlan(planId);
|
|
if (task != null && "LOADING".equals(task.getTaskStatus())) {
|
|
syncItem(task.getId(), planItem);
|
|
}
|
|
}
|
|
|
|
public Task task(String containerNo, long warehouseId) {
|
|
if (!StringUtils.hasText(containerNo)) {
|
|
throw new BizException(ResultCode.BAD_REQUEST, "集装箱号不能为空");
|
|
}
|
|
if (warehouseId <= 0) {
|
|
throw new BizException(ResultCode.BAD_REQUEST, "warehouseId必须大于0");
|
|
}
|
|
LoadingPickTaskEntity task = repository.taskByContainer(containerNo.trim(), warehouseId);
|
|
if (task == null) {
|
|
throw new BizException(ResultCode.NOT_FOUND, "未找到装柜中的PDA拣货任务");
|
|
}
|
|
return view(task, warehouseId);
|
|
}
|
|
|
|
@Transactional
|
|
public Task claim(long taskId, ClaimRequest request, long operatorId, String operatorName) {
|
|
String operationId = operation(request.operationId());
|
|
String requestHash = hash("CLAIM|" + taskId + '|' + request.expectedAssignmentVersion());
|
|
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
|
|
if (replay != null) return replay;
|
|
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
|
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
|
|
if (existing == null) {
|
|
if (request.expectedAssignmentVersion() != 0
|
|
|| repository.createAssignment(taskId, operatorId, operatorName, operatorId,
|
|
operatorName, "CLAIMED") != 1) {
|
|
throw conflict("PDA任务已被其他作业员领取");
|
|
}
|
|
} else {
|
|
if (!Long.valueOf(operatorId).equals(existing.getAssigneeId())) {
|
|
throw conflict("PDA任务已分配给其他作业员");
|
|
}
|
|
if (!request.expectedAssignmentVersion().equals(existing.getVersion())) {
|
|
throw conflict("PDA任务分配版本已变化");
|
|
}
|
|
}
|
|
insertOperation(taskId, operationId, requestHash, "CLAIM", operatorId, operatorName);
|
|
return view(task, null);
|
|
}
|
|
|
|
@Transactional
|
|
public Task assign(long taskId, AssignRequest request, long operatorId, String operatorName) {
|
|
String operationId = operation(request.operationId());
|
|
String assigneeName = request.assigneeName().trim();
|
|
String requestHash = hash("ASSIGN|" + taskId + '|' + request.assigneeId() + '|'
|
|
+ assigneeName + '|' + request.expectedAssignmentVersion());
|
|
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
|
|
if (replay != null) return replay;
|
|
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
|
LoadingPickAssignmentEntity existing = repository.assignment(taskId);
|
|
int changed = existing == null
|
|
? request.expectedAssignmentVersion() == 0
|
|
? repository.createAssignment(taskId, request.assigneeId(), assigneeName, operatorId,
|
|
operatorName, "ASSIGNED") : 0
|
|
: repository.reassign(taskId, request.assigneeId(), assigneeName, operatorId, operatorName,
|
|
"ASSIGNED", request.expectedAssignmentVersion());
|
|
if (changed != 1) throw conflict("PDA任务分配版本已变化");
|
|
insertOperation(taskId, operationId, requestHash, "ASSIGN", operatorId, operatorName);
|
|
return view(task, null);
|
|
}
|
|
|
|
@Transactional
|
|
public Task pick(long taskId, long itemId, PickRequest request, long operatorId, String operatorName) {
|
|
ReleaseSelection release = releaseSelection(request.releaseMode(), request.locationIds());
|
|
String operationId = request.operationId().trim();
|
|
String requestHash = hash(taskId + "|" + itemId + "|" + request.warehouseId()
|
|
+ "|" + request.pickedPackageCount() + "|" + release.mode() + "|" + release.json());
|
|
requireNoTaskOperation(operationId);
|
|
LoadingPickHistoryEntity replay = repository.history(operationId);
|
|
if (replay != null) {
|
|
if (!replay.getPickTaskId().equals(taskId) || !replay.getPickItemId().equals(itemId)
|
|
|| !replay.getRequestHash().equals(requestHash)) {
|
|
throw conflict("operationId 已用于不同拣货请求");
|
|
}
|
|
return view(requiredTask(taskId), request.warehouseId());
|
|
}
|
|
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
|
|
LoadingPickItemEntity item = repository.item(taskId, itemId, request.warehouseId());
|
|
if (item == null) {
|
|
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在");
|
|
}
|
|
if (repository.pick(itemId, request.pickedPackageCount(), release.mode(), release.json(),
|
|
request.version(), request.warehouseId()) != 1) {
|
|
throw conflict("拣货数量超过计划箱数或明细版本已变化");
|
|
}
|
|
LoadingPickHistoryEntity history = new LoadingPickHistoryEntity();
|
|
history.setPickTaskId(taskId);
|
|
history.setPickItemId(itemId);
|
|
history.setOperationId(operationId);
|
|
history.setRequestHash(requestHash);
|
|
history.setActionType("PICK");
|
|
history.setPickedPackageCount(request.pickedPackageCount());
|
|
history.setOperatorId(operatorId);
|
|
history.setOperatorName(operatorName);
|
|
repository.insert(history);
|
|
touchSettleFinishAndSync(task, operatorId, operatorName);
|
|
return view(requiredTask(taskId), request.warehouseId());
|
|
}
|
|
|
|
@Transactional
|
|
public Task partial(long taskId, long itemId, PartialPickRequest request,
|
|
long operatorId, String operatorName) {
|
|
ReleaseSelection release = releaseSelection(request.releaseMode(), request.locationIds());
|
|
String operationId = request.operationId().trim();
|
|
String requestHash = hash("PARTIAL|" + taskId + "|" + itemId + "|" + request.warehouseId()
|
|
+ "|" + request.finalPickedPackageCount() + "|" + release.mode() + "|" + release.json());
|
|
requireNoTaskOperation(operationId);
|
|
LoadingPickHistoryEntity replay = repository.history(operationId);
|
|
if (replay != null) {
|
|
assertReplay(replay, taskId, itemId, requestHash);
|
|
return view(requiredTask(taskId), request.warehouseId());
|
|
}
|
|
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
|
|
requireWarehouseItem(taskId, itemId, request.warehouseId());
|
|
if (repository.partial(itemId, request.finalPickedPackageCount(), release.mode(), release.json(),
|
|
request.version(), request.warehouseId()) != 1) {
|
|
throw conflict("部分拣货箱数必须大于已拣箱数且小于计划箱数,或明细版本已变化");
|
|
}
|
|
insertHistory(taskId, itemId, operationId, requestHash, "PARTIAL",
|
|
request.finalPickedPackageCount(), null, operatorId, operatorName);
|
|
touchSettleFinishAndSync(task, operatorId, operatorName);
|
|
return view(requiredTask(taskId), request.warehouseId());
|
|
}
|
|
|
|
@Transactional
|
|
public Task skip(long taskId, long itemId, SkipRequest request, long operatorId, String operatorName) {
|
|
String operationId = request.operationId().trim();
|
|
String reason = trim(request.reason());
|
|
String requestHash = hash("SKIP|" + taskId + "|" + itemId + "|" + request.warehouseId()
|
|
+ "|" + reason);
|
|
requireNoTaskOperation(operationId);
|
|
LoadingPickHistoryEntity replay = repository.history(operationId);
|
|
if (replay != null) {
|
|
assertReplay(replay, taskId, itemId, requestHash);
|
|
return view(requiredTask(taskId), request.warehouseId());
|
|
}
|
|
LoadingPickTaskEntity task = requiredAssignedLoadingTaskForUpdate(taskId, operatorId);
|
|
LoadingPickItemEntity item = requireWarehouseItem(taskId, itemId, request.warehouseId());
|
|
if (repository.hasPickedOrPartialItem(taskId, item.getInboundId())) {
|
|
throw conflict("同一入库单已存在已拣或部分拣货明细,不能跳拣并恢复智能装柜候选");
|
|
}
|
|
if (repository.skip(itemId, request.version(), request.warehouseId()) != 1) {
|
|
throw conflict("跳拣仅允许未开始拣货的待拣明细,或明细版本已变化");
|
|
}
|
|
insertHistory(taskId, itemId, operationId, requestHash, "SKIP", 0, reason,
|
|
operatorId, operatorName);
|
|
touchSettleFinishAndSync(task, operatorId, operatorName);
|
|
return view(requiredTask(taskId), request.warehouseId());
|
|
}
|
|
|
|
@Transactional
|
|
public Task review(long taskId, ReviewRequest request, long operatorId, String operatorName) {
|
|
String operationId = operation(request.operationId());
|
|
String remark = trim(request.remark());
|
|
String requestHash = hash("REVIEW|" + taskId + '|' + request.expectedTaskVersion() + '|' + remark);
|
|
Task replay = replayTaskOperation(taskId, operationId, requestHash, operatorId);
|
|
if (replay != null) return replay;
|
|
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
|
if (!request.expectedTaskVersion().equals(task.getVersion())) {
|
|
throw conflict("PDA任务版本已变化,请刷新后复核");
|
|
}
|
|
if (repository.review(taskId) != null) throw conflict("PDA任务已经复核");
|
|
List<LoadingPickItemEntity> taskItems = repository.items(taskId);
|
|
PdaPickSettlementPlan.Resolution resolution = PdaPickSettlementPlan.resolve(taskItems);
|
|
for (List<PdaPickSettlementPlan.SplitLine> lines : resolution.partialByInbound().values()) {
|
|
for (PdaPickSettlementPlan.SplitLine line : lines) {
|
|
if (repository.settlePartial(line.loadingPlanItemId(), line.pickItemId(),
|
|
line.pickedPackageCount(), line.pickedPackageCount() + line.remainingPackageCount(),
|
|
operatorId, operatorName) != 1) {
|
|
throw conflict("部分拣货结算版本已变化");
|
|
}
|
|
}
|
|
}
|
|
for (Long planItemId : resolution.skippedPlanItemIds()) {
|
|
if (repository.settleSkipped(planItemId, operatorId, operatorName) != 1) {
|
|
throw conflict("跳拣结算版本已变化");
|
|
}
|
|
}
|
|
if (!repository.allPlanItemsPicked(task.getLoadingPlanId())) {
|
|
throw conflict("柜次仍有未完成PDA拣货的箱数");
|
|
}
|
|
if (repository.approve(taskId, operatorId, operatorName, remark) != 1) {
|
|
throw conflict("PDA任务已经复核");
|
|
}
|
|
insertOperation(taskId, operationId, requestHash, "REVIEW", operatorId, operatorName);
|
|
loadingListService.finishFromPda(task.getLoadingPlanId(), taskId, operatorId, operatorName);
|
|
repository.syncInboundProjections(taskId, operatorId, operatorName);
|
|
return view(requiredTask(taskId), null);
|
|
}
|
|
|
|
public List<Operation> operations(long taskId) {
|
|
requiredTask(taskId);
|
|
return repository.operations(taskId).stream().map(item -> new Operation(item.getId(), item.getPickTaskId(),
|
|
item.getOperationId(), item.getActionType(), item.getOperatorId(), item.getOperatorName(),
|
|
item.getCreatedAt())).toList();
|
|
}
|
|
|
|
@Transactional
|
|
public void remove(long planItemId) {
|
|
LoadingPickItemEntity item = repository.itemByPlanItem(planItemId);
|
|
if (item == null) {
|
|
return;
|
|
}
|
|
if (item.getPickedPackageCount() != null && item.getPickedPackageCount() > 0) {
|
|
throw conflict("已有拣货记录的明细不能删除");
|
|
}
|
|
repository.deleteItem(item.getId());
|
|
}
|
|
|
|
private void syncItem(long taskId, LoadingPlanItemEntity planItem) {
|
|
LoadingPickItemEntity item = repository.itemByPlanItem(planItem.getId());
|
|
if (item == null) {
|
|
item = new LoadingPickItemEntity();
|
|
item.setPickTaskId(taskId);
|
|
item.setLoadingPlanItemId(planItem.getId());
|
|
item.setInboundId(planItem.getInboundId());
|
|
item.setInboundDetailId(planItem.getInboundDetailId());
|
|
item.setPlannedPackageCount(planItem.getPackageCount());
|
|
item.setPickedPackageCount(0);
|
|
item.setItemStatus("PENDING");
|
|
item.setVersion(0);
|
|
repository.insert(item);
|
|
return;
|
|
}
|
|
if (item.getPickedPackageCount() > planItem.getPackageCount()) {
|
|
throw conflict("已拣货箱数不能低于计划箱数");
|
|
}
|
|
item.setPlannedPackageCount(planItem.getPackageCount());
|
|
item.setItemStatus(item.getPickedPackageCount().equals(planItem.getPackageCount()) ? "PICKED" : "PICKING");
|
|
repository.update(item);
|
|
}
|
|
|
|
private void settleAndFinish(LoadingPickTaskEntity task, long operatorId, String operatorName) {
|
|
if (!repository.allTaskItemsResolved(task.getId())) {
|
|
return;
|
|
}
|
|
settlementService.settle(task.getId(), operatorId, operatorName);
|
|
if (repository.allPlanItemsPicked(task.getLoadingPlanId())) {
|
|
loadingListService.finishFromPda(task.getLoadingPlanId(), task.getId(), operatorId, operatorName);
|
|
}
|
|
}
|
|
|
|
private LoadingPickTaskEntity requiredTask(long taskId) {
|
|
LoadingPickTaskEntity task = repository.task(taskId);
|
|
if (task == null) {
|
|
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货任务不存在");
|
|
}
|
|
return task;
|
|
}
|
|
|
|
private LoadingPickTaskEntity requiredLoadingTask(long taskId) {
|
|
LoadingPickTaskEntity task = requiredTask(taskId);
|
|
if (!"LOADING".equals(task.getTaskStatus())) {
|
|
throw conflict("装柜任务已完成,不能继续拣货");
|
|
}
|
|
return task;
|
|
}
|
|
|
|
private LoadingPickTaskEntity requiredLoadingTaskForUpdate(long taskId) {
|
|
LoadingPickTaskEntity task = repository.taskForUpdate(taskId);
|
|
if (task == null) throw new BizException(ResultCode.NOT_FOUND, "PDA拣货任务不存在");
|
|
if (!"LOADING".equals(task.getTaskStatus())) throw conflict("装柜任务已完成,不能继续拣货");
|
|
return task;
|
|
}
|
|
|
|
private LoadingPickTaskEntity requiredAssignedLoadingTaskForUpdate(long taskId, long operatorId) {
|
|
LoadingPickTaskEntity task = requiredLoadingTaskForUpdate(taskId);
|
|
LoadingPickAssignmentEntity assignment = repository.assignment(taskId);
|
|
if (assignment == null) throw conflict("请先领取PDA拣货任务");
|
|
if (!Long.valueOf(operatorId).equals(assignment.getAssigneeId())) {
|
|
throw conflict("PDA拣货任务已分配给其他作业员");
|
|
}
|
|
return task;
|
|
}
|
|
|
|
private LoadingPickItemEntity requireWarehouseItem(long taskId, long itemId, long warehouseId) {
|
|
LoadingPickItemEntity item = repository.item(taskId, itemId, warehouseId);
|
|
if (item == null) {
|
|
throw new BizException(ResultCode.NOT_FOUND, "PDA拣货明细不存在或不属于当前仓库");
|
|
}
|
|
return item;
|
|
}
|
|
|
|
private Task view(LoadingPickTaskEntity task, Long warehouseId) {
|
|
List<LoadingPickItemEntity> source = warehouseId == null
|
|
? repository.items(task.getId()) : repository.items(task.getId(), warehouseId);
|
|
List<PickItem> items = source.stream()
|
|
.map(item -> new PickItem(item.getId(), item.getLoadingPlanItemId(), item.getInboundId(),
|
|
item.getInboundDetailId(), item.getPlannedPackageCount(), item.getPickedPackageCount(),
|
|
item.getItemStatus(), item.getVersion())).toList();
|
|
LoadingPickAssignmentEntity assigned = repository.assignment(task.getId());
|
|
LoadingPickReviewEntity reviewed = repository.review(task.getId());
|
|
Assignment assignment = assigned == null ? null : new Assignment(assigned.getAssigneeId(),
|
|
assigned.getAssigneeName(), assigned.getAssignedBy(), assigned.getAssignedByName(),
|
|
assigned.getAssignmentType(), assigned.getVersion());
|
|
Review review = reviewed == null ? null : new Review(reviewed.getReviewStatus(),
|
|
reviewed.getReviewedBy(), reviewed.getReviewedByName(), reviewed.getRemark(), reviewed.getVersion());
|
|
String workflowStatus = "COMPLETED".equals(task.getTaskStatus()) ? "COMPLETED"
|
|
: assigned == null ? "UNASSIGNED"
|
|
: repository.allTaskItemsResolved(task.getId()) ? "AWAITING_REVIEW" : "IN_PROGRESS";
|
|
return new Task(task.getId(), task.getLoadingPlanId(), task.getContainerNo(), task.getTaskStatus(),
|
|
workflowStatus, task.getVersion(), assignment, review, items);
|
|
}
|
|
|
|
/**
|
|
* 先触碰仍处于 LOADING 的任务,再结算并推进装柜计划。计划完成触发器会同步把任务改为
|
|
* COMPLETED,因此绝不能在 finishFromPda 之后再以 LOADING 状态触碰任务。
|
|
*/
|
|
private void touchSettleFinishAndSync(LoadingPickTaskEntity task, long operatorId, String operatorName) {
|
|
if (repository.touchTask(task.getId(), task.getVersion()) != 1) {
|
|
throw conflict("PDA任务版本已变化");
|
|
}
|
|
settleAndFinish(task, operatorId, operatorName);
|
|
repository.syncInboundProjections(task.getId(), operatorId, operatorName);
|
|
}
|
|
|
|
private Task replayTaskOperation(long taskId, String operationId, String requestHash, long operatorId) {
|
|
if (repository.history(operationId) != null) throw conflict("operationId已用于明细拣货操作");
|
|
LoadingPickOperationEntity replay = repository.operation(operationId);
|
|
if (replay == null) return null;
|
|
if (!Long.valueOf(taskId).equals(replay.getPickTaskId())
|
|
|| !Long.valueOf(operatorId).equals(replay.getOperatorId())
|
|
|| !requestHash.equals(replay.getRequestHash())) {
|
|
throw conflict("operationId已用于不同PDA任务操作");
|
|
}
|
|
return view(requiredTask(taskId), null);
|
|
}
|
|
|
|
private void insertOperation(long taskId, String operationId, String requestHash, String actionType,
|
|
long operatorId, String operatorName) {
|
|
LoadingPickOperationEntity entity = new LoadingPickOperationEntity();
|
|
entity.setPickTaskId(taskId); entity.setOperationId(operationId); entity.setRequestHash(requestHash);
|
|
entity.setActionType(actionType); entity.setOperatorId(operatorId); entity.setOperatorName(operatorName);
|
|
try {
|
|
repository.insert(entity);
|
|
} catch (DuplicateKeyException exception) {
|
|
throw conflict("operationId已被并发使用");
|
|
}
|
|
}
|
|
|
|
private void requireNoTaskOperation(String operationId) {
|
|
if (repository.operation(operationId) != null) throw conflict("operationId已用于PDA任务操作");
|
|
}
|
|
|
|
private static String operation(String value) {
|
|
String result = trim(value);
|
|
if (!StringUtils.hasText(result)) throw new BizException(ResultCode.BAD_REQUEST, "operationId不能为空");
|
|
return result;
|
|
}
|
|
|
|
private void insertHistory(long taskId, long itemId, String operationId, String requestHash,
|
|
String actionType, int pickedPackageCount, String reason,
|
|
long operatorId, String operatorName) {
|
|
LoadingPickHistoryEntity history = new LoadingPickHistoryEntity();
|
|
history.setPickTaskId(taskId);
|
|
history.setPickItemId(itemId);
|
|
history.setOperationId(operationId);
|
|
history.setRequestHash(requestHash);
|
|
history.setActionType(actionType);
|
|
history.setPickedPackageCount(pickedPackageCount);
|
|
history.setOperationReason(reason);
|
|
history.setOperatorId(operatorId);
|
|
history.setOperatorName(operatorName);
|
|
repository.insert(history);
|
|
}
|
|
|
|
private static void assertReplay(LoadingPickHistoryEntity replay, long taskId, long itemId,
|
|
String requestHash) {
|
|
if (!replay.getPickTaskId().equals(taskId) || !replay.getPickItemId().equals(itemId)
|
|
|| !replay.getRequestHash().equals(requestHash)) {
|
|
throw conflict("operationId已用于不同拣货请求");
|
|
}
|
|
}
|
|
|
|
private static String hash(String value) {
|
|
try {
|
|
return java.util.HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
|
.digest(value.getBytes(StandardCharsets.UTF_8)));
|
|
} catch (NoSuchAlgorithmException exception) {
|
|
throw new IllegalStateException("JVM 不支持 SHA-256", exception);
|
|
}
|
|
}
|
|
|
|
private static ReleaseSelection releaseSelection(String requestedMode, List<Long> requestedLocationIds) {
|
|
String mode = requestedMode == null || requestedMode.isBlank()
|
|
? (requestedLocationIds == null ? "AUTO" : "MANUAL") : requestedMode.trim().toUpperCase();
|
|
if (!"AUTO".equals(mode) && !"MANUAL".equals(mode)) {
|
|
throw new BizException(ResultCode.BAD_REQUEST, "releaseMode只能是AUTO或MANUAL");
|
|
}
|
|
List<Long> ids = requestedLocationIds == null ? List.of() : requestedLocationIds.stream().sorted().toList();
|
|
if (new HashSet<>(ids).size() != ids.size()) {
|
|
throw new BizException(ResultCode.BAD_REQUEST, "释放库位不能重复");
|
|
}
|
|
if ("AUTO".equals(mode) && !ids.isEmpty()) {
|
|
throw new BizException(ResultCode.BAD_REQUEST, "AUTO模式不能指定释放库位");
|
|
}
|
|
String json = ids.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(",", "[", "]"));
|
|
return new ReleaseSelection(mode, json);
|
|
}
|
|
|
|
private record ReleaseSelection(String mode, String json) {}
|
|
|
|
private static String trim(String value) {
|
|
return value == null ? null : value.trim();
|
|
}
|
|
|
|
private static BizException conflict(String message) {
|
|
return new BizException(ResultCode.CONFLICT, message);
|
|
}
|
|
}
|