feat: add dense lane outbound workflow
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package com.ruoyi.out.dense.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundQuery;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundRequest;
|
||||
import com.ruoyi.out.dense.service.IDenseLaneOutboundService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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/denseLaneOutbound")
|
||||
public class DenseLaneOutboundController extends BaseController
|
||||
{
|
||||
private final IDenseLaneOutboundService service;
|
||||
|
||||
public DenseLaneOutboundController(IDenseLaneOutboundService service)
|
||||
{
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('out:denseLaneOutbound:list')")
|
||||
@GetMapping("/list")
|
||||
public AjaxResult list(DenseLaneOutboundQuery query)
|
||||
{
|
||||
return success(service.list(query));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('out:denseLaneOutbound:preview')")
|
||||
@GetMapping("/preview")
|
||||
public AjaxResult preview(DenseLaneOutboundRequest request)
|
||||
{
|
||||
return success(service.preview(request));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('out:denseLaneOutbound:generate')")
|
||||
@PostMapping("/generate")
|
||||
public AjaxResult generate(@RequestBody DenseLaneOutboundRequest request)
|
||||
{
|
||||
return success(service.generate(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.out.dense.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 指定通道出库托盘预览。
|
||||
*/
|
||||
public class DenseLaneOutboundPreview
|
||||
{
|
||||
private List<DenseLanePosition> trays = new ArrayList<>();
|
||||
private BigDecimal totalQuantity = BigDecimal.ZERO;
|
||||
private String previewToken;
|
||||
|
||||
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; }
|
||||
public String getPreviewToken() { return previewToken; }
|
||||
public void setPreviewToken(String previewToken) { this.previewToken = previewToken; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.out.dense.domain;
|
||||
|
||||
/**
|
||||
* 指定通道出库列表查询。
|
||||
*/
|
||||
public class DenseLaneOutboundQuery
|
||||
{
|
||||
private String warehouseId = "03";
|
||||
private Long theFloor;
|
||||
private String groupCoding;
|
||||
|
||||
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 getGroupCoding() { return groupCoding; }
|
||||
public void setGroupCoding(String groupCoding) { this.groupCoding = groupCoding; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.out.dense.domain;
|
||||
|
||||
/**
|
||||
* 指定通道整托出库预览/生成请求。
|
||||
*/
|
||||
public class DenseLaneOutboundRequest
|
||||
{
|
||||
private String warehouseId = "03";
|
||||
private Long theFloor;
|
||||
private String groupCoding;
|
||||
private Integer trayCount;
|
||||
private String exitId;
|
||||
private String previewToken;
|
||||
|
||||
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 getGroupCoding() { return groupCoding; }
|
||||
public void setGroupCoding(String groupCoding) { this.groupCoding = groupCoding; }
|
||||
public Integer getTrayCount() { return trayCount; }
|
||||
public void setTrayCount(Integer trayCount) { this.trayCount = trayCount; }
|
||||
public String getExitId() { return exitId; }
|
||||
public void setExitId(String exitId) { this.exitId = exitId; }
|
||||
public String getPreviewToken() { return previewToken; }
|
||||
public void setPreviewToken(String previewToken) { this.previewToken = previewToken; }
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.ruoyi.out.dense.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||
|
||||
/**
|
||||
* 密集库通道货位及其几何信息。
|
||||
@@ -22,6 +25,7 @@ public class DenseLanePosition
|
||||
private Long minColumn;
|
||||
private Long maxColumn;
|
||||
private BigDecimal stockQuantity;
|
||||
private List<MydoJobDetail> stockDetails = new ArrayList<>();
|
||||
|
||||
public String getCargoSpaceId() { return cargoSpaceId; }
|
||||
public void setCargoSpaceId(String cargoSpaceId) { this.cargoSpaceId = cargoSpaceId; }
|
||||
@@ -53,4 +57,6 @@ public class DenseLanePosition
|
||||
public void setMaxColumn(Long maxColumn) { this.maxColumn = maxColumn; }
|
||||
public BigDecimal getStockQuantity() { return stockQuantity; }
|
||||
public void setStockQuantity(BigDecimal stockQuantity) { this.stockQuantity = stockQuantity; }
|
||||
public List<MydoJobDetail> getStockDetails() { return stockDetails; }
|
||||
public void setStockDetails(List<MydoJobDetail> stockDetails) { this.stockDetails = stockDetails; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ruoyi.out.dense.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 03仓密集库通道整托库存汇总。
|
||||
*/
|
||||
public class DenseLaneSummary
|
||||
{
|
||||
private String groupCoding;
|
||||
private String cargoArea;
|
||||
private Long thePlatoon;
|
||||
private Long theFloor;
|
||||
private String directionType;
|
||||
private Integer fullTrayCount;
|
||||
private Integer availableTrayCount;
|
||||
private BigDecimal totalQuantity;
|
||||
private String outermostTrayCode;
|
||||
private String outermostCargoSpaceId;
|
||||
|
||||
public String getGroupCoding() { return groupCoding; }
|
||||
public void setGroupCoding(String groupCoding) { this.groupCoding = groupCoding; }
|
||||
public String getCargoArea() { return cargoArea; }
|
||||
public void setCargoArea(String cargoArea) { this.cargoArea = cargoArea; }
|
||||
public Long getThePlatoon() { return thePlatoon; }
|
||||
public void setThePlatoon(Long thePlatoon) { this.thePlatoon = thePlatoon; }
|
||||
public Long getTheFloor() { return theFloor; }
|
||||
public void setTheFloor(Long theFloor) { this.theFloor = theFloor; }
|
||||
public String getDirectionType() { return directionType; }
|
||||
public void setDirectionType(String directionType) { this.directionType = directionType; }
|
||||
public Integer getFullTrayCount() { return fullTrayCount; }
|
||||
public void setFullTrayCount(Integer fullTrayCount) { this.fullTrayCount = fullTrayCount; }
|
||||
public Integer getAvailableTrayCount() { return availableTrayCount; }
|
||||
public void setAvailableTrayCount(Integer availableTrayCount) { this.availableTrayCount = availableTrayCount; }
|
||||
public BigDecimal getTotalQuantity() { return totalQuantity; }
|
||||
public void setTotalQuantity(BigDecimal totalQuantity) { this.totalQuantity = totalQuantity; }
|
||||
public String getOutermostTrayCode() { return outermostTrayCode; }
|
||||
public void setOutermostTrayCode(String outermostTrayCode) { this.outermostTrayCode = outermostTrayCode; }
|
||||
public String getOutermostCargoSpaceId() { return outermostCargoSpaceId; }
|
||||
public void setOutermostCargoSpaceId(String outermostCargoSpaceId) { this.outermostCargoSpaceId = outermostCargoSpaceId; }
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import java.util.List;
|
||||
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
|
||||
import com.ruoyi.out.dense.domain.DenseLanePosition;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOption;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundQuery;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneSummary;
|
||||
import com.ruoyi.out.dense.domain.RelocationTargetQuery;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@@ -24,4 +26,6 @@ public interface DenseLaneMapper
|
||||
|
||||
List<DenseLaneOption> selectLaneOptions(@Param("warehouseId") String warehouseId,
|
||||
@Param("theFloor") Long theFloor);
|
||||
|
||||
List<DenseLaneSummary> selectLaneSummaries(DenseLaneOutboundQuery query);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.out.dense.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundPreview;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundQuery;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundRequest;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneSummary;
|
||||
|
||||
/**
|
||||
* 指定密集库通道整托出库服务。
|
||||
*/
|
||||
public interface IDenseLaneOutboundService
|
||||
{
|
||||
List<DenseLaneSummary> list(DenseLaneOutboundQuery query);
|
||||
|
||||
DenseLaneOutboundPreview preview(DenseLaneOutboundRequest request);
|
||||
|
||||
List<String> generate(DenseLaneOutboundRequest request);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
package com.ruoyi.out.dense.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||
import com.ruoyi.bas.stationpoint.domain.BasStationPoint;
|
||||
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockMapper;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundPreview;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundQuery;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundRequest;
|
||||
import com.ruoyi.out.dense.domain.DenseLanePosition;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneSummary;
|
||||
import com.ruoyi.out.dense.mapper.DenseLaneMapper;
|
||||
import com.ruoyi.out.dense.service.IDenseLaneOutboundService;
|
||||
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.location.mapper.LocationOutboundMapper;
|
||||
import com.ruoyi.out.relocation.domain.RelocationGenerateRequest;
|
||||
import com.ruoyi.out.relocation.mapper.RelocationJobMapper;
|
||||
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 DenseLaneOutboundServiceImpl implements IDenseLaneOutboundService
|
||||
{
|
||||
private final DenseLaneMapper denseLaneMapper;
|
||||
private final IDenseLaneService denseLaneService;
|
||||
private final RelocationJobMapper relocationJobMapper;
|
||||
private final IBasStationPointService stationPointService;
|
||||
private final IMydoJobService mydoJobService;
|
||||
private final IMydoJobDetailService mydoJobDetailService;
|
||||
private final LocationOutboundMapper locationOutboundMapper;
|
||||
private final BasStockMapper basStockMapper;
|
||||
private final BasCargoSpaceMapper cargoSpaceMapper;
|
||||
private final JobIdService jobIdService;
|
||||
private final TaskNoService taskNoService;
|
||||
private final IOutboundDispatchService dispatchService;
|
||||
|
||||
public DenseLaneOutboundServiceImpl(DenseLaneMapper denseLaneMapper,
|
||||
IDenseLaneService denseLaneService,
|
||||
RelocationJobMapper relocationJobMapper,
|
||||
IBasStationPointService stationPointService,
|
||||
IMydoJobService mydoJobService,
|
||||
IMydoJobDetailService mydoJobDetailService,
|
||||
LocationOutboundMapper locationOutboundMapper,
|
||||
BasStockMapper basStockMapper,
|
||||
BasCargoSpaceMapper cargoSpaceMapper,
|
||||
JobIdService jobIdService,
|
||||
TaskNoService taskNoService,
|
||||
IOutboundDispatchService dispatchService)
|
||||
{
|
||||
this.denseLaneMapper = denseLaneMapper;
|
||||
this.denseLaneService = denseLaneService;
|
||||
this.relocationJobMapper = relocationJobMapper;
|
||||
this.stationPointService = stationPointService;
|
||||
this.mydoJobService = mydoJobService;
|
||||
this.mydoJobDetailService = mydoJobDetailService;
|
||||
this.locationOutboundMapper = locationOutboundMapper;
|
||||
this.basStockMapper = basStockMapper;
|
||||
this.cargoSpaceMapper = cargoSpaceMapper;
|
||||
this.jobIdService = jobIdService;
|
||||
this.taskNoService = taskNoService;
|
||||
this.dispatchService = dispatchService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DenseLaneSummary> list(DenseLaneOutboundQuery query)
|
||||
{
|
||||
if (query == null) {
|
||||
query = new DenseLaneOutboundQuery();
|
||||
}
|
||||
if (!"03".equals(query.getWarehouseId())) {
|
||||
throw new RuntimeException("指定通道出库仅支持03仓密集库");
|
||||
}
|
||||
List<DenseLaneSummary> summaries = denseLaneMapper.selectLaneSummaries(query);
|
||||
return summaries == null ? new ArrayList<>() : summaries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DenseLaneOutboundPreview preview(DenseLaneOutboundRequest request)
|
||||
{
|
||||
validateRequest(request, false);
|
||||
return buildPreview(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<String> generate(DenseLaneOutboundRequest request)
|
||||
{
|
||||
validateRequest(request, true);
|
||||
DenseLaneOutboundPreview current = buildPreview(request);
|
||||
if (!request.getPreviewToken().equals(current.getPreviewToken())) {
|
||||
throw new RuntimeException("预览已失效,请刷新后重新确认");
|
||||
}
|
||||
BasStationPoint exit = validateExit(request.getExitId(), current.getTrays());
|
||||
List<String> jobIds = new ArrayList<>();
|
||||
for (DenseLanePosition position : current.getTrays()) {
|
||||
List<MydoJobDetail> stockDetails = relocationJobMapper.selectStockDetailsForUpdate(
|
||||
position.getCargoSpaceId(), position.getTrayCode());
|
||||
if (stockDetails == null || stockDetails.isEmpty()) {
|
||||
throw new RuntimeException("托盘" + position.getTrayCode() + "没有可出库库存明细");
|
||||
}
|
||||
String jobId = jobIdService.getId("CK");
|
||||
if (!StringUtils.hasText(jobId)) {
|
||||
throw new RuntimeException("指定通道出库作业ID生成失败");
|
||||
}
|
||||
MydoJob job = buildJob(jobId, taskNoService.getTaskNo("20"), position, exit);
|
||||
if (mydoJobService.insertMydoJob(job) != 1) {
|
||||
throw new RuntimeException("指定通道出库作业生成失败");
|
||||
}
|
||||
int index = 1;
|
||||
for (MydoJobDetail template : stockDetails) {
|
||||
MydoJobDetail detail = buildDetail(template, jobId, index++);
|
||||
if (mydoJobDetailService.insertMydoJobDetail(detail) != 1) {
|
||||
throw new RuntimeException("指定通道出库作业明细生成失败");
|
||||
}
|
||||
HashMap<String, Object> reserve = new HashMap<>();
|
||||
reserve.put("stockDetailId", template.getStockinDetailId());
|
||||
reserve.put("stockId", template.getStockId());
|
||||
reserve.put("jobDetailId", detail.getJobDetailId());
|
||||
reserve.put("quantity", detail.getOutQuantity());
|
||||
if (locationOutboundMapper.reserveStockDetailForOutbound(reserve) != 1) {
|
||||
throw new RuntimeException("托盘库存状态已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
stockDetails.stream().map(MydoJobDetail::getStockId).filter(StringUtils::hasText)
|
||||
.distinct().forEach(basStockMapper::refreshBasStockSummary);
|
||||
if (cargoSpaceMapper.markOutboundAssignedByCargoSpaceId(position.getCargoSpaceId()) != 1) {
|
||||
throw new RuntimeException("端头货位状态已变化,请刷新后重试");
|
||||
}
|
||||
jobIds.add(jobId);
|
||||
}
|
||||
afterCommit(() -> jobIds.forEach(id -> dispatchService.dispatch(id, DispatchTrigger.INITIAL)));
|
||||
return jobIds;
|
||||
}
|
||||
|
||||
private DenseLaneOutboundPreview buildPreview(DenseLaneOutboundRequest request)
|
||||
{
|
||||
RelocationGenerateRequest sourceQuery = new RelocationGenerateRequest();
|
||||
sourceQuery.setWarehouseId(request.getWarehouseId());
|
||||
sourceQuery.setTheFloor(request.getTheFloor());
|
||||
sourceQuery.setSourceGroupCoding(request.getGroupCoding());
|
||||
sourceQuery.setTrayCount(request.getTrayCount());
|
||||
List<DenseLanePosition> available = relocationJobMapper.selectRelocationSourcesForUpdate(sourceQuery);
|
||||
if (available == null || available.size() < request.getTrayCount()) {
|
||||
throw new RuntimeException("当前通道可出库托盘数量不足");
|
||||
}
|
||||
List<DenseLanePosition> selected = denseLaneService.orderOutermostFirst(available)
|
||||
.stream().limit(request.getTrayCount()).collect(Collectors.toList());
|
||||
selected.forEach(position -> {
|
||||
List<MydoJobDetail> details = relocationJobMapper.selectStockDetailsForUpdate(
|
||||
position.getCargoSpaceId(), position.getTrayCode());
|
||||
position.setStockDetails(details == null ? new ArrayList<>() : details);
|
||||
});
|
||||
String[] trayCodes = selected.stream().map(DenseLanePosition::getTrayCode).toArray(String[]::new);
|
||||
BigDecimal total = relocationJobMapper.sumTrayStockQuantity(trayCodes);
|
||||
DenseLaneOutboundPreview preview = new DenseLaneOutboundPreview();
|
||||
preview.setTrays(selected);
|
||||
preview.setTotalQuantity(total == null ? BigDecimal.ZERO : total);
|
||||
preview.setPreviewToken(previewToken(request, selected));
|
||||
return preview;
|
||||
}
|
||||
|
||||
private void validateRequest(DenseLaneOutboundRequest request, boolean requireToken)
|
||||
{
|
||||
if (request == null || !"03".equals(request.getWarehouseId())) {
|
||||
throw new RuntimeException("指定通道出库仅支持03仓密集库");
|
||||
}
|
||||
if (request.getTheFloor() == null || !StringUtils.hasText(request.getGroupCoding())) {
|
||||
throw new RuntimeException("请选择密集库层和通道");
|
||||
}
|
||||
if (request.getTrayCount() == null || request.getTrayCount() <= 0) {
|
||||
throw new RuntimeException("出库托盘数量必须大于0");
|
||||
}
|
||||
if (requireToken && !StringUtils.hasText(request.getPreviewToken())) {
|
||||
throw new RuntimeException("请先预览要出库的端头托盘");
|
||||
}
|
||||
}
|
||||
|
||||
private BasStationPoint validateExit(String exitId, List<DenseLanePosition> positions)
|
||||
{
|
||||
if (!StringUtils.hasText(exitId)) {
|
||||
throw new RuntimeException("请选择出库口");
|
||||
}
|
||||
BasStationPoint point = stationPointService.selectBasStationPointByPointId(exitId);
|
||||
if (point == null || !"03".equals(point.getWarehouseId())) {
|
||||
throw new RuntimeException("出库口不存在或不属于03仓");
|
||||
}
|
||||
if ((!"2".equals(point.getPointType()) && !"3".equals(point.getPointType()))
|
||||
|| !"0".equals(point.getStatus())
|
||||
|| (!StringUtils.isEmpty(point.getPointStatus()) && !"0".equals(point.getPointStatus()))) {
|
||||
throw new RuntimeException("当前出库口不可用");
|
||||
}
|
||||
for (DenseLanePosition position : positions) {
|
||||
String trayType = trayType(position.getTrayCode());
|
||||
if (StringUtils.hasText(trayType) && StringUtils.hasText(point.getTrayType())
|
||||
&& !supportsTrayType(point.getTrayType(), trayType)) {
|
||||
throw new RuntimeException("出库口不支持托盘类型" + trayType);
|
||||
}
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
private MydoJob buildJob(String jobId, String taskNo, DenseLanePosition position,
|
||||
BasStationPoint exit)
|
||||
{
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId(jobId);
|
||||
job.setTaskNo(StringUtils.hasText(taskNo) ? taskNo : jobId);
|
||||
job.setInOrOut(20L);
|
||||
job.setStatus("1");
|
||||
job.setExecutionPhase("0");
|
||||
job.setTrayCode(position.getTrayCode());
|
||||
job.setWarehouseId("03");
|
||||
job.setCargoSpaceId(position.getCargoSpaceId());
|
||||
job.setCargoSpaceName(position.getCargoSpaceName());
|
||||
job.setExitId(exit.getPointId());
|
||||
job.setLineType("1");
|
||||
job.setPriority(10L);
|
||||
job.setCreateTime(DateUtils.getNowDate());
|
||||
return job;
|
||||
}
|
||||
|
||||
private MydoJobDetail buildDetail(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.setProductId(source.getProductId());
|
||||
detail.setProductName(source.getProductName());
|
||||
detail.setStockQuantity(source.getStockQuantity());
|
||||
detail.setOutQuantity(source.getStockQuantity());
|
||||
detail.setStockinId(source.getStockinId());
|
||||
detail.setStockinDetailId(source.getStockinDetailId());
|
||||
detail.setSupplierId(source.getSupplierId());
|
||||
detail.setSupplierName(source.getSupplierName());
|
||||
detail.setProjectNo(source.getProjectNo());
|
||||
return detail;
|
||||
}
|
||||
|
||||
private String previewToken(DenseLaneOutboundRequest request, List<DenseLanePosition> selected)
|
||||
{
|
||||
String payload = request.getWarehouseId() + "|" + request.getTheFloor() + "|"
|
||||
+ request.getGroupCoding() + "|" + request.getTrayCount() + "|"
|
||||
+ selected.stream().map(item -> item.getCargoSpaceId() + ":" + item.getTrayCode()
|
||||
+ "[" + item.getStockDetails().stream()
|
||||
.map(detail -> String.valueOf(detail.getStockinDetailId()) + ":"
|
||||
+ String.valueOf(detail.getStockId()) + ":"
|
||||
+ String.valueOf(detail.getStockQuantity()))
|
||||
.sorted()
|
||||
.collect(Collectors.joining(";")) + "]")
|
||||
.collect(Collectors.joining(","));
|
||||
try {
|
||||
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
||||
.digest(payload.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("生成通道出库预览签名失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String trayType(String trayCode)
|
||||
{
|
||||
return StringUtils.hasText(trayCode) ? trayCode.trim().substring(0, 1).toUpperCase() : null;
|
||||
}
|
||||
|
||||
private boolean supportsTrayType(String supported, String trayType)
|
||||
{
|
||||
for (String item : supported.split(",")) {
|
||||
if (trayType.equalsIgnoreCase(item.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void afterCommit(Runnable action)
|
||||
{
|
||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
action.run();
|
||||
return;
|
||||
}
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization()
|
||||
{
|
||||
@Override
|
||||
public void afterCommit()
|
||||
{
|
||||
action.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -185,6 +185,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
on area_config.warehouse_id = target_space.warehouse_id
|
||||
and area_config.cargo_area_code = target_space.this_cargo_area
|
||||
and area_config.is_active = '1'
|
||||
cross apply
|
||||
(
|
||||
select min(lane_boundary.the_column) as min_column,
|
||||
max(lane_boundary.the_column) as max_column
|
||||
from bas_cargo_space lane_boundary with (UPDLOCK,HOLDLOCK)
|
||||
where lane_boundary.warehouse_id = target_space.warehouse_id
|
||||
and lane_boundary.group_coding = target_space.group_coding
|
||||
and lane_boundary.the_floor = target_space.the_floor
|
||||
) boundary
|
||||
cross apply
|
||||
(
|
||||
select case when target_space.the_column - boundary.min_column
|
||||
<= boundary.max_column - target_space.the_column
|
||||
then target_space.the_column - boundary.min_column
|
||||
else boundary.max_column - target_space.the_column end as distance_to_edge
|
||||
) geometry
|
||||
where target_space.warehouse_id = #{warehouseId}
|
||||
and target_space.the_floor = #{theFloor}
|
||||
and target_space.group_coding != #{sourceGroupCoding}
|
||||
@@ -214,7 +230,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
case when area_config.direction_type = '2'
|
||||
then target_space.the_column end desc,
|
||||
case when area_config.direction_type = '3'
|
||||
then abs(target_space.the_column - 1) end asc,
|
||||
then geometry.distance_to_edge end asc,
|
||||
target_space.group_coding asc,
|
||||
target_space.cargo_space_id asc
|
||||
</select>
|
||||
@@ -266,6 +282,108 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
order by lane.group_coding
|
||||
</select>
|
||||
|
||||
<select id="selectLaneSummaries" resultType="com.ruoyi.out.dense.domain.DenseLaneSummary">
|
||||
;with lane_positions as
|
||||
(
|
||||
select cargo.cargo_space_id,
|
||||
cargo.warehouse_id,
|
||||
cargo.group_coding,
|
||||
cargo.this_cargo_area,
|
||||
cargo.the_platoon,
|
||||
cargo.the_column,
|
||||
cargo.the_floor,
|
||||
cargo.status,
|
||||
cargo.tray_code,
|
||||
isnull(config.direction_type, '1') as direction_type,
|
||||
min(cargo.the_column) over
|
||||
(partition by cargo.group_coding, cargo.the_floor) as min_column,
|
||||
max(cargo.the_column) over
|
||||
(partition by cargo.group_coding, cargo.the_floor) as max_column,
|
||||
case when exists
|
||||
(
|
||||
select 1 from mydo_job active_job
|
||||
where active_job.status in ('1', '2', '3')
|
||||
and active_job.in_or_out >= 20
|
||||
and (active_job.cargo_space_id = cargo.cargo_space_id
|
||||
or active_job.tray_code = cargo.tray_code)
|
||||
) then 1 else 0 end as has_active_job
|
||||
from bas_cargo_space cargo
|
||||
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'
|
||||
where cargo.warehouse_id = #{warehouseId}
|
||||
and cargo.group_coding is not null
|
||||
and ltrim(rtrim(cargo.group_coding)) != ''
|
||||
<if test="theFloor != null">
|
||||
and cargo.the_floor = #{theFloor}
|
||||
</if>
|
||||
<if test="groupCoding != null and groupCoding != ''">
|
||||
and cargo.group_coding like concat('%', #{groupCoding}, '%')
|
||||
</if>
|
||||
), lane_totals as
|
||||
(
|
||||
select group_coding,
|
||||
the_floor,
|
||||
min(this_cargo_area) as cargo_area,
|
||||
min(the_platoon) as the_platoon,
|
||||
min(direction_type) as direction_type,
|
||||
sum(case when status = '2' and tray_code is not null then 1 else 0 end) as full_tray_count,
|
||||
sum(case when status = '2' and tray_code is not null
|
||||
and has_active_job = 0 then 1 else 0 end) as available_tray_count
|
||||
from lane_positions
|
||||
group by group_coding, the_floor
|
||||
), stock_totals as
|
||||
(
|
||||
select position.group_coding,
|
||||
position.the_floor,
|
||||
sum(isnull(detail.quantity, 0)) as total_quantity
|
||||
from lane_positions position
|
||||
inner join bas_stock stock on stock.cargo_space_id = position.cargo_space_id
|
||||
inner join bas_stock_detail detail on detail.stock_id = stock.stock_id
|
||||
group by position.group_coding, position.the_floor
|
||||
)
|
||||
select totals.group_coding,
|
||||
totals.cargo_area,
|
||||
totals.the_platoon,
|
||||
totals.the_floor,
|
||||
totals.direction_type,
|
||||
totals.full_tray_count,
|
||||
totals.available_tray_count,
|
||||
isnull(stock_totals.total_quantity, 0) as total_quantity,
|
||||
outer_position.tray_code as outermost_tray_code,
|
||||
outer_position.cargo_space_id as outermost_cargo_space_id
|
||||
from lane_totals totals
|
||||
left join stock_totals
|
||||
on stock_totals.group_coding = totals.group_coding
|
||||
and stock_totals.the_floor = totals.the_floor
|
||||
outer apply
|
||||
(
|
||||
select top 1 position.tray_code,
|
||||
position.cargo_space_id,
|
||||
case when position.the_column - position.min_column
|
||||
<= position.max_column - position.the_column
|
||||
then position.the_column - position.min_column
|
||||
else position.max_column - position.the_column end as distance_to_edge
|
||||
from lane_positions position
|
||||
where position.group_coding = totals.group_coding
|
||||
and position.the_floor = totals.the_floor
|
||||
and position.status = '2'
|
||||
and position.tray_code is not null
|
||||
and position.has_active_job = 0
|
||||
order by case when totals.direction_type = '1' then position.the_column end desc,
|
||||
case when totals.direction_type = '2' then position.the_column end asc,
|
||||
case when totals.direction_type = '3' then
|
||||
case when position.the_column - position.min_column
|
||||
<= position.max_column - position.the_column
|
||||
then position.the_column - position.min_column
|
||||
else position.max_column - position.the_column end end asc,
|
||||
position.the_column asc
|
||||
) outer_position
|
||||
where totals.full_tray_count > 0
|
||||
order by totals.the_floor, totals.group_coding
|
||||
</select>
|
||||
|
||||
<select id="isCurrentOutermost" resultType="java.lang.Integer">
|
||||
select case
|
||||
when source_space.warehouse_id != '03'
|
||||
|
||||
@@ -31,6 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where stock.cargo_space_id = #{cargoSpaceId}
|
||||
and stock.tray_code = #{trayCode}
|
||||
and isnull(detail.quantity, 0) > 0
|
||||
order by detail.detail_id
|
||||
</select>
|
||||
|
||||
<select id="selectRelocationSourcesForUpdate"
|
||||
|
||||
@@ -63,6 +63,25 @@ class DenseLaneMapperSqlTest
|
||||
.contains("not exists");
|
||||
}
|
||||
|
||||
@Test
|
||||
void directionThreeOrderingUsesActualLaneBoundaries() throws Exception
|
||||
{
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String summarySql = section(xml, "<select id=\"selectLaneSummaries\"", "</select>");
|
||||
String targetSql = section(xml, "<select id=\"selectRelocationTargetForUpdate\"", "</select>");
|
||||
|
||||
assertThat(summarySql)
|
||||
.contains("min(cargo.the_column) over")
|
||||
.contains("max(cargo.the_column) over")
|
||||
.contains("distance_to_edge")
|
||||
.doesNotContain("abs(position.the_column - 1)");
|
||||
assertThat(targetSql)
|
||||
.contains("min(lane_boundary.the_column)")
|
||||
.contains("max(lane_boundary.the_column)")
|
||||
.contains("distance_to_edge")
|
||||
.doesNotContain("abs(target_space.the_column - 1)");
|
||||
}
|
||||
|
||||
private Path resolveMapperXml()
|
||||
{
|
||||
return Stream.of(
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
package com.ruoyi.out.dense.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||
import com.ruoyi.bas.stationpoint.domain.BasStationPoint;
|
||||
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockMapper;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundPreview;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundQuery;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneOutboundRequest;
|
||||
import com.ruoyi.out.dense.domain.DenseLanePosition;
|
||||
import com.ruoyi.out.dense.domain.DenseLaneSummary;
|
||||
import com.ruoyi.out.dense.mapper.DenseLaneMapper;
|
||||
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.location.mapper.LocationOutboundMapper;
|
||||
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.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class DenseLaneOutboundServiceImplTest
|
||||
{
|
||||
private DenseLaneMapper denseLaneMapper;
|
||||
private IDenseLaneService denseLaneService;
|
||||
private RelocationJobMapper relocationJobMapper;
|
||||
private IBasStationPointService stationPointService;
|
||||
private IMydoJobService mydoJobService;
|
||||
private IMydoJobDetailService mydoJobDetailService;
|
||||
private LocationOutboundMapper locationOutboundMapper;
|
||||
private BasStockMapper basStockMapper;
|
||||
private BasCargoSpaceMapper cargoSpaceMapper;
|
||||
private JobIdService jobIdService;
|
||||
private TaskNoService taskNoService;
|
||||
private IOutboundDispatchService dispatchService;
|
||||
private DenseLaneOutboundServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp()
|
||||
{
|
||||
denseLaneMapper = mock(DenseLaneMapper.class);
|
||||
denseLaneService = mock(IDenseLaneService.class);
|
||||
relocationJobMapper = mock(RelocationJobMapper.class);
|
||||
stationPointService = mock(IBasStationPointService.class);
|
||||
mydoJobService = mock(IMydoJobService.class);
|
||||
mydoJobDetailService = mock(IMydoJobDetailService.class);
|
||||
locationOutboundMapper = mock(LocationOutboundMapper.class);
|
||||
basStockMapper = mock(BasStockMapper.class);
|
||||
cargoSpaceMapper = mock(BasCargoSpaceMapper.class);
|
||||
jobIdService = mock(JobIdService.class);
|
||||
taskNoService = mock(TaskNoService.class);
|
||||
dispatchService = mock(IOutboundDispatchService.class);
|
||||
service = new DenseLaneOutboundServiceImpl(denseLaneMapper, denseLaneService,
|
||||
relocationJobMapper, stationPointService, mydoJobService, mydoJobDetailService,
|
||||
locationOutboundMapper, basStockMapper, cargoSpaceMapper, jobIdService,
|
||||
taskNoService, dispatchService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listOnlyAcceptsWarehouse03DenseLanes()
|
||||
{
|
||||
DenseLaneOutboundQuery query = new DenseLaneOutboundQuery();
|
||||
query.setWarehouseId("02");
|
||||
|
||||
assertThatThrownBy(() -> service.list(query)).hasMessageContaining("03仓");
|
||||
}
|
||||
|
||||
@Test
|
||||
void listReturnsFullLaneTotalsAndCurrentOuterTray()
|
||||
{
|
||||
DenseLaneOutboundQuery query = new DenseLaneOutboundQuery();
|
||||
query.setWarehouseId("03");
|
||||
DenseLaneSummary summary = new DenseLaneSummary();
|
||||
summary.setGroupCoding("G-01");
|
||||
summary.setFullTrayCount(3);
|
||||
summary.setAvailableTrayCount(2);
|
||||
summary.setTotalQuantity(new BigDecimal("40"));
|
||||
summary.setOutermostTrayCode("A001");
|
||||
when(denseLaneMapper.selectLaneSummaries(query)).thenReturn(List.of(summary));
|
||||
|
||||
assertThat(service.list(query)).containsExactly(summary);
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewSelectsExactlyOutermostNUnreservedTrays()
|
||||
{
|
||||
DenseLaneOutboundRequest request = request(2);
|
||||
List<DenseLanePosition> positions = List.of(position("CS-2", 2L, "A002"),
|
||||
position("CS-3", 3L, "A003"), position("CS-1", 1L, "A001"));
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(positions);
|
||||
when(denseLaneService.orderOutermostFirst(positions))
|
||||
.thenReturn(List.of(positions.get(1), positions.get(0), positions.get(2)));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class)))
|
||||
.thenReturn(new BigDecimal("25"));
|
||||
|
||||
DenseLaneOutboundPreview preview = service.preview(request);
|
||||
|
||||
assertThat(preview.getTrays()).extracting(DenseLanePosition::getTrayCode)
|
||||
.containsExactly("A003", "A002");
|
||||
assertThat(preview.getTotalQuantity()).isEqualByComparingTo("25");
|
||||
assertThat(preview.getPreviewToken()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewIncludesCompleteInventoryDetailsForEverySelectedTray()
|
||||
{
|
||||
DenseLaneOutboundRequest request = request(1);
|
||||
DenseLanePosition position = position("CS-1", 1L, "A001");
|
||||
List<MydoJobDetail> details = List.of(
|
||||
detail("STOCK-1", "D-1", "P-1", "7"),
|
||||
detail("STOCK-1", "D-2", "P-2", "5"));
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(position));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenReturn(List.of(position));
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-1", "A001")).thenReturn(details);
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class)))
|
||||
.thenReturn(new BigDecimal("12"));
|
||||
|
||||
DenseLaneOutboundPreview preview = service.preview(request);
|
||||
|
||||
assertThat(preview.getTrays()).singleElement().satisfies(tray ->
|
||||
assertThat(tray.getStockDetails()).containsExactlyElementsOf(details));
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewRejectsQuantityOverAvailableCapacity()
|
||||
{
|
||||
DenseLaneOutboundRequest request = request(2);
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any()))
|
||||
.thenReturn(List.of(position("CS-1", 1L, "A001")));
|
||||
|
||||
assertThatThrownBy(() -> service.preview(request)).hasMessageContaining("可出库托盘数量不足");
|
||||
}
|
||||
|
||||
@Test
|
||||
void previewTokenChangesWhenSelectedTrayInventoryChanges()
|
||||
{
|
||||
DenseLaneOutboundRequest request = request(1);
|
||||
DenseLanePosition position = position("CS-1", 1L, "A001");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(position));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenReturn(List.of(position));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class))).thenReturn(BigDecimal.TEN);
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-1", "A001"))
|
||||
.thenReturn(List.of(detail("STOCK-1", "D-1", "P-1", "10")))
|
||||
.thenReturn(List.of(detail("STOCK-1", "D-1", "P-1", "9")));
|
||||
|
||||
String firstToken = service.preview(request).getPreviewToken();
|
||||
String changedToken = service.preview(request).getPreviewToken();
|
||||
|
||||
assertThat(changedToken).isNotEqualTo(firstToken);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generationRejectsStalePreview()
|
||||
{
|
||||
DenseLaneOutboundRequest request = request(1);
|
||||
DenseLanePosition first = position("CS-1", 1L, "A001");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(first));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class))).thenReturn(BigDecimal.TEN);
|
||||
request.setPreviewToken(service.preview(request).getPreviewToken());
|
||||
DenseLanePosition changed = position("CS-2", 2L, "A002");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(changed));
|
||||
|
||||
assertThatThrownBy(() -> service.generate(request)).hasMessageContaining("预览已失效");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generationValidatesExitAndCreatesCompleteType20Jobs()
|
||||
{
|
||||
DenseLaneOutboundRequest request = request(1);
|
||||
DenseLanePosition position = position("CS-1", 1L, "A001");
|
||||
when(relocationJobMapper.selectRelocationSourcesForUpdate(any())).thenReturn(List.of(position));
|
||||
when(denseLaneService.orderOutermostFirst(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(relocationJobMapper.sumTrayStockQuantity(any(String[].class))).thenReturn(new BigDecimal("12"));
|
||||
List<MydoJobDetail> stockDetails = List.of(detail("STOCK-1", "D-1", "P-1", "7"),
|
||||
detail("STOCK-1", "D-2", "P-2", "5"));
|
||||
when(relocationJobMapper.selectStockDetailsForUpdate("CS-1", "A001"))
|
||||
.thenReturn(stockDetails);
|
||||
request.setPreviewToken(service.preview(request).getPreviewToken());
|
||||
BasStationPoint exit = new BasStationPoint();
|
||||
exit.setPointId("OUT-1");
|
||||
exit.setWarehouseId("03");
|
||||
exit.setPointType("2");
|
||||
exit.setStatus("0");
|
||||
exit.setPointStatus("0");
|
||||
exit.setTrayType("A,B");
|
||||
when(stationPointService.selectBasStationPointByPointId("OUT-1")).thenReturn(exit);
|
||||
when(jobIdService.getId("CK")).thenReturn("OUT-JOB-1");
|
||||
when(taskNoService.getTaskNo("20")).thenReturn("TASK-20-1");
|
||||
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(mydoJobDetailService.insertMydoJobDetail(any(MydoJobDetail.class))).thenReturn(1);
|
||||
when(locationOutboundMapper.reserveStockDetailForOutbound(any())).thenReturn(1);
|
||||
when(cargoSpaceMapper.markOutboundAssignedByCargoSpaceId("CS-1")).thenReturn(1);
|
||||
when(dispatchService.dispatch("OUT-JOB-1", DispatchTrigger.INITIAL))
|
||||
.thenReturn(AjaxResult.success());
|
||||
|
||||
List<String> jobIds = service.generate(request);
|
||||
|
||||
assertThat(jobIds).containsExactly("OUT-JOB-1");
|
||||
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
|
||||
assertThat(jobCaptor.getValue().getInOrOut()).isEqualTo(20L);
|
||||
assertThat(jobCaptor.getValue().getTrayCode()).isEqualTo("A001");
|
||||
verify(mydoJobDetailService, org.mockito.Mockito.times(2))
|
||||
.insertMydoJobDetail(any(MydoJobDetail.class));
|
||||
verify(dispatchService).dispatch("OUT-JOB-1", DispatchTrigger.INITIAL);
|
||||
}
|
||||
|
||||
private DenseLaneOutboundRequest request(int count)
|
||||
{
|
||||
DenseLaneOutboundRequest request = new DenseLaneOutboundRequest();
|
||||
request.setWarehouseId("03");
|
||||
request.setTheFloor(1L);
|
||||
request.setGroupCoding("G-01");
|
||||
request.setTrayCount(count);
|
||||
request.setExitId("OUT-1");
|
||||
return request;
|
||||
}
|
||||
|
||||
private DenseLanePosition position(String cargoSpaceId, Long column, String trayCode)
|
||||
{
|
||||
DenseLanePosition position = new DenseLanePosition();
|
||||
position.setCargoSpaceId(cargoSpaceId);
|
||||
position.setCargoSpaceName(cargoSpaceId);
|
||||
position.setWarehouseId("03");
|
||||
position.setGroupCoding("G-01");
|
||||
position.setTheFloor(1L);
|
||||
position.setTheColumn(column);
|
||||
position.setTrayCode(trayCode);
|
||||
position.setStockQuantity(BigDecimal.TEN);
|
||||
return position;
|
||||
}
|
||||
|
||||
private MydoJobDetail detail(String stockId, String detailId, String productId, String quantity)
|
||||
{
|
||||
MydoJobDetail detail = new MydoJobDetail();
|
||||
detail.setStockId(stockId);
|
||||
detail.setStockinDetailId(detailId);
|
||||
detail.setProductId(productId);
|
||||
detail.setProductName(productId);
|
||||
detail.setStockQuantity(new BigDecimal(quantity));
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user