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 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 searchRows = parseRows(rule.getSearchRowsCsv()); List shallowRows = parseRows(rule.getShallowRowsCsv()); List deepRows = parseRows(rule.getDeepRowsCsv()); if (searchRows.isEmpty()) { throw new CoolException("搜索排顺序不能为空"); } if (Integer.valueOf(1).equals(rule.getLayoutType())) { if (shallowRows.isEmpty()) { shallowRows = new ArrayList(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() .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 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 rules = new ArrayList(); 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 previewRules = previewTemplate(param); Date now = new Date(); for (BasCrnDepthRule previewRule : previewRules) { validateRule(previewRule); BasCrnDepthRule exists = this.selectOne(new EntityWrapper() .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(profile.getShallowRows())); } else { profile.setSearchRows(new ArrayList(profile.getDeepRows())); } } if (profile.isSingleExtension() && profile.getShallowRows().isEmpty()) { profile.setShallowRows(new ArrayList(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(profile.getSearchRows())); profile.setDeepRows(new ArrayList()); return profile; } List shallowRows = new ArrayList(); List deepRows = new ArrayList(); 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 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 getLegacySearchRows(RowLastno rowLastno, Integer crnNo, Integer preferredNearRow) { List searchRows = new ArrayList(); 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 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 normalizeRows(List rows) { LinkedHashSet normalizedRows = new LinkedHashSet(); if (Cools.isEmpty(rows)) { return new ArrayList(); } for (Integer row : rows) { if (row != null && row > 0) { normalizedRows.add(row); } } return new ArrayList(normalizedRows); } /** * 把 CSV/范围表达式解析成排号列表。 */ private List parseRows(String rowsCsv) { List rows = new ArrayList(); 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 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(); } }