| | |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchWorkerMapper; |
| | | 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.md.autocode.MesMdAutoCodeRuleCodeEnum; |
| | | 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 org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | import java.util.regex.Pattern; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| | |
| | | @Validated |
| | | @Slf4j |
| | | public class MesProBatchServiceImpl implements MesProBatchService { |
| | | |
| | | /** |
| | | * 批次号格式:4 位年份 + 3 位流水(如 2026001) |
| | | */ |
| | | private static final Pattern BATCH_CODE_PATTERN = Pattern.compile("^[0-9]{7}$"); |
| | | private static final int BATCH_CODE_MAX_SERIAL = 999; |
| | | |
| | | @Resource |
| | | private MesProBatchMapper batchMapper; |
| | |
| | | private MesCodeRedisDAO codeRedisDao; |
| | | |
| | | @Resource |
| | | private MesMdAutoCodeRecordService autoCodeRecordService; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private MesProLineService lineService; |
| | |
| | | validateRelations(createReqVO); |
| | | createReqVO.setItemId(resolveBindingItem(null, createReqVO)); |
| | | |
| | | // 2. 生成批次号(编码规则:前缀 + 日期 + 产线编码 + 流水号) |
| | | String code = generateBatchCode(line.getCode()); |
| | | // 2. 确定批次号(人工输入优先,未输入则按「年份+3位流水」自动生成) |
| | | String code = resolveBatchCode(createReqVO.getCode(), null); |
| | | |
| | | // 3. 插入批次(产线编码/名称冗余回填;勾选绑定托盘时批次直接进入生产中) |
| | | boolean hasBindings = hasBindings(createReqVO); |
| | |
| | | if (StrUtil.isNotEmpty(batch.getCode())) { |
| | | throw exception(PRO_BATCH_ALREADY_FILLED); |
| | | } |
| | | // 2. 校验关联数据并生成正式批次号 |
| | | // 2. 校验关联数据并确定正式批次号(人工输入优先,未输入则自动生成) |
| | | MesProLineDO line = lineService.validateLineExistsAndEnable(fillReqVO.getLineId()); |
| | | validateRelations(fillReqVO); |
| | | fillReqVO.setItemId(resolveBindingItem(batch, fillReqVO)); |
| | | String code = generateBatchCode(line.getCode()); |
| | | String code = resolveBatchCode(fillReqVO.getCode(), null); |
| | | // 3. 落库业务信息(保留临时批次码用于历史扫码追溯;产线编码/名称冗余回填;勾选绑定托盘时批次直接进入生产中) |
| | | boolean hasBindings = hasBindings(fillReqVO); |
| | | MesProBatchDO updateObj = BeanUtils.toBean(fillReqVO, MesProBatchDO.class) |
| | |
| | | validateRelations(updateReqVO); |
| | | updateReqVO.setItemId(resolveBindingItem(oldBatch, updateReqVO)); |
| | | |
| | | // 3. 未生成正式批次号或产线变化时重新生成批次号(批次号包含产线编码要素) |
| | | // 3. 批次号仅含年份与流水号,生成后不再随产线变化;未生成正式批次号时补生成 |
| | | boolean codeEmpty = StrUtil.isEmpty(oldBatch.getCode()); |
| | | boolean lineChanged = !Objects.equals(oldBatch.getLineId(), updateReqVO.getLineId()); |
| | | String code = oldBatch.getCode(); |
| | | String code = codeEmpty ? resolveBatchCode(updateReqVO.getCode(), oldBatch.getId()) : oldBatch.getCode(); |
| | | MesProBatchDO updateObj = BeanUtils.toBean(updateReqVO, MesProBatchDO.class); |
| | | if (codeEmpty || lineChanged) { |
| | | // 变更产线时,目标产线必须启用;未变更则仅校验存在(避免停用产线的历史批次无法编辑其他字段) |
| | | MesProLineDO line = (lineChanged || codeEmpty) |
| | | ? lineService.validateLineExistsAndEnable(updateReqVO.getLineId()) |
| | | : lineService.validateLineExists(updateReqVO.getLineId()); |
| | | code = generateBatchCode(line.getCode()); |
| | | if (lineChanged || codeEmpty) { |
| | | // 变更产线时,目标产线必须启用 |
| | | MesProLineDO line = lineService.validateLineExistsAndEnable(updateReqVO.getLineId()); |
| | | updateObj.setLineCode(line.getCode()).setLineName(line.getName()); |
| | | } |
| | | // 4. 更新批次(勾选绑定托盘时批次直接进入生产中;取消勾选不回退,已绑定托盘不可解绑) |
| | |
| | | } |
| | | |
| | | /** |
| | | * 生成批次号 |
| | | * <p> |
| | | * 编码规则:固定前缀(PC) + 日期(yyyyMMdd) + 输入字符(产线编码) + 流水号(按日循环) |
| | | * 确定批次号:人工输入优先,未输入则按「当前年份 + 3 位流水」自动生成 |
| | | * |
| | | * @param lineCode 产线编码 |
| | | * @return 批次号 |
| | | * @param requestedCode 人工输入的批次号,可为空 |
| | | * @param selfId 当前批次编号(更新时排除自身,新增传 null) |
| | | * @return 最终批次号 |
| | | */ |
| | | private String generateBatchCode(String lineCode) { |
| | | String inputChar = StrUtil.emptyToDefault(lineCode, ""); |
| | | return autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_BATCH_CODE.getCode(), inputChar); |
| | | private String resolveBatchCode(String requestedCode, Long selfId) { |
| | | if (StrUtil.isBlank(requestedCode)) { |
| | | return generateNextBatchCode(); |
| | | } |
| | | String code = StrUtil.trim(requestedCode); |
| | | validateBatchCode(code, selfId); |
| | | return code; |
| | | } |
| | | |
| | | /** |
| | | * 校验批次号格式(4 位年份 + 3 位流水,如 2026001)及唯一性 |
| | | */ |
| | | private void validateBatchCode(String code, Long selfId) { |
| | | if (!BATCH_CODE_PATTERN.matcher(code).matches()) { |
| | | throw exception(PRO_BATCH_CODE_INVALID); |
| | | } |
| | | int year = Integer.parseInt(code.substring(0, 4)); |
| | | int serial = Integer.parseInt(code.substring(4)); |
| | | if (year < 2000 || year > 2100 || serial < 1) { |
| | | throw exception(PRO_BATCH_CODE_INVALID); |
| | | } |
| | | MesProBatchDO existing = batchMapper.selectByCodeIgnoreDeleted(code); |
| | | if (existing != null && !Objects.equals(existing.getId(), selfId)) { |
| | | throw exception(PRO_BATCH_CODE_DUPLICATE, code); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 生成当前年份的下一个批次号 |
| | | */ |
| | | private String generateNextBatchCode() { |
| | | String year = String.valueOf(LocalDate.now().getYear()); |
| | | Integer maxSerial = batchMapper.selectMaxCodeSerialByYear(year); |
| | | int serial = codeRedisDao.allocateBatchNo(year, maxSerial); |
| | | if (serial > BATCH_CODE_MAX_SERIAL) { |
| | | throw exception(PRO_BATCH_CODE_EXHAUSTED); |
| | | } |
| | | return year + String.format("%03d", serial); |
| | | } |
| | | |
| | | /** |