| | |
| | | // 验证设备站点 |
| | | DeviceSite deviceSite = validateDeviceSite(param); |
| | | |
| | | // 空板入库:与非空板同一入口,仅不校验组托,只做分配库位、建任务、更新库位 |
| | | if (param.getIoType() != null && param.getIoType().equals(TaskType.TASK_TYPE_EMPITY_IN.type)) { |
| | | return createInTaskForEmptyPallet(param.getBarcode(), param.getSourceStaNo(), param.getLocType1()); |
| | | } |
| | | |
| | | // 提前定义 waitPakin / waitPakinItems,供后续其他入库逻辑使用 |
| | | WaitPakin waitPakin = null; |
| | | List<WaitPakinItem> waitPakinItems = Collections.emptyList(); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 空板入库:RCS 申请时 full=true,无需组托,分配库位并创建 TASK_TYPE_EMPITY_IN 任务。 |
| | | * 需在设备站点中配置 type=10(空板入库)的站点路径。 |
| | | * 空板入库:与非空板同一流程(校验站点、分配库位、建任务、更新库位),仅不校验组托、不写任务明细、不更新组托状态。 |
| | | * 由 createInTask 在 ioType=空板时调用;需在设备站点中配置 type=10(空板入库)的站点路径。 |
| | | */ |
| | | private InTaskMsgDto createInTaskForEmptyPallet(String barcode, String staNo, Integer type) { |
| | | TaskInParam param = new TaskInParam(); |
| | |
| | | log.info("========== 开始申请入库任务,分配库位 =========="); |
| | | log.info("料箱码:{},入库站点:{},入库类型:{},空板:{}", barcode, staNo, type, full); |
| | | |
| | | // full=true 时走空板入库(无需组托);否则走普通入库(需组托或自动组托) |
| | | if (Boolean.TRUE.equals(full)) { |
| | | InTaskMsgDto msgDto = createInTaskForEmptyPallet(barcode, staNo, type); |
| | | JSONObject result = new JSONObject(); |
| | | result.put("locNo", msgDto.getLocNo()); |
| | | result.put("batchNo", msgDto.getWorkNo()); |
| | | result.put("taskNo", msgDto.getWorkNo()); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | // 构建 TaskInParam 参数,与 /wcs/create/in/task 接口参数一致 |
| | | // 统一走 createInTask:空板(full=true)仅不校验组托,仍校验站点、分配库位、建任务;非空板需组托 |
| | | TaskInParam param = new TaskInParam(); |
| | | param.setBarcode(barcode); |
| | | param.setSourceStaNo(staNo); |
| | | param.setIoType(TaskType.TASK_TYPE_IN.type); // 入库类型 |
| | | param.setLocType1(type); // 库位类型(高低检测信号) |
| | | param.setUser(1L); // 默认用户ID,可以根据实际需求调整 |
| | | param.setLocType1(type != null ? type : 1); |
| | | param.setUser(1L); |
| | | param.setIoType(Boolean.TRUE.equals(full) ? TaskType.TASK_TYPE_EMPITY_IN.type : TaskType.TASK_TYPE_IN.type); |
| | | |
| | | // 调用 createInTask 方法,创建完整的入库任务 |
| | | // 该方法会执行以下流程: |
| | | // 1. 验证设备站点 |
| | | // 2. 验证组拖状态 |
| | | // 3. 检查是否有匹配的入库任务(拣料/盘点入库会匹配状态199并更新为2) |
| | | // 4. 生成任务编码(如果需要创建新任务) |
| | | // 5. 获取库位号 |
| | | // 6. 创建并保存任务(如果需要创建新任务) |
| | | // 7. 更新库位状态 |
| | | // 8. 获取并验证组拖明细 |
| | | // 9. 创建并保存任务明细 |
| | | // 10. 更新组托状态 |
| | | InTaskMsgDto msgDto = createInTask(param); |
| | | |
| | | // 查询任务当前状态 |
| | |
| | | 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.manager.controller.params.LocToTaskParams; |
| | | import com.vincent.rsf.server.manager.controller.params.OutStockToTaskParams; |
| | | import com.vincent.rsf.server.manager.controller.params.PakinItem; |
| | | import com.vincent.rsf.server.manager.controller.params.WaitPakinParam; |
| | | import com.vincent.rsf.server.manager.entity.*; |
| | | import com.vincent.rsf.server.common.constant.Constants; |
| | | import com.vincent.rsf.server.manager.enums.AsnExceStatus; |
| | | import com.vincent.rsf.server.manager.enums.LocStsType; |
| | | import com.vincent.rsf.server.manager.enums.OrderType; |
| | | import com.vincent.rsf.server.manager.enums.OrderWorkType; |
| | | import com.vincent.rsf.server.manager.enums.TaskStsType; |
| | |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 指定物料自动化定时任务:可配置物料编码后, |
| | | * 指定物料/空板自动化定时任务:可配置后 |
| | | * 1)有库存时自动生成全版出库单; |
| | | * 2)该物料出库单自动下发任务; |
| | | * 3)RCS 入库通知时(可选)自动组托,数量可配置。 |
| | | * 3)RCS 入库通知时(可选)自动组托,数量可配置; |
| | | * 4)空板(D)库位定时自动生成空板出库任务并下发 RCS(AUTO_EMPTY_OUT_ENABLED)。 |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | |
| | | } |
| | | |
| | | /** |
| | | * 定时任务:空板库存自动出库(每 2 分钟) |
| | | * 配置:AUTO_EMPTY_OUT_ENABLED=true 时,扫描空板(D)库位,生成空板出库任务并下发 RCS,流程与 AUTO_FULL_OUT 对应定时任务一致。 |
| | | */ |
| | | @Scheduled(cron = "0 0/2 * * * ?") |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void autoEmptyOutTask() { |
| | | Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_EMPTY_OUT_ENABLED)); |
| | | if (enabledConfig == null || !Boolean.parseBoolean(enabledConfig.getVal())) { |
| | | return; |
| | | } |
| | | List<Loc> emptyLocs = locService.list(new LambdaQueryWrapper<Loc>() |
| | | .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_D.type)); |
| | | if (emptyLocs.isEmpty()) { |
| | | return; |
| | | } |
| | | List<Task> existingEmptyOut = taskService.list(new LambdaQueryWrapper<Task>() |
| | | .eq(Task::getTaskType, TaskType.TASK_TYPE_EMPITY_OUT.type) |
| | | .lt(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id)); |
| | | Set<String> locCodesInProgress = existingEmptyOut.stream() |
| | | .map(Task::getOrgLoc).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | List<Task> created = new ArrayList<>(); |
| | | for (Loc loc : emptyLocs) { |
| | | if (locCodesInProgress.contains(loc.getCode())) { |
| | | continue; |
| | | } |
| | | try { |
| | | LocToTaskParams params = new LocToTaskParams(); |
| | | params.setType(Constants.TASK_TYPE_OUT_STOCK_EMPTY) |
| | | .setOrgLoc(loc.getCode()) |
| | | .setSiteNo(DEFAULT_SITE_NO); |
| | | Task task = locItemService.generateTaskEmpty(params, SYSTEM_USER_ID); |
| | | created.add(task); |
| | | locCodesInProgress.add(loc.getCode()); |
| | | } catch (Exception e) { |
| | | log.warn("[自动空板出库] 库位 {} 生成任务失败: {}", loc.getCode(), e.getMessage()); |
| | | } |
| | | } |
| | | if (!created.isEmpty()) { |
| | | List<Task> toPublish = created.stream() |
| | | .filter(t -> TaskStsType.GENERATE_OUT.id.equals(t.getTaskStatus())) |
| | | .collect(Collectors.toList()); |
| | | if (!toPublish.isEmpty()) { |
| | | try { |
| | | taskService.pubTaskToWcs(toPublish); |
| | | log.info("[自动空板出库] 已生成并下发 {} 个空板出库任务", toPublish.size()); |
| | | } catch (Exception e) { |
| | | log.error("[自动空板出库] 下发 RCS 失败", e); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 定时任务:配置物料出库任务在 RCS 回调为 199(待确认)后自动拣货完成,无需 PDA 快速拣货确认即可更新库存。 |
| | | * 配置:AUTO_FULL_OUT_MATNR_CODE(物料编码,配置了则对该物料生效) |
| | | */ |
| | |
| | | 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<LocItemMapper, LocItem> implements LocItemService { |
| | | |
| | | private static final BigDecimal FULL_OUT_QTY_TOLERANCE = new BigDecimal("0.000001"); |
| | | |
| | | Logger logger = LoggerFactory.getLogger(LocItemServiceImpl.class); |
| | | |
| | |
| | | Double orgQty = locItems.stream().mapToDouble(LocItem::getAnfme).sum(); |
| | | List<LocItem> 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 (orgQty.compareTo(outQty) > 0) { |
| | | //拣料出库 -- 盘点出库 |
| | | // 出库量达到库位库存(含容差)视为全版出库,避免浮点误差导致误判为拣料/部分出库 |
| | | if (orgQtyBd.subtract(outQtyBd).compareTo(FULL_OUT_QTY_TOLERANCE) > 0) { |
| | | // 拣料出库(部分出库) |
| | | DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>() |
| | | .eq(DeviceSite::getSite, siteNo) |
| | | .eq(!Objects.isNull(loc.getChannel()),DeviceSite::getChannel, loc.getChannel()) |
| | |
| | | /** 自动组托数量(与 AUTO_PAKIN_ON_ASN_ENABLED 配合,每条明细组托数量) */ |
| | | public final static String AUTO_PAKIN_QTY = "AUTO_PAKIN_QTY"; |
| | | |
| | | /** 是否启用:定时自动生成空板出库任务并下发 RCS(每 2 分钟扫描 D 空板库位) */ |
| | | public final static String AUTO_EMPTY_OUT_ENABLED = "AUTO_EMPTY_OUT_ENABLED"; |
| | | |
| | | } |
| | |
| | | (UPPER(UUID()), '启用:有库存时自动生成全版出库单', 'AUTO_FULL_OUT_ENABLED', 1, 'false', 'true/false', 1, 0, 1, NULL, NOW(), NULL, NOW(), '需配置 AUTO_FULL_OUT_MATNR_CODE'), |
| | | (UPPER(UUID()), '启用:该物料出库单自动下发任务', 'AUTO_FULL_OUT_DISPATCH_ENABLED', 1, 'false', 'true/false', 1, 0, 1, NULL, NOW(), NULL, NOW(), '需配置 AUTO_FULL_OUT_MATNR_CODE'), |
| | | (UPPER(UUID()), '启用:RCS入库通知时自动组托', 'AUTO_PAKIN_ON_ASN_ENABLED', 1, 'false', 'true/false', 1, 0, 1, NULL, NOW(), NULL, NOW(), '需配置 AUTO_FULL_OUT_MATNR_CODE、AUTO_PAKIN_QTY'), |
| | | (UPPER(UUID()), '自动组托数量', 'AUTO_PAKIN_QTY', 2, '1', '每条入库明细自动组托数量', 1, 0, 1, NULL, NOW(), NULL, NOW(), '与 AUTO_PAKIN_ON_ASN_ENABLED 配合') |
| | | (UPPER(UUID()), '自动组托数量', 'AUTO_PAKIN_QTY', 2, '1', '每条入库明细自动组托数量', 1, 0, 1, NULL, NOW(), NULL, NOW(), '与 AUTO_PAKIN_ON_ASN_ENABLED 配合'), |
| | | (UPPER(UUID()), '启用:定时自动生成空板出库任务并下发RCS', 'AUTO_EMPTY_OUT_ENABLED', 1, 'false', 'true/false,每2分钟扫描空板(D)库位并生成出库任务、呼叫RCS', 1, 0, 1, NULL, NOW(), NULL, NOW(), '需在设备站点配置 type=110 空板出库的站点(如1001)') |
| | | ; |