| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchPreGenerateReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindBatchReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletBindBatchReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchWorkerDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.line.MesProLineService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; |
| | | import cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | |
| | | |
| | | @Resource |
| | | private MesProPalletService palletService; |
| | | @Resource |
| | | private MesProBagCodeService bagCodeService; |
| | | @Resource |
| | | private MesMdmItemService mdmItemService; |
| | | @Resource |
| | | private MdmUnitMeasureApi unitMeasureApi; |
| | | |
| | | @Resource |
| | | private MesCodeRedisDAO codeRedisDao; |
| | |
| | | String code = generateBatchCode(line.getCode()); |
| | | |
| | | // 3. 插入批次(产线编码/名称冗余回填;勾选绑定托盘时批次直接进入生产中) |
| | | boolean bindPallets = CollUtil.isNotEmpty(createReqVO.getPalletIds()); |
| | | boolean hasBindings = hasBindings(createReqVO); |
| | | MesProBatchDO batch = BeanUtils.toBean(createReqVO, MesProBatchDO.class) |
| | | .setCode(code) |
| | | .setBusinessCode(codeRedisDao.generate()) |
| | |
| | | .setLineName(line.getName()) |
| | | .setBagQuantity(0) |
| | | .setPalletQuantity(0) |
| | | .setStatus(bindPallets ? MesProBatchStatusEnum.PRODUCING.getStatus() |
| | | .setStatus(hasBindings ? MesProBatchStatusEnum.PRODUCING.getStatus() |
| | | : MesProBatchStatusEnum.DRAFT.getStatus()); |
| | | batchMapper.insert(batch); |
| | | |
| | |
| | | insertBatchWorkers(batch.getId(), createReqVO.getWorkers()); |
| | | // 5. 绑定托盘(级联生成正式托盘码并录入托盘下袋码,失败整体回滚) |
| | | bindPalletsToBatch(batch.getId(), createReqVO.getPalletIds()); |
| | | bindTonBagsToBatch(batch.getId(), createReqVO.getTonBagIds()); |
| | | // 6. 生成批次完整性摘要 |
| | | integrityService.recordBatchIntegrity(batch.getId()); |
| | | return batch.getId(); |
| | |
| | | fillReqVO.setItemId(resolveBindingItem(batch, fillReqVO)); |
| | | String code = generateBatchCode(line.getCode()); |
| | | // 3. 落库业务信息(保留临时批次码用于历史扫码追溯;产线编码/名称冗余回填;勾选绑定托盘时批次直接进入生产中) |
| | | boolean bindPallets = CollUtil.isNotEmpty(fillReqVO.getPalletIds()); |
| | | boolean hasBindings = hasBindings(fillReqVO); |
| | | MesProBatchDO updateObj = BeanUtils.toBean(fillReqVO, MesProBatchDO.class) |
| | | .setCode(code) |
| | | .setLineCode(line.getCode()) |
| | | .setLineName(line.getName()) |
| | | .setStatus(bindPallets ? MesProBatchStatusEnum.PRODUCING.getStatus() |
| | | .setStatus(hasBindings ? MesProBatchStatusEnum.PRODUCING.getStatus() |
| | | : MesProBatchStatusEnum.DRAFT.getStatus()); |
| | | batchMapper.updateById(updateObj); |
| | | // 4. 重建生产人员配置 |
| | |
| | | insertBatchWorkers(fillReqVO.getId(), fillReqVO.getWorkers()); |
| | | // 5. 绑定托盘(级联生成正式托盘码并录入托盘下袋码,失败整体回滚) |
| | | bindPalletsToBatch(fillReqVO.getId(), fillReqVO.getPalletIds()); |
| | | bindTonBagsToBatch(fillReqVO.getId(), fillReqVO.getTonBagIds()); |
| | | // 6. 生成批次完整性摘要(补录正式信息后覆盖) |
| | | integrityService.recordBatchIntegrity(fillReqVO.getId()); |
| | | } |
| | |
| | | updateObj.setLineCode(line.getCode()).setLineName(line.getName()); |
| | | } |
| | | // 4. 更新批次(勾选绑定托盘时批次直接进入生产中;取消勾选不回退,已绑定托盘不可解绑) |
| | | boolean bindPallets = CollUtil.isNotEmpty(updateReqVO.getPalletIds()); |
| | | boolean hasBindings = hasBindings(updateReqVO); |
| | | updateObj.setCode(code); |
| | | if (bindPallets) { |
| | | if (hasBindings) { |
| | | updateObj.setStatus(MesProBatchStatusEnum.PRODUCING.getStatus()); |
| | | } |
| | | batchMapper.updateById(updateObj); |
| | |
| | | insertBatchWorkers(updateReqVO.getId(), updateReqVO.getWorkers()); |
| | | // 6. 绑定新增勾选的托盘(级联生成正式托盘码并录入托盘下袋码,失败整体回滚) |
| | | bindPalletsToBatch(updateReqVO.getId(), updateReqVO.getPalletIds()); |
| | | bindTonBagsToBatch(updateReqVO.getId(), updateReqVO.getTonBagIds()); |
| | | // 7. 摘要覆盖范围含品种/产线/生产日期/计划数量,编辑后重新生成避免误报篡改 |
| | | integrityService.recordBatchIntegrity(updateReqVO.getId()); |
| | | } |
| | |
| | | @Override |
| | | public MesProBatchDO getBatch(Long id) { |
| | | return batchMapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public String getBindingMode(Long id) { |
| | | if (!palletMapper.selectListByBatchId(id).isEmpty()) return "pallet"; |
| | | return bagCodeMapper.selectListByBatchId(id).stream() |
| | | .filter(bag -> bag.getPalletId() == null) |
| | | .map(MesProBagCodeDO::getItemId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .anyMatch(this::isTonItem) |
| | | ? "tonBag" : null; |
| | | } |
| | | |
| | | @Override |
| | |
| | | * 生成正式托盘码并级联录入托盘下未绑批次的袋码,失败整体回滚 |
| | | */ |
| | | private Long resolveBindingItem(MesProBatchDO batch, MesProBatchSaveReqVO req) { |
| | | boolean palletsRequested = CollUtil.isNotEmpty(req.getPalletIds()); |
| | | boolean tonsRequested = CollUtil.isNotEmpty(req.getTonBagIds()); |
| | | if (palletsRequested && tonsRequested) throw bindingError("绑定托盘和绑定吨袋只能选择一种"); |
| | | if (batch != null && (palletsRequested || tonsRequested)) { |
| | | String existingMode = getBindingMode(batch.getId()); |
| | | if ((tonsRequested && "pallet".equals(existingMode)) |
| | | || (palletsRequested && "tonBag".equals(existingMode))) |
| | | throw bindingError("该批次已绑定另一种码,绑定托盘和绑定吨袋只能选择一种"); |
| | | } |
| | | Long itemId = batch == null ? null : batch.getItemId(); |
| | | if (batch != null) { |
| | | for (Long existing : bagCodeMapper.selectDistinctItemIdsByBatchId(batch.getId())) |
| | |
| | | if (candidate == null) throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "托盘缺少品种,请先绑定同品种袋码"); |
| | | itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, candidate); |
| | | } |
| | | // 请求里的品种只能与已绑定/待绑定托盘一致,不能通过编辑改变批次品种。 |
| | | if (tonsRequested) { |
| | | if (req.getTonBagIds().contains(null)) throw bindingError("请选择有效的吨袋"); |
| | | List<Long> ids = req.getTonBagIds().stream().distinct().sorted().toList(); |
| | | List<MesProBagCodeDO> bags = bagCodeMapper.selectListByIdsForUpdate(ids); |
| | | if (bags.size() != ids.size()) throw bindingError("所选吨袋不存在"); |
| | | for (MesProBagCodeDO bag : bags) { |
| | | if (bag.getBatchId() != null || bag.getPalletId() != null || !isTonItem(bag.getItemId())) |
| | | throw bindingError("只能绑定未录入批次、没有托盘且计量单位为吨的袋码"); |
| | | itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, bag.getItemId()); |
| | | } |
| | | } |
| | | // 请求里的品种只能与已绑定或待绑定的托盘/吨袋一致,不能通过编辑改变批次品种。 |
| | | if (req.getItemId() != null && !Objects.equals(req.getItemId(), itemId)) |
| | | throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "品种由绑定托盘带入,不可修改为其他品种"); |
| | | throw bindingError("品种由绑定托盘或吨袋带入,不可修改为其他品种"); |
| | | return itemId; |
| | | } |
| | | |
| | | private boolean hasBindings(MesProBatchSaveReqVO req) { |
| | | return CollUtil.isNotEmpty(req.getPalletIds()) || CollUtil.isNotEmpty(req.getTonBagIds()); |
| | | } |
| | | |
| | | private boolean isTonItem(Long itemId) { |
| | | if (itemId == null) return false; |
| | | MesMdmItemDO item = mdmItemService.getItem(itemId); |
| | | if (item == null || item.getUnitMeasureId() == null) return false; |
| | | var result = unitMeasureApi.getUnitMeasure(item.getUnitMeasureId()); |
| | | return result != null && result.isSuccess() && result.getData() != null |
| | | && "吨".equals(StrUtil.trim(result.getData().getType())); |
| | | } |
| | | |
| | | private cn.iocoder.yudao.framework.common.exception.ServiceException bindingError(String message) { |
| | | return new cn.iocoder.yudao.framework.common.exception.ServiceException(400, message); |
| | | } |
| | | |
| | | private void bindTonBagsToBatch(Long batchId, List<Long> tonBagIds) { |
| | | if (CollUtil.isEmpty(tonBagIds)) return; |
| | | MesProBagCodeBindBatchReqVO req = new MesProBagCodeBindBatchReqVO(); |
| | | req.setBatchId(batchId); |
| | | req.setIds(tonBagIds); |
| | | bagCodeService.bindBatch(req); |
| | | } |
| | | |
| | | private void bindPalletsToBatch(Long batchId, List<Long> palletIds) { |
| | | if (CollUtil.isEmpty(palletIds)) { |
| | | return; |