package jnpf.limsService.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import jnpf.audit.sdk.annotation.AuditLog; import jnpf.base.ActionResult; import jnpf.base.BillRuleApi; import jnpf.base.UserInfo; import jnpf.base.service.SuperServiceImpl; import jnpf.exception.DataException; import jnpf.limsEntity.LimsHaocaiChukuEntity; import jnpf.limsEntity.LimsHaocaiChukuShenqingEntity; import jnpf.limsEntity.LimsHaocaiChukuShenqingZixiangEntity; import jnpf.limsEntity.LimsHaocaiChukuZixiangEntity; import jnpf.limsEntity.LimsHaocaiJiechuEntity; import jnpf.limsEntity.LimsHaocaiLogEntity; import jnpf.limsEntity.LimsHaocaiRukuEntity; import jnpf.limsEntity.LimsHaocaiRukuSubmitResult; import jnpf.limsEntity.LimsHaocaiRukuYanshouEntity; import jnpf.limsEntity.LimsHaocaiRukuZixiangEntity; import jnpf.limsEntity.LimsHaocaiXiaohuiShenqingEntity; import jnpf.limsEntity.LimsHaocaiXiaohuiShenqingZixiangEntity; import jnpf.limsEntity.LimsHaocaiYanshouShenqingEntity; import jnpf.limsMapper.LimsHaocaiChukuShenqingMapper; import jnpf.limsMapper.LimsHaocaiChukuShenqingZixiangMapper; import jnpf.limsMapper.LimsHaocaiChukuZixiangMapper; import jnpf.limsMapper.LimsHaocaiJiechuMapper; import jnpf.limsMapper.LimsHaocaiLogMapper; import jnpf.limsMapper.LimsHaocaiRukuMapper; import jnpf.limsMapper.LimsHaocaiRukuYanshouMapper; import jnpf.limsMapper.LimsHaocaiRukuZixiangMapper; import jnpf.limsMapper.LimsHaocaiXiaohuiShenqingMapper; import jnpf.limsMapper.LimsHaocaiXiaohuiShenqingZixiangMapper; import jnpf.limsMapper.LimsHaocaiYanshouShenqingMapper; import jnpf.limsService.LimsHaocaiService; import jnpf.util.RandomUtil; import jnpf.util.StringUtil; import jnpf.util.UserProvider; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 耗材业务服务实现。子表 lims_haocai_ruku_zixiang 不单设 service, * 经 Mapper 直接读写(平台 ProductServiceImpl 同款模式)。 */ @Slf4j @Service public class LimsHaocaiServiceImpl extends SuperServiceImpl implements LimsHaocaiService { /** 条码编码的单据规则 enCode(base_bill_rule,HC_ + YYYYMMDD + 6位流水) */ private static final String TIAOMA_BILL_RULE = "haocai_ruku_tiaoma"; /** 子表初始状态:未出库 */ private static final String BIZ_STATUS_WEICHUKU = "weichuku"; /** 子表状态:已出库 */ private static final String BIZ_STATUS_YICHUKU = "yichuku"; /** 子表状态:已开瓶 */ private static final String BIZ_STATUS_YIKAIPING = "yikaiping"; /** 子表状态:已销毁 */ private static final String BIZ_STATUS_YIXIAOHUI = "yixiaohui"; /** 子表状态:待销毁 */ private static final String BIZ_STATUS_DAIXIAOHUI = "daixiaohui"; /** 申领流程状态:已通过 */ private static final int FLOW_STATE_APPROVED = 2; /** 借出单状态:已借出 */ private static final String JIECHU_STATUS_LENT = "lent"; /** 借出单状态:已归还 */ private static final String JIECHU_STATUS_RETURNED = "returned"; /** 耗材日志操作类型:入库 */ private static final String LOG_ACTION_RUKU = "耗材入库"; /** 耗材日志操作类型:借出 */ private static final String LOG_ACTION_JIECHU = "耗材借出"; /** 耗材日志操作类型:归还 */ private static final String LOG_ACTION_GUIHUAN = "耗材归还"; /** 耗材日志操作类型:出库 */ private static final String LOG_ACTION_CHUKU = "耗材出库"; /** 耗材日志操作类型:销毁 */ private static final String LOG_ACTION_XIAOHUI = "耗材销毁"; /** 验收状态:已验收 */ private static final String YANSHOU_STATUS_YIYANSHOU = "yiyanshou"; /** 验收状态:不适用 */ private static final String YANSHOU_STATUS_NA = "na"; /** 耗材验收申请状态:待验收 */ private static final String YANSHOU_SHENQING_STATUS_DAIYANSHOU = "daiyanshou"; /** 耗材验收申请状态:验收中 */ private static final String YANSHOU_SHENQING_STATUS_YANSHOUZHONG = "yanshouzhong"; /** 耗材验收申请状态:已验收 */ private static final String YANSHOU_SHENQING_STATUS_YIYANSHOU = "yiyanshou"; @Autowired private BillRuleApi billRuleApi; @Autowired private LimsHaocaiRukuZixiangMapper zixiangMapper; @Autowired private LimsHaocaiJiechuMapper jiechuMapper; @Autowired private LimsHaocaiChukuShenqingMapper chukuShenqingMapper; @Autowired private LimsHaocaiChukuShenqingZixiangMapper chukuShenqingZixiangMapper; @Autowired private LimsHaocaiChukuZixiangMapper chukuZixiangMapper; @Autowired private LimsHaocaiLogMapper haocaiLogMapper; @Autowired private LimsHaocaiXiaohuiShenqingMapper xiaohuiShenqingMapper; @Autowired private LimsHaocaiXiaohuiShenqingZixiangMapper xiaohuiShenqingZixiangMapper; @Autowired private LimsHaocaiYanshouShenqingMapper yanshouShenqingMapper; @Autowired private LimsHaocaiRukuYanshouMapper rukuYanshouMapper; @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "RUKU_SUBMIT", label = "耗材入库提交", bizType = "HAOCAI", bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_ruku", targetIdExpr = "#ruku.id", diff = true, entityClass = LimsHaocaiRukuEntity.class) public LimsHaocaiRukuSubmitResult submitRuku(LimsHaocaiRukuEntity ruku) throws DataException { BigDecimal shuliang = parseShuliang(ruku.getRukuShuliang()); BigDecimal rongliang = parseRongliang(ruku.getRongliang()); String yanshouZhuangtai = resolveYanshouZhuangtai(ruku); ruku.setYanshouZhuangtai(yanshouZhuangtai); ruku.setBizStatus(BIZ_STATUS_WEICHUKU); if (!this.updateById(ruku)) { throw new DataException("入库单状态更新失败:" + ruku.getId()); } if (isYanshouEnabled(ruku)) { updateRukuYanshouRukuId(ruku); } boolean yigeTiaoma = "yes".equalsIgnoreCase(trimToEmpty(ruku.getShifouYigeTiaoma())); int completeCount = calculateCompleteTiaomaCount(shuliang, ruku.getRukuShuliang()); BigDecimal fractionalQuantity = shuliang.remainder(BigDecimal.ONE); int count = yigeTiaoma ? 1 : completeCount + (fractionalQuantity.signum() > 0 ? 1 : 0); // 一个条码:总量 = 入库数量 × 容量;多个条码:整数部分每条为容量,小数部分按比例生成最后一条。 BigDecimal singleTiaomaZongliang = rongliang.multiply(shuliang); // 编号 = 批号 + "-" + 序号,序号按本批总条数动态补零(3→1位、20→2位、100→3位), // 数量多大都自适应;批号为空时回退用主表 id,避免编号缺前缀 String bianhaoPrefix = trimToEmpty(ruku.getPihao()); if (bianhaoPrefix.isEmpty()) { bianhaoPrefix = ruku.getId(); } int width = String.valueOf(count).length(); List tiaomaBianmaList = new ArrayList<>(count); List bianhaoList = new ArrayList<>(count); for (int i = 1; i <= count; i++) { String tiaomaBianma = nextTiaomaBianma(); String bianhao = bianhaoPrefix + "-" + String.format("%0" + width + "d", i); BigDecimal zongliang = yigeTiaoma ? singleTiaomaZongliang : (i > completeCount ? rongliang.multiply(fractionalQuantity) : rongliang); tiaomaBianmaList.add(tiaomaBianma); bianhaoList.add(bianhao); LimsHaocaiRukuZixiangEntity zixiang = buildZixiang(ruku, tiaomaBianma, bianhao, toPlainString(zongliang), yanshouZhuangtai); zixiangMapper.insert(zixiang); haocaiLogMapper.insert(buildRukuLog(ruku, zixiang)); } log.info("耗材入库提交完成:rukuId={}, tiaomaList={}, count={}, rukuShuliang={}", ruku.getId(), tiaomaBianmaList, count, ruku.getRukuShuliang()); LimsHaocaiRukuSubmitResult result = new LimsHaocaiRukuSubmitResult(); result.setTiaomaBianma(tiaomaBianmaList.isEmpty() ? null : tiaomaBianmaList.get(0)); result.setTiaomaBianmaList(tiaomaBianmaList); result.setBianhaoList(bianhaoList); result.setCount(count); return result; } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "JIECHU_SUBMIT", label = "耗材借出提交", bizType = "HAOCAI", bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_jiechu", targetIdExpr = "#jiechu.id", diff = true, entityClass = LimsHaocaiJiechuEntity.class) public void submitJiechu(LimsHaocaiJiechuEntity jiechu) throws DataException { String tiaomaBianma = trimToEmpty(jiechu == null ? null : jiechu.getTiaomaBianma()); if (tiaomaBianma.isEmpty()) { throw new DataException("条码编码不能为空"); } List list = listRukuZixiangByTiaomaBianma(tiaomaBianma); if (list == null || list.isEmpty()) { throw new DataException("未找到对应的耗材入库子项:" + tiaomaBianma); } LimsHaocaiRukuZixiangEntity zixiang = list.get(0); String bizStatus = trimToEmpty(zixiang.getBizStatus()); if (BIZ_STATUS_YICHUKU.equalsIgnoreCase(bizStatus)) { throw new DataException("该耗材已出库,不能重复借出:" + tiaomaBianma); } if (!isBorrowableStatus(bizStatus)) { throw new DataException("该耗材状态不允许借出:" + bizStatus); } BigDecimal jiechuShuliang = parseRequiredDecimal(jiechu.getJiechuShuliang(), "借出数量不能为空或格式无效"); BigDecimal shengyuliang = parseRequiredDecimal(zixiang.getShengyuliang(), "耗材剩余量无效:" + tiaomaBianma); if (jiechuShuliang.compareTo(BigDecimal.ZERO) <= 0) { throw new DataException("借出数量必须大于0"); } if (jiechuShuliang.compareTo(shengyuliang) != 0) { throw new DataException("耗材状态已变化,请刷新后重新提交"); } LimsHaocaiRukuZixiangEntity update = new LimsHaocaiRukuZixiangEntity(); update.setId(zixiang.getId()); update.setBizStatus(BIZ_STATUS_YICHUKU); update.setShengyuliang("0"); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiRukuZixiangEntity::getId, zixiang.getId()); wrapper.in(LimsHaocaiRukuZixiangEntity::getBizStatus, BIZ_STATUS_WEICHUKU, BIZ_STATUS_YIKAIPING); wrapper.eq(LimsHaocaiRukuZixiangEntity::getShengyuliang, zixiang.getShengyuliang()); int updated = zixiangMapper.update(update, wrapper); if (updated <= 0) { throw new DataException("耗材状态已变化,请刷新后重新提交"); } updateRukuJieyuShuliang(zixiang, false, BIZ_STATUS_YICHUKU); zixiang.setBizStatus(BIZ_STATUS_YICHUKU); zixiang.setShengyuliang("0"); haocaiLogMapper.insert(buildJiechuLog(jiechu, zixiang)); log.info("耗材借出提交完成:tiaomaBianma={}, zixiangId={}", tiaomaBianma, zixiang.getId()); } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "GUIHUAN_SUBMIT", label = "耗材归还提交", bizType = "HAOCAI", bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_jiechu", targetIdExpr = "#guihuan.id", diff = true, entityClass = LimsHaocaiJiechuEntity.class) public void submitGuihuan(LimsHaocaiJiechuEntity guihuan) throws DataException { String tiaomaBianma = trimToEmpty(guihuan == null ? null : guihuan.getTiaomaBianma()); if (tiaomaBianma.isEmpty()) { throw new DataException("条码编码不能为空"); } LimsHaocaiJiechuEntity lentRecord = getLentJiechuByTiaomaBianma(tiaomaBianma); if (lentRecord == null) { throw new DataException("未找到已借出的耗材借出记录:" + tiaomaBianma); } LimsHaocaiJiechuEntity jiechuUpdate = new LimsHaocaiJiechuEntity(); jiechuUpdate.setId(lentRecord.getId()); jiechuUpdate.setBizStatus(JIECHU_STATUS_RETURNED); jiechuUpdate.setGuihuanRen(guihuan.getGuihuanRen()); jiechuUpdate.setGuihuanRiqi(guihuan.getGuihuanRiqi()); jiechuUpdate.setGuihuanRenSign(guihuan.getGuihuanRenSign()); LambdaQueryWrapper jiechuWrapper = new LambdaQueryWrapper<>(); jiechuWrapper.eq(LimsHaocaiJiechuEntity::getId, lentRecord.getId()); jiechuWrapper.eq(LimsHaocaiJiechuEntity::getBizStatus, JIECHU_STATUS_LENT); int jiechuUpdated = jiechuMapper.update(jiechuUpdate, jiechuWrapper); if (jiechuUpdated <= 0) { throw new DataException("该耗材借出记录状态已变化,不能重复归还:" + tiaomaBianma); } List list = listRukuZixiangByTiaomaBianma(tiaomaBianma); if (list == null || list.isEmpty()) { throw new DataException("未找到对应的耗材入库子项:" + tiaomaBianma); } LimsHaocaiRukuZixiangEntity zixiang = list.get(0); String restoreShuliang = trimToEmpty(lentRecord.getJiechuShuliang()); BigDecimal guihuanShuliang = parseRequiredDecimal(restoreShuliang, "借出记录借出数量无效:" + tiaomaBianma); if (guihuanShuliang.compareTo(BigDecimal.ZERO) <= 0) { throw new DataException("借出记录借出数量必须大于0:" + tiaomaBianma); } BigDecimal zongliang = parseRequiredDecimal(zixiang.getZongliang(), "耗材总量无效:" + tiaomaBianma); if (guihuanShuliang.compareTo(zongliang) > 0) { throw new DataException("归还数量不能大于耗材总量:" + tiaomaBianma); } LimsHaocaiRukuZixiangEntity zixiangUpdate = new LimsHaocaiRukuZixiangEntity(); zixiangUpdate.setId(zixiang.getId()); String newBizStatus = resolveAvailableBizStatus(guihuanShuliang, zixiang); zixiangUpdate.setBizStatus(newBizStatus); zixiangUpdate.setShengyuliang(toPlainString(guihuanShuliang)); LambdaQueryWrapper zixiangWrapper = new LambdaQueryWrapper<>(); zixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getId, zixiang.getId()); zixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getBizStatus, BIZ_STATUS_YICHUKU); int zixiangUpdated = zixiangMapper.update(zixiangUpdate, zixiangWrapper); if (zixiangUpdated <= 0) { throw new DataException("该耗材批次状态不允许归还:" + zixiang.getBizStatus()); } zixiang.setShengyuliang(toPlainString(guihuanShuliang)); updateRukuJieyuShuliang(zixiang, guihuanShuliang, true, BIZ_STATUS_YICHUKU); zixiang.setBizStatus(newBizStatus); haocaiLogMapper.insert(buildGuihuanLog(guihuan, lentRecord, zixiang)); log.info("耗材归还提交完成:tiaomaBianma={}, jiechuId={}, zixiangId={}", tiaomaBianma, lentRecord.getId(), zixiang.getId()); } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "CHUKU_SUBMIT", label = "耗材出库提交", bizType = "HAOCAI", bizCodeExpr = "", bizCodeRequired = false, targetTable = "lims_haocai_chuku", targetIdExpr = "#chuku.id", diff = true, entityClass = LimsHaocaiChukuEntity.class) public void submitChuku(LimsHaocaiChukuEntity chuku) throws DataException { if (chuku == null) { throw new DataException("出库表单数据不能为空"); } String chukuId = trimToEmpty(chuku.getId()); if (chukuId.isEmpty()) { throw new DataException("出库单主键(f_id)不能为空"); } String shenlingDanhao = trimToEmpty(chuku.getShenlingDanhao()); if (shenlingDanhao.isEmpty()) { throw new DataException("申领单号不能为空"); } List chukuZixiangList = listChukuZixiang(chukuId); if (chukuZixiangList == null || chukuZixiangList.isEmpty()) { throw new DataException("出库子项不能为空:" + chukuId); } LimsHaocaiChukuShenqingEntity shenqing = getApprovedChukuShenqingByDanhao(shenlingDanhao); if (shenqing == null) { throw new DataException("申领单不存在或未通过:" + shenlingDanhao); } int count = 0; for (LimsHaocaiChukuZixiangEntity chukuZixiang : chukuZixiangList) { submitChukuZixiang(chuku, shenqing, chukuZixiang); count++; } log.info("耗材出库提交完成:shenlingDanhao={}, count={}", shenlingDanhao, count); } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "XIAOHUI_SUBMIT", label = "耗材销毁提交", bizType = "HAOCAI", bizCodeExpr = "#xiaohuiDanhao", bizCodeRequired = false, targetTable = "lims_haocai_ruku_zixiang", targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).firstHaocaiDestroyItem(#xiaohuiDanhao)", diff = true, entityClass = LimsHaocaiRukuZixiangEntity.class) public void submitXiaohui(String xiaohuiDanhao) throws DataException { String danhao = trimToEmpty(xiaohuiDanhao); if (danhao.isEmpty()) { throw new DataException("销毁单号不能为空"); } LimsHaocaiXiaohuiShenqingEntity shenqing = getXiaohuiShenqingByDanhao(danhao); if (shenqing == null) { throw new DataException("销毁申请不存在:" + danhao); } List zixiangList = listXiaohuiZixiang(shenqing.getId()); if (zixiangList.isEmpty()) { throw new DataException("销毁申请子项不能为空:" + danhao); } int count = 0; for (LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang : zixiangList) { String tiaomaBianma = trimToEmpty(xiaohuiZixiang.getTiaomaBianma()); if (tiaomaBianma.isEmpty()) { throw new DataException("销毁申请子项条码编码不能为空"); } LimsHaocaiRukuZixiangEntity rukuZixiang = getUniqueRukuZixiangByTiaomaBianma(tiaomaBianma); if (rukuZixiang == null) { throw new DataException("未找到对应的耗材入库子项:" + tiaomaBianma); } if (BIZ_STATUS_YIXIAOHUI.equalsIgnoreCase(trimToEmpty(rukuZixiang.getBizStatus()))) { throw new DataException("该耗材已销毁,不能重复销毁:" + tiaomaBianma); } BigDecimal xiaohuiShuliang = parseRequiredDecimal(rukuZixiang.getShengyuliang(), "耗材剩余量无效:" + tiaomaBianma); if (xiaohuiShuliang.compareTo(BigDecimal.ZERO) < 0) { throw new DataException("耗材剩余量不能小于0:" + tiaomaBianma); } LimsHaocaiRukuZixiangEntity rukuZixiangUpdate = new LimsHaocaiRukuZixiangEntity(); rukuZixiangUpdate.setId(rukuZixiang.getId()); rukuZixiangUpdate.setBizStatus(BIZ_STATUS_YIXIAOHUI); rukuZixiangUpdate.setShengyuliang("0"); int updated = zixiangMapper.updateById(rukuZixiangUpdate); if (updated <= 0) { throw new DataException("耗材销毁失败:" + tiaomaBianma); } updateRukuJieyuShuliang(rukuZixiang, BIZ_STATUS_YIXIAOHUI); rukuZixiang.setBizStatus(BIZ_STATUS_YIXIAOHUI); rukuZixiang.setShengyuliang("0"); haocaiLogMapper.insert(buildXiaohuiLog(shenqing, xiaohuiZixiang, rukuZixiang, xiaohuiShuliang)); markXiaohuiZixiangDestroyed(xiaohuiZixiang); count++; } log.info("耗材销毁提交完成:xiaohuiDanhao={}, xiaohuiShenqingId={}, count={}", danhao, shenqing.getId(), count); } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "UPDATE", label = "耗材批量转待销毁", bizType = "HAOCAI", bizCodeExpr = "#result > 0 ? 'HAOCAI_RUKU_BATCH' : ''", targetTable = "lims_haocai_ruku_zixiang", targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).firstHaocaiRukuItem(#rukuIds)", diff = true, entityClass = LimsHaocaiRukuZixiangEntity.class) public int batchMarkRukuZixiangDaixiaohui(List rukuIds) throws DataException { List ids = normalizeIds(rukuIds); if (ids.isEmpty()) { throw new DataException("入库单主键数组不能为空"); } UserInfo userInfo = UserProvider.getUser(); String currentUserId = userInfo == null ? "" : trimToEmpty(userInfo.getUserId()); if (currentUserId.isEmpty()) { throw new DataException("未获取到当前登录用户"); } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(LimsHaocaiRukuZixiangEntity::getForeignId, ids); queryWrapper.and(w -> w.isNull(LimsHaocaiRukuZixiangEntity::getDeleteMark) .or() .ne(LimsHaocaiRukuZixiangEntity::getDeleteMark, 1)); List zixiangList = zixiangMapper.selectList(queryWrapper); List zixiangIds = new ArrayList<>(); for (LimsHaocaiRukuZixiangEntity zixiang : zixiangList) { BigDecimal shengyuliang = parseOptionalDecimal(zixiang.getShengyuliang()); if (shengyuliang != null && shengyuliang.compareTo(BigDecimal.ZERO) > 0) { zixiangIds.add(zixiang.getId()); } } if (zixiangIds.isEmpty()) { log.info("耗材入库批量转待销毁无可更新子项:rukuIds={}", ids); return 0; } LimsHaocaiRukuZixiangEntity update = new LimsHaocaiRukuZixiangEntity(); update.setBizStatus(BIZ_STATUS_DAIXIAOHUI); update.setZhuanxiaohuiRen(currentUserId); LambdaQueryWrapper updateWrapper = new LambdaQueryWrapper<>(); updateWrapper.in(LimsHaocaiRukuZixiangEntity::getId, zixiangIds); int updated = zixiangMapper.update(update, updateWrapper); updateRukuBizStatus(ids, BIZ_STATUS_DAIXIAOHUI); log.info("耗材入库批量转待销毁完成:rukuIds={}, zixiangCount={}", ids, updated); return updated; } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "XIAOHUI_START", label = "耗材销毁申请发起", bizType = "HAOCAI", bizCodeExpr = "#result > 0 ? #flowId : ''", targetTable = "lims_haocai_ruku_zixiang", targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).firstHaocaiDestroyItemByFlow(#flowId, #taskId)", diff = true, entityClass = LimsHaocaiRukuZixiangEntity.class) public int startXiaohuiShenqing(String flowId, String taskId) throws DataException { String flow = trimToEmpty(flowId); String task = trimToEmpty(taskId); if (flow.isEmpty()) { throw new DataException("流程id(flow_id)不能为空"); } if (task.isEmpty()) { throw new DataException("流程任务id(task_id)不能为空"); } LimsHaocaiXiaohuiShenqingEntity shenqing = getXiaohuiShenqingByFlow(flow, task); if (shenqing == null) { throw new DataException("销毁申请不存在:flow_id=" + flow + ", task_id=" + task); } String id = shenqing.getId(); String xiaohuiDanhao = trimToEmpty(shenqing.getXiaohuiDanhao()); if (xiaohuiDanhao.isEmpty()) { throw new DataException("销毁单号不能为空:" + id); } List xiaohuiZixiangList = listXiaohuiZixiang(id); if (xiaohuiZixiangList == null || xiaohuiZixiangList.isEmpty()) { throw new DataException("销毁申请子项不能为空:" + xiaohuiDanhao); } int updated = 0; for (LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang : xiaohuiZixiangList) { String tiaomaBianma = trimToEmpty(xiaohuiZixiang.getTiaomaBianma()); if (tiaomaBianma.isEmpty()) { throw new DataException("销毁申请子项条码不能为空:" + xiaohuiDanhao); } LimsHaocaiRukuZixiangEntity update = new LimsHaocaiRukuZixiangEntity(); update.setBizStatus(BIZ_STATUS_DAIXIAOHUI); update.setXiaohuiDanhao(xiaohuiDanhao); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiRukuZixiangEntity::getTiaomaBianma, tiaomaBianma); wrapper.and(w -> w.isNull(LimsHaocaiRukuZixiangEntity::getDeleteMark) .or() .ne(LimsHaocaiRukuZixiangEntity::getDeleteMark, 1)); int rowUpdated = zixiangMapper.update(update, wrapper); if (rowUpdated <= 0) { throw new DataException("未找到对应的耗材入库子项:" + tiaomaBianma); } updated += rowUpdated; } log.info("耗材销毁申请发起完成:xiaohuiShenqingId={}, xiaohuiDanhao={}, zixiangCount={}", id, xiaohuiDanhao, updated); return updated; } @Override public List listRukuZixiangByTiaomaBianma(String tiaomaBianma) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiRukuZixiangEntity::getTiaomaBianma, trimToEmpty(tiaomaBianma)); wrapper.and(w -> w.isNull(LimsHaocaiRukuZixiangEntity::getDeleteMark) .or() .ne(LimsHaocaiRukuZixiangEntity::getDeleteMark, 1)); wrapper.orderByDesc(LimsHaocaiRukuZixiangEntity::getRukuShijian); wrapper.orderByAsc(LimsHaocaiRukuZixiangEntity::getBianhao); return zixiangMapper.selectList(wrapper); } @Override public LimsHaocaiChukuShenqingZixiangEntity getChukuShenqingZixiangForChuku(String shenlingDanhao, String tiaomaBianma) throws DataException { String danhao = trimToEmpty(shenlingDanhao); String tiaoma = trimToEmpty(tiaomaBianma); if (danhao.isEmpty()) { throw new DataException("申领单号不能为空"); } if (tiaoma.isEmpty()) { throw new DataException("条码编码不能为空"); } LimsHaocaiRukuZixiangEntity rukuZixiang = getUniqueRukuZixiangByTiaomaBianma(tiaoma); if (rukuZixiang == null) { throw new DataException("未找到对应的耗材入库子项:" + tiaoma); } if (!isChukuAvailableStatus(rukuZixiang.getBizStatus())) { throw new DataException("该耗材状态不允许出库:" + trimToEmpty(rukuZixiang.getBizStatus())); } BigDecimal shengyuliang = parseRequiredDecimal(rukuZixiang.getShengyuliang(), "耗材剩余量无效:" + tiaoma); if (shengyuliang.compareTo(BigDecimal.ZERO) <= 0) { throw new DataException("该耗材剩余量不足,无法出库:" + tiaoma); } LimsHaocaiChukuShenqingEntity shenqing = getApprovedChukuShenqingByDanhao(danhao); if (shenqing == null) { throw new DataException("申领单不存在或未通过:" + danhao); } LimsHaocaiChukuShenqingZixiangEntity chukuZixiang = getChukuShenqingZixiang(shenqing.getId(), tiaoma); if (chukuZixiang == null) { throw new DataException("条码编码不在该申领单子表中:" + tiaoma); } if ("yes".equalsIgnoreCase(trimToEmpty(chukuZixiang.getShifouYichuku()))) { throw new DataException("该申领子项已出库,不能重复出库:" + tiaoma); } chukuZixiang.setYongtu(shenqing.getYongtu()); chukuZixiang.setShifouShuangrenChuku(rukuZixiang.getShifouShuangrenChuku()); return chukuZixiang; } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "YANSHOU_SUBMIT", label = "耗材入库验收提交", bizType = "HAOCAI", bizCodeExpr = "#flowId", bizCodeRequired = false, targetTable = "lims_haocai_yanshou_shenqing", targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).haocaiAcceptanceRequestByFlow(#flowId, #taskId)", diff = true, entityClass = LimsHaocaiYanshouShenqingEntity.class) public void submitRukuYanshou(String flowId, String taskId) throws DataException { String flow = trimToEmpty(flowId); String task = trimToEmpty(taskId); if (flow.isEmpty()) { throw new DataException("流程id(flow_id)不能为空"); } if (task.isEmpty()) { throw new DataException("流程任务id(task_id)不能为空"); } LimsHaocaiRukuYanshouEntity yanshou = getUniqueRukuYanshouByFlow(flow, task); String shenqingId = trimToEmpty(yanshou.getShenqingId()); if (shenqingId.isEmpty()) { throw new DataException("耗材入库验收记录未关联验收申请:" + yanshou.getId()); } LambdaUpdateWrapper update = new LambdaUpdateWrapper<>(); update.eq(LimsHaocaiYanshouShenqingEntity::getId, shenqingId) .and(w -> w.isNull(LimsHaocaiYanshouShenqingEntity::getDeleteMark) .or().ne(LimsHaocaiYanshouShenqingEntity::getDeleteMark, 1)) .set(LimsHaocaiYanshouShenqingEntity::getYanshouId, yanshou.getId()); if (yanshouShenqingMapper.update(null, update) != 1) { throw new DataException("耗材验收申请不存在或已删除:" + shenqingId); } log.info("耗材入库验收提交完成:flowId={}, taskId={}, yanshouId={}, shenqingId={}", flow, task, yanshou.getId(), shenqingId); } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "YANSHOU_FINISH", label = "耗材入库验收完成", bizType = "HAOCAI", bizCodeExpr = "#flowId", bizCodeRequired = false, targetTable = "lims_haocai_yanshou_shenqing", targetIdExpr = "T(jnpf.limsService.support.LimsAuditTargetIds).haocaiAcceptanceRequestByFlow(#flowId, #taskId)", diff = true, entityClass = LimsHaocaiYanshouShenqingEntity.class) public void finishRukuYanshou(String flowId, String taskId) throws DataException { String flow = trimToEmpty(flowId); String task = trimToEmpty(taskId); if (flow.isEmpty()) { throw new DataException("流程id(flow_id)不能为空"); } if (task.isEmpty()) { throw new DataException("流程任务id(task_id)不能为空"); } LimsHaocaiRukuYanshouEntity yanshou = getUniqueRukuYanshouByFlow(flow, task); String shenqingId = trimToEmpty(yanshou.getShenqingId()); if (shenqingId.isEmpty()) { throw new DataException("耗材入库验收记录未关联验收申请:" + yanshou.getId()); } boolean approved = Integer.valueOf(FLOW_STATE_APPROVED).equals(yanshou.getFlowState()); LambdaUpdateWrapper update = new LambdaUpdateWrapper<>(); update.eq(LimsHaocaiYanshouShenqingEntity::getId, shenqingId) .and(w -> w.isNull(LimsHaocaiYanshouShenqingEntity::getDeleteMark) .or().ne(LimsHaocaiYanshouShenqingEntity::getDeleteMark, 1)); if (approved) { update.set(LimsHaocaiYanshouShenqingEntity::getBizStatus, YANSHOU_SHENQING_STATUS_YIYANSHOU); } else { update.set(LimsHaocaiYanshouShenqingEntity::getBizStatus, YANSHOU_SHENQING_STATUS_DAIYANSHOU) .set(LimsHaocaiYanshouShenqingEntity::getShiyanYuan, null) .set(LimsHaocaiYanshouShenqingEntity::getRenwuLingquShijian, null) .set(LimsHaocaiYanshouShenqingEntity::getYanshouId, null); } if (yanshouShenqingMapper.update(null, update) != 1) { throw new DataException("耗材验收申请不存在或已删除:" + shenqingId); } log.info("耗材入库验收完成处理成功:flowId={}, taskId={}, yanshouId={}, flowState={}, approved={}", flow, task, yanshou.getId(), yanshou.getFlowState(), approved); } @Override @Transactional(rollbackFor = Exception.class) @AuditLog(action = "YANSHOU_CLAIM", label = "领取耗材验收申请", bizType = "HAOCAI", bizCodeExpr = "#shenqingId", bizCodeRequired = false, targetTable = "lims_haocai_yanshou_shenqing", targetIdExpr = "#shenqingId", diff = true, entityClass = LimsHaocaiYanshouShenqingEntity.class) public void lingquYanshouShenqing(String shenqingId) throws DataException { String id = trimToEmpty(shenqingId); if (id.isEmpty()) { throw new DataException("耗材验收申请主键(shenqing_id)不能为空"); } LimsHaocaiYanshouShenqingEntity shenqing = yanshouShenqingMapper.selectById(id); if (shenqing == null || Integer.valueOf(1).equals(shenqing.getDeleteMark())) { throw new DataException("耗材验收申请不存在:" + id); } if (!YANSHOU_SHENQING_STATUS_DAIYANSHOU.equals(shenqing.getBizStatus())) { throw new DataException("耗材验收申请当前状态为【" + yanshouShenqingStatusLabel(shenqing.getBizStatus()) + "】,仅【待验收】状态可领取"); } UserInfo userInfo = UserProvider.getUser(); if (userInfo == null || trimToEmpty(userInfo.getUserId()).isEmpty()) { throw new DataException("当前登录用户不存在"); } LambdaUpdateWrapper update = new LambdaUpdateWrapper<>(); update.eq(LimsHaocaiYanshouShenqingEntity::getId, id) .eq(LimsHaocaiYanshouShenqingEntity::getBizStatus, YANSHOU_SHENQING_STATUS_DAIYANSHOU) .and(w -> w.isNull(LimsHaocaiYanshouShenqingEntity::getDeleteMark) .or().ne(LimsHaocaiYanshouShenqingEntity::getDeleteMark, 1)) .set(LimsHaocaiYanshouShenqingEntity::getBizStatus, YANSHOU_SHENQING_STATUS_YANSHOUZHONG) .set(LimsHaocaiYanshouShenqingEntity::getShiyanYuan, userInfo.getUserId()) .set(LimsHaocaiYanshouShenqingEntity::getRenwuLingquShijian, new Date()); if (yanshouShenqingMapper.update(null, update) != 1) { throw new DataException("耗材验收申请状态已变化,请刷新后重新领取"); } } private LimsHaocaiRukuYanshouEntity getUniqueRukuYanshouByFlow(String flowId, String taskId) throws DataException { LambdaQueryWrapper query = new LambdaQueryWrapper<>(); query.eq(LimsHaocaiRukuYanshouEntity::getFlowId, flowId) .eq(LimsHaocaiRukuYanshouEntity::getFlowTaskId, taskId) .and(w -> w.isNull(LimsHaocaiRukuYanshouEntity::getDeleteMark) .or().ne(LimsHaocaiRukuYanshouEntity::getDeleteMark, 1)); List list = rukuYanshouMapper.selectList(query); if (list == null || list.isEmpty()) { throw new DataException("耗材入库验收记录不存在:flow_id=" + flowId + ", task_id=" + taskId); } if (list.size() > 1) { throw new DataException("多个耗材入库验收记录关联同一流程任务:task_id=" + taskId); } return list.get(0); } private LimsHaocaiRukuZixiangEntity buildZixiang(LimsHaocaiRukuEntity ruku, String tiaomaBianma, String bianhao, String zongliang, String yanshouZhuangtai) { LimsHaocaiRukuZixiangEntity record = new LimsHaocaiRukuZixiangEntity(); record.setId(RandomUtil.uuId()); record.setForeignId(ruku.getId()); record.setRukuId(ruku.getId()); record.setTiaomaBianma(tiaomaBianma); record.setBianhao(bianhao); record.setZongliang(zongliang); record.setShengyuliang(zongliang); record.setBizStatus(BIZ_STATUS_WEICHUKU); record.setYanshouZhuangtai(yanshouZhuangtai); record.setMingcheng(ruku.getHaocaiMingcheng()); record.setFenlei(ruku.getHaocaiFenlei()); record.setGuige(ruku.getGuige()); record.setPihao(ruku.getPihao()); record.setDanwei(ruku.getRongliangDanwei()); record.setCunfangWeizhi(ruku.getCunfangWeizhi()); record.setYouxiaoqi(ruku.getYouxiaoqi()); record.setKaipingYouxiaoqi(ruku.getKaipingYouxiaoqi()); record.setKaipingYouxiaoqiDanwei(ruku.getKaipingYouxiaoqiDanwei()); record.setRukuShijian(ruku.getRukuShijian() != null ? ruku.getRukuShijian() : new Date()); record.setRukuRen(ruku.getRukuRen()); record.setShenheRen(ruku.getShenheRen()); record.setHaocaiId(ruku.getHaocaiId()); record.setShifouShuangrenChuku(ruku.getShifouShuangrenChuku()); return record; } private LimsHaocaiLogEntity buildRukuLog(LimsHaocaiRukuEntity ruku, LimsHaocaiRukuZixiangEntity zixiang) { LimsHaocaiLogEntity record = new LimsHaocaiLogEntity(); record.setId(RandomUtil.uuId()); record.setFenlei(zixiang.getFenlei()); record.setTiaomaBianma(zixiang.getTiaomaBianma()); record.setBianhao(zixiang.getBianhao()); record.setMingcheng(zixiang.getMingcheng()); record.setGuige(zixiang.getGuige()); record.setPihao(zixiang.getPihao()); record.setChurukuShuliang(zixiang.getZongliang()); record.setShengyuShuliang(zixiang.getShengyuliang()); record.setDanwei(zixiang.getDanwei()); record.setCaozuoRen(zixiang.getRukuRen()); record.setFuheRen(zixiang.getShenheRen()); record.setCaozuoRiqi(zixiang.getRukuShijian()); record.setCaozuoLeixing(LOG_ACTION_RUKU); record.setBeizhu(ruku.getBeizhu()); return record; } private LimsHaocaiLogEntity buildJiechuLog(LimsHaocaiJiechuEntity jiechu, LimsHaocaiRukuZixiangEntity zixiang) { LimsHaocaiLogEntity record = new LimsHaocaiLogEntity(); record.setId(RandomUtil.uuId()); record.setFenlei(zixiang.getFenlei()); record.setTiaomaBianma(zixiang.getTiaomaBianma()); record.setBianhao(zixiang.getBianhao()); record.setMingcheng(zixiang.getMingcheng()); record.setGuige(zixiang.getGuige()); record.setPihao(zixiang.getPihao()); record.setChurukuShuliang(jiechu.getJiechuShuliang()); record.setShengyuShuliang(zixiang.getShengyuliang()); record.setDanwei(firstNotBlank(jiechu.getDanwei(), zixiang.getDanwei())); record.setCaozuoRen(jiechu.getJiechuRen()); record.setCaozuoRiqi(jiechu.getJiechuRiqi() != null ? jiechu.getJiechuRiqi() : new Date()); record.setCaozuoLeixing(LOG_ACTION_JIECHU); record.setBeizhu(jiechu.getYongtu()); return record; } private LimsHaocaiLogEntity buildGuihuanLog(LimsHaocaiJiechuEntity guihuan, LimsHaocaiJiechuEntity lentRecord, LimsHaocaiRukuZixiangEntity zixiang) { LimsHaocaiLogEntity record = new LimsHaocaiLogEntity(); record.setId(RandomUtil.uuId()); record.setFenlei(zixiang.getFenlei()); record.setTiaomaBianma(zixiang.getTiaomaBianma()); record.setBianhao(zixiang.getBianhao()); record.setMingcheng(zixiang.getMingcheng()); record.setGuige(zixiang.getGuige()); record.setPihao(zixiang.getPihao()); record.setChurukuShuliang(lentRecord.getJiechuShuliang()); record.setShengyuShuliang(zixiang.getShengyuliang()); record.setDanwei(firstNotBlank(lentRecord.getDanwei(), zixiang.getDanwei())); record.setCaozuoRen(firstNotBlank(guihuan.getGuihuanRen(), firstNotBlank(guihuan.getJiechuRen(), lentRecord.getJiechuRen()))); record.setCaozuoRiqi(guihuan.getGuihuanRiqi() != null ? guihuan.getGuihuanRiqi() : new Date()); record.setCaozuoLeixing(LOG_ACTION_GUIHUAN); record.setBeizhu(firstNotBlank(guihuan.getYongtu(), lentRecord.getYongtu())); return record; } private LimsHaocaiLogEntity buildChukuLog(LimsHaocaiChukuEntity chuku, LimsHaocaiChukuZixiangEntity chukuZixiang, LimsHaocaiRukuZixiangEntity rukuZixiang, BigDecimal chukuliang) { LimsHaocaiLogEntity record = new LimsHaocaiLogEntity(); record.setId(RandomUtil.uuId()); record.setFenlei(rukuZixiang.getFenlei()); record.setTiaomaBianma(rukuZixiang.getTiaomaBianma()); record.setBianhao(rukuZixiang.getBianhao()); record.setMingcheng(rukuZixiang.getMingcheng()); record.setGuige(rukuZixiang.getGuige()); record.setPihao(rukuZixiang.getPihao()); record.setChurukuShuliang(toPlainString(chukuliang)); record.setShengyuShuliang(rukuZixiang.getShengyuliang()); record.setDanwei(firstNotBlank(chukuZixiang.getDanwei(), rukuZixiang.getDanwei())); record.setCaozuoRen(chuku.getChukuRen()); record.setFuheRen(chuku.getFuheRen()); record.setCaozuoRiqi(chuku.getChukuRiqi() != null ? chuku.getChukuRiqi() : new Date()); record.setCaozuoLeixing(LOG_ACTION_CHUKU); record.setBeizhu(chukuZixiang.getYongtu()); return record; } private LimsHaocaiLogEntity buildXiaohuiLog(LimsHaocaiXiaohuiShenqingEntity shenqing, LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang, LimsHaocaiRukuZixiangEntity rukuZixiang, BigDecimal xiaohuiShuliang) { LimsHaocaiLogEntity record = new LimsHaocaiLogEntity(); record.setId(RandomUtil.uuId()); record.setFenlei(rukuZixiang.getFenlei()); record.setTiaomaBianma(rukuZixiang.getTiaomaBianma()); record.setBianhao(rukuZixiang.getBianhao()); record.setMingcheng(rukuZixiang.getMingcheng()); record.setGuige(rukuZixiang.getGuige()); record.setPihao(rukuZixiang.getPihao()); record.setChurukuShuliang(toPlainString(xiaohuiShuliang)); record.setShengyuShuliang(rukuZixiang.getShengyuliang()); record.setDanwei(rukuZixiang.getDanwei()); record.setCaozuoRen(shenqing.getShenqingRen()); record.setCaozuoRiqi(shenqing.getShenqingRiqi() != null ? shenqing.getShenqingRiqi() : new Date()); record.setCaozuoLeixing(LOG_ACTION_XIAOHUI); record.setBeizhu(firstNotBlank(xiaohuiZixiang.getYuanyin(), firstNotBlank(shenqing.getYuanyin(), shenqing.getFangfa()))); return record; } private LimsHaocaiJiechuEntity getLentJiechuByTiaomaBianma(String tiaomaBianma) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiJiechuEntity::getTiaomaBianma, trimToEmpty(tiaomaBianma)); wrapper.eq(LimsHaocaiJiechuEntity::getBizStatus, JIECHU_STATUS_LENT); wrapper.and(w -> w.isNull(LimsHaocaiJiechuEntity::getDeleteMark) .or() .ne(LimsHaocaiJiechuEntity::getDeleteMark, 1)); wrapper.orderByDesc(LimsHaocaiJiechuEntity::getJiechuRiqi); return jiechuMapper.selectList(wrapper).stream().findFirst().orElse(null); } private void submitChukuZixiang(LimsHaocaiChukuEntity chuku, LimsHaocaiChukuShenqingEntity shenqing, LimsHaocaiChukuZixiangEntity chukuZixiang) throws DataException { String tiaomaBianma = trimToEmpty(chukuZixiang == null ? null : chukuZixiang.getTiaomaBianma()); if (tiaomaBianma.isEmpty()) { throw new DataException("出库子项条码编码不能为空"); } BigDecimal chukuliang = parseRequiredDecimal(chukuZixiang.getChukuliang(), "本次出库量不能为空或格式无效:" + tiaomaBianma); if (chukuliang.compareTo(BigDecimal.ZERO) <= 0) { throw new DataException("本次出库量必须大于0:" + tiaomaBianma); } LimsHaocaiRukuZixiangEntity rukuZixiang = getUniqueRukuZixiangByTiaomaBianma(tiaomaBianma); if (rukuZixiang == null) { throw new DataException("未找到对应的耗材入库子项:" + tiaomaBianma); } String bizStatus = trimToEmpty(rukuZixiang.getBizStatus()); if (!isChukuAvailableStatus(bizStatus)) { throw new DataException("该耗材状态不允许出库:" + bizStatus); } BigDecimal shengyuliang = parseRequiredDecimal(rukuZixiang.getShengyuliang(), "耗材剩余量无效:" + tiaomaBianma); if (chukuliang.compareTo(shengyuliang) > 0) { throw new DataException("本次出库量不能大于剩余量:" + tiaomaBianma); } LimsHaocaiChukuShenqingZixiangEntity shenqingZixiang = getChukuShenqingZixiang(shenqing.getId(), tiaomaBianma); if (shenqingZixiang == null) { throw new DataException("条码编码不在该申领单子表中:" + tiaomaBianma); } if ("yes".equalsIgnoreCase(trimToEmpty(shenqingZixiang.getShifouYichuku()))) { throw new DataException("该申领子项已出库,不能重复出库:" + tiaomaBianma); } BigDecimal newShengyuliang = shengyuliang.subtract(chukuliang); String newBizStatus = newShengyuliang.compareTo(BigDecimal.ZERO) == 0 ? BIZ_STATUS_YICHUKU : BIZ_STATUS_YIKAIPING; LimsHaocaiRukuZixiangEntity rukuZixiangUpdate = new LimsHaocaiRukuZixiangEntity(); rukuZixiangUpdate.setId(rukuZixiang.getId()); rukuZixiangUpdate.setShengyuliang(toPlainString(newShengyuliang)); rukuZixiangUpdate.setBizStatus(newBizStatus); LambdaQueryWrapper rukuZixiangWrapper = new LambdaQueryWrapper<>(); rukuZixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getId, rukuZixiang.getId()); rukuZixiangWrapper.in(LimsHaocaiRukuZixiangEntity::getBizStatus, BIZ_STATUS_WEICHUKU, BIZ_STATUS_YIKAIPING); rukuZixiangWrapper.eq(LimsHaocaiRukuZixiangEntity::getShengyuliang, rukuZixiang.getShengyuliang()); int rukuZixiangUpdated = zixiangMapper.update(rukuZixiangUpdate, rukuZixiangWrapper); if (rukuZixiangUpdated <= 0) { throw new DataException("耗材状态已变化,请刷新后重新提交:" + tiaomaBianma); } markChukuShenqingZixiangOut(shenqingZixiang); updateRukuJieyuShuliang(rukuZixiang, chukuliang, false, BIZ_STATUS_YICHUKU); rukuZixiang.setShengyuliang(toPlainString(newShengyuliang)); rukuZixiang.setBizStatus(newBizStatus); haocaiLogMapper.insert(buildChukuLog(chuku, chukuZixiang, rukuZixiang, chukuliang)); } private List listXiaohuiZixiang(String foreignId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiXiaohuiShenqingZixiangEntity::getForeignId, foreignId); wrapper.and(w -> w.isNull(LimsHaocaiXiaohuiShenqingZixiangEntity::getDeleteMark) .or() .ne(LimsHaocaiXiaohuiShenqingZixiangEntity::getDeleteMark, 1)); return xiaohuiShenqingZixiangMapper.selectList(wrapper); } private List listChukuZixiang(String foreignId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiChukuZixiangEntity::getForeignId, foreignId); wrapper.and(w -> w.isNull(LimsHaocaiChukuZixiangEntity::getDeleteMark) .or() .ne(LimsHaocaiChukuZixiangEntity::getDeleteMark, 1)); return chukuZixiangMapper.selectList(wrapper); } private void markXiaohuiZixiangDestroyed(LimsHaocaiXiaohuiShenqingZixiangEntity xiaohuiZixiang) throws DataException { LimsHaocaiXiaohuiShenqingZixiangEntity update = new LimsHaocaiXiaohuiShenqingZixiangEntity(); update.setId(xiaohuiZixiang.getId()); update.setShifouYixiaohui("yes"); int updated = xiaohuiShenqingZixiangMapper.updateById(update); if (updated <= 0) { throw new DataException("销毁申请子项状态更新失败:" + xiaohuiZixiang.getTiaomaBianma()); } } private void markChukuShenqingZixiangOut(LimsHaocaiChukuShenqingZixiangEntity shenqingZixiang) throws DataException { LimsHaocaiChukuShenqingZixiangEntity update = new LimsHaocaiChukuShenqingZixiangEntity(); update.setId(shenqingZixiang.getId()); update.setShifouYichuku("yes"); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiChukuShenqingZixiangEntity::getId, shenqingZixiang.getId()); wrapper.and(w -> w.isNull(LimsHaocaiChukuShenqingZixiangEntity::getShifouYichuku) .or() .ne(LimsHaocaiChukuShenqingZixiangEntity::getShifouYichuku, "yes")); int updated = chukuShenqingZixiangMapper.update(update, wrapper); if (updated <= 0) { throw new DataException("申领子项已出库,不能重复出库:" + shenqingZixiang.getTiaomaBianma()); } } private LimsHaocaiXiaohuiShenqingEntity getXiaohuiShenqingByDanhao(String xiaohuiDanhao) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiXiaohuiShenqingEntity::getXiaohuiDanhao, trimToEmpty(xiaohuiDanhao)); wrapper.and(w -> w.isNull(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark) .or() .ne(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark, 1)); return xiaohuiShenqingMapper.selectList(wrapper).stream().findFirst().orElse(null); } private LimsHaocaiXiaohuiShenqingEntity getXiaohuiShenqingByFlow(String flowId, String taskId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiXiaohuiShenqingEntity::getFlowId, trimToEmpty(flowId)); wrapper.eq(LimsHaocaiXiaohuiShenqingEntity::getFlowTaskId, trimToEmpty(taskId)); wrapper.and(w -> w.isNull(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark) .or() .ne(LimsHaocaiXiaohuiShenqingEntity::getDeleteMark, 1)); return xiaohuiShenqingMapper.selectList(wrapper).stream().findFirst().orElse(null); } private LimsHaocaiChukuShenqingEntity getApprovedChukuShenqingByDanhao(String shenlingDanhao) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiChukuShenqingEntity::getShenlingDanhao, trimToEmpty(shenlingDanhao)); wrapper.eq(LimsHaocaiChukuShenqingEntity::getFlowState, FLOW_STATE_APPROVED); wrapper.and(w -> w.isNull(LimsHaocaiChukuShenqingEntity::getDeleteMark) .or() .ne(LimsHaocaiChukuShenqingEntity::getDeleteMark, 1)); return chukuShenqingMapper.selectList(wrapper).stream().findFirst().orElse(null); } private LimsHaocaiChukuShenqingZixiangEntity getChukuShenqingZixiang(String foreignId, String tiaomaBianma) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(LimsHaocaiChukuShenqingZixiangEntity::getForeignId, trimToEmpty(foreignId)); wrapper.eq(LimsHaocaiChukuShenqingZixiangEntity::getTiaomaBianma, trimToEmpty(tiaomaBianma)); wrapper.and(w -> w.isNull(LimsHaocaiChukuShenqingZixiangEntity::getDeleteMark) .or() .ne(LimsHaocaiChukuShenqingZixiangEntity::getDeleteMark, 1)); return chukuShenqingZixiangMapper.selectList(wrapper).stream().findFirst().orElse(null); } private LimsHaocaiRukuZixiangEntity getUniqueRukuZixiangByTiaomaBianma(String tiaomaBianma) { List list = listRukuZixiangByTiaomaBianma(tiaomaBianma); return list == null || list.isEmpty() ? null : list.get(0); } private void updateRukuJieyuShuliang(LimsHaocaiRukuZixiangEntity rukuZixiang, String zeroBizStatus) throws DataException { updateRukuJieyuShuliang(rukuZixiang, false, zeroBizStatus); } private void updateRukuJieyuShuliang(LimsHaocaiRukuZixiangEntity rukuZixiang, boolean increase, String zeroBizStatus) throws DataException { BigDecimal changeAmount = parseRequiredDecimal(rukuZixiang.getShengyuliang(), "耗材剩余量无效:" + rukuZixiang.getTiaomaBianma()); updateRukuJieyuShuliang(rukuZixiang, changeAmount, increase, zeroBizStatus); } private void updateRukuJieyuShuliang(LimsHaocaiRukuZixiangEntity rukuZixiang, BigDecimal changeAmount, boolean increase, String zeroBizStatus) throws DataException { String id = trimToEmpty(rukuZixiang.getRukuId()); if (id.isEmpty()) { throw new DataException("耗材入库主表主键不能为空"); } LimsHaocaiRukuEntity ruku = this.getById(id); if (ruku == null || Integer.valueOf(1).equals(ruku.getDeleteMark())) { throw new DataException("耗材入库主表不存在:" + id); } BigDecimal jieyuShuliang = parseOptionalDecimal(ruku.getJieyuShuliang()); if (jieyuShuliang == null) { jieyuShuliang = parseOptionalDecimal(ruku.getRukuShuliang()); } if (jieyuShuliang == null) { throw new DataException("入库主表结余数量无效:" + id); } BigDecimal changeShuliang = calculateJieyuChangeShuliang(ruku, rukuZixiang, changeAmount); BigDecimal newJieyuShuliang = increase ? jieyuShuliang.add(changeShuliang) : jieyuShuliang.subtract(changeShuliang); if (newJieyuShuliang.compareTo(BigDecimal.ZERO) < 0) { newJieyuShuliang = BigDecimal.ZERO; } LimsHaocaiRukuEntity update = new LimsHaocaiRukuEntity(); update.setId(id); update.setJieyuShuliang(toPlainString(newJieyuShuliang)); update.setBizStatus(resolveRukuBizStatus(ruku, newJieyuShuliang, zeroBizStatus)); this.updateById(update); } private void updateRukuBizStatus(List rukuIds, String bizStatus) { if (rukuIds == null || rukuIds.isEmpty()) { return; } LimsHaocaiRukuEntity update = new LimsHaocaiRukuEntity(); update.setBizStatus(bizStatus); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.in(LimsHaocaiRukuEntity::getId, rukuIds); wrapper.and(w -> w.isNull(LimsHaocaiRukuEntity::getDeleteMark) .or() .ne(LimsHaocaiRukuEntity::getDeleteMark, 1)); this.update(update, wrapper); } private String resolveRukuBizStatus(LimsHaocaiRukuEntity ruku, BigDecimal jieyuShuliang, String zeroBizStatus) throws DataException { if (jieyuShuliang.compareTo(BigDecimal.ZERO) <= 0) { return zeroBizStatus; } BigDecimal rukuShuliang = parseRequiredDecimal(ruku.getRukuShuliang(), "入库主表入库数量无效:" + ruku.getId()); if (jieyuShuliang.compareTo(rukuShuliang) >= 0) { return BIZ_STATUS_WEICHUKU; } return BIZ_STATUS_YIKAIPING; } private BigDecimal calculateJieyuChangeShuliang(LimsHaocaiRukuEntity ruku, LimsHaocaiRukuZixiangEntity rukuZixiang) throws DataException { BigDecimal changeAmount = parseRequiredDecimal(rukuZixiang.getShengyuliang(), "耗材剩余量无效:" + rukuZixiang.getTiaomaBianma()); return calculateJieyuChangeShuliang(ruku, rukuZixiang, changeAmount); } private BigDecimal calculateJieyuChangeShuliang(LimsHaocaiRukuEntity ruku, LimsHaocaiRukuZixiangEntity rukuZixiang, BigDecimal changeAmount) throws DataException { BigDecimal zongliang = parseRequiredDecimal(rukuZixiang.getZongliang(), "耗材总量无效:" + rukuZixiang.getTiaomaBianma()); if (zongliang.compareTo(BigDecimal.ZERO) <= 0) { throw new DataException("耗材总量必须大于0:" + rukuZixiang.getTiaomaBianma()); } if (changeAmount.compareTo(BigDecimal.ZERO) < 0) { throw new DataException("耗材变化量不能小于0:" + rukuZixiang.getTiaomaBianma()); } BigDecimal ratio = changeAmount.divide(zongliang, 8, java.math.RoundingMode.HALF_UP); if ("yes".equalsIgnoreCase(trimToEmpty(ruku.getShifouYigeTiaoma()))) { BigDecimal rukuShuliang = parseRequiredDecimal(ruku.getRukuShuliang(), "入库主表入库数量无效:" + ruku.getId()); return ratio.multiply(rukuShuliang).stripTrailingZeros(); } return ratio.stripTrailingZeros(); } private boolean isChukuAvailableStatus(String bizStatus) { String status = trimToEmpty(bizStatus); return BIZ_STATUS_WEICHUKU.equalsIgnoreCase(status) || BIZ_STATUS_YIKAIPING.equalsIgnoreCase(status); } private boolean isBorrowableStatus(String bizStatus) { return isChukuAvailableStatus(bizStatus); } private String resolveAvailableBizStatus(BigDecimal shengyuliang, LimsHaocaiRukuZixiangEntity rukuZixiang) throws DataException { BigDecimal zongliang = parseRequiredDecimal(rukuZixiang.getZongliang(), "耗材总量无效:" + rukuZixiang.getTiaomaBianma()); if (shengyuliang.compareTo(zongliang) >= 0) { return BIZ_STATUS_WEICHUKU; } return BIZ_STATUS_YIKAIPING; } private String resolveYanshouZhuangtai(LimsHaocaiRukuEntity ruku) { return isYanshouEnabled(ruku) ? YANSHOU_STATUS_YIYANSHOU : YANSHOU_STATUS_NA; } private boolean isYanshouEnabled(LimsHaocaiRukuEntity ruku) { return ruku != null && "yes".equalsIgnoreCase(trimToEmpty(ruku.getShifouQiyongYanshou())); } private void updateRukuYanshouRukuId(LimsHaocaiRukuEntity ruku) throws DataException { String yanshouId = trimToEmpty(ruku.getYanshouId()); if (yanshouId.isEmpty()) { throw new DataException("启用验收的入库单必须关联验收记录主键(yanshou_id):" + ruku.getId()); } LambdaUpdateWrapper update = new LambdaUpdateWrapper<>(); update.eq(LimsHaocaiRukuYanshouEntity::getId, yanshouId) .and(w -> w.isNull(LimsHaocaiRukuYanshouEntity::getDeleteMark) .or().ne(LimsHaocaiRukuYanshouEntity::getDeleteMark, 1)) .set(LimsHaocaiRukuYanshouEntity::getRukuId, ruku.getId()); if (rukuYanshouMapper.update(null, update) != 1) { throw new DataException("耗材入库验收记录不存在或已删除:" + yanshouId); } } private String yanshouShenqingStatusLabel(String status) { String value = trimToEmpty(status); if (YANSHOU_SHENQING_STATUS_DAIYANSHOU.equals(value)) { return "待验收"; } if (YANSHOU_SHENQING_STATUS_YANSHOUZHONG.equals(value)) { return "验收中"; } return value.isEmpty() ? "未设置" : value; } /** * 取条码号。BillRuleApi 配了静态 fallback(L009):system 服务不可用时 * 静默返回空 ActionResult 而不抛异常,必须显式校验取号结果,空号立即失败回滚。 */ private String nextTiaomaBianma() throws DataException { ActionResult result = billRuleApi.getBillNumber(TIAOMA_BILL_RULE); String tiaoma = result == null ? null : result.getData(); if (StringUtil.isEmpty(tiaoma)) { throw new DataException("条码取号失败:单据规则[" + TIAOMA_BILL_RULE + "]不可用,请确认规则已配置且系统服务在线"); } return tiaoma; } private BigDecimal parseShuliang(String rukuShuliang) throws DataException { try { BigDecimal shuliang = new BigDecimal(trimToEmpty(rukuShuliang)); if (shuliang.compareTo(BigDecimal.ZERO) <= 0) { throw new DataException("入库数量必须大于0:" + rukuShuliang); } return shuliang; } catch (NumberFormatException e) { throw new DataException("入库数量无效:" + rukuShuliang); } } private int calculateCompleteTiaomaCount(BigDecimal shuliang, String originalQuantity) throws DataException { try { return shuliang.setScale(0, RoundingMode.DOWN).intValueExact(); } catch (ArithmeticException e) { throw new DataException("入库数量过大:" + originalQuantity); } } private BigDecimal parseRongliang(String rongliang) throws DataException { try { return new BigDecimal(trimToEmpty(rongliang)); } catch (NumberFormatException e) { throw new DataException("容量无效:" + rongliang); } } private BigDecimal parseRequiredDecimal(String value, String errorMessage) throws DataException { BigDecimal decimal = parseOptionalDecimal(value); if (decimal == null) { throw new DataException(errorMessage); } return decimal; } private BigDecimal parseOptionalDecimal(String value) { String text = trimToEmpty(value); if (text.isEmpty()) { return null; } try { return new BigDecimal(text); } catch (NumberFormatException e) { return null; } } private List normalizeIds(List ids) { List result = new ArrayList<>(); if (ids == null) { return result; } for (String id : ids) { String value = trimToEmpty(id); if (!value.isEmpty() && !result.contains(value)) { result.add(value); } } return result; } private String trimToEmpty(String value) { return value == null ? "" : value.trim(); } private String firstNotBlank(String first, String second) { String value = trimToEmpty(first); return value.isEmpty() ? second : value; } private String toPlainString(BigDecimal value) { return value.stripTrailingZeros().toPlainString(); } }