feat: add dense warehouse relocation jobs
This commit is contained in:
@@ -14,6 +14,7 @@ import com.ruoyi.common.core.controller.BaseController;
|
|||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.in.job.domain.MydoJob;
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
import com.ruoyi.in.job.service.IMydoJobService;
|
import com.ruoyi.in.job.service.IMydoJobService;
|
||||||
|
import com.ruoyi.out.relocation.service.IRelocationJobService;
|
||||||
import com.ruoyi.pda.service.PdaService;
|
import com.ruoyi.pda.service.PdaService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
@@ -37,6 +38,7 @@ public class ExternalApiController extends BaseController {
|
|||||||
@Autowired private MesClient mesClient;
|
@Autowired private MesClient mesClient;
|
||||||
@Autowired private WcsClient wcsClient;
|
@Autowired private WcsClient wcsClient;
|
||||||
@Autowired private IMydoJobService mydoJobService;
|
@Autowired private IMydoJobService mydoJobService;
|
||||||
|
@Autowired private IRelocationJobService relocationJobService;
|
||||||
@Autowired private PdaService pdaService;
|
@Autowired private PdaService pdaService;
|
||||||
|
|
||||||
// ═══════════════ ERP ═══════════════
|
// ═══════════════ ERP ═══════════════
|
||||||
@@ -353,11 +355,20 @@ public class ExternalApiController extends BaseController {
|
|||||||
job = mydoJobService.selectLatestOutMydoJobByTrayCodeAndWarehouseId(request.getTrayCode().trim(), request.getWarehouseId().trim());
|
job = mydoJobService.selectLatestOutMydoJobByTrayCodeAndWarehouseId(request.getTrayCode().trim(), request.getWarehouseId().trim());
|
||||||
}
|
}
|
||||||
if (job == null) {
|
if (job == null) {
|
||||||
|
if (request != null && request.getJobId() != null && !request.getJobId().trim().isEmpty()) {
|
||||||
|
AjaxResult relocationResult = relocationJobService.completeFromWcs(request.getJobId().trim());
|
||||||
|
if (relocationResult != null && relocationResult.isSuccess()) {
|
||||||
|
return wcsResult(relocationResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
return wcsError("未找到对应出库作业");
|
return wcsError("未找到对应出库作业");
|
||||||
}
|
}
|
||||||
if (!isExpectedJobType(job, false)) {
|
if (!isExpectedJobType(job, false)) {
|
||||||
return wcsError("非法出库作业");
|
return wcsError("非法出库作业");
|
||||||
}
|
}
|
||||||
|
if (Long.valueOf(23L).equals(job.getInOrOut())) {
|
||||||
|
return wcsResult(relocationJobService.completeFromWcs(job.getJobId()));
|
||||||
|
}
|
||||||
return wcsResult(mydoJobService.completeOutJobFromWcs(job.getJobId()));
|
return wcsResult(mydoJobService.completeOutJobFromWcs(job.getJobId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.ruoyi.out.relocation.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.out.relocation.domain.RelocationGenerateRequest;
|
||||||
|
import com.ruoyi.out.relocation.service.IRelocationJobService;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库移库作业接口。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/out/relocation")
|
||||||
|
public class RelocationJobController extends BaseController
|
||||||
|
{
|
||||||
|
private final IRelocationJobService relocationJobService;
|
||||||
|
|
||||||
|
public RelocationJobController(IRelocationJobService relocationJobService)
|
||||||
|
{
|
||||||
|
this.relocationJobService = relocationJobService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('out:relocation:preview')")
|
||||||
|
@PostMapping("/preview")
|
||||||
|
public AjaxResult preview(@RequestBody RelocationGenerateRequest request)
|
||||||
|
{
|
||||||
|
return success(relocationJobService.preview(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("@ss.hasPermi('out:relocation:generate')")
|
||||||
|
@PostMapping("/generate")
|
||||||
|
public AjaxResult generate(@RequestBody RelocationGenerateRequest request)
|
||||||
|
{
|
||||||
|
return success(relocationJobService.generate(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ruoyi.out.relocation.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库指定移库请求。
|
||||||
|
*/
|
||||||
|
public class RelocationGenerateRequest
|
||||||
|
{
|
||||||
|
private String warehouseId;
|
||||||
|
private Long theFloor;
|
||||||
|
private String sourceGroupCoding;
|
||||||
|
private String targetGroupCoding;
|
||||||
|
private Integer trayCount;
|
||||||
|
|
||||||
|
public String getWarehouseId() { return warehouseId; }
|
||||||
|
public void setWarehouseId(String warehouseId) { this.warehouseId = warehouseId; }
|
||||||
|
public Long getTheFloor() { return theFloor; }
|
||||||
|
public void setTheFloor(Long theFloor) { this.theFloor = theFloor; }
|
||||||
|
public String getSourceGroupCoding() { return sourceGroupCoding; }
|
||||||
|
public void setSourceGroupCoding(String sourceGroupCoding) { this.sourceGroupCoding = sourceGroupCoding; }
|
||||||
|
public String getTargetGroupCoding() { return targetGroupCoding; }
|
||||||
|
public void setTargetGroupCoding(String targetGroupCoding) { this.targetGroupCoding = targetGroupCoding; }
|
||||||
|
public Integer getTrayCount() { return trayCount; }
|
||||||
|
public void setTrayCount(Integer trayCount) { this.trayCount = trayCount; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ruoyi.out.relocation.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.out.dense.domain.DenseLanePosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定移库外侧托盘预览。
|
||||||
|
*/
|
||||||
|
public class RelocationPreview
|
||||||
|
{
|
||||||
|
private List<DenseLanePosition> trays = new ArrayList<>();
|
||||||
|
private BigDecimal totalQuantity = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
public List<DenseLanePosition> getTrays() { return trays; }
|
||||||
|
public void setTrays(List<DenseLanePosition> trays) { this.trays = trays; }
|
||||||
|
public BigDecimal getTotalQuantity() { return totalQuantity; }
|
||||||
|
public void setTotalQuantity(BigDecimal totalQuantity) { this.totalQuantity = totalQuantity; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.ruoyi.out.relocation.mapper;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||||
|
import com.ruoyi.out.dense.domain.DenseLanePosition;
|
||||||
|
import com.ruoyi.out.relocation.domain.RelocationGenerateRequest;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库移库作业 Mapper。
|
||||||
|
*/
|
||||||
|
public interface RelocationJobMapper
|
||||||
|
{
|
||||||
|
MydoJob selectJobForUpdate(@Param("jobId") String jobId);
|
||||||
|
|
||||||
|
List<MydoJobDetail> selectStockDetailsForUpdate(@Param("cargoSpaceId") String cargoSpaceId,
|
||||||
|
@Param("trayCode") String trayCode);
|
||||||
|
|
||||||
|
List<DenseLanePosition> selectRelocationSourcesForUpdate(RelocationGenerateRequest request);
|
||||||
|
|
||||||
|
BigDecimal sumTrayStockQuantity(@Param("trayCodes") String[] trayCodes);
|
||||||
|
|
||||||
|
int reserveTarget(Map<String, Object> params);
|
||||||
|
|
||||||
|
int reserveSource(Map<String, Object> params);
|
||||||
|
|
||||||
|
int insertRelocationJob(MydoJob job);
|
||||||
|
|
||||||
|
int insertRelocationDetail(MydoJobDetail detail);
|
||||||
|
|
||||||
|
int lockRelocationState(MydoJob job);
|
||||||
|
|
||||||
|
int moveTrayStock(MydoJob job);
|
||||||
|
|
||||||
|
int completeCargoSpaces(MydoJob job);
|
||||||
|
|
||||||
|
int archiveRelocationDetails(@Param("jobId") String jobId);
|
||||||
|
|
||||||
|
int archiveRelocationJob(@Param("jobId") String jobId);
|
||||||
|
|
||||||
|
int deleteRelocationDetails(@Param("jobId") String jobId);
|
||||||
|
|
||||||
|
int deleteRelocationJob(@Param("jobId") String jobId);
|
||||||
|
|
||||||
|
Integer isFinishedRelocation(@Param("jobId") String jobId);
|
||||||
|
|
||||||
|
String selectNextSourceLaneJob(@Param("cargoSpaceId") String cargoSpaceId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ruoyi.out.relocation.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.out.relocation.domain.RelocationGenerateRequest;
|
||||||
|
import com.ruoyi.out.relocation.domain.RelocationPreview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库移库作业服务。
|
||||||
|
*/
|
||||||
|
public interface IRelocationJobService
|
||||||
|
{
|
||||||
|
List<String> createBlockerRelocations(String blockedJobId);
|
||||||
|
|
||||||
|
RelocationPreview preview(RelocationGenerateRequest request);
|
||||||
|
|
||||||
|
List<String> generate(RelocationGenerateRequest request);
|
||||||
|
|
||||||
|
AjaxResult completeFromWcs(String jobId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
package com.ruoyi.out.relocation.service.impl;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
|
||||||
|
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||||
|
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||||
|
import com.ruoyi.out.dense.domain.DenseLanePosition;
|
||||||
|
import com.ruoyi.out.dense.domain.RelocationTargetQuery;
|
||||||
|
import com.ruoyi.out.dense.service.IDenseLaneService;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import com.ruoyi.out.relocation.domain.RelocationGenerateRequest;
|
||||||
|
import com.ruoyi.out.relocation.domain.RelocationPreview;
|
||||||
|
import com.ruoyi.out.relocation.mapper.RelocationJobMapper;
|
||||||
|
import com.ruoyi.out.relocation.service.IRelocationJobService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密集库移库作业服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class RelocationJobServiceImpl implements IRelocationJobService
|
||||||
|
{
|
||||||
|
private final RelocationJobMapper mapper;
|
||||||
|
private final IDenseLaneService denseLaneService;
|
||||||
|
private final JobIdService jobIdService;
|
||||||
|
private final TaskNoService taskNoService;
|
||||||
|
private final IOutboundDispatchService dispatchService;
|
||||||
|
|
||||||
|
public RelocationJobServiceImpl(RelocationJobMapper mapper,
|
||||||
|
IDenseLaneService denseLaneService,
|
||||||
|
JobIdService jobIdService,
|
||||||
|
TaskNoService taskNoService,
|
||||||
|
IOutboundDispatchService dispatchService)
|
||||||
|
{
|
||||||
|
this.mapper = mapper;
|
||||||
|
this.denseLaneService = denseLaneService;
|
||||||
|
this.jobIdService = jobIdService;
|
||||||
|
this.taskNoService = taskNoService;
|
||||||
|
this.dispatchService = dispatchService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public List<String> createBlockerRelocations(String blockedJobId)
|
||||||
|
{
|
||||||
|
if (!StringUtils.hasText(blockedJobId)) {
|
||||||
|
throw new RuntimeException("被遮挡作业ID不能为空");
|
||||||
|
}
|
||||||
|
MydoJob blockedJob = mapper.selectJobForUpdate(blockedJobId.trim());
|
||||||
|
if (blockedJob == null) {
|
||||||
|
throw new RuntimeException("被遮挡出库作业不存在");
|
||||||
|
}
|
||||||
|
List<DenseLanePosition> blockers = denseLaneService.selectBlockersForUpdate(
|
||||||
|
blockedJob.getCargoSpaceId());
|
||||||
|
return createRelocations(blockers, blockedJob.getWarehouseName(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public RelocationPreview preview(RelocationGenerateRequest request)
|
||||||
|
{
|
||||||
|
validateRequest(request);
|
||||||
|
List<DenseLanePosition> candidates = mapper.selectRelocationSourcesForUpdate(request);
|
||||||
|
if (candidates == null || candidates.size() < request.getTrayCount()) {
|
||||||
|
throw new RuntimeException("源通道可移库托盘数量不足");
|
||||||
|
}
|
||||||
|
List<DenseLanePosition> selected = denseLaneService.orderOutermostFirst(candidates)
|
||||||
|
.stream().limit(request.getTrayCount()).collect(Collectors.toList());
|
||||||
|
RelocationPreview preview = new RelocationPreview();
|
||||||
|
preview.setTrays(selected);
|
||||||
|
String[] trayCodes = selected.stream().map(DenseLanePosition::getTrayCode).toArray(String[]::new);
|
||||||
|
BigDecimal total = mapper.sumTrayStockQuantity(trayCodes);
|
||||||
|
preview.setTotalQuantity(total == null ? BigDecimal.ZERO : total);
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public List<String> generate(RelocationGenerateRequest request)
|
||||||
|
{
|
||||||
|
validateRequest(request);
|
||||||
|
List<DenseLanePosition> candidates = mapper.selectRelocationSourcesForUpdate(request);
|
||||||
|
if (candidates == null || candidates.size() < request.getTrayCount()) {
|
||||||
|
throw new RuntimeException("源通道可移库托盘数量不足");
|
||||||
|
}
|
||||||
|
List<DenseLanePosition> selected = denseLaneService.orderOutermostFirst(candidates)
|
||||||
|
.stream().limit(request.getTrayCount()).collect(Collectors.toList());
|
||||||
|
return createRelocations(selected, null, request.getTargetGroupCoding());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public AjaxResult completeFromWcs(String jobId)
|
||||||
|
{
|
||||||
|
if (!StringUtils.hasText(jobId)) {
|
||||||
|
return AjaxResult.error("移库作业ID不能为空");
|
||||||
|
}
|
||||||
|
MydoJob job = mapper.selectJobForUpdate(jobId.trim());
|
||||||
|
if (job == null) {
|
||||||
|
if (Integer.valueOf(1).equals(mapper.isFinishedRelocation(jobId.trim()))) {
|
||||||
|
return AjaxResult.success("移库作业已完成");
|
||||||
|
}
|
||||||
|
return AjaxResult.error("移库作业不存在");
|
||||||
|
}
|
||||||
|
if (!Long.valueOf(23L).equals(job.getInOrOut())) {
|
||||||
|
return AjaxResult.error("当前作业不是移库作业");
|
||||||
|
}
|
||||||
|
if (!"3".equals(job.getStatus()) || !"1".equals(job.getExecutionPhase())) {
|
||||||
|
return AjaxResult.error("移库作业尚未成功下发WCS");
|
||||||
|
}
|
||||||
|
if (mapper.lockRelocationState(job) != 1) {
|
||||||
|
throw new RuntimeException("移库源货位、目标货位或托盘状态已变化");
|
||||||
|
}
|
||||||
|
if (mapper.moveTrayStock(job) <= 0) {
|
||||||
|
throw new RuntimeException("移库托盘库存不存在");
|
||||||
|
}
|
||||||
|
if (mapper.completeCargoSpaces(job) != 2) {
|
||||||
|
throw new RuntimeException("移库货位状态更新失败");
|
||||||
|
}
|
||||||
|
mapper.archiveRelocationDetails(job.getJobId());
|
||||||
|
if (mapper.archiveRelocationJob(job.getJobId()) != 1) {
|
||||||
|
throw new RuntimeException("移库作业归档失败");
|
||||||
|
}
|
||||||
|
mapper.deleteRelocationDetails(job.getJobId());
|
||||||
|
if (mapper.deleteRelocationJob(job.getJobId()) != 1) {
|
||||||
|
throw new RuntimeException("移库活动作业删除失败");
|
||||||
|
}
|
||||||
|
afterCommit(() -> dispatchNextSourceLaneJob(job.getCargoSpaceId()));
|
||||||
|
return AjaxResult.success("移库作业完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> createRelocations(List<DenseLanePosition> sources,
|
||||||
|
String warehouseName,
|
||||||
|
String targetGroupCoding)
|
||||||
|
{
|
||||||
|
if (sources == null || sources.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<BasCargoSpace> targets = new ArrayList<>();
|
||||||
|
for (DenseLanePosition source : sources) {
|
||||||
|
RelocationTargetQuery query = new RelocationTargetQuery();
|
||||||
|
query.setWarehouseId(source.getWarehouseId());
|
||||||
|
query.setTheFloor(source.getTheFloor());
|
||||||
|
query.setSourceGroupCoding(source.getGroupCoding());
|
||||||
|
query.setTargetGroupCoding(targetGroupCoding);
|
||||||
|
query.setTrayCode(source.getTrayCode());
|
||||||
|
BasCargoSpace target = denseLaneService.selectRelocationTargetForUpdate(query);
|
||||||
|
if (target == null) {
|
||||||
|
throw new RuntimeException("同层可用移库货位不足");
|
||||||
|
}
|
||||||
|
Map<String, Object> reserve = new HashMap<>();
|
||||||
|
reserve.put("cargoSpaceId", target.getCargoSpaceId());
|
||||||
|
reserve.put("trayCode", source.getTrayCode());
|
||||||
|
if (mapper.reserveTarget(reserve) != 1) {
|
||||||
|
throw new RuntimeException("移库目标货位已被占用");
|
||||||
|
}
|
||||||
|
targets.add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> jobIds = new ArrayList<>();
|
||||||
|
for (int index = 0; index < sources.size(); index++) {
|
||||||
|
DenseLanePosition source = sources.get(index);
|
||||||
|
BasCargoSpace target = targets.get(index);
|
||||||
|
Map<String, Object> sourceReserve = new HashMap<>();
|
||||||
|
sourceReserve.put("cargoSpaceId", source.getCargoSpaceId());
|
||||||
|
sourceReserve.put("trayCode", source.getTrayCode());
|
||||||
|
if (mapper.reserveSource(sourceReserve) != 1) {
|
||||||
|
throw new RuntimeException("移库源货位状态已变化");
|
||||||
|
}
|
||||||
|
List<MydoJobDetail> stockDetails = mapper.selectStockDetailsForUpdate(
|
||||||
|
source.getCargoSpaceId(), source.getTrayCode());
|
||||||
|
if (stockDetails == null || stockDetails.isEmpty()) {
|
||||||
|
throw new RuntimeException("移库托盘没有库存明细");
|
||||||
|
}
|
||||||
|
String jobId = jobIdService.getId("CK");
|
||||||
|
if (!StringUtils.hasText(jobId)) {
|
||||||
|
throw new RuntimeException("移库作业ID生成失败");
|
||||||
|
}
|
||||||
|
MydoJob job = buildJob(jobId, taskNoService.getTaskNo("23"), source,
|
||||||
|
target, warehouseName);
|
||||||
|
if (mapper.insertRelocationJob(job) != 1) {
|
||||||
|
throw new RuntimeException("移库作业生成失败");
|
||||||
|
}
|
||||||
|
int detailIndex = 1;
|
||||||
|
for (MydoJobDetail template : stockDetails) {
|
||||||
|
MydoJobDetail detail = copyDetail(template, jobId, detailIndex++);
|
||||||
|
if (mapper.insertRelocationDetail(detail) != 1) {
|
||||||
|
throw new RuntimeException("移库作业明细生成失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jobIds.add(jobId);
|
||||||
|
}
|
||||||
|
afterCommit(() -> jobIds.forEach(id -> dispatchService.dispatch(id, DispatchTrigger.INITIAL)));
|
||||||
|
return jobIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MydoJob buildJob(String jobId, String taskNo, DenseLanePosition source,
|
||||||
|
BasCargoSpace target, String warehouseName)
|
||||||
|
{
|
||||||
|
MydoJob job = new MydoJob();
|
||||||
|
job.setJobId(jobId);
|
||||||
|
job.setTaskNo(StringUtils.hasText(taskNo) ? taskNo : jobId);
|
||||||
|
job.setInOrOut(23L);
|
||||||
|
job.setStatus("1");
|
||||||
|
job.setExecutionPhase("0");
|
||||||
|
job.setTrayCode(source.getTrayCode());
|
||||||
|
job.setWarehouseId(source.getWarehouseId());
|
||||||
|
job.setWarehouseName(warehouseName);
|
||||||
|
job.setCargoSpaceId(source.getCargoSpaceId());
|
||||||
|
job.setCargoSpaceName(source.getCargoSpaceName());
|
||||||
|
job.setTargetCargoSpaceId(target.getCargoSpaceId());
|
||||||
|
job.setTargetCargoSpaceName(target.getCargoSpaceName());
|
||||||
|
job.setLineType("1");
|
||||||
|
job.setPriority(10L);
|
||||||
|
job.setCreateTime(DateUtils.getNowDate());
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MydoJobDetail copyDetail(MydoJobDetail source, String jobId, int index)
|
||||||
|
{
|
||||||
|
MydoJobDetail detail = new MydoJobDetail();
|
||||||
|
detail.setJobDetailId(jobId + "-" + index);
|
||||||
|
detail.setJobId(jobId);
|
||||||
|
detail.setStockId(source.getStockId());
|
||||||
|
detail.setLotNo(source.getLotNo());
|
||||||
|
detail.setInDate(source.getInDate());
|
||||||
|
detail.setInOrOutId(source.getInOrOutId());
|
||||||
|
detail.setInOrOutDId(source.getInOrOutDId());
|
||||||
|
detail.setProductId(source.getProductId());
|
||||||
|
detail.setProductName(source.getProductName());
|
||||||
|
detail.setStockQuantity(source.getStockQuantity());
|
||||||
|
detail.setStockinId(source.getStockinId());
|
||||||
|
detail.setStockinDetailId(source.getStockinDetailId());
|
||||||
|
detail.setCustomerId(source.getCustomerId());
|
||||||
|
detail.setCustomerName(source.getCustomerName());
|
||||||
|
detail.setSupplierId(source.getSupplierId());
|
||||||
|
detail.setSupplierName(source.getSupplierName());
|
||||||
|
detail.setProjectNo(source.getProjectNo());
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRequest(RelocationGenerateRequest request)
|
||||||
|
{
|
||||||
|
if (request == null || !"03".equals(request.getWarehouseId())) {
|
||||||
|
throw new RuntimeException("指定移库仅支持03仓密集库");
|
||||||
|
}
|
||||||
|
if (request.getTheFloor() == null) {
|
||||||
|
throw new RuntimeException("请选择移库层");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(request.getSourceGroupCoding())
|
||||||
|
|| !StringUtils.hasText(request.getTargetGroupCoding())) {
|
||||||
|
throw new RuntimeException("请选择源通道和目标通道");
|
||||||
|
}
|
||||||
|
if (request.getSourceGroupCoding().equals(request.getTargetGroupCoding())) {
|
||||||
|
throw new RuntimeException("移库目标必须是同层不同通道");
|
||||||
|
}
|
||||||
|
if (request.getTrayCount() == null || request.getTrayCount() <= 0) {
|
||||||
|
throw new RuntimeException("移库托盘数量必须大于0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatchNextSourceLaneJob(String sourceCargoSpaceId)
|
||||||
|
{
|
||||||
|
String nextJobId = mapper.selectNextSourceLaneJob(sourceCargoSpaceId);
|
||||||
|
if (StringUtils.hasText(nextJobId)) {
|
||||||
|
dispatchService.dispatch(nextJobId, DispatchTrigger.INITIAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void afterCommit(Runnable action)
|
||||||
|
{
|
||||||
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void afterCommit()
|
||||||
|
{
|
||||||
|
action.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
action.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.out.relocation.mapper.RelocationJobMapper">
|
||||||
|
|
||||||
|
<select id="selectJobForUpdate" resultType="com.ruoyi.in.job.domain.MydoJob">
|
||||||
|
select *
|
||||||
|
from mydo_job with (UPDLOCK,HOLDLOCK)
|
||||||
|
where job_id = #{jobId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectStockDetailsForUpdate"
|
||||||
|
resultType="com.ruoyi.in.jobdetail.domain.MydoJobDetail">
|
||||||
|
select detail.stock_id,
|
||||||
|
detail.lot_no,
|
||||||
|
convert(varchar(19), stock.in_date, 120) as in_date,
|
||||||
|
stock.stockin_id,
|
||||||
|
detail.detail_id as stockin_detail_id,
|
||||||
|
detail.product_id,
|
||||||
|
detail.product_name,
|
||||||
|
detail.quantity as stock_quantity,
|
||||||
|
cast(null as nvarchar(255)) as customer_id,
|
||||||
|
cast(null as nvarchar(255)) as customer_name,
|
||||||
|
stock.suplier_id as supplier_id,
|
||||||
|
stock.suplier_name as supplier_name,
|
||||||
|
detail.project_no
|
||||||
|
from bas_stock stock with (UPDLOCK,HOLDLOCK)
|
||||||
|
inner join bas_stock_detail detail with (UPDLOCK,HOLDLOCK)
|
||||||
|
on detail.stock_id = stock.stock_id
|
||||||
|
where stock.cargo_space_id = #{cargoSpaceId}
|
||||||
|
and stock.tray_code = #{trayCode}
|
||||||
|
and isnull(detail.quantity, 0) > 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectRelocationSourcesForUpdate"
|
||||||
|
resultType="com.ruoyi.out.dense.domain.DenseLanePosition">
|
||||||
|
select cargo.cargo_space_id,
|
||||||
|
cargo.cargo_space_name,
|
||||||
|
cargo.warehouse_id,
|
||||||
|
cargo.group_coding,
|
||||||
|
cargo.this_cargo_area as cargo_area,
|
||||||
|
cargo.the_platoon,
|
||||||
|
cargo.the_column,
|
||||||
|
cargo.the_floor,
|
||||||
|
cargo.status,
|
||||||
|
cargo.locked,
|
||||||
|
cargo.tray_code,
|
||||||
|
isnull(config.direction_type, '1') as direction_type,
|
||||||
|
boundary.min_column,
|
||||||
|
boundary.max_column
|
||||||
|
from bas_cargo_space cargo with (UPDLOCK,HOLDLOCK)
|
||||||
|
left join bas_cargo_area_config config
|
||||||
|
on config.warehouse_id = cargo.warehouse_id
|
||||||
|
and config.cargo_area_code = cargo.this_cargo_area
|
||||||
|
and config.is_active = '1'
|
||||||
|
cross apply
|
||||||
|
(
|
||||||
|
select min(lane.the_column) as min_column,
|
||||||
|
max(lane.the_column) as max_column
|
||||||
|
from bas_cargo_space lane with (UPDLOCK,HOLDLOCK)
|
||||||
|
where lane.warehouse_id = cargo.warehouse_id
|
||||||
|
and lane.group_coding = cargo.group_coding
|
||||||
|
and lane.the_floor = cargo.the_floor
|
||||||
|
) boundary
|
||||||
|
where cargo.warehouse_id = #{warehouseId}
|
||||||
|
and cargo.the_floor = #{theFloor}
|
||||||
|
and cargo.group_coding = #{sourceGroupCoding}
|
||||||
|
and cargo.status = '2'
|
||||||
|
and cargo.tray_code is not null
|
||||||
|
and not exists
|
||||||
|
(
|
||||||
|
select 1 from mydo_job active_job with (UPDLOCK,HOLDLOCK)
|
||||||
|
where active_job.status in ('1', '2', '3')
|
||||||
|
and (active_job.cargo_space_id = cargo.cargo_space_id
|
||||||
|
or active_job.tray_code = cargo.tray_code)
|
||||||
|
)
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="sumTrayStockQuantity" resultType="java.math.BigDecimal">
|
||||||
|
select isnull(sum(isnull(detail.quantity, 0)), 0)
|
||||||
|
from bas_stock stock
|
||||||
|
inner join bas_stock_detail detail on detail.stock_id = stock.stock_id
|
||||||
|
where stock.tray_code in
|
||||||
|
<foreach collection="trayCodes" item="trayCode" open="(" separator="," close=")">
|
||||||
|
#{trayCode}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="reserveTarget" parameterType="java.util.Map">
|
||||||
|
update bas_cargo_space
|
||||||
|
set status = '3', tray_code = #{trayCode}
|
||||||
|
where cargo_space_id = #{cargoSpaceId}
|
||||||
|
and status = '1'
|
||||||
|
and isnull(locked, 'N') = 'N'
|
||||||
|
and tray_code is null
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="reserveSource" parameterType="java.util.Map">
|
||||||
|
update bas_cargo_space
|
||||||
|
set status = '4'
|
||||||
|
where cargo_space_id = #{cargoSpaceId}
|
||||||
|
and status in ('2', '3')
|
||||||
|
and tray_code = #{trayCode}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<insert id="insertRelocationJob" parameterType="com.ruoyi.in.job.domain.MydoJob">
|
||||||
|
insert into mydo_job
|
||||||
|
(job_id, status, task_no, in_or_out, execution_phase, tray_code,
|
||||||
|
warehouse_id, warehouse_name, cargo_space_id, cargo_space_name,
|
||||||
|
target_cargo_space_id, target_cargo_space_name, line_type, priority, create_time)
|
||||||
|
values
|
||||||
|
(#{jobId}, #{status}, #{taskNo}, #{inOrOut}, #{executionPhase}, #{trayCode},
|
||||||
|
#{warehouseId}, #{warehouseName}, #{cargoSpaceId}, #{cargoSpaceName},
|
||||||
|
#{targetCargoSpaceId}, #{targetCargoSpaceName}, #{lineType}, #{priority}, #{createTime})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<insert id="insertRelocationDetail" parameterType="com.ruoyi.in.jobdetail.domain.MydoJobDetail">
|
||||||
|
insert into mydo_job_detail
|
||||||
|
(job_detail_id, job_id, stock_id, lot_no, in_date, in_or_out_id,
|
||||||
|
in_or_out_d_id, product_id, product_name, stock_quantity, stockin_id,
|
||||||
|
stockin_detail_id, customer_id, customer_name, supplier_id, supplier_name, project_no)
|
||||||
|
values
|
||||||
|
(#{jobDetailId}, #{jobId}, #{stockId}, #{lotNo}, #{inDate}, #{inOrOutId},
|
||||||
|
#{inOrOutDId}, #{productId}, #{productName}, #{stockQuantity}, #{stockinId},
|
||||||
|
#{stockinDetailId}, #{customerId}, #{customerName}, #{supplierId}, #{supplierName}, #{projectNo})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="lockRelocationState" resultType="java.lang.Integer">
|
||||||
|
select case when count(1) = 2 then 1 else 0 end
|
||||||
|
from bas_cargo_space cargo with (UPDLOCK,HOLDLOCK)
|
||||||
|
where (cargo.cargo_space_id = #{cargoSpaceId}
|
||||||
|
and cargo.status = '4' and cargo.tray_code = #{trayCode})
|
||||||
|
or (cargo.cargo_space_id = #{targetCargoSpaceId}
|
||||||
|
and cargo.status = '3' and cargo.tray_code = #{trayCode})
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="moveTrayStock" parameterType="com.ruoyi.in.job.domain.MydoJob">
|
||||||
|
update bas_stock
|
||||||
|
set cargo_space_id = #{targetCargoSpaceId}
|
||||||
|
where cargo_space_id = #{cargoSpaceId}
|
||||||
|
and tray_code = #{trayCode}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="completeCargoSpaces" parameterType="com.ruoyi.in.job.domain.MydoJob">
|
||||||
|
update bas_cargo_space
|
||||||
|
set status = case when cargo_space_id = #{cargoSpaceId} then '1' else '2' end,
|
||||||
|
tray_code = case when cargo_space_id = #{cargoSpaceId} then null else #{trayCode} end
|
||||||
|
where (cargo_space_id = #{cargoSpaceId} and status = '4' and tray_code = #{trayCode})
|
||||||
|
or (cargo_space_id = #{targetCargoSpaceId} and status = '3' and tray_code = #{trayCode})
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<insert id="archiveRelocationDetails">
|
||||||
|
insert into mydo_job_detail_finish
|
||||||
|
(job_detail_id, job_id, stock_id, lot_no, in_date, in_or_out_id,
|
||||||
|
in_or_out_d_id, product_id, product_name, in_quantity, stock_quantity,
|
||||||
|
out_quantity, stockin_id, stockin_detail_id, customer_id, customer_name,
|
||||||
|
supplier_id, supplier_name, project_no)
|
||||||
|
select job_detail_id, job_id, stock_id, lot_no, in_date, in_or_out_id,
|
||||||
|
in_or_out_d_id, product_id, product_name, in_quantity, stock_quantity,
|
||||||
|
out_quantity, stockin_id, stockin_detail_id, customer_id, customer_name,
|
||||||
|
supplier_id, supplier_name, project_no
|
||||||
|
from mydo_job_detail with (UPDLOCK,HOLDLOCK)
|
||||||
|
where job_id = #{jobId}
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<insert id="archiveRelocationJob">
|
||||||
|
insert into mydo_job_finish
|
||||||
|
(job_id, status, task_no, in_or_out, execution_phase, tray_code,
|
||||||
|
warehouse_id, warehouse_name, roadway_id, cargo_space_id, cargo_space_name,
|
||||||
|
target_roadway_id, target_cargo_space_id, target_cargo_space_name, line_type,
|
||||||
|
priority, height, weight, create_time, start_time, finish_time, entrance_id,
|
||||||
|
exit_id, dept_id, dept_name, user_id, user_name, supplier_id, supplier_name,
|
||||||
|
customer_id, customer_name)
|
||||||
|
select job_id, '4', task_no, in_or_out, '5', tray_code,
|
||||||
|
warehouse_id, warehouse_name, roadway_id, cargo_space_id, cargo_space_name,
|
||||||
|
target_roadway_id, target_cargo_space_id, target_cargo_space_name, line_type,
|
||||||
|
priority, height, weight, create_time, start_time, getdate(), entrance_id,
|
||||||
|
exit_id, dept_id, dept_name, user_id, user_name, supplier_id, supplier_name,
|
||||||
|
customer_id, customer_name
|
||||||
|
from mydo_job with (UPDLOCK,HOLDLOCK)
|
||||||
|
where job_id = #{jobId} and in_or_out = 23
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<delete id="deleteRelocationDetails">
|
||||||
|
delete from mydo_job_detail where job_id = #{jobId}
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<delete id="deleteRelocationJob">
|
||||||
|
delete from mydo_job where job_id = #{jobId} and in_or_out = 23
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<select id="isFinishedRelocation" resultType="java.lang.Integer">
|
||||||
|
select case when exists
|
||||||
|
(select 1 from mydo_job_finish where job_id = #{jobId} and in_or_out = 23)
|
||||||
|
then 1 else 0 end
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectNextSourceLaneJob" resultType="java.lang.String">
|
||||||
|
select top 1 job.job_id
|
||||||
|
from mydo_job job
|
||||||
|
inner join bas_cargo_space job_space on job_space.cargo_space_id = job.cargo_space_id
|
||||||
|
inner join bas_cargo_space source_space on source_space.cargo_space_id = #{cargoSpaceId}
|
||||||
|
left join wcs_job_dispatch dispatch on dispatch.job_id = job.job_id
|
||||||
|
where job.in_or_out >= 20
|
||||||
|
and job.status = '1'
|
||||||
|
and job_space.warehouse_id = source_space.warehouse_id
|
||||||
|
and job_space.group_coding = source_space.group_coding
|
||||||
|
and job_space.the_floor = source_space.the_floor
|
||||||
|
and (dispatch.dispatch_status is null or dispatch.dispatch_status != 'SUCCESS')
|
||||||
|
order by job.priority desc, job.create_time asc, job.job_id asc
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package com.ruoyi.out.relocation.service.impl;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
|
||||||
|
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||||
|
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||||
|
import com.ruoyi.out.dense.domain.DenseLanePosition;
|
||||||
|
import com.ruoyi.out.dense.domain.RelocationTargetQuery;
|
||||||
|
import com.ruoyi.out.dense.service.IDenseLaneService;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import com.ruoyi.out.relocation.domain.RelocationGenerateRequest;
|
||||||
|
import com.ruoyi.out.relocation.mapper.RelocationJobMapper;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class RelocationJobServiceImplTest
|
||||||
|
{
|
||||||
|
private RelocationJobMapper mapper;
|
||||||
|
private IDenseLaneService denseLaneService;
|
||||||
|
private JobIdService jobIdService;
|
||||||
|
private TaskNoService taskNoService;
|
||||||
|
private IOutboundDispatchService dispatchService;
|
||||||
|
private RelocationJobServiceImpl service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp()
|
||||||
|
{
|
||||||
|
mapper = mock(RelocationJobMapper.class);
|
||||||
|
denseLaneService = mock(IDenseLaneService.class);
|
||||||
|
jobIdService = mock(JobIdService.class);
|
||||||
|
taskNoService = mock(TaskNoService.class);
|
||||||
|
dispatchService = mock(IOutboundDispatchService.class);
|
||||||
|
service = new RelocationJobServiceImpl(
|
||||||
|
mapper, denseLaneService, jobIdService, taskNoService, dispatchService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsOneType23JobPerBlockerAndCopiesEveryStockDetail()
|
||||||
|
{
|
||||||
|
MydoJob blocked = blockedJob("OUT-1", "CS-3");
|
||||||
|
DenseLanePosition outer = source("CS-5", "T-5");
|
||||||
|
DenseLanePosition inner = source("CS-4", "T-4");
|
||||||
|
when(mapper.selectJobForUpdate("OUT-1")).thenReturn(blocked);
|
||||||
|
when(denseLaneService.selectBlockersForUpdate("CS-3")).thenReturn(List.of(outer, inner));
|
||||||
|
when(denseLaneService.selectRelocationTargetForUpdate(any(RelocationTargetQuery.class)))
|
||||||
|
.thenReturn(target("TG-1", "TARGET-1"), target("TG-2", "TARGET-2"));
|
||||||
|
when(mapper.reserveTarget(any())).thenReturn(1);
|
||||||
|
when(mapper.reserveSource(any())).thenReturn(1);
|
||||||
|
when(jobIdService.getId("CK")).thenReturn("MOVE-1", "MOVE-2");
|
||||||
|
when(taskNoService.getTaskNo("23")).thenReturn("MOVE-TASK-1", "MOVE-TASK-2");
|
||||||
|
when(mapper.selectStockDetailsForUpdate(anyString(), anyString()))
|
||||||
|
.thenReturn(List.of(detail("S-1", "P-1"), detail("S-2", "P-2")));
|
||||||
|
when(mapper.insertRelocationJob(any(MydoJob.class))).thenReturn(1);
|
||||||
|
when(mapper.insertRelocationDetail(any(MydoJobDetail.class))).thenReturn(1);
|
||||||
|
when(dispatchService.dispatch(anyString(), any(DispatchTrigger.class)))
|
||||||
|
.thenReturn(AjaxResult.success());
|
||||||
|
|
||||||
|
List<String> ids = service.createBlockerRelocations("OUT-1");
|
||||||
|
|
||||||
|
assertThat(ids).containsExactly("MOVE-1", "MOVE-2");
|
||||||
|
ArgumentCaptor<MydoJob> jobs = ArgumentCaptor.forClass(MydoJob.class);
|
||||||
|
verify(mapper, org.mockito.Mockito.times(2)).insertRelocationJob(jobs.capture());
|
||||||
|
assertThat(jobs.getAllValues()).allSatisfy(job -> {
|
||||||
|
assertThat(job.getInOrOut()).isEqualTo(23L);
|
||||||
|
assertThat(job.getStatus()).isEqualTo("1");
|
||||||
|
assertThat(job.getExecutionPhase()).isEqualTo("0");
|
||||||
|
assertThat(job.getTargetCargoSpaceId()).isNotBlank();
|
||||||
|
});
|
||||||
|
verify(mapper, org.mockito.Mockito.times(4)).insertRelocationDetail(any(MydoJobDetail.class));
|
||||||
|
verify(taskNoService, org.mockito.Mockito.times(2)).getTaskNo("23");
|
||||||
|
verify(dispatchService).dispatch("MOVE-1", DispatchTrigger.INITIAL);
|
||||||
|
verify(dispatchService).dispatch("MOVE-2", DispatchTrigger.INITIAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reservesAllTargetsBeforeInsertingAnyRelocationJob()
|
||||||
|
{
|
||||||
|
MydoJob blocked = blockedJob("OUT-2", "CS-3");
|
||||||
|
when(mapper.selectJobForUpdate("OUT-2")).thenReturn(blocked);
|
||||||
|
when(denseLaneService.selectBlockersForUpdate("CS-3"))
|
||||||
|
.thenReturn(List.of(source("CS-5", "T-5"), source("CS-4", "T-4")));
|
||||||
|
when(denseLaneService.selectRelocationTargetForUpdate(any(RelocationTargetQuery.class)))
|
||||||
|
.thenReturn(target("TG-1", "TARGET-1"), (BasCargoSpace) null);
|
||||||
|
when(mapper.reserveTarget(any())).thenReturn(1);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> service.createBlockerRelocations("OUT-2"))
|
||||||
|
.hasMessageContaining("同层可用移库货位不足");
|
||||||
|
verify(mapper, never()).insertRelocationJob(any(MydoJob.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsManualRelocationToSameLaneOrDifferentFloor()
|
||||||
|
{
|
||||||
|
RelocationGenerateRequest sameLane = new RelocationGenerateRequest();
|
||||||
|
sameLane.setWarehouseId("03");
|
||||||
|
sameLane.setTheFloor(2L);
|
||||||
|
sameLane.setSourceGroupCoding("G-02");
|
||||||
|
sameLane.setTargetGroupCoding("G-02");
|
||||||
|
sameLane.setTrayCount(1);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> service.generate(sameLane))
|
||||||
|
.hasMessageContaining("不同通道");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void completionMovesInventorySwapsCargoSpacesArchivesAndWakesNextJob()
|
||||||
|
{
|
||||||
|
MydoJob move = blockedJob("MOVE-1", "SOURCE");
|
||||||
|
move.setInOrOut(23L);
|
||||||
|
move.setStatus("3");
|
||||||
|
move.setExecutionPhase("1");
|
||||||
|
move.setTargetCargoSpaceId("TARGET");
|
||||||
|
move.setTrayCode("T-1");
|
||||||
|
when(mapper.selectJobForUpdate("MOVE-1")).thenReturn(move);
|
||||||
|
when(mapper.lockRelocationState(move)).thenReturn(1);
|
||||||
|
when(mapper.moveTrayStock(move)).thenReturn(2);
|
||||||
|
when(mapper.completeCargoSpaces(move)).thenReturn(2);
|
||||||
|
when(mapper.archiveRelocationDetails("MOVE-1")).thenReturn(2);
|
||||||
|
when(mapper.archiveRelocationJob("MOVE-1")).thenReturn(1);
|
||||||
|
when(mapper.deleteRelocationDetails("MOVE-1")).thenReturn(2);
|
||||||
|
when(mapper.deleteRelocationJob("MOVE-1")).thenReturn(1);
|
||||||
|
when(mapper.selectNextSourceLaneJob("SOURCE")).thenReturn("NEXT-1");
|
||||||
|
when(dispatchService.dispatch("NEXT-1", DispatchTrigger.INITIAL))
|
||||||
|
.thenReturn(AjaxResult.success());
|
||||||
|
|
||||||
|
AjaxResult result = service.completeFromWcs("MOVE-1");
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
var ordered = inOrder(mapper);
|
||||||
|
ordered.verify(mapper).moveTrayStock(move);
|
||||||
|
ordered.verify(mapper).completeCargoSpaces(move);
|
||||||
|
ordered.verify(mapper).archiveRelocationDetails("MOVE-1");
|
||||||
|
ordered.verify(mapper).archiveRelocationJob("MOVE-1");
|
||||||
|
ordered.verify(mapper).deleteRelocationDetails("MOVE-1");
|
||||||
|
ordered.verify(mapper).deleteRelocationJob("MOVE-1");
|
||||||
|
verify(dispatchService).dispatch("NEXT-1", DispatchTrigger.INITIAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void duplicateCompletionOfArchivedRelocationIsIdempotent()
|
||||||
|
{
|
||||||
|
when(mapper.selectJobForUpdate("MOVE-DONE")).thenReturn(null);
|
||||||
|
when(mapper.isFinishedRelocation("MOVE-DONE")).thenReturn(1);
|
||||||
|
|
||||||
|
assertThat(service.completeFromWcs("MOVE-DONE").isSuccess()).isTrue();
|
||||||
|
verify(mapper, never()).moveTrayStock(any(MydoJob.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MydoJob blockedJob(String jobId, String cargoSpaceId)
|
||||||
|
{
|
||||||
|
MydoJob job = new MydoJob();
|
||||||
|
job.setJobId(jobId);
|
||||||
|
job.setWarehouseId("03");
|
||||||
|
job.setWarehouseName("三号仓");
|
||||||
|
job.setCargoSpaceId(cargoSpaceId);
|
||||||
|
job.setStatus("1");
|
||||||
|
job.setInOrOut(20L);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DenseLanePosition source(String cargoSpaceId, String trayCode)
|
||||||
|
{
|
||||||
|
DenseLanePosition position = new DenseLanePosition();
|
||||||
|
position.setCargoSpaceId(cargoSpaceId);
|
||||||
|
position.setCargoSpaceName(cargoSpaceId);
|
||||||
|
position.setWarehouseId("03");
|
||||||
|
position.setGroupCoding("SOURCE-GROUP");
|
||||||
|
position.setTheFloor(1L);
|
||||||
|
position.setTrayCode(trayCode);
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BasCargoSpace target(String group, String cargoSpaceId)
|
||||||
|
{
|
||||||
|
BasCargoSpace target = new BasCargoSpace();
|
||||||
|
target.setCargoSpaceId(cargoSpaceId);
|
||||||
|
target.setCargoSpaceName(cargoSpaceId);
|
||||||
|
target.setWarehouseId("03");
|
||||||
|
target.setGroupCoding(group);
|
||||||
|
target.setTheFloor(1L);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MydoJobDetail detail(String stockId, String productId)
|
||||||
|
{
|
||||||
|
MydoJobDetail detail = new MydoJobDetail();
|
||||||
|
detail.setStockId(stockId);
|
||||||
|
detail.setStockinDetailId("DETAIL-" + stockId);
|
||||||
|
detail.setStockinId("IN-" + stockId);
|
||||||
|
detail.setProductId(productId);
|
||||||
|
detail.setProductName(productId);
|
||||||
|
detail.setProjectNo("PROJECT-1");
|
||||||
|
detail.setStockQuantity(BigDecimal.TEN);
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user