package com.vincent.rsf.server.manager.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.vincent.rsf.framework.exception.CoolException; import com.vincent.rsf.server.api.controller.erp.params.TaskInParam; import com.vincent.rsf.server.api.entity.dto.InTaskMsgDto; import com.vincent.rsf.server.api.service.WcsService; import com.vincent.rsf.server.api.utils.LocUtils; import com.vincent.rsf.server.common.constant.Constants; import com.vincent.rsf.server.common.utils.QuantityUtils; import com.vincent.rsf.server.manager.controller.params.CheckLocQueryParams; import com.vincent.rsf.server.manager.controller.params.LocToTaskParams; import com.vincent.rsf.server.manager.entity.*; import com.vincent.rsf.server.manager.enums.*; import com.vincent.rsf.server.manager.mapper.LocItemMapper; import com.vincent.rsf.server.manager.service.*; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.vincent.rsf.server.system.constant.GlobalConfigCode; import com.vincent.rsf.server.system.constant.SerialRuleCode; import com.vincent.rsf.server.system.service.ConfigService; import com.vincent.rsf.server.system.utils.SerialRuleUtils; import lombok.Synchronized; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; @Service("locItemService") public class LocItemServiceImpl extends ServiceImpl implements LocItemService { private static final BigDecimal FULL_OUT_QTY_TOLERANCE = new BigDecimal("0.000001"); /** 可与 {@link TaskStsType#GENERATE_OUT}~{@link TaskStsType#WAVE_SEED} 衔接追加明细的出库任务类型 */ private static final List OUTBOUND_TASK_TYPES_FOR_APPEND = Arrays.asList( TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_MERGE_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type ); Logger logger = LoggerFactory.getLogger(LocItemServiceImpl.class); @Autowired private LocService locService; @Autowired private TaskService taskService; @Autowired private TaskItemService taskItemService; @Autowired private LocItemService locItemService; @Autowired private DeviceSiteService deviceSiteService; @Autowired private WcsService wcsService; @Autowired private OutStockService outStockService; @Autowired private WaveService waveService; @Autowired private BasStationService basStationService; @Autowired private MatnrService matnrService; @Autowired private AsnOrderItemService asnOrderItemService; @Autowired private ConfigService configService; /** 入库/出库保存前:若规格或型号为空则从物料带出 */ private void fillSpecModelFromMatnr(LocItem item) { if (item == null || item.getMatnrId() == null) { return; } if (StringUtils.isNotBlank(item.getSpec()) && StringUtils.isNotBlank(item.getModel())) { return; } Matnr matnr = matnrService.getById(item.getMatnrId()); if (matnr == null) { return; } if (StringUtils.isBlank(item.getSpec())) { item.setSpec(matnr.getSpec()); } if (StringUtils.isBlank(item.getModel())) { item.setModel(matnr.getModel()); } } @Override public boolean save(LocItem entity) { fillSpecModelFromMatnr(entity); return super.save(entity); } @Override public boolean saveBatch(Collection entityList) { if (entityList != null) { entityList.forEach(this::fillSpecModelFromMatnr); } return super.saveBatch(entityList); } @Override public boolean saveBatch(Collection entityList, int batchSize) { if (entityList != null) { entityList.forEach(this::fillSpecModelFromMatnr); } return super.saveBatch(entityList, batchSize); } @Override public void fillSpecModelFromMatnrForRecords(List records) { if (records == null || records.isEmpty()) { return; } Set matnrIds = new HashSet<>(); for (LocItem r : records) { if (r.getMatnrId() != null && (StringUtils.isBlank(r.getSpec()) || StringUtils.isBlank(r.getModel()))) { matnrIds.add(r.getMatnrId()); } } if (matnrIds.isEmpty()) { return; } Map matnrMap = matnrService.listByIds(matnrIds).stream() .collect(Collectors.toMap(Matnr::getId, m -> m)); for (LocItem r : records) { if (r.getMatnrId() == null) { continue; } Matnr m = matnrMap.get(r.getMatnrId()); if (m == null) { continue; } if (StringUtils.isBlank(r.getSpec())) { r.setSpec(m.getSpec()); } if (StringUtils.isBlank(r.getModel())) { r.setModel(m.getModel()); } } } /** * 库存出库生成出库任务 * type: check 盘点, stock: 库存出库 * * @param resouce * @param map * @param loginUserId * @return */ @Override @Synchronized @Transactional(rollbackFor = Exception.class) public synchronized void generateTask(Short resouce, LocToTaskParams map, Long loginUserId) throws Exception { // 出库口未传时默认 1001 String siteNo = StringUtils.isNotBlank(map.getSiteNo()) ? map.getSiteNo() : "1001"; if (Objects.isNull(map.getItems()) || map.getItems().isEmpty()) { throw new CoolException("明细不能为空!"); } List items = map.getItems(); Map> listMap = items.stream().collect(Collectors.groupingBy(LocItem::getLocId)); WkOrder order; Wave wave; if (!Objects.isNull(map.getSourceId())) { if (map.getType().equals(Constants.TASK_TYPE_WAVE_OUT_STOCK)) { order = new WkOrder(); wave = waveService.getById(map.getSourceId()); } else { wave = new Wave(); order = outStockService.getById(map.getSourceId()); } } else { wave = new Wave(); order = new WkOrder(); } final boolean appendSameLocEnabled = isOutAppendSameLocTaskEnabled(); listMap.keySet().forEach(key -> { Loc loc = locService.getById(key); logger.info("库位:>{}", loc.getCode()); if (Objects.isNull(loc)) { throw new CoolException("数据错误:所选库存信息不存在!!"); } if (appendSameLocEnabled) { if (LocStsType.LOC_STS_TYPE_S.type.equals(loc.getUseStatus())) { throw new CoolException("库位:" + loc.getCode() + " 处于S.入库预约,须待拣料/盘点入库回库至F.在库后再下发出库任务!!"); } if (!LocStsType.LOC_STS_TYPE_F.type.equals(loc.getUseStatus()) && !LocStsType.LOC_STS_TYPE_R.type.equals(loc.getUseStatus())) { throw new CoolException("库位:" + loc.getCode() + ",不处于F.在库或R.出库预约状态,不可执行出库分配!!"); } } else { if (!LocStsType.LOC_STS_TYPE_F.type.equals(loc.getUseStatus())) { throw new CoolException("库位:" + loc.getCode() + " 不处于F.在库状态,不可执行出库分配!!"); } } final boolean startedAsReserved = LocStsType.LOC_STS_TYPE_R.type.equals(loc.getUseStatus()); if (LocStsType.LOC_STS_TYPE_F.type.equals(loc.getUseStatus())) { loc.setUseStatus(LocStsType.LOC_STS_TYPE_R.type); if (!locService.updateById(loc)) { throw new CoolException("库位状态更新失败!!"); } } List openOutboundOnLoc = taskService.list(new LambdaQueryWrapper() .eq(Task::getOrgLoc, loc.getCode()) .in(Task::getTaskType, OUTBOUND_TASK_TYPES_FOR_APPEND) .ge(Task::getTaskStatus, TaskStsType.GENERATE_OUT.id) .le(Task::getTaskStatus, TaskStsType.WAVE_SEED.id)); boolean reuseExistingTask = false; Task task = null; if (appendSameLocEnabled && startedAsReserved) { if (openOutboundOnLoc.size() > 1) { throw new CoolException("库位:" + loc.getCode() + " 出库预约下存在多笔未完结出库任务,不允许再生成任务,请待拣料回库至F后再分配!!"); } if (openOutboundOnLoc.isEmpty()) { throw new CoolException("库位:" + loc.getCode() + " 为R.出库预约但未找到未完结出库任务,库位数据异常,不可下发出库或波次!!"); } task = openOutboundOnLoc.get(0); reuseExistingTask = true; if (StringUtils.isNotBlank(task.getTargSite()) && !StringUtils.equals(StringUtils.trimToEmpty(siteNo), StringUtils.trimToEmpty(task.getTargSite()))) { throw new CoolException("库位:" + loc.getCode() + " 已有出库任务目标站点为" + task.getTargSite() + ",与本次站点不一致,不可追加明细!!"); } } Map allocatedByKey = new HashMap<>(); for (Task t : openOutboundOnLoc) { List existItems = taskItemService.list(new LambdaQueryWrapper().eq(TaskItem::getTaskId, t.getId())); for (TaskItem ti : existItems) { String k = buildAllocKey(ti.getMatnrId(), ti.getBatch(), ti.getFieldsIndex()); allocatedByKey.put(k, allocatedByKey.getOrDefault(k, 0.0) + (ti.getAnfme() != null ? ti.getAnfme() : 0.0)); } } String barcodeToUse = StringUtils.isNotBlank(loc.getBarcode()) ? loc.getBarcode() : null; if (barcodeToUse == null && !openOutboundOnLoc.isEmpty()) { barcodeToUse = openOutboundOnLoc.get(0).getBarcode(); if (StringUtils.isNotBlank(barcodeToUse)) { Task refTask = openOutboundOnLoc.get(0); LambdaUpdateWrapper locUw = new LambdaUpdateWrapper().eq(Loc::getId, loc.getId()) .set(Loc::getBarcode, barcodeToUse) .set(Loc::getUpdateBy, loginUserId) .set(Loc::getUpdateTime, new Date()); if (refTask.getWeight() != null) { locUw.set(Loc::getWeight, refTask.getWeight()); } locService.update(locUw); } } if (barcodeToUse == null) { List allocItems = listMap.get(key); if (allocItems != null) { barcodeToUse = allocItems.stream() .map(LocItem::getBarcode) .filter(StringUtils::isNotBlank) .findFirst() .orElse(null); } } if (barcodeToUse == null) { barcodeToUse = loc.getBarcode(); } Task moveTask = new Task(); if (!reuseExistingTask) { task = new Task(); String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null); task.setOrgLoc(loc.getCode()) .setTaskCode(ruleCode) .setResource(resouce) .setTargSite(siteNo) .setSort(Constants.TASK_SORT_DEFAULT_VALUE) .setUpdateBy(loginUserId) .setCreateBy(loginUserId) .setCreateTime(new Date()) .setUpdateTime(new Date()) .setTaskStatus(TaskStsType.GENERATE_OUT.id) .setBarcode(barcodeToUse) .setMemo(map.getMemo()); List locItems = this.list(new LambdaQueryWrapper().eq(LocItem::getLocId, key)); if (locItems.isEmpty()) { throw new CoolException("数据错误:所选库存明细不存在!!"); } Double orgQty = locItems.stream().mapToDouble(LocItem::getAnfme).sum(); List locItemList = listMap.get(key); Double outQty = locItemList.stream().mapToDouble(LocItem::getOutQty).sum(); BigDecimal orgQtyBd = BigDecimal.valueOf(orgQty).setScale(6, RoundingMode.HALF_UP); BigDecimal outQtyBd = BigDecimal.valueOf(outQty).setScale(6, RoundingMode.HALF_UP); if (map.getType().equals(Constants.TASK_TYPE_OUT_STOCK) || map.getType().equals(Constants.TASK_TYPE_ORDER_OUT_STOCK) || map.getType().equals(Constants.TASK_TYPE_WAVE_OUT_STOCK)) { if (orgQtyBd.subtract(outQtyBd).compareTo(FULL_OUT_QTY_TOLERANCE) > 0) { DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper() .eq(DeviceSite::getSite, siteNo) .eq(!Objects.isNull(loc.getChannel()), DeviceSite::getChannel, loc.getChannel()) .eq(DeviceSite::getType, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)); if (Objects.isNull(deviceSite)) { throw new CoolException("站点不支持拣料出库!!"); } task.setTaskType(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type).setWarehType(deviceSite.getDevice()); } else { DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper() .eq(!Objects.isNull(loc.getChannel()), DeviceSite::getChannel, loc.getChannel()) .eq(DeviceSite::getSite, siteNo).eq(DeviceSite::getType, TaskType.TASK_TYPE_OUT.type)); if (Objects.isNull(deviceSite)) { throw new CoolException("站点不支持全板出库!!"); } task.setTaskType(TaskType.TASK_TYPE_OUT.type).setWarehType(deviceSite.getDevice()); } } else if (map.getType().equals(Constants.TASK_TYPE_OUT_CHECK)) { DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper() .eq(!Objects.isNull(loc.getChannel()), DeviceSite::getChannel, loc.getChannel()) .eq(DeviceSite::getSite, siteNo) .eq(DeviceSite::getType, TaskType.TASK_TYPE_CHECK_OUT.type)); if (Objects.isNull(deviceSite)) { throw new CoolException("当前站点不支持盘点出库!!"); } task.setTaskType(TaskType.TASK_TYPE_CHECK_OUT.type).setWarehType(deviceSite.getDevice()); } if (!taskService.save(task)) { throw new CoolException("任务创建失败!!"); } } else { if (StringUtils.isNotBlank(task.getBarcode()) && StringUtils.isBlank(barcodeToUse)) { barcodeToUse = task.getBarcode(); LambdaUpdateWrapper locUw = new LambdaUpdateWrapper().eq(Loc::getId, loc.getId()) .set(Loc::getBarcode, barcodeToUse) .set(Loc::getUpdateBy, loginUserId) .set(Loc::getUpdateTime, new Date()); if (task.getWeight() != null) { locUw.set(Loc::getWeight, task.getWeight()); } locService.update(locUw); } List locItemsVerify = this.list(new LambdaQueryWrapper().eq(LocItem::getLocId, key)); if (locItemsVerify.isEmpty()) { throw new CoolException("数据错误:所选库存明细不存在!!"); } } // if (!LocUtils.isShallowLoc(loc.getCode())) { // //获取深库位对应浅库位 // String shallowLoc = LocUtils.getShallowLoc(loc.getCode()); // Loc one = locService.getOne(new LambdaQueryWrapper().eq(Loc::getCode, shallowLoc)); // if (Objects.isNull(one)) { // throw new CoolException("对应库位不存在!!"); // } // Task workTask = taskService.getOne(new LambdaQueryWrapper().eq(Task::getBarcode, one.getBarcode())); // if (Objects.isNull(workTask)) { // map.setOrgLoc(one.getCode()); // //优先生成移库任务 // if (one.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)) { // moveTask = genMoveTask(map, loginUserId); // } // } else { // workTask.setSort(task.getSort() + 1).setParentId(task.getId()); // if (!taskService.updateById(workTask)) { // throw new CoolException("优先级修改失败!!"); // } // } // } if (!Objects.isNull(moveTask.getId())) { moveTask.setParentId(task.getId()).setSort(moveTask.getSort() + 1); if (!taskService.saveOrUpdate(moveTask)) { throw new CoolException("任务信息修改失败!!"); } task.setParentId(moveTask.getId()); if (!taskService.updateById(task)) { throw new CoolException("主任务关联失败!!"); } } final Task outTask = task; if (outTask == null) { throw new CoolException("任务数据异常!!"); } List taskItems = new ArrayList<>(); listMap.get(key).forEach(item -> { LocItem locItem = locItemService.getById(item.getId()); if (Objects.isNull(locItem)) { throw new CoolException("库存信息不存在!"); } if (item.getOutQty().compareTo(0.0) < 0) { throw new CoolException("出库数里不能小于0!!"); } Double allocQty = item.getOutQty(); String allocKey = buildAllocKey(locItem.getMatnrId(), locItem.getBatch(), locItem.getFieldsIndex()); Double already = allocatedByKey.getOrDefault(allocKey, 0.0); Double available = Math.round((locItem.getAnfme() - already) * 1000000) / 1000000.0; if (available.compareTo(0.0) <= 0) { throw new CoolException("库位:" + loc.getCode() + " 该物料已无剩余可分配数量,不可再追加订单!"); } if (allocQty.compareTo(available) > 0) { allocQty = available; item.setOutQty(allocQty); } allocatedByKey.put(allocKey, already + allocQty); TaskItem taskItem = new TaskItem(); BeanUtils.copyProperties(item, taskItem); taskItem.setTaskId(outTask.getId()) .setAnfme(allocQty) .setBatch(item.getBatch()) .setUpdateBy(loginUserId) .setCreateBy(loginUserId) .setCreateTime(new Date()) .setUpdateTime(new Date()) .setOrderType(OrderType.ORDER_OUT.type); if (map.getType().equals(Constants.TASK_TYPE_ORDER_OUT_STOCK)) { taskItem.setWkType(order.getWkType()) .setSourceCode(order.getCode()) .setSourceId(order.getId()) .setOrderId(order.getId()) .setOrderItemId(item.getOrderItemId()); String poOrCode = StringUtils.isNotBlank(order.getPoCode()) ? order.getPoCode() : order.getCode(); if (StringUtils.isBlank(taskItem.getPlatOrderCode()) && StringUtils.isNotBlank(poOrCode)) { taskItem.setPlatOrderCode(poOrCode); } if (item.getOrderItemId() != null) { WkOrderItem oi = asnOrderItemService.getById(item.getOrderItemId()); if (oi != null) { if (StringUtils.isBlank(taskItem.getPlatItemId()) && StringUtils.isNotBlank(oi.getPlatItemId())) { taskItem.setPlatItemId(oi.getPlatItemId()); } if (StringUtils.isBlank(taskItem.getPlatWorkCode()) && StringUtils.isNotBlank(oi.getPlatWorkCode())) { taskItem.setPlatWorkCode(oi.getPlatWorkCode()); } if (StringUtils.isBlank(taskItem.getPlatOrderCode())) { String p = StringUtils.isNotBlank(oi.getPlatOrderCode()) ? oi.getPlatOrderCode() : poOrCode; if (StringUtils.isNotBlank(p)) { taskItem.setPlatOrderCode(p); } } } } } else if (map.getType().equals(Constants.TASK_TYPE_WAVE_OUT_STOCK)) { taskItem.setSourceId(wave.getId()) .setWkType(OrderWorkType.ORDER_WORK_TYPE_OTHER.type) .setSourceCode(wave.getCode()) .setSource(item.getSource()); } else if (map.getType().equals(Constants.TASK_TYPE_OUT_CHECK) || map.getType().equals(Constants.TASK_TYPE_OUT_STOCK)) { taskItem.setWkType(OrderWorkType.ORDER_WORK_TYPE_STOCK_OUT.type) .setSource(item.getId()) .setSourceId(item.getLocId()) .setSourceCode(item.getLocCode()); } taskItems.add(taskItem); Double qty = Math.round((item.getWorkQty() != null ? item.getWorkQty() : 0.0) + allocQty) * 1000000.0 / 1000000.0; if (locItem.getAnfme().compareTo(qty) < 0) { Double minusQty = Math.round((locItem.getAnfme() - (locItem.getWorkQty() != null ? locItem.getWorkQty() : 0.0)) * 1000000) / 1000000.0; item.setWorkQty(minusQty); } else { item.setWorkQty(qty); } item.setUpdateBy(loginUserId).setUpdateTime(new Date()); if (!locItemService.updateById(item)) { throw new CoolException("库存信息修改失败!!"); } }); if (!taskItemService.saveBatch(taskItems)) { throw new CoolException("任务明细生成失败!!"); } for (TaskItem ti : taskItems) { if (ti.getAnfme() == null) { throw new CoolException("任务明细数量异常"); } taskService.enqueueCloudWmsOutNotifyLogEarly(outTask, ti, QuantityUtils.toScaledBigDecimal(ti.getAnfme())); } }); } /** * 生成移库任务 * * @param map * @param loginUserId * @return */ @Override @Transactional(rollbackFor = Exception.class) public Task genMoveTask(LocToTaskParams map, Long loginUserId) { if (Objects.isNull(map.getOrgLoc()) || StringUtils.isBlank(map.getOrgLoc())) { throw new CoolException("源库位不能为空!"); } Loc orgLoc = locService.getOne(new LambdaQueryWrapper().eq(Loc::getCode, map.getOrgLoc())); if (Objects.isNull(orgLoc)) { throw new CoolException("源库位不存在!!"); } // if (orgLoc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type) // || orgLoc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_S.type) // || orgLoc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_X.type ) ) { // throw new CoolException("源库位有任务正在执行中..."); // } orgLoc.setUseStatus(LocStsType.LOC_STS_TYPE_R.type); if (!locService.updateById(orgLoc)) { throw new CoolException("出库预约失败!!"); } Loc targetLoc; if (Objects.isNull(map.getTarLoc()) || StringUtils.isBlank(map.getTarLoc())) { //目标库位为空,自动获取新库位 DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper() .eq(DeviceSite::getType, TaskType.TASK_TYPE_LOC_MOVE.type) .eq(!Objects.isNull(orgLoc.getChannel()), DeviceSite::getChannel, orgLoc.getChannel()), false); if (Objects.isNull(deviceSite)) { throw new CoolException("站点信息不存在!!"); } TaskInParam param = new TaskInParam(); param.setIoType(TaskType.TASK_TYPE_OUT.type) .setOrgLoc(map.getOrgLoc()) .setSourceStaNo(deviceSite.getSite()) .setLocType1(Integer.parseInt(orgLoc.getType()) ); InTaskMsgDto locNo; try { locNo = wcsService.getLocNo(param); } catch (Exception e) { log.error("", e); throw new CoolException(e.getMessage()); } targetLoc = locService.getOne(new LambdaQueryWrapper().eq(Loc::getCode, locNo.getLocNo())); } else { targetLoc = locService.getOne(new LambdaQueryWrapper().eq(Loc::getCode, map.getTarLoc())); } if (Objects.isNull(targetLoc)) { throw new CoolException("目标库位不存在!!"); } targetLoc.setUseStatus(LocStsType.LOC_STS_TYPE_S.type) .setWeight(orgLoc.getWeight()); if (!locService.updateById(targetLoc)) { throw new CoolException("目标库位预约失败!!"); } String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null); Task task = new Task(); task.setOrgLoc(orgLoc.getCode()) .setTaskCode(ruleCode) .setTaskType(TaskType.TASK_TYPE_LOC_MOVE.type) .setTargLoc(targetLoc.getCode()) .setUpdateBy(loginUserId) .setSort(Constants.TASK_SORT_DEFAULT_VALUE) .setUpdateTime(new Date()) .setTaskStatus(TaskStsType.GENERATE_IN.id) .setBarcode(orgLoc.getBarcode()) .setMemo(map.getMemo()); if (!taskService.save(task)) { throw new CoolException("新建移库任务失败!!"); } List locItems = locItemService.list(new LambdaQueryWrapper().eq(LocItem::getLocId, orgLoc.getId())); if (!locItems.isEmpty()) { List taskItems = new ArrayList<>(); for (LocItem item : locItems) { TaskItem taskItem = new TaskItem(); BeanUtils.copyProperties(item, taskItem); taskItem.setTaskId(task.getId()) .setAnfme(item.getAnfme()) .setBatch(item.getBatch()) .setUpdateBy(loginUserId) .setSourceId(item.getLocId()) .setSourceCode(item.getLocCode()) .setSource(item.getId()) .setUpdateTime(new Date()) .setOrderType(OrderType.ORDER_IN.type) .setWkType(OrderWorkType.ORDER_WORK_TYPE_OTHER_IN.type); taskItems.add(taskItem); } if (!taskItemService.saveBatch(taskItems)) { throw new CoolException("任务明细生成失败!!"); } } return task; } /** * 空板出库:从指定空板库位(useStatus=D)生成 TASK_TYPE_EMPITY_OUT 任务至目标站点。 * 需在设备站点中配置 type=110(空板出库)的站点路径。 */ @Override @Transactional(rollbackFor = Exception.class) public Task generateTaskEmpty(LocToTaskParams map, Long loginUserId) { if (StringUtils.isBlank(map.getSiteNo())) { throw new CoolException("目标站点不能为空!"); } if (StringUtils.isBlank(map.getOrgLoc())) { throw new CoolException("源库位不能为空!"); } if (!Constants.TASK_TYPE_OUT_STOCK_EMPTY.equals(map.getType())) { throw new CoolException("类型必须为 empty(空板出库)!"); } Loc loc = locService.getOne(new LambdaQueryWrapper().eq(Loc::getCode, map.getOrgLoc())); if (loc == null) { throw new CoolException("源库位不存在!"); } if (!LocStsType.LOC_STS_TYPE_D.type.equals(loc.getUseStatus())) { throw new CoolException("库位 " + loc.getCode() + " 不处于空板状态(D),不可执行空板出库!"); } DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper() .eq(DeviceSite::getSite, map.getSiteNo()) .eq(DeviceSite::getType, TaskType.TASK_TYPE_EMPITY_OUT.type) .last("limit 1")); if (deviceSite == null) { throw new CoolException("站点不支持空板出库或未配置空板出库路径!"); } if (!locService.update(new LambdaUpdateWrapper() .eq(Loc::getId, loc.getId()) .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_R.type))) { throw new CoolException("库位出库预约失败!"); } String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, null); if (StringUtils.isBlank(ruleCode)) { throw new CoolException("编码错误:请确认是否已生成!"); } Task task = new Task(); task.setTaskCode(ruleCode) .setTaskStatus(TaskStsType.GENERATE_OUT.id) .setTaskType(TaskType.TASK_TYPE_EMPITY_OUT.type) .setWarehType(WarehType.WAREHOUSE_TYPE_CRN.val) .setOrgLoc(loc.getCode()) .setTargSite(map.getSiteNo()) .setBarcode(loc.getBarcode()) .setSort(Constants.TASK_SORT_DEFAULT_VALUE) .setCreateBy(loginUserId) .setUpdateBy(loginUserId) .setCreateTime(new Date()) .setUpdateTime(new Date()) .setMemo(map.getMemo()); if (!taskService.save(task)) { throw new CoolException("空板出库任务创建失败!"); } logger.info("[空板出库] 已创建任务: {}, 源库位: {}, 目标站点: {}", ruleCode, loc.getCode(), map.getSiteNo()); return task; } /** * @author Ryan * @date 2025/7/16 * @description: 获取当前物料所有库存信息 * @version 1.0 */ @Override public List listByMatnr(CheckLocQueryParams matnr) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(StringUtils.isNotBlank(matnr.getLocCode()), LocItem::getLocCode, matnr.getLocCode()) // .eq(StringUtils.isNotBlank(matnr.getChannel()), LocItem::getChannel, matnr.getChannel()) .in(!matnr.getMatnrCode().isEmpty(), LocItem::getMatnrCode, matnr.getMatnrCode()); return this.baseMapper.listByMatnr(LocStsType.LOC_STS_TYPE_F.type, matnr.getChannel(), wrapper); } /** 库位已分配量按物料+批次+票号聚合的 key */ private static String buildAllocKey(Long matnrId, String batch, String fieldsIndex) { return (matnrId != null ? matnrId : "") + "|" + (batch != null ? batch : "") + "|" + (fieldsIndex != null ? fieldsIndex : ""); } private boolean isOutAppendSameLocTaskEnabled() { Boolean v = configService.getVal(GlobalConfigCode.OUT_APPEND_SAME_LOC_TASK_ENABLED, Boolean.class); return Boolean.TRUE.equals(v); } }