cuijian
2025-06-18 a65937bb7d85168af8256f917aad1aa12c0b8c1d
lxzn-module-tms/src/main/java/org/jeecg/modules/tms/service/impl/OutboundOrderServiceImpl.java
@@ -2,32 +2,50 @@
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.flowable.engine.TaskService;
import org.flowable.task.api.Task;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.flowable.apithird.business.entity.FlowMyBusiness;
import org.jeecg.modules.flowable.apithird.business.service.IFlowMyBusinessService;
import org.jeecg.modules.flowable.apithird.service.FlowCallBackServiceI;
import org.jeecg.modules.flowable.apithird.service.FlowCommonService;
import org.jeecg.modules.flowable.service.IFlowDefinitionService;
import org.jeecg.modules.flowable.service.IFlowTaskService;
import org.jeecg.modules.system.service.ISysBusinessCodeRuleService;
import org.jeecg.modules.tms.entity.OutboundOrder;
import org.jeecg.modules.tms.entity.OutboundDetail;
import org.jeecg.modules.tms.entity.*;
import org.jeecg.modules.tms.entity.dto.OutBoundAddDto;
import org.jeecg.modules.tms.entity.dto.OutBoundOrderFlowDto;
import org.jeecg.modules.tms.entity.dto.OutBoundRequestDto;
import org.jeecg.modules.tms.entity.dto.OutboundOrderAndDetailDto;
import org.jeecg.modules.tms.enums.OutBillStatus;
import org.jeecg.modules.tms.entity.vo.SelectOutboundToolVo;
import org.jeecg.modules.tms.enums.*;
import org.jeecg.modules.tms.mapper.OutboundDetailMapper;
import org.jeecg.modules.tms.convert.OutboundOrderConvert;
import org.jeecg.modules.tms.mapper.OutboundOrderMapper;
import org.jeecg.modules.tms.service.IOutboundDetailService;
import org.jeecg.modules.tms.service.IOutboundOrderService;
import org.jeecg.modules.tms.service.*;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @Description: tms_outbound_order
@@ -35,17 +53,40 @@
 * @Date:   2025-05-16
 * @Version: V1.0
 */
@Slf4j
@Service
public class OutboundOrderServiceImpl extends ServiceImpl<OutboundOrderMapper, OutboundOrder> implements IOutboundOrderService {
public class OutboundOrderServiceImpl extends ServiceImpl<OutboundOrderMapper, OutboundOrder> implements IOutboundOrderService, FlowCallBackServiceI {
   @Autowired
   private IOutboundDetailService outboundDetailService;
   @Autowired
   private IToolLedgerService toolLedgerService;
   @Autowired
   private IToolLedgerDetailService toolLedgerDetailService;
   @Autowired
   private IOutStoreDetailService outStoreDetailService;
   @Autowired
   private IBaseToolsService baseToolsService;
   @Autowired
   private IToolsSharpeningService toolsSharpeningService;
   @Autowired
   private ISysBusinessCodeRuleService businessCodeRuleService;
   @Autowired
   private IFlowDefinitionService flowDefinitionService;
   @Autowired
   private IFlowMyBusinessService flowMyBusinessService;
   @Autowired
   private IFlowTaskService flowTaskService;
   @Autowired
   private TaskService taskService;
   @Autowired
   private FlowCommonService flowCommonService;
   @Autowired
   private OutboundOrderMapper outboundOrderMapper;
   @Autowired
   private OutboundDetailMapper outboundDetailMapper;
   @Autowired
   private OutboundOrderConvert outboundOrderConvert;
   
   @Override
   @Transactional(rollbackFor = Exception.class)
@@ -66,13 +107,14 @@
    @Override
   @Transactional(rollbackFor = Exception.class)
    public void addTotal(OutboundOrderAndDetailDto outboundOrder) {
      OutboundOrder order = BeanUtil.copyProperties(outboundOrder, OutboundOrder.class);
      OutboundOrder order = outboundOrderConvert.convert(outboundOrder);
      order.setHandler(Objects.requireNonNull(getCurrentUser()).getId());
      order.setOutNum(businessCodeRuleService.generateBusinessCodeSeq("outBoundOrder"));
      order.setOrderStatus(OutBillStatus.DRAFT.getValue());
      save(order);
      List<OutboundDetail> detailList = CollectionUtil.newArrayList();
      outboundOrder.getOutboundDetailList().forEach(item->{
         item.setId(null);
         item.setOutStorehouseId(order.getId());
         detailList.add(item);
      });
@@ -90,9 +132,14 @@
      if (outStorehouseTypes != null && outStorehouseTypes.length > 0) {
         queryWrapper.eq("t.out_storehouse_type", outStorehouseTypes[0]);
      }
      String[] statuses = parameterMap.get("orderStatus");
      if (statuses != null && statuses.length > 0) {
         queryWrapper.eq("t.order_status", statuses[0]);
      String[] orderStatuses = parameterMap.get("orderStatus");
      if (orderStatuses != null && orderStatuses.length > 0) {
         queryWrapper.eq("t.order_status", orderStatuses[0]);
      }
      String[] outStatuses = parameterMap.get("outStatus");
      if (outStatuses != null && outStatuses.length > 0) {
         String[] statusArr = outStatuses[0].split(",");
         queryWrapper.in("t.out_status", statusArr);
      }
      String[] startTimes = parameterMap.get("startTime");
      if (startTimes != null && startTimes.length > 0) {
@@ -112,7 +159,7 @@
      //删除所有明细
      outboundDetailService.remove(new LambdaQueryWrapper<OutboundDetail>()
            .eq(OutboundDetail::getOutStorehouseId, outboundOrder.getId()));
      OutboundOrder outboundOrderUpdate = BeanUtil.copyProperties(outboundOrder, OutboundOrder.class);
      OutboundOrder outboundOrderUpdate = outboundOrderConvert.convert(outboundOrder);
      outboundOrderMapper.updateById(outboundOrderUpdate);
      List<OutboundDetail> detailList = CollectionUtil.newArrayList();
      outboundOrder.getOutboundDetailList().forEach(item->{
@@ -122,7 +169,598 @@
      outboundDetailService.saveBatch(detailList);
   }
   private LoginUser getCurrentUser() {
    @Override
   @Transactional(rollbackFor = Exception.class)
    public void submit(String id) {
      OutboundOrder outboundOrder = getById(id);
      if (outboundOrder == null) {
         throw new JeecgBootException("出库单申请单不存在,无法提交!");
      }
      if (!Objects.equals(outboundOrder.getOrderStatus(), OutBillStatus.DRAFT.getValue())) {
         throw new JeecgBootException("无法提交非草稿状态的出库申请单!");
      }
      if (!OutStorehouseType.PREPARATION_OUTBOUND.getValue().equals(outboundOrder.getOutStorehouseType())) {
         //不是从准备单转入的出库申请,执行锁库
         lockOutboundStock(id);
      }
      //启动流程
      if (triggerProcess(outboundOrder)) {
         outboundOrder.setOrderStatus(OutBillStatus.SUBMITTED.getValue());
         updateById(outboundOrder);
      }
    }
   private boolean lockOutboundStock(String id) {
      List<OutboundDetail> outboundDetailList = outboundDetailService.list(new LambdaQueryWrapper<OutboundDetail>()
            .eq(OutboundDetail::getOutStorehouseId, id));
      LambdaQueryWrapper<ToolLedgerDetail> queryWrapper = new LambdaQueryWrapper<>();
      for (int i = 0; i < outboundDetailList.size(); i++) {
         if (i > 0) {
            queryWrapper.or();
         }
         OutboundDetail detail = outboundDetailList.get(i);
         queryWrapper.or(wrapper ->
               wrapper.eq(StrUtil.isNotBlank(detail.getToolCode()), ToolLedgerDetail::getToolCode, detail.getToolCode())
                     .eq(StrUtil.isNotBlank(detail.getToolId()), ToolLedgerDetail::getToolId, detail.getToolId()) //如果不管到把,该字段为空
         );
      }
      List<ToolLedgerDetail> toolLedgerDetailList = toolLedgerDetailService.list(queryWrapper);
      List<ToolLedgerDetail> toolLedgerDetailUpdateList = CollectionUtil.newArrayList();
      for (OutboundDetail detail : outboundDetailList) {
         String toolCode = detail.getToolCode();
         List<ToolLedgerDetail> ledgerDetailList = toolLedgerDetailList.stream().filter(item -> Objects.equals(item.getToolCode(), toolCode)
               && Objects.equals(item.getToolId(), detail.getToolId())).collect(Collectors.toList());
         BaseTools tools = baseToolsService.getById(toolCode);
         if (CollectionUtil.isEmpty(ledgerDetailList)) {
            throw new JeecgBootException("编码为【" + tools.getToolCode() + "】的工具,库存不足!");
         } else {
            ToolLedgerDetail toolLedgerDetail = ledgerDetailList.get(0);
            if (toolLedgerDetail.getQuantity().compareTo(detail.getOutboundQuantity()) < 0) {
               throw new JeecgBootException("编码为【" + tools.getToolCode() + "】的工具,库存不足!");
            } else {
               //扣减台账明细中的库存,执行锁库
               if (StrUtil.isBlank(detail.getToolId())) {
                  //管到类的刀具
                  toolLedgerDetail.setQuantity(toolLedgerDetail.getQuantity().subtract(detail.getOutboundQuantity()));
               } else {
                  //管到把的刀具,数量只有 0和1
                  toolLedgerDetail.setQuantity(BigDecimal.ZERO);
               }
               toolLedgerDetailUpdateList.add(toolLedgerDetail);
            }
         }
      }
      Map<String, BigDecimal> outToolCodeQuantityMap = outboundDetailList.stream().collect(Collectors.groupingBy(OutboundDetail::getToolCode,
            Collectors.mapping(OutboundDetail::getOutboundQuantity, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
      Map<String, ToolLedger> toolLedgerMap = toolLedgerService.list(new LambdaQueryWrapper<ToolLedger>().in(ToolLedger::getToolId, outToolCodeQuantityMap.keySet()))
            .stream().collect(Collectors.toMap(ToolLedger::getToolId, toolLedger -> toolLedger, (k1, k2) -> k1));
      List<ToolLedger> toolLedgerUpdateList = CollectionUtil.newArrayList();
      for (String toolCode : outToolCodeQuantityMap.keySet()) {
         BigDecimal outQuantity = outToolCodeQuantityMap.get(toolCode);
         ToolLedger toolLedger = toolLedgerMap.get(toolCode);
         BaseTools tools = baseToolsService.getById(toolCode);
         if (Objects.isNull(toolLedger) || toolLedger.getAvailableCount().compareTo(outQuantity) < 0) {
            throw new JeecgBootException("编码为【" + tools.getToolCode() + "】的工具,库存不足!");
         } else {
            //扣减台账主表中的可用存库,执行锁库
            toolLedger.setAvailableCount(toolLedger.getAvailableCount().subtract(outQuantity));
            toolLedgerUpdateList.add(toolLedger);
         }
      }
      toolLedgerDetailService.updateBatchById(toolLedgerDetailUpdateList);
      toolLedgerService.updateBatchById(toolLedgerUpdateList);
      return true;
   }
   @Transactional(rollbackFor = Exception.class)
   @Override
   public void approvalProcess(OutBoundOrderFlowDto outBoundOrderFlowDto) {
      if (StrUtil.isBlank(outBoundOrderFlowDto.getTaskId()) || StrUtil.isBlank(outBoundOrderFlowDto.getDataId())) {
         throw new JeecgBootException("非法参数!");
      }
      // 获取当前登录用户
      LoginUser user = getCurrentUser();
      if (user == null || StrUtil.isBlank(user.getId())) {
         throw new JeecgBootException("账号不存在");
      }
      //获取出库申请单信息
      OutboundOrder outboundOrder = getById(outBoundOrderFlowDto.getDataId());
      if (outboundOrder == null) {
         throw new JeecgBootException("未找到对应的出库申请单!");
      }
      //获取流程业务记录
      FlowMyBusiness flowMyBusiness = getFlowMyBusiness(outBoundOrderFlowDto.getInstanceId());
      if (flowMyBusiness == null) {
         throw new JeecgBootException("流程记录不存在");
      }
      // 检查用户是否有权限操作任务
      if (!isUserAuthorized(flowMyBusiness, user)) {
         throw new JeecgBootException("用户无权操作此任务");
      }
      // 认领任务
      if (!claimTask(flowMyBusiness.getTaskId(), user)) {
         throw new JeecgBootException("任务不存在、已完成或已被他人认领");
      }
      //设置流程变量
      setupProcessVariables(outBoundOrderFlowDto, outboundOrder, user);
      //完成流程任务
      Result result = flowTaskService.complete(outBoundOrderFlowDto);
      //根据任务完成结果更新申请单状态
      if (result.isSuccess()) {
         if (OutBillStatus.APPROVED.getValue().equals(outBoundOrderFlowDto.getStatus())) {
            outboundOrder.setOutStatus(OutBoundStatusEnum.NOT_OUTBOUND.getValue());
            outboundOrder.setOrderStatus(OutBillStatus.APPROVED.getValue());
            //初始化申请明细信息的出库状态/实际出库数量字段
            outboundDetailService.update(new LambdaUpdateWrapper<OutboundDetail>()
                  .eq(OutboundDetail::getOutStorehouseId, outboundOrder.getId())
                  .set(OutboundDetail::getStatus, OutBoundStatusEnum.NOT_OUTBOUND.getValue())
                  .set(OutboundDetail::getOutActualCount, BigDecimal.ZERO));
         }
         if (OutBillStatus.REJECTED.getValue().equals(outBoundOrderFlowDto.getStatus())) {
            //如果驳回,审批单状态回到草稿
            outboundOrder.setOrderStatus(OutBillStatus.DRAFT.getValue());
            //释放申请单中锁定的库存
            releaseStock(outboundOrder);
         }
         outboundOrder.setAuditDate(new Date());
         outboundOrder.setApprovalOpinion(outBoundOrderFlowDto.getApprovalOpinion());
         updateById(outboundOrder);
      }
   }
   private void releaseStock(OutboundOrder outboundOrder) {
      List<OutboundDetail> outboundDetailList = outboundDetailService.list(new LambdaQueryWrapper<OutboundDetail>()
            .eq(OutboundDetail::getOutStorehouseId, outboundOrder.getId()));
      LambdaQueryWrapper<ToolLedgerDetail> queryWrapper = new LambdaQueryWrapper<>();
      for (int i = 0; i < outboundDetailList.size(); i++) {
         if (i > 0) {
            queryWrapper.or();
         }
         OutboundDetail detail = outboundDetailList.get(i);
         queryWrapper.or(wrapper ->
               wrapper.eq(StrUtil.isNotBlank(detail.getToolCode()), ToolLedgerDetail::getToolCode, detail.getToolCode())
                     .eq(StrUtil.isNotBlank(detail.getToolId()), ToolLedgerDetail::getToolId, detail.getToolId()) //如果不管到把,该字段为空
         );
      }
      List<ToolLedgerDetail> toolLedgerDetailList = toolLedgerDetailService.list(queryWrapper);
      List<ToolLedgerDetail> toolLedgerDetailUpdateList = CollectionUtil.newArrayList();
      for (OutboundDetail detail : outboundDetailList) {
         Optional<ToolLedgerDetail> optonal = toolLedgerDetailList.stream().filter(item -> Objects.equals(item.getToolCode(), detail.getToolCode())
               && Objects.equals(item.getToolId(), detail.getToolId())).findFirst();
         if (optonal.isPresent()) {
            ToolLedgerDetail toolLedgerDetail = optonal.get();
            if (StrUtil.isBlank(detail.getToolId())) {
               //到类
               toolLedgerDetail.setQuantity(toolLedgerDetail.getQuantity().add(detail.getOutboundQuantity()));
            } else {
               //到把
               toolLedgerDetail.setQuantity(BigDecimal.ONE);
            }
            toolLedgerDetailUpdateList.add(toolLedgerDetail);
         }
      }
      Map<String, BigDecimal> outToolCodeQuantityMap = outboundDetailList.stream().collect(Collectors.groupingBy(OutboundDetail::getToolCode,
            Collectors.mapping(OutboundDetail::getOutboundQuantity, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
      Map<String, ToolLedger> toolLedgerMap = toolLedgerService.list(new LambdaQueryWrapper<ToolLedger>().in(ToolLedger::getToolId, outToolCodeQuantityMap.keySet()))
            .stream().collect(Collectors.toMap(ToolLedger::getToolId, toolLedger -> toolLedger, (k1, k2) -> k1));
      List<ToolLedger> toolLedgerUpdateList = new ArrayList<>();
      for (String baseToolId : outToolCodeQuantityMap.keySet()) {
         BigDecimal outQuantity = outToolCodeQuantityMap.get(baseToolId);
         ToolLedger toolLedger = toolLedgerMap.get(baseToolId);
         if (Objects.nonNull(toolLedger)) {
            toolLedger.setAvailableCount(toolLedger.getAvailableCount().add(outQuantity));
            toolLedgerUpdateList.add(toolLedger);
         }
      }
      toolLedgerDetailService.updateBatchById(toolLedgerDetailUpdateList);
      toolLedgerService.updateBatchById(toolLedgerUpdateList);
   }
   @Override
   @Transactional(rollbackFor = Exception.class)
   public void outBoundByApply(List<OutBoundRequestDto> outBoundRequestList) {
      //校验是否来自一个申请单
      if (!fromOneApply(outBoundRequestList)) {
         throw new JeecgBootException("出库明细必须来自同一出库申请单!");
      }
      //是否可出库校验
      if (!readyToOutbound(outBoundRequestList)) {
         throw new JeecgBootException("未经审批通过的申请单不能出库!");
      }
      //开始出库
      OutboundOrder outboundOrder = getById(outBoundRequestList.get(0).getOutBoundOrderId());
      List<String> detailIds = outBoundRequestList.stream().map(OutBoundRequestDto::getOutboundDetailId).collect(Collectors.toList());
      List<OutboundDetail> outboundDetailList = outboundDetailService.listByIds(detailIds);
      List<String> toolIdList = outboundDetailList.stream().map(OutboundDetail::getToolCode).collect(Collectors.toList());
      Map<String, BigDecimal> outBoundRequestMap = outBoundRequestList.stream()
            .collect(Collectors.toMap(OutBoundRequestDto::getOutboundDetailId, OutBoundRequestDto::getOutboundQuantity));
      Map<String, BigDecimal> ratedLifeMap = outBoundRequestList.stream()
            .collect(Collectors.toMap(OutBoundRequestDto::getOutboundDetailId, dto->dto.getRatedLife() != null ? dto.getRatedLife() : BigDecimal.ZERO));
      Map<String, BigDecimal> useLifeMap = outBoundRequestList.stream()
            .collect(Collectors.toMap(OutBoundRequestDto::getOutboundDetailId, dto->dto.getUseLife() != null ? dto.getUseLife() : BigDecimal.ZERO));
      //处理库存台账及台账明细
      Map<String, ToolLedger> toolLedgerMap = toolLedgerService.list(new LambdaQueryWrapper<ToolLedger>().in(ToolLedger::getToolId, toolIdList)).stream()
            .collect(Collectors.toMap(ToolLedger::getToolId, item -> item, (k1, k2) -> k1));
      LambdaQueryWrapper<ToolLedgerDetail> ledgerDetailQueryWrapper = new LambdaQueryWrapper<>();
      LambdaQueryWrapper<ToolSharpening> toolSharpenQueryWrapper = new LambdaQueryWrapper<>();
      for (int i = 0; i < outboundDetailList.size(); i++) {
         if (i > 0) {
            ledgerDetailQueryWrapper.or();
            toolSharpenQueryWrapper.or();
         }
         OutboundDetail detail = outboundDetailList.get(i);
         ledgerDetailQueryWrapper.or(wrapper ->
               wrapper.eq(ToolLedgerDetail::getToolCode, detail.getToolCode())
                     .eq(ToolLedgerDetail::getToolId, detail.getToolId()) //如果不管到把,该字段为空
         );
         toolSharpenQueryWrapper.or(wrapper ->
               wrapper.eq(ToolSharpening::getToolCode, detail.getToolCode())
                     .eq(ToolSharpening::getToolId, detail.getToolId())
                     .eq(ToolSharpening::getSharpeningStatus, SharpenStatus.PENDING.getValue())
         );
      }
      List<ToolLedgerDetail> toolLedgerDetailList = toolLedgerDetailService.list(ledgerDetailQueryWrapper);
      List<ToolSharpening> toolSharpeningList = toolsSharpeningService.list(toolSharpenQueryWrapper);
      List<ToolLedger> toolLedgerUpdateList = CollectionUtil.newArrayList();
      List<ToolLedgerDetail> toolLedgerDetailUpdateList = CollectionUtil.newArrayList();
      List<OutStoreDetail> outStoreAddList = CollectionUtil.newArrayList();
      List<OutboundDetail> outboundDetailUpdateList = CollectionUtil.newArrayList();
      List<ToolSharpening> toolSharpeningUpdateList = CollectionUtil.newArrayList();
      for (OutboundDetail detail : outboundDetailList) {
         //更新库存台账
         ToolLedger toolLedger = toolLedgerMap.get(detail.getToolCode());
         BigDecimal outboundQuantity = outBoundRequestMap.get(detail.getId());
         BigDecimal ratedLife = ratedLifeMap.get(detail.getId());
         BigDecimal useLife = useLifeMap.get(detail.getId());
         //唯一编码不为空时(管到把),计算剩余寿命
         BigDecimal remandLife = null;
         if(StringUtils.isNotBlank(detail.getToolId())){
            ToolLedgerDetail toolLedgerDetail = toolLedgerDetailService.lambdaQuery().eq(ToolLedgerDetail::getToolId,detail.getToolId()).one();
            remandLife = (ratedLife.multiply(toolLedgerDetail.getRemainingPercentage()).subtract(useLife).max(BigDecimal.ZERO)).divide(ratedLife, 4, RoundingMode.HALF_UP);
         }
         OutStorehouseType outStorehouseType = OutStorehouseType.getByValue(outboundOrder.getOutStorehouseType());
         switch (outStorehouseType) {
            case TOOL_BORROW:
               toolLedger.setLendCount(toolLedger.getLendCount().add(outboundQuantity));
               break;
            case MAINTENANCE_OUTBOUND:
               toolLedger.setRepairCount(toolLedger.getRepairCount().add(outboundQuantity));
               break;
            case CALIBRATION_OUTBOUND:
               toolLedger.setDetectionCount(toolLedger.getDetectionCount().add(outboundQuantity));
               break;
            case GRINDING_OUTBOUND:
               toolLedger.setSharpeningCount(toolLedger.getSharpeningCount().add(outboundQuantity));
               break;
            default:
               throw new JeecgBootException("未知的出库类型!");
         }
         toolLedgerUpdateList.add(toolLedger);
         //更新台账明细
         BigDecimal finalRemandLife = remandLife;
         toolLedgerDetailList.stream().filter(item -> item.getToolCode().equals(detail.getToolCode()) && item.getToolId().equals(detail.getToolId()))
               .findFirst().ifPresent(item -> {
                  switch (outStorehouseType) {
                     case TOOL_BORROW:
                        item.setStatus(ToolCirculationStatus.BORROWED.getValue());
                        item.setRatedLife(ratedLife);
                        item.setUseLife(useLife);
                        item.setRemainingPercentage(finalRemandLife);
                        break;
                     case MAINTENANCE_OUTBOUND:
                        item.setStatus(ToolCirculationStatus.REPAIRING.getValue());
                        item.setRatedLife(ratedLife);
                        item.setUseLife(useLife);
                        item.setRemainingPercentage(finalRemandLife);
                        break;
                     case CALIBRATION_OUTBOUND:
                        item.setStatus(ToolCirculationStatus.INSPECTING.getValue());
                        item.setRatedLife(ratedLife);
                        item.setUseLife(useLife);
                        item.setRemainingPercentage(finalRemandLife);
                        break;
                     case GRINDING_OUTBOUND:
                        item.setStatus(ToolCirculationStatus.GRINDING.getValue());
                        item.setRatedLife(ratedLife);
                        item.setUseLife(useLife);
                        item.setRemainingPercentage(finalRemandLife);
                        break;
                     default:
                        throw new JeecgBootException("未知的出库类型!");
                  }
                  toolLedgerDetailUpdateList.add(item);
               });
         if (outStorehouseType == OutStorehouseType.GRINDING_OUTBOUND) {
            //更新刃磨单状态
            toolSharpeningList.stream().filter(item -> item.getToolCode().equals(detail.getToolCode()) && item.getToolId().equals(detail.getToolId()))
                  .findFirst().ifPresent(item -> {
                     item.setSharpeningStatus(SharpenStatus.IN_PROGRESS.getValue());
                     toolSharpeningUpdateList.add(item);
                  });
         }
         //记录出库流水
         OutStoreDetail outStoreDetail = new OutStoreDetail()
               .setToolCode(detail.getToolCode())
               .setToolId(detail.getToolId())
               .setOutStorehouseType(outboundOrder.getOutStorehouseType())
               .setGoodsShelvesCode(detail.getOutboundLocation())
               .setOutNumber(outboundQuantity)
               .setOutStorehouseId(outboundOrder.getId())
               .setRatedLife(ratedLife)
               .setUseLife(useLife)
               .setRemainingPercentage(remandLife)
               .setOperateType("1");//1:按申请单
         outStoreAddList.add(outStoreDetail);
         //更新申请单明细
         detail.setOutActualCount(detail.getOutActualCount().add(outboundQuantity));
         detail.setOutboundTime(new Date());
         if (detail.getOutActualCount().compareTo(detail.getOutboundQuantity()) == 0) {
            detail.setStatus(OutBoundStatusEnum.COMPLETED.getValue());
         } else {
            detail.setStatus(OutBoundStatusEnum.PARTIAL_OUTBOUND.getValue());
         }
         outboundDetailUpdateList.add(detail);
      }
      toolLedgerService.updateBatchById(toolLedgerUpdateList);
      toolLedgerDetailService.updateBatchById(toolLedgerDetailUpdateList);
      outStoreDetailService.saveBatch(outStoreAddList);
      outboundDetailService.updateBatchById(outboundDetailUpdateList);
      toolsSharpeningService.updateBatchById(toolSharpeningUpdateList);
      //更新申请单
      boolean allMatch = outboundDetailService.list(new LambdaQueryWrapper<OutboundDetail>()
                  .eq(OutboundDetail::getOutStorehouseId, outboundOrder.getId()))
            .stream().filter(i -> !detailIds.contains(i.getId()))
            .allMatch(i -> i.getStatus().equals(OutBoundStatusEnum.COMPLETED.getValue()));
      List<OutboundDetail> partialOutboundList = outboundDetailUpdateList.stream()
            .filter(i -> !i.getStatus().equals(OutBoundStatusEnum.COMPLETED.getValue())).collect(Collectors.toList());
      if (allMatch && partialOutboundList.isEmpty()) {
         outboundOrder.setOutStatus(OutBoundStatusEnum.COMPLETED.getValue());
      } else {
         outboundOrder.setOutStatus(OutBoundStatusEnum.PARTIAL_OUTBOUND.getValue());
      }
      outboundOrder.setOutboundTime(new Date());
      updateById(outboundOrder);
   }
   @Override
   @Transactional(rollbackFor = Exception.class)
   public void outBoundByAdd(List<OutBoundAddDto> boundAddList) {
      //校验台账主表库存数量
      Map<String, BigDecimal> toolBoundQuantityMap = boundAddList.stream()
            .collect(Collectors.groupingBy(OutBoundAddDto::getToolCode,
                  Collectors.mapping(OutBoundAddDto::getOutboundQuantity, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
      Map<String, ToolLedger> toolLedgerMap = toolLedgerService.list(new LambdaQueryWrapper<ToolLedger>().in(ToolLedger::getToolId, toolBoundQuantityMap.keySet()))
            .stream().collect(Collectors.toMap(ToolLedger::getToolId, item -> item, (k1, k2) -> k1));
      List<ToolLedger> toolLedgerUpdateList = CollectionUtil.newArrayList();
      for (String toolCode : toolBoundQuantityMap.keySet()) {
         ToolLedger toolLedger = toolLedgerMap.get(toolCode);
         BigDecimal outBoundQuantity = toolBoundQuantityMap.get(toolCode);
         if (Objects.isNull(toolLedger) || toolLedger.getAvailableCount().compareTo(outBoundQuantity) < 0) {
            BaseTools tools = baseToolsService.getById(toolCode);
            throw new JeecgBootException("编码为【" + tools.getToolCode() + "】的工具,库存不足!");
         } else {
            OutStorehouseType outStorehouseType = OutStorehouseType.getByValue(boundAddList.get(0).getOutStorehouseType());
            switch (outStorehouseType) {
               case TOOL_BORROW:
                  toolLedger.setLendCount(toolLedger.getLendCount().add(outBoundQuantity));
                  break;
               case MAINTENANCE_OUTBOUND:
                  toolLedger.setRepairCount(toolLedger.getRepairCount().add(outBoundQuantity));
                  break;
               case CALIBRATION_OUTBOUND:
                  toolLedger.setDetectionCount(toolLedger.getDetectionCount().add(outBoundQuantity));
                  break;
               case GRINDING_OUTBOUND:
                  toolLedger.setSharpeningCount(toolLedger.getSharpeningCount().add(outBoundQuantity));
                  break;
               default:
                  throw new JeecgBootException("未知的出库类型!");
            }
            //扣减台账主表可用库存
            toolLedger.setAvailableCount(toolLedger.getAvailableCount().subtract(outBoundQuantity));
            toolLedgerUpdateList.add(toolLedger);
         }
      }
      //校验台账明细库存数量
      Map<String, OutBoundAddDto> boundAddDtoMap = boundAddList.stream().collect(Collectors
            .toMap(OutBoundAddDto::getToolLedgerDetailId, item -> item, (k1, k2) -> k1));
      Map<String, ToolLedgerDetail> toolLedgerDetailMap = toolLedgerDetailService.listByIds(boundAddDtoMap.keySet()).stream()
            .collect(Collectors.toMap(ToolLedgerDetail::getId, item -> item, (k1, k2) -> k1));
      List<ToolSharpening> toolSharpeningList = toolsSharpeningService.list(new LambdaQueryWrapper<ToolSharpening>()
            .in(ToolSharpening::getToolCode, toolBoundQuantityMap.keySet()).eq(ToolSharpening::getSharpeningStatus, SharpenStatus.PENDING.getValue()));
      List<ToolLedgerDetail> toolLedgerDetailUpdateList = CollectionUtil.newArrayList();
      List<ToolSharpening> toolSharpeningUpdateList = CollectionUtil.newArrayList();
      for (String toolLedgerDetailId : boundAddDtoMap.keySet()) {
         OutBoundAddDto outBoundAddDto = boundAddDtoMap.get(toolLedgerDetailId);
         BigDecimal outboundQuantity = outBoundAddDto.getOutboundQuantity();
         BigDecimal ratedLife = outBoundAddDto.getRatedLife();
         BigDecimal useLife = outBoundAddDto.getUseLife();
         ToolLedgerDetail toolLedgerDetail = toolLedgerDetailMap.get(toolLedgerDetailId);
         if (Objects.isNull(toolLedgerDetail) || toolLedgerDetail.getQuantity().compareTo(outboundQuantity) < 0) {
            BaseTools tools = baseToolsService.getById(outBoundAddDto.getToolCode());
            throw new JeecgBootException("编码为【" + tools.getToolCode() + "】的工具,库存不足!");
         } else {
            //扣减台账明细库存
            if (StrUtil.isBlank(toolLedgerDetail.getToolId())) {
               //没有唯一编码,不管到把的
               toolLedgerDetail.setQuantity(toolLedgerDetail.getQuantity().subtract(outboundQuantity));
            } else {
               //有唯一编码,管道把的
               //计算剩余寿命
               BigDecimal remandLife = (ratedLife.multiply(toolLedgerDetail.getRemainingPercentage()).subtract(useLife).max(BigDecimal.ZERO)).divide(ratedLife, 4, RoundingMode.HALF_UP);
               toolLedgerDetail.setQuantity(BigDecimal.ZERO);//数量只会是0和1
               OutStorehouseType outStorehouseType = OutStorehouseType.getByValue(boundAddList.get(0).getOutStorehouseType());
               switch (outStorehouseType) {
                  case TOOL_BORROW:
                     toolLedgerDetail.setStatus(ToolCirculationStatus.BORROWED.getValue());
                     toolLedgerDetail.setRatedLife(ratedLife);
                     toolLedgerDetail.setUseLife(useLife);
                     toolLedgerDetail.setRemainingPercentage(remandLife);
                     break;
                  case MAINTENANCE_OUTBOUND:
                     toolLedgerDetail.setStatus(ToolCirculationStatus.REPAIRING.getValue());
                     toolLedgerDetail.setRatedLife(ratedLife);
                     toolLedgerDetail.setUseLife(useLife);
                     toolLedgerDetail.setRemainingPercentage(remandLife);
                     break;
                  case CALIBRATION_OUTBOUND:
                     toolLedgerDetail.setStatus(ToolCirculationStatus.INSPECTING.getValue());
                     toolLedgerDetail.setRatedLife(ratedLife);
                     toolLedgerDetail.setUseLife(useLife);
                     toolLedgerDetail.setRemainingPercentage(remandLife);
                     break;
                  case GRINDING_OUTBOUND:
                     toolLedgerDetail.setStatus(ToolCirculationStatus.GRINDING.getValue());
                     toolLedgerDetail.setRatedLife(ratedLife);
                     toolLedgerDetail.setUseLife(useLife);
                     toolLedgerDetail.setRemainingPercentage(remandLife);
                     //同时更新刃磨单状态
                     toolSharpeningList.stream().filter(item -> item.getToolCode().equals(outBoundAddDto.getToolCode())
                           && item.getToolId().equals(outBoundAddDto.getToolId())).findFirst().ifPresent(item -> {
                        item.setSharpeningStatus(SharpenStatus.IN_PROGRESS.getValue());
                        toolSharpeningUpdateList.add(item);
                     });
                     break;
                  default:
                     throw new JeecgBootException("未知的出库类型!");
               }
            }
            toolLedgerDetailUpdateList.add(toolLedgerDetail);
         }
      }
      toolLedgerService.updateBatchById(toolLedgerUpdateList);
      toolLedgerDetailService.updateBatchById(toolLedgerDetailUpdateList);
      toolsSharpeningService.updateBatchById(toolSharpeningUpdateList);
      //记录出库流水
      List<OutStoreDetail> outStoreAddList = CollectionUtil.newArrayList();
      for (OutBoundAddDto outBoundAddDto : boundAddList) {
         //获取剩余寿命
         ToolLedgerDetail toolLedgerDetail = toolLedgerDetailService.getById(outBoundAddDto.getToolLedgerDetailId());
         OutStoreDetail outStoreDetail = new OutStoreDetail()
               .setToolCode(outBoundAddDto.getToolCode())
               .setToolId(outBoundAddDto.getToolId())
               .setOutStorehouseType(outBoundAddDto.getOutStorehouseType())
               .setGoodsShelvesCode(outBoundAddDto.getOutboundLocation())
               .setOutNumber(outBoundAddDto.getOutboundQuantity())
               .setRatedLife(outBoundAddDto.getRatedLife())
               .setUseLife(outBoundAddDto.getUseLife())
               .setRemainingPercentage(toolLedgerDetail.getRemainingPercentage())
               .setOperateType("2");//2:手工操作
         outStoreAddList.add(outStoreDetail);
      }
      outStoreDetailService.saveBatch(outStoreAddList);
   }
   @Override
   public IPage<SelectOutboundToolVo> querySharpenOutboundToolPageList(Page<SelectOutboundToolVo> page, Map<String, String[]> parameterMap) {
      QueryWrapper<SelectOutboundToolVo> queryWrapper = Wrappers.query();
      String[] toolCodes = parameterMap.get("toolCode");
      if (toolCodes != null && toolCodes.length > 0) {
         queryWrapper.like("t3.tool_code", toolCodes[0]);
      }
      String[] classifyIds = parameterMap.get("classifyId");
      if (classifyIds != null && classifyIds.length > 0) {
         queryWrapper.eq("t3.classify_id", classifyIds[0]);
      }
      String[] excludeIds = parameterMap.get("excludeIds");
      if (excludeIds != null && excludeIds.length > 0) {
         String[] idArray = excludeIds[0].split(",");
         queryWrapper.notIn("t1.id", idArray);
      }
      queryWrapper.eq("t1.sharpening_status", SharpenStatus.PENDING.getValue());
      return this.baseMapper.querySharpenOutboundToolPageList(page, queryWrapper);
   }
   @Override
   public IPage<SelectOutboundToolVo> queryBorrowOutboundToolPageList(Page<SelectOutboundToolVo> page, Map<String, String[]> parameterMap) {
      QueryWrapper<SelectOutboundToolVo> queryWrapper = Wrappers.query();
      String[] toolCodes = parameterMap.get("toolCode");
      if (toolCodes != null && toolCodes.length > 0) {
         queryWrapper.like("p.tool_code", toolCodes[0]);
      }
      String[] statuses = parameterMap.get("status");
      if (statuses != null && statuses.length > 0) {
         queryWrapper.eq("t.status", statuses[0]);
      }
      String[] quantities = parameterMap.get("quantity");
      if (quantities != null && quantities.length > 0) {
         queryWrapper.gt("t.quantity", quantities[0]);
      }
      String[] classifyIds = parameterMap.get("classifyId");
      if (classifyIds != null && classifyIds.length > 0) {
         queryWrapper.eq("p.classify_id", classifyIds[0]);
      }
      String[] excludeIds = parameterMap.get("excludeIds");
      if (excludeIds != null && excludeIds.length > 0) {
         String[] idArray = excludeIds[0].split(",");
         queryWrapper.notIn("t.id", idArray);
      }
      return this.baseMapper.queryBorrowOutboundToolPageList(page, queryWrapper);
   }
   private boolean fromOneApply(List<OutBoundRequestDto> outBoundRequestList) {
      long count = outBoundRequestList.stream().map(OutBoundRequestDto::getOutBoundOrderId).distinct().count();
      if (count > 1) {
         return false;
      }
      List<String> outDetailIds = outBoundRequestList.stream().map(OutBoundRequestDto::getOutboundDetailId).collect(Collectors.toList());
      String outboundDetailId = outBoundRequestList.get(0).getOutboundDetailId();
      OutboundDetail outboundDetail = outboundDetailService.getById(outboundDetailId);
      List<String> detailIds = outboundDetailService.list(new LambdaQueryWrapper<OutboundDetail>()
            .eq(OutboundDetail::getOutStorehouseId, outboundDetail.getOutStorehouseId())).stream()
            .map(OutboundDetail::getId).collect(Collectors.toList());
        return new HashSet<>(detailIds).containsAll(outDetailIds);
    }
   private boolean readyToOutbound(List<OutBoundRequestDto> outBoundRequestList) {
      String outBoundOrderId = outBoundRequestList.get(0).getOutBoundOrderId();
      OutboundOrder outboundOrder = getById(outBoundOrderId);
        return OutBillStatus.APPROVED.getValue().equals(outboundOrder.getOrderStatus());
    }
   public boolean triggerProcess(OutboundOrder outboundOrder) {
      flowCommonService.initActBusiness("单号为:" + outboundOrder.getOutNum() + " 的出库申请,开始进行审批",
            outboundOrder.getId(), "outboundOrderServiceImpl", "tool_out_storage", null);
      Map<String, Object> variables = new HashMap<>();
      variables.put("dataId", outboundOrder.getId());
      if (StrUtil.isEmpty(outboundOrder.getRemark())) {
         variables.put("organization", "新增出库申请单默认启动流程");
         variables.put("comment", "新增出库申请单默认启动流程");
      } else {
         variables.put("organization", outboundOrder.getRemark());
         variables.put("comment", outboundOrder.getRemark());
      }
      variables.put("proofreading", true);
      List<String> usernames = new ArrayList<>();
      usernames.add(outboundOrder.getReviewer());
      variables.put("NextAssignee", usernames);
      Result result = flowDefinitionService.startProcessInstanceByKey("tool_out_storage", variables);
      return result.isSuccess();
   }
    private LoginUser getCurrentUser() {
      // 获取当前认证的登录用户信息
      Subject currentUser = SecurityUtils.getSubject();
      if (currentUser != null && currentUser.isAuthenticated()) {
@@ -133,4 +771,64 @@
      }
      return null;
   }
   private FlowMyBusiness getFlowMyBusiness(String instanceId) {
      List<FlowMyBusiness> businessList = flowMyBusinessService.list(
            new LambdaQueryWrapper<FlowMyBusiness>().eq(FlowMyBusiness::getProcessInstanceId, instanceId));
      return businessList.isEmpty() ? null : businessList.get(0);
   }
   private boolean isUserAuthorized(FlowMyBusiness flowMyBusiness, LoginUser user) {
      List<String> todoUsers = JSON.parseArray(flowMyBusiness.getTodoUsers(), String.class);
      return todoUsers != null && todoUsers.contains(user.getUsername());
   }
   private boolean claimTask(String taskId, LoginUser user) {
      Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
      if (task == null) {
         return false;
      }
      if (task.getAssignee() != null && !task.getAssignee().equals(user.getUsername())) {
         return false;
      }
      taskService.claim(taskId, user.getUsername());
      return true;
   }
   private void setupProcessVariables(OutBoundOrderFlowDto outBoundOrderFlowDto, OutboundOrder outboundOrder, LoginUser user) {
      if (OutBillStatus.SUBMITTED.getValue().equals(outboundOrder.getOrderStatus()) && user.getUsername().equals(outboundOrder.getReviewer())) {
         Map<String, Object> values = new HashMap<>();
         values.put("dataId", outboundOrder.getId());
         values.put("organization", outBoundOrderFlowDto.getApprovalOpinion());
         values.put("comment", outBoundOrderFlowDto.getApprovalOpinion());
         values.put("status", outBoundOrderFlowDto.getStatus());
         values.put("NextAssignee", Collections.singletonList(outboundOrder.getReviewer()));
         outBoundOrderFlowDto.setValues(values);
      }
   }
   @Override
   public void afterFlowHandle(FlowMyBusiness business) {
      business.getTaskNameId();//接下来审批的节点
      business.getValues();//前端传进来的参数
      business.getActStatus();
   }
   @Override
   public Object getBusinessDataById(String dataId) {
      return this.getById(dataId);
   }
   @Override
   public Map<String, Object> flowValuesOfTask(String taskNameId, Map<String, Object> values) {
      return Collections.emptyMap();
   }
   @Override
   public List<String> flowCandidateUsernamesOfTask(String taskNameId, Map<String, Object> values) {
      //业务是否干预流程,业务干预,流程干预,指定人员进行处理
      //获取下一步处理人
      Object object = values.get("NextAssignee");
      return (List<String>) object;
   }
}