| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void bindBatch(MesProBagCodeBindBatchReqVO bindReqVO) { |
| | | MesProBatchDO batch = batchMapper.selectByIdForUpdate(bindReqVO.getBatchId()); |
| | | doBindBatch(bindReqVO.getBatchId(), bindReqVO.getIds()); |
| | | } |
| | | |
| | | /** |
| | | * 将袋码录入指定批次,加锁顺序为「批次 → 袋码 → 托盘」。 |
| | | * <p> |
| | | * 「录入批次」与「绑定托盘」共用此实现:绑定托盘时若托盘已属某批次、而所选袋码尚未录入批次, |
| | | * 先按本方法完成录入,从而保证批次准入校验、品种一致性校验、批次汇总回写与完整性记录只有一份。 |
| | | * |
| | | * @param batchId 生产批次编号 |
| | | * @param rawIds 待录入的袋码编号(未去重) |
| | | */ |
| | | private void doBindBatch(Long batchId, List<Long> rawIds) { |
| | | MesProBatchDO batch = batchMapper.selectByIdForUpdate(batchId); |
| | | if (batch == null) { |
| | | throw exception(PRO_BATCH_NOT_EXISTS); |
| | | } |
| | |
| | | if (StrUtil.isEmpty(batch.getCode())) { |
| | | throw exception(PRO_BATCH_NOT_FILLED); |
| | | } |
| | | List<Long> ids = bindReqVO.getIds().stream().distinct().toList(); |
| | | List<Long> ids = rawIds.stream().distinct().toList(); |
| | | List<MesProBagCodeDO> bagCodes = bagCodeMapper.selectListByIdsForUpdate(ids); |
| | | if (bagCodes.size() != ids.size() || bagCodes.stream().anyMatch(code -> |
| | | code.getBatchId() != null |
| | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void bindPallet(MesProBagCodeBindReqVO bindReqVO) { |
| | | // 1. 锁定托盘,避免与自动归集并发修改袋数 |
| | | // 1. 无锁预读托盘与所选袋码,判断是否需要先为未录入批次的袋码补录批次。 |
| | | // 加锁顺序必须与「录入批次」一致(批次 → 袋码 → 托盘),否则两个入口并发时交叉等待会死锁。 |
| | | List<Long> ids = bindReqVO.getIds().stream().distinct().toList(); |
| | | MesProPalletDO palletSnapshot = palletMapper.selectById(bindReqVO.getPalletId()); |
| | | if (palletSnapshot == null) { |
| | | throw exception(PRO_PALLET_NOT_EXISTS); |
| | | } |
| | | List<MesProBagCodeDO> snapshots = bagCodeMapper.selectListByIds(ids); |
| | | if (snapshots.size() != ids.size()) { |
| | | throw exception(PRO_BAG_CODE_NOT_EXISTS); |
| | | } |
| | | // 2. 托盘未绑定批次时,袋码也必须未录入批次(反向不成立:未录入批次的袋码可绑到任意托盘) |
| | | if (palletSnapshot.getBatchId() == null |
| | | && snapshots.stream().anyMatch(code -> code.getBatchId() != null)) { |
| | | throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, |
| | | "所选袋码已录入生产批次,请改选该批次下的托盘"); |
| | | } |
| | | // 3. 托盘已绑定批次、且存在未录入批次的袋码时,复用「录入批次」逻辑补录 |
| | | if (palletSnapshot.getBatchId() != null) { |
| | | List<Long> unboundIds = snapshots.stream() |
| | | .filter(code -> code.getBatchId() == null && code.getPalletId() == null |
| | | && !MesProBagCodeStatusEnum.CANCELED.getStatus().equals(code.getStatus())) |
| | | .map(MesProBagCodeDO::getId).toList(); |
| | | if (!unboundIds.isEmpty()) { |
| | | doBindBatch(palletSnapshot.getBatchId(), unboundIds); |
| | | } |
| | | } |
| | | // 4. 锁定托盘,避免与自动归集并发修改袋数 |
| | | MesProPalletDO pallet = palletMapper.selectByIdForUpdate(bindReqVO.getPalletId()); |
| | | if (pallet == null) { |
| | | throw exception(PRO_PALLET_NOT_EXISTS); |
| | | } |
| | | // 2. 锁定并校验袋码 |
| | | List<MesProBagCodeDO> bagCodes = validateBagCodesForBind(bindReqVO.getIds(), pallet, true); |
| | | // 5. 锁定并校验袋码(录入批次后,袋码与托盘必然同批次) |
| | | List<MesProBagCodeDO> bagCodes = validateBagCodesForBind(ids, pallet, true); |
| | | Long itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.resolve(pallet, |
| | | bagCodeMapper.selectListByPalletId(pallet.getId())); |
| | | for (MesProBagCodeDO bag : bagCodes) { |
| | |
| | | itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, bag.getItemId()); |
| | | } |
| | | palletMapper.updateById(new MesProPalletDO().setId(pallet.getId()).setItemId(itemId)); |
| | | // 3. 绑定袋码并累加托盘已装袋数(托盘无容量上限) |
| | | // 6. 绑定袋码并累加托盘已装袋数(托盘无容量上限) |
| | | int currentCount = pallet.getBagCount() == null ? 0 : pallet.getBagCount(); |
| | | bagCodeMapper.bindPalletByIds(bindReqVO.getIds(), pallet.getId(), StrUtil.isNotBlank(pallet.getCode()) ? pallet.getCode() : pallet.getTempCode()); |
| | | bagCodeMapper.bindPalletByIds(ids, pallet.getId(), StrUtil.isNotBlank(pallet.getCode()) ? pallet.getCode() : pallet.getTempCode()); |
| | | palletService.updatePalletBagCount(pallet.getId(), currentCount + bagCodes.size()); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Long getPrintableBagCodeCountByUuid(String uuid) { |
| | | return bagCodeMapper.selectPrintableCountByUuid(uuid); |
| | | } |
| | | |
| | | @Override |
| | | public Long getPrintableBagCodeCountByBatchId(Long batchId) { |
| | | return bagCodeMapper.selectPrintableCountByBatchId(batchId); |
| | | } |
| | | |
| | | @Override |
| | | public List<MesProBagCodeDO> getPrintableBagCodePageByUuid(String uuid, int pageNo, int pageSize) { |
| | | return bagCodeMapper.selectPrintablePageByUuid(uuid, pageNo, pageSize); |
| | | } |
| | | |
| | | @Override |
| | | public List<MesProBagCodeDO> getPrintableBagCodePageByBatchId(Long batchId, int pageNo, int pageSize) { |
| | | return bagCodeMapper.selectPrintablePageByBatchId(batchId, pageNo, pageSize); |
| | | } |
| | | |
| | | @Override |
| | | public void markOutbound(Long id, Long salesId) { |
| | | bagCodeMapper.markOutbound(id, salesId, LocalDateTime.now()); |
| | | } |