云
7 小时以前 02a1ae81eba7b48223052fb833859f0a65dd7d21
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java
@@ -18,6 +18,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceRespVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeGenerateTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeReplaceDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
@@ -26,9 +27,11 @@
import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
import cn.iocoder.yudao.module.mes.dal.redis.MesCodeRedisDAO;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeGenerateBizTypeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum;
import cn.iocoder.yudao.module.mes.framework.bagcode.MesProBagCodeGenerateProperties;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService;
import jakarta.annotation.Resource;
@@ -46,6 +49,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -92,8 +96,16 @@
    @Resource
    private cn.iocoder.yudao.module.mes.service.trace.integrity.MesTraceIntegrityService integrityService;
    @Resource
    private MesProBagCodeBatchGenerator batchGenerator;
    @Resource
    private MesProBagCodeGenerateCleaner generateCleaner;
    @Resource
    private MesProBagCodeGenerateTaskService generateTaskService;
    @Resource
    private MesProBagCodeGenerateProperties generateProperties;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProBagCodeGenerateRespVO generateBagCodes(MesProBagCodeGenerateReqVO generateReqVO) {
        // 1. 校验品种并读取主计量单位类型
        MesMdmItemDO item = mdmItemService.validateItemExists(generateReqVO.getItemId());
@@ -102,7 +114,7 @@
        // 2. 按类型换算袋码数量:N袋 = 每托 N 袋 × 托盘数;吨 = 直接输入袋码数(无托盘)
        boolean ton = UNIT_TYPE_TON.equals(type);
        int perPallet = ton ? 0 : parseBagsPerPallet(type);
        int totalCount;
        long totalCount;
        int palletCount = 0;
        if (ton) {
            if (generateReqVO.getBagCount() == null || generateReqVO.getBagCount() <= 0) {
@@ -114,79 +126,94 @@
                throw exception(PRO_BAG_CODE_PALLET_COUNT_REQUIRED);
            }
            palletCount = generateReqVO.getPalletCount();
            totalCount = perPallet * palletCount;
            // 用 long 计算,避免大托盘数相乘溢出 int
            totalCount = (long) perPallet * palletCount;
        }
        assertGenerateCountAllowed(totalCount);
        // 3. 生成阶段:袋码直接绑定品种;生产批次在后续录入
        String uuid = IdUtil.fastSimpleUUID();
        List<MesProPalletDO> pallets = new ArrayList<>(palletCount);
        if (!ton) {
            for (int i = 0; i < palletCount; i++) {
                pallets.add(new MesProPalletDO()
                        .setTempCode(codeRedisDao.generate())
                        .setBusinessCode(codeRedisDao.generate())
                        .setBagCount(perPallet)
                        .setAutoGenerated(true)
                        .setItemId(item.getId())
                        .setStatus(MesProPalletStatusEnum.IN_USE.getStatus()));
            }
            palletMapper.insertBatch(pallets);
        MesProBagCodeGenerateSpec spec = new MesProBagCodeGenerateSpec(
                MesProBagCodeGenerateBizTypeEnum.PACKAGE, IdUtil.fastSimpleUUID(),
                item.getId(), item.getCode(), item.getName(), type,
                perPallet, palletCount, (int) totalCount, generateReqVO.getRemark());
        // 4. 小批量同步完成,大批量转后台任务
        if (totalCount <= generateProperties.getSyncThreshold()) {
            MesProBagCodeBatchGenerator.GenerateResult result = runSyncGenerate(spec);
            return new MesProBagCodeGenerateRespVO()
                    .setUuid(spec.uuid())
                    .setType(type)
                    .setPerPallet(perPallet)
                    .setTotalCount(result.finishedCount())
                    .setPalletCount(result.palletIds().size())
                    .setPalletIds(result.palletIds())
                    .setAsync(false);
        }
        // 袋序号原子分配:先取库内最大序号作基准,再由 Redis 原子自增,避免并发生成产生重复袋序号
        int startNo = codeRedisDao.allocateBagNoStart(totalCount, bagCodeMapper.selectMaxBagNo());
        List<MesProBagCodeDO> bagCodes = new ArrayList<>(totalCount);
        for (int i = 0; i < totalCount; i++) {
            int bagNo = startNo + i;
            MesProPalletDO pallet = ton ? null : pallets.get(i / perPallet);
            bagCodes.add(new MesProBagCodeDO()
                    .setUuid(uuid)
                    .setCode(codeRedisDao.generate())
                    .setBusinessCode(codeRedisDao.generate())
                    .setBagNo(bagNo)
                    .setItemId(item.getId())
                    .setItemCode(item.getCode())
                    .setItemName(item.getName())
                    .setPalletId(pallet == null ? null : pallet.getId())
                    .setPalletCode(pallet == null ? null : pallet.getTempCode())
                    .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus())
                    .setRemark(generateReqVO.getRemark()));
        }
        bagCodeMapper.insertBatch(bagCodes);
        MesProBagCodeGenerateTaskDO task = generateTaskService.submit(spec, generateReqVO.getRequestId());
        return new MesProBagCodeGenerateRespVO()
                .setUuid(uuid)
                .setType(type)
                .setPerPallet(perPallet)
                .setTotalCount(totalCount)
                .setPalletIds(pallets.stream().map(MesProPalletDO::getId).toList());
                .setUuid(task.getUuid())
                .setType(task.getUnitType())
                .setPerPallet(task.getPerPallet())
                .setTotalCount(task.getTotalCount())
                .setPalletCount(task.getPalletCount())
                .setAsync(true)
                .setTaskId(task.getId());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProBagCodeGenerateRespVO generateRawBagCodes(MesProBagCodeRawGenerateReqVO generateReqVO) {
        // 原始袋码不依赖计量单位类型与托盘:仅按数量生成,袋码带品种冗余,后续可正常录入批次/绑定托盘
        MesMdmItemDO item = mdmItemService.validateItemExists(generateReqVO.getItemId());
        String uuid = IdUtil.fastSimpleUUID();
        // 袋序号原子分配:先取库内最大序号作基准,再由 Redis 原子自增,避免并发生成产生重复袋序号
        int startNo = codeRedisDao.allocateBagNoStart(generateReqVO.getCount(), bagCodeMapper.selectMaxBagNo());
        List<MesProBagCodeDO> bagCodes = new ArrayList<>(generateReqVO.getCount());
        for (int i = 0; i < generateReqVO.getCount(); i++) {
            int bagNo = startNo + i;
            bagCodes.add(new MesProBagCodeDO()
                    .setUuid(uuid)
                    .setCode(codeRedisDao.generate())
                    .setBusinessCode(codeRedisDao.generate())
                    .setBagNo(bagNo)
                    .setItemId(item.getId())
                    .setItemCode(item.getCode())
                    .setItemName(item.getName())
                    .setStatus(MesProBagCodeStatusEnum.GENERATED.getStatus())
                    .setRemark(generateReqVO.getRemark()));
        long totalCount = generateReqVO.getCount();
        assertGenerateCountAllowed(totalCount);
        MesProBagCodeGenerateSpec spec = new MesProBagCodeGenerateSpec(
                MesProBagCodeGenerateBizTypeEnum.RAW, IdUtil.fastSimpleUUID(),
                item.getId(), item.getCode(), item.getName(), null,
                0, 0, (int) totalCount, generateReqVO.getRemark());
        if (totalCount <= generateProperties.getSyncThreshold()) {
            MesProBagCodeBatchGenerator.GenerateResult result = runSyncGenerate(spec);
            return new MesProBagCodeGenerateRespVO()
                    .setUuid(spec.uuid())
                    .setTotalCount(result.finishedCount())
                    .setPalletCount(0)
                    .setPalletIds(List.of())
                    .setAsync(false);
        }
        bagCodeMapper.insertBatch(bagCodes);
        MesProBagCodeGenerateTaskDO task = generateTaskService.submit(spec, generateReqVO.getRequestId());
        return new MesProBagCodeGenerateRespVO()
                .setUuid(uuid)
                .setTotalCount(bagCodes.size())
                .setPalletIds(List.of());
                .setUuid(task.getUuid())
                .setTotalCount(task.getTotalCount())
                .setPalletCount(task.getPalletCount())
                .setAsync(true)
                .setTaskId(task.getId());
    }
    /**
     * 同步生成:失败时清理已写入的分段,避免留下不完整的数据包
     */
    private MesProBagCodeBatchGenerator.GenerateResult runSyncGenerate(MesProBagCodeGenerateSpec spec) {
        try {
            return batchGenerator.generate(spec, null, new AtomicBoolean(false));
        } catch (RuntimeException e) {
            try {
                generateCleaner.cleanup(spec.uuid());
            } catch (Exception cleanupException) {
                log.error("[runSyncGenerate][生成组 {} 半成品清理失败]", spec.uuid(), cleanupException);
            }
            throw e;
        }
    }
    /**
     * 校验单次生成袋码数量不超过上限
     */
    private void assertGenerateCountAllowed(long totalCount) {
        int maxCount = generateProperties.getMaxTaskCount();
        if (totalCount > maxCount) {
            throw exception(PRO_BAG_CODE_GENERATE_COUNT_TOO_MANY, maxCount);
        }
    }
    /**