package com.zy.asrs.service.impl;
|
|
import com.baomidou.mybatisplus.mapper.EntityWrapper;
|
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
|
import com.core.common.Cools;
|
import com.core.exception.CoolException;
|
import com.zy.asrs.entity.BasCrnDepthRule;
|
import com.zy.asrs.entity.RowLastno;
|
import com.zy.asrs.entity.param.BasCrnDepthRuleTemplateParam;
|
import com.zy.asrs.mapper.BasCrnDepthRuleMapper;
|
import com.zy.asrs.service.BasCrnDepthRuleService;
|
import com.zy.asrs.service.RowLastnoService;
|
import com.zy.asrs.utils.Utils;
|
import com.zy.common.model.CrnDepthRuleProfile;
|
import com.zy.common.properties.SlaveProperties;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
|
@Service("basCrnDepthRuleService")
|
public class BasCrnDepthRuleServiceImpl extends ServiceImpl<BasCrnDepthRuleMapper, BasCrnDepthRule> implements BasCrnDepthRuleService {
|
|
private static final String PROFILE_SOURCE_CONFIG = "CONFIG";
|
private static final String PROFILE_SOURCE_LEGACY = "LEGACY";
|
|
@Autowired
|
private RowLastnoService rowLastnoService;
|
@Autowired
|
private SlaveProperties slaveProperties;
|
|
/**
|
* 校验并标准化单条堆垛机深浅规则。
|
*/
|
@Override
|
public void validateRule(BasCrnDepthRule rule) {
|
if (rule == null || rule.getWhsType() == null || rule.getCrnNo() == null) {
|
throw new CoolException("仓库和堆垛机不能为空");
|
}
|
if (!Integer.valueOf(1).equals(rule.getLayoutType()) && !Integer.valueOf(2).equals(rule.getLayoutType())) {
|
throw new CoolException("布局类型只允许 1=单伸 或 2=双伸");
|
}
|
List<Integer> searchRows = parseRows(rule.getSearchRowsCsv());
|
List<Integer> shallowRows = parseRows(rule.getShallowRowsCsv());
|
List<Integer> deepRows = parseRows(rule.getDeepRowsCsv());
|
if (searchRows.isEmpty()) {
|
throw new CoolException("搜索排顺序不能为空");
|
}
|
if (Integer.valueOf(1).equals(rule.getLayoutType())) {
|
if (shallowRows.isEmpty()) {
|
shallowRows = new ArrayList<Integer>(searchRows);
|
}
|
if (!deepRows.isEmpty()) {
|
throw new CoolException("单伸规则不允许填写深库位排");
|
}
|
} else {
|
if (shallowRows.isEmpty()) {
|
throw new CoolException("双伸规则必须填写浅库位排");
|
}
|
if (deepRows.isEmpty()) {
|
throw new CoolException("双伸规则必须填写深库位排");
|
}
|
}
|
rule.setSearchRowsCsv(joinRows(searchRows));
|
rule.setShallowRowsCsv(joinRows(shallowRows));
|
rule.setDeepRowsCsv(joinRows(deepRows));
|
if (rule.getEnabled() == null) {
|
rule.setEnabled(1);
|
}
|
}
|
|
/**
|
* 查询某仓库某堆垛机当前启用的深浅规则。
|
*/
|
@Override
|
public BasCrnDepthRule findEnabledRule(Integer whsType, Integer crnNo) {
|
if (whsType == null || crnNo == null) {
|
return null;
|
}
|
return this.selectOne(new EntityWrapper<BasCrnDepthRule>()
|
.eq("whs_type", whsType)
|
.eq("crn_no", crnNo)
|
.eq("enabled", 1));
|
}
|
|
/**
|
* 解析运行时使用的深浅排画像,优先走配置,缺失时回退旧逻辑。
|
*/
|
@Override
|
public CrnDepthRuleProfile resolveProfile(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow) {
|
if (rowLastno == null || crnNo == null) {
|
return new CrnDepthRuleProfile();
|
}
|
BasCrnDepthRule rule = findEnabledRule(rowLastno.getWhsType(), crnNo);
|
if (!Cools.isEmpty(rule)) {
|
return buildProfileFromRule(rule);
|
}
|
return buildLegacyProfile(rowLastno, crnNo, preferredNearRow);
|
}
|
|
/**
|
* 按旧逻辑预览一批堆垛机的默认深浅规则模板。
|
*/
|
@Override
|
public List<BasCrnDepthRule> previewTemplate(BasCrnDepthRuleTemplateParam param) {
|
if (param == null || param.getWhsType() == null) {
|
throw new CoolException("仓库不能为空");
|
}
|
RowLastno rowLastno = rowLastnoService.selectById(param.getWhsType());
|
if (Cools.isEmpty(rowLastno)) {
|
throw new CoolException("未找到仓库对应的轮询规则");
|
}
|
int startCrnNo = param.getStartCrnNo() == null ? defaultStartCrnNo(rowLastno) : param.getStartCrnNo();
|
int endCrnNo = param.getEndCrnNo() == null ? defaultEndCrnNo(rowLastno) : param.getEndCrnNo();
|
if (startCrnNo <= 0 || endCrnNo < startCrnNo) {
|
throw new CoolException("堆垛机范围错误");
|
}
|
List<BasCrnDepthRule> rules = new ArrayList<BasCrnDepthRule>();
|
for (int crnNo = startCrnNo; crnNo <= endCrnNo; crnNo++) {
|
CrnDepthRuleProfile profile = buildLegacyProfile(rowLastno, crnNo, null);
|
BasCrnDepthRule rule = new BasCrnDepthRule();
|
rule.setWhsType(param.getWhsType());
|
rule.setCrnNo(crnNo);
|
rule.setLayoutType(profile.getLayoutType());
|
rule.setSearchRowsCsv(joinRows(profile.getSearchRows()));
|
rule.setShallowRowsCsv(joinRows(profile.getShallowRows()));
|
rule.setDeepRowsCsv(joinRows(profile.getDeepRows()));
|
rule.setEnabled(param.getEnabled() == null ? 1 : param.getEnabled());
|
rule.setMemo(param.getMemo());
|
rules.add(rule);
|
}
|
return rules;
|
}
|
|
/**
|
* 按模板批量保存规则,已存在则覆盖,不存在则新增。
|
*/
|
@Override
|
public void saveTemplate(BasCrnDepthRuleTemplateParam param, Long userId) {
|
List<BasCrnDepthRule> previewRules = previewTemplate(param);
|
Date now = new Date();
|
for (BasCrnDepthRule previewRule : previewRules) {
|
validateRule(previewRule);
|
BasCrnDepthRule exists = this.selectOne(new EntityWrapper<BasCrnDepthRule>()
|
.eq("whs_type", previewRule.getWhsType())
|
.eq("crn_no", previewRule.getCrnNo()));
|
if (exists == null) {
|
previewRule.setCreateBy(userId);
|
previewRule.setCreateTime(now);
|
previewRule.setUpdateBy(userId);
|
previewRule.setUpdateTime(now);
|
this.insert(previewRule);
|
continue;
|
}
|
previewRule.setId(exists.getId());
|
previewRule.setCreateBy(exists.getCreateBy());
|
previewRule.setCreateTime(exists.getCreateTime());
|
previewRule.setUpdateBy(userId);
|
previewRule.setUpdateTime(now);
|
this.updateById(previewRule);
|
}
|
}
|
|
/**
|
* 把数据库规则转换成统一的运行时画像对象。
|
*/
|
private CrnDepthRuleProfile buildProfileFromRule(BasCrnDepthRule rule) {
|
CrnDepthRuleProfile profile = new CrnDepthRuleProfile();
|
profile.setWhsType(rule.getWhsType());
|
profile.setCrnNo(rule.getCrnNo());
|
profile.setLayoutType(rule.getLayoutType());
|
profile.setSource(PROFILE_SOURCE_CONFIG);
|
profile.setSearchRows(normalizeRows(parseRows(rule.getSearchRowsCsv())));
|
profile.setShallowRows(normalizeRows(parseRows(rule.getShallowRowsCsv())));
|
profile.setDeepRows(normalizeRows(parseRows(rule.getDeepRowsCsv())));
|
if (profile.getSearchRows().isEmpty()) {
|
if (!profile.getShallowRows().isEmpty()) {
|
profile.setSearchRows(new ArrayList<Integer>(profile.getShallowRows()));
|
} else {
|
profile.setSearchRows(new ArrayList<Integer>(profile.getDeepRows()));
|
}
|
}
|
if (profile.isSingleExtension() && profile.getShallowRows().isEmpty()) {
|
profile.setShallowRows(new ArrayList<Integer>(profile.getSearchRows()));
|
}
|
buildPairRows(profile);
|
return profile;
|
}
|
|
/**
|
* 用旧的 row_lastno + slaveProperties 规则构建运行时画像。
|
*/
|
private CrnDepthRuleProfile buildLegacyProfile(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow) {
|
CrnDepthRuleProfile profile = new CrnDepthRuleProfile();
|
profile.setWhsType(rowLastno.getWhsType());
|
profile.setCrnNo(crnNo);
|
profile.setLayoutType(resolveLegacyLayoutType(rowLastno));
|
profile.setSource(PROFILE_SOURCE_LEGACY);
|
profile.setSearchRows(getLegacySearchRows(rowLastno, crnNo, preferredNearRow));
|
if (profile.isSingleExtension()) {
|
profile.setShallowRows(new ArrayList<Integer>(profile.getSearchRows()));
|
profile.setDeepRows(new ArrayList<Integer>());
|
return profile;
|
}
|
List<Integer> shallowRows = new ArrayList<Integer>();
|
List<Integer> deepRows = new ArrayList<Integer>();
|
for (Integer searchRow : profile.getSearchRows()) {
|
if (searchRow == null) {
|
continue;
|
}
|
if (Utils.isShallowLoc(slaveProperties, searchRow)) {
|
shallowRows.add(searchRow);
|
Integer deepRow = safeGetLegacyDeepRow(rowLastno, searchRow);
|
if (deepRow != null) {
|
deepRows.add(deepRow);
|
}
|
} else {
|
deepRows.add(searchRow);
|
}
|
}
|
profile.setShallowRows(normalizeRows(shallowRows));
|
profile.setDeepRows(normalizeRows(deepRows));
|
buildPairRows(profile);
|
return profile;
|
}
|
|
/**
|
* 建立浅排与深排之间的对应关系,供双伸选位时配对使用。
|
*/
|
private void buildPairRows(CrnDepthRuleProfile profile) {
|
if (profile == null || !profile.isDoubleExtension()) {
|
return;
|
}
|
for (Integer shallowRow : profile.getShallowRows()) {
|
Integer deepRow = findNearestAdjacentRow(shallowRow, profile.getDeepRows());
|
if (deepRow == null) {
|
continue;
|
}
|
profile.getShallowToDeepRow().put(shallowRow, deepRow);
|
profile.getDeepToShallowRow().put(deepRow, shallowRow);
|
}
|
}
|
|
/**
|
* 在候选排中找到与当前排距离最近的配对排。
|
*/
|
private Integer findNearestAdjacentRow(Integer currentRow, List<Integer> candidateRows) {
|
if (currentRow == null || Cools.isEmpty(candidateRows)) {
|
return null;
|
}
|
Integer best = null;
|
int bestDistance = Integer.MAX_VALUE;
|
for (Integer candidateRow : candidateRows) {
|
if (candidateRow == null) {
|
continue;
|
}
|
int distance = Math.abs(candidateRow - currentRow);
|
if (distance < bestDistance) {
|
bestDistance = distance;
|
best = candidateRow;
|
}
|
if (distance == 1) {
|
return candidateRow;
|
}
|
}
|
return best;
|
}
|
|
/**
|
* 根据旧库型规则推断单伸/双伸布局类型。
|
*/
|
private Integer resolveLegacyLayoutType(RowLastno rowLastno) {
|
if (rowLastno == null) {
|
return 1;
|
}
|
if (rowLastno.getTypeId() != null && rowLastno.getTypeId() == 2) {
|
return 1;
|
}
|
return 2;
|
}
|
|
/**
|
* 按旧逻辑推导某台堆垛机的搜索排顺序。
|
*/
|
private List<Integer> getLegacySearchRows(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow) {
|
List<Integer> searchRows = new ArrayList<Integer>();
|
if (rowLastno == null || crnNo == null) {
|
return searchRows;
|
}
|
addRow(searchRows, preferredNearRow, rowLastno);
|
int rowSpan = getLegacyRowSpan(rowLastno.getTypeId());
|
int startCrnNo = rowLastno.getsCrnNo() == null ? 1 : rowLastno.getsCrnNo();
|
int startRow = rowLastno.getsRow() == null ? 1 : rowLastno.getsRow();
|
int crnOffset = crnNo - startCrnNo;
|
if (crnOffset < 0) {
|
return searchRows;
|
}
|
int crnStartRow = startRow + crnOffset * rowSpan;
|
if (rowLastno.getTypeId() != null && rowLastno.getTypeId() == 2) {
|
addRow(searchRows, crnStartRow, rowLastno);
|
addRow(searchRows, crnStartRow + 1, rowLastno);
|
return searchRows;
|
}
|
addRow(searchRows, crnStartRow + 1, rowLastno);
|
addRow(searchRows, crnStartRow + 2, rowLastno);
|
if (searchRows.isEmpty()) {
|
for (int row = crnStartRow; row < crnStartRow + rowSpan; row++) {
|
addRow(searchRows, row, rowLastno);
|
}
|
}
|
return searchRows;
|
}
|
|
/**
|
* 由旧浅排规则推断对应的深排。
|
*/
|
private Integer safeGetLegacyDeepRow(RowLastno rowLastno, Integer shallowRow) {
|
if (rowLastno == null || shallowRow == null) {
|
return null;
|
}
|
try {
|
String shallowLocNo = Utils.zerofill(String.valueOf(shallowRow), 2) + "00101";
|
Integer deepRow = Utils.getRow(Utils.getDeepLoc(slaveProperties, shallowLocNo));
|
if (deepRow < rowLastno.getsRow() || deepRow > rowLastno.geteRow()) {
|
return null;
|
}
|
return deepRow;
|
} catch (Exception ignored) {
|
return null;
|
}
|
}
|
|
/**
|
* 取模板生成时的默认起始堆垛机号。
|
*/
|
private Integer defaultStartCrnNo(RowLastno rowLastno) {
|
return rowLastno.getsCrnNo() == null ? 1 : rowLastno.getsCrnNo();
|
}
|
|
/**
|
* 取模板生成时的默认结束堆垛机号。
|
*/
|
private Integer defaultEndCrnNo(RowLastno rowLastno) {
|
if (rowLastno.geteCrnNo() != null && rowLastno.geteCrnNo() >= defaultStartCrnNo(rowLastno)) {
|
return rowLastno.geteCrnNo();
|
}
|
if (rowLastno.getCrnQty() != null && rowLastno.getCrnQty() > 0) {
|
return defaultStartCrnNo(rowLastno) + rowLastno.getCrnQty() - 1;
|
}
|
return defaultStartCrnNo(rowLastno);
|
}
|
|
/**
|
* 按旧库型规则返回每台堆垛机占用的排跨度。
|
*/
|
private int getLegacyRowSpan(Integer typeId) {
|
if (typeId != null && typeId == 2) {
|
return 2;
|
}
|
return 4;
|
}
|
|
/**
|
* 向搜索排列表追加一个合法且不重复的排号。
|
*/
|
private void addRow(List<Integer> searchRows, Integer row, RowLastno rowLastno) {
|
if (row == null || rowLastno == null) {
|
return;
|
}
|
if (row < rowLastno.getsRow() || row > rowLastno.geteRow()) {
|
return;
|
}
|
if (!searchRows.contains(row)) {
|
searchRows.add(row);
|
}
|
}
|
|
/**
|
* 去重并过滤非法排号。
|
*/
|
private List<Integer> normalizeRows(List<Integer> rows) {
|
LinkedHashSet<Integer> normalizedRows = new LinkedHashSet<Integer>();
|
if (Cools.isEmpty(rows)) {
|
return new ArrayList<Integer>();
|
}
|
for (Integer row : rows) {
|
if (row != null && row > 0) {
|
normalizedRows.add(row);
|
}
|
}
|
return new ArrayList<Integer>(normalizedRows);
|
}
|
|
/**
|
* 把 CSV/范围表达式解析成排号列表。
|
*/
|
private List<Integer> parseRows(String rowsCsv) {
|
List<Integer> rows = new ArrayList<Integer>();
|
if (Cools.isEmpty(rowsCsv)) {
|
return rows;
|
}
|
String normalized = rowsCsv.replace(",", ",")
|
.replace(";", ";")
|
.replace("、", ",")
|
.replaceAll("\\s+", "");
|
if (normalized.isEmpty()) {
|
return rows;
|
}
|
String[] segments = normalized.split("[,;]");
|
for (String segment : segments) {
|
if (Cools.isEmpty(segment)) {
|
continue;
|
}
|
if (segment.contains("-")) {
|
String[] rangeParts = segment.split("-", 2);
|
Integer startRow = safeParseInt(rangeParts[0]);
|
Integer endRow = safeParseInt(rangeParts[1]);
|
if (startRow == null || endRow == null) {
|
continue;
|
}
|
int step = startRow <= endRow ? 1 : -1;
|
for (int row = startRow; step > 0 ? row <= endRow : row >= endRow; row += step) {
|
rows.add(row);
|
}
|
continue;
|
}
|
Integer row = safeParseInt(segment);
|
if (row != null) {
|
rows.add(row);
|
}
|
}
|
return normalizeRows(rows);
|
}
|
|
/**
|
* 安全地把字符串转换为整数,失败时返回 null。
|
*/
|
private Integer safeParseInt(String value) {
|
if (Cools.isEmpty(value)) {
|
return null;
|
}
|
try {
|
return Integer.parseInt(value.trim());
|
} catch (NumberFormatException ignored) {
|
return null;
|
}
|
}
|
|
/**
|
* 把排号列表序列化成逗号分隔字符串。
|
*/
|
private String joinRows(List<Integer> rows) {
|
if (Cools.isEmpty(rows)) {
|
return null;
|
}
|
StringBuilder builder = new StringBuilder();
|
for (Integer row : rows) {
|
if (row == null) {
|
continue;
|
}
|
if (builder.length() > 0) {
|
builder.append(",");
|
}
|
builder.append(row);
|
}
|
return builder.length() == 0 ? null : builder.toString();
|
}
|
}
|