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.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.SerialRuleCode; 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.util.*; import java.util.stream.Collectors; @Service("locItemService") public class LocItemServiceImpl extends ServiceImpl implements LocItemService { 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; /** * 库存出库生成出库任务 * 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(); } listMap.keySet().forEach(key -> { Task task = new Task(); Loc loc = locService.getById(key); logger.info("库位:>{}", loc.getCode()); if (Objects.isNull(loc)) { throw new CoolException("数据错误:所选库存信息不存在!!"); } // 支持 F.在库 或 R.出库预约/拣货中 状态下分配(拣货中可追加同库位订单,分配量不超过已分配剩余) if (!loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type) && !loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) { throw new CoolException("库位:" + loc.getCode() + ",不处于F.在库或R.出库预约状态,不可执行出库分配!!"); } if (loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_F.type)) { loc.setUseStatus(LocStsType.LOC_STS_TYPE_R.type); if (!locService.updateById(loc)) { throw new CoolException("库位状态更新失败!!"); } } // 库位已为 R 时:计算该库位当前任务已分配量,新分配不能超过 (库位数量 - 已分配) Map allocatedByKey = new HashMap<>(); List existTasks = new ArrayList<>(); if (loc.getUseStatus().equals(LocStsType.LOC_STS_TYPE_R.type)) { existTasks = taskService.list(new LambdaQueryWrapper() .eq(Task::getOrgLoc, loc.getCode()) .in(Task::getTaskStatus, Arrays.asList(TaskStsType.GENERATE_OUT.id, TaskStsType.WAVE_SEED.id))); for (Task t : existTasks) { 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)); } } } // 料箱码:优先用库位;预约出库(R) 时若库位未绑定则从同库位已有任务带出并回写库位;再否则从本次分配明细带出 String barcodeToUse = StringUtils.isNotBlank(loc.getBarcode()) ? loc.getBarcode() : null; if (barcodeToUse == null && !existTasks.isEmpty()) { barcodeToUse = existTasks.get(0).getBarcode(); if (StringUtils.isNotBlank(barcodeToUse)) { locService.update(new LambdaUpdateWrapper().eq(Loc::getId, loc.getId()) .set(Loc::getBarcode, barcodeToUse).set(Loc::getUpdateBy, loginUserId).set(Loc::getUpdateTime, new Date())); } } 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(); 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(); 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 (orgQty.compareTo(outQty) > 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("任务创建失败!!"); } // 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("主任务关联失败!!"); } } 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(); if (!allocatedByKey.isEmpty()) { 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(task.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(Short.parseShort(order.getWkType())) .setSourceCode(order.getCode()) .setSourceId(order.getId()) .setOrderItemId(item.getOrderItemId()); } else if (map.getType().equals(Constants.TASK_TYPE_WAVE_OUT_STOCK)) { taskItem.setSourceId(wave.getId()) .setWkType(Short.parseShort(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(Short.parseShort(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("任务明细生成失败!!"); } }); } /** * 生成移库任务 * * @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); 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(Short.parseShort(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 : ""); } }