云
21 小时以前 704265308b1227dbada5a36b2027b588c062ded2
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java
@@ -80,6 +80,7 @@
        // 1. 校验关联数据(产线、生产人员)
        MesProLineDO line = lineService.validateLineExistsAndEnable(createReqVO.getLineId());
        validateRelations(createReqVO);
        createReqVO.setItemId(resolveBindingItem(null, createReqVO));
        // 2. 生成批次号(编码规则:前缀 + 日期 + 产线编码 + 流水号)
        String code = generateBatchCode(line.getCode());
@@ -88,6 +89,7 @@
        boolean bindPallets = CollUtil.isNotEmpty(createReqVO.getPalletIds());
        MesProBatchDO batch = BeanUtils.toBean(createReqVO, MesProBatchDO.class)
                .setCode(code)
                .setBusinessCode(codeRedisDao.generate())
                .setLineCode(line.getCode())
                .setLineName(line.getName())
                .setBagQuantity(0)
@@ -108,12 +110,12 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<MesProBatchDO> preGenerateBatch(MesProBatchPreGenerateReqVO reqVO) {
        // 1. 生成临时批次码(全局 8 位序号,格式 PB-00000001)
        int startSeq = batchMapper.selectMaxTempCodeSeq() + 1;
        // 1. 通过统一编码生成器分配临时码及业务码,不再解析历史临时码序号。
        List<MesProBatchDO> list = new ArrayList<>(reqVO.getCount());
        for (int i = 0; i < reqVO.getCount(); i++) {
            list.add(new MesProBatchDO()
                    .setTempCode(codeRedisDao.generate())
                    .setBusinessCode(codeRedisDao.generate())
                    .setBagQuantity(0)
                    .setPalletQuantity(0)
                    .setStatus(MesProBatchStatusEnum.DRAFT.getStatus())
@@ -140,6 +142,7 @@
        // 2. 校验关联数据并生成正式批次号
        MesProLineDO line = lineService.validateLineExistsAndEnable(fillReqVO.getLineId());
        validateRelations(fillReqVO);
        fillReqVO.setItemId(resolveBindingItem(batch, fillReqVO));
        String code = generateBatchCode(line.getCode());
        // 3. 落库业务信息(保留临时批次码用于历史扫码追溯;产线编码/名称冗余回填;勾选绑定托盘时批次直接进入生产中)
        boolean bindPallets = CollUtil.isNotEmpty(fillReqVO.getPalletIds());
@@ -163,10 +166,12 @@
    @Transactional(rollbackFor = Exception.class)
    public void updateBatch(MesProBatchSaveReqVO updateReqVO) {
        // 1. 校验存在 + 状态(草稿/生产中才允许修改)
        MesProBatchDO oldBatch = validateBatchExists(updateReqVO.getId());
        MesProBatchDO oldBatch = batchMapper.selectByIdForUpdate(updateReqVO.getId());
        if (oldBatch == null) throw exception(PRO_BATCH_NOT_EXISTS);
        validateBatchEditable(oldBatch);
        // 2. 校验关联数据(生产人员)
        validateRelations(updateReqVO);
        updateReqVO.setItemId(resolveBindingItem(oldBatch, updateReqVO));
        // 3. 未生成正式批次号或产线变化时重新生成批次号(批次号包含产线编码要素)
        boolean codeEmpty = StrUtil.isEmpty(oldBatch.getCode());
@@ -250,6 +255,33 @@
    }
    @Override
    public MesProBatchDO getBatchByScanCode(String code) {
        return batchMapper.selectByScanCode(code);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int generateBusinessCodes(java.util.List<Long> ids) {
        if (ids == null || ids.isEmpty() || ids.size() > 200 || ids.stream().anyMatch(java.util.Objects::isNull))
            throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "请选择 1 至 200 条记录");
        java.util.List<MesProBatchDO> records = new java.util.ArrayList<>();
        for (Long id : ids.stream().distinct().sorted().toList()) {
            MesProBatchDO record = batchMapper.selectByIdForUpdate(id);
            if (record == null) throw exception(PRO_BATCH_NOT_EXISTS);
            if (MesProBatchStatusEnum.CANCELED.getStatus().equals(record.getStatus()))
                throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "已作废记录不可补生成业务码");
            records.add(record);
        }
        int count = 0;
        for (MesProBatchDO record : records) {
            if (StrUtil.isNotBlank(record.getBusinessCode())) continue;
            batchMapper.updateById(new MesProBatchDO().setId(record.getId()).setBusinessCode(codeRedisDao.generate()));
            count++;
        }
        return count;
    }
    @Override
    public MesProBatchDO getBatchByCode(String code) {
        MesProBatchDO batch = batchMapper.selectByCode(code);
        if (batch == null) {
@@ -311,6 +343,28 @@
     * 批次绑定托盘(可选):同事务内复用托盘绑定批次逻辑,
     * 生成正式托盘码并级联录入托盘下未绑批次的袋码,失败整体回滚
     */
    private Long resolveBindingItem(MesProBatchDO batch, MesProBatchSaveReqVO req) {
        Long itemId = batch == null ? null : batch.getItemId();
        if (batch != null) {
            for (Long existing : bagCodeMapper.selectDistinctItemIdsByBatchId(batch.getId()))
                itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, existing);
            for (var pallet : palletMapper.selectListByBatchId(batch.getId()))
                itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId,
                        cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.resolve(pallet, bagCodeMapper.selectListByPalletId(pallet.getId())));
        }
        if (CollUtil.isNotEmpty(req.getPalletIds())) for (Long id : req.getPalletIds().stream().distinct().sorted().toList()) {
            var pallet = palletMapper.selectByIdForUpdate(id);
            if (pallet == null) throw exception(PRO_PALLET_NOT_EXISTS);
            Long candidate = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.resolve(pallet, bagCodeMapper.selectListByPalletId(id));
            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 (req.getItemId() != null && !Objects.equals(req.getItemId(), itemId))
            throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400, "品种由绑定托盘带入,不可修改为其他品种");
        return itemId;
    }
    private void bindPalletsToBatch(Long batchId, List<Long> palletIds) {
        if (CollUtil.isEmpty(palletIds)) {
            return;