package com.ruoyi.approve.service.impl;
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.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
import com.ruoyi.approve.pojo.*;
import com.ruoyi.approve.service.*;
import com.ruoyi.approve.utils.ApproveProcessConfigNodeUtils;
import com.ruoyi.common.enums.*;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.project.system.service.ISysNoticeService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.mapper.QualityInspectParamMapper;
import com.ruoyi.quality.mapper.QualityTestStandardMapper;
import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
import com.ruoyi.quality.utils.QualityInspectHelper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
*
* 审批实例服务实现类
*
*
* @since 2026-05-18 03:27:46
*/
@Service
@RequiredArgsConstructor
public class ApprovalInstanceServiceImpl extends ServiceImpl implements ApprovalInstanceService {
private final ApprovalInstanceMapper approvalInstanceMapper;
private final ApproveProcessConfigNodeUtils approveProcessConfigNodeUtils;
private final ApprovalInstanceNodeService approvalInstanceNodeService;
private final ApprovalTaskService approvalTaskService;
private final ApprovalRecordService approvalRecordService;
private final ApprovalTemplateNodeService approvalTemplateNodeService;
private final ApprovalTemplateNodeApproverService approvalTemplateNodeApproverService;
private final ISysNoticeService sysNoticeService;
private final PurchaseLedgerMapper purchaseLedgerMapper;
private final SalesLedgerProductMapper salesLedgerProductMapper;
private final StockUtils stockUtils;
private final QualityInspectMapper qualityInspectMapper;
private final QualityTestStandardMapper qualityTestStandardMapper;
private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
private final QualityInspectParamMapper qualityInspectParamMapper;
private final SalesQuotationMapper salesQuotationMapper;
private final ShippingInfoMapper shippingInfoMapper;
private final ApprovalTemplateMapper approvalTemplateMapper;
private final QualityInspectHelper qualityInspectHelper;
@Override
public R listPage(Page page, ApprovalInstanceDto approvalInstanceDto) {
IPage approvalInstanceVoIPage = approvalInstanceMapper.listPage(page, approvalInstanceDto);
List records = approvalInstanceVoIPage.getRecords();
if (records == null || records.isEmpty()) {
return R.ok(approvalInstanceVoIPage);
}
records.forEach(vo -> {
vo.setBusinessName(TypeEnums.getLabelByValue(vo.getBusinessType()));
});
Long currentUserId = SecurityUtils.getUserId();
List instanceIds = records.stream()
.map(ApprovalInstanceVo::getId)
.filter(id -> id != null)
.distinct()
.collect(Collectors.toList());
if (!instanceIds.isEmpty()) {
Map> recordMap = approvalRecordService.list(
Wrappers.lambdaQuery()
.in(ApprovalRecord::getInstanceId, instanceIds)
.eq(ApprovalRecord::getDeleted, 0)
).stream().collect(Collectors.groupingBy(ApprovalRecord::getInstanceId));
Map> taskMap = approvalTaskService.list(
Wrappers.lambdaQuery()
.in(ApprovalTask::getInstanceId, instanceIds)
.eq(ApprovalTask::getDeleted, 0)
).stream().collect(Collectors.groupingBy(ApprovalTask::getInstanceId));
for (ApprovalInstanceVo vo : records) {
vo.setIsApprove(approveProcessConfigNodeUtils.isCurrentApprover(vo.getId(), currentUserId));
vo.setRecords(recordMap.getOrDefault(vo.getId(), new ArrayList<>()));
vo.setTasks(taskMap.getOrDefault(vo.getId(), new ArrayList<>()));
}
}
return R.ok(approvalInstanceVoIPage);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean add(ApprovalInstanceDto approvalInstanceDto) {
String instanceNo = OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no");
approvalInstanceDto.setInstanceNo(instanceNo);
approvalInstanceDto.setStatus("PENDING");
approvalInstanceDto.setCurrentLevel(1);
boolean saved = this.save(approvalInstanceDto);
if (!saved) {
return false;
}
approveProcessConfigNodeUtils.createCurrentNodeAndTasks(approvalInstanceDto);
sendApproveNotice(approvalInstanceDto, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean delete(List ids) {
if (ids == null || ids.isEmpty()) {
return false;
}
int instanceRows = approvalInstanceMapper.update(
null,
Wrappers.lambdaUpdate()
.in(ApprovalInstance::getId, ids)
.eq(ApprovalInstance::getDeleted, 0)
.set(ApprovalInstance::getDeleted, (byte) 1)
);
LambdaUpdateWrapper nodeUpdateWrapper = Wrappers.lambdaUpdate();
nodeUpdateWrapper.in(ApprovalInstanceNode::getInstanceId, ids)
.eq(ApprovalInstanceNode::getDeleted, 0)
.set(ApprovalInstanceNode::getDeleted, (byte) 1);
approvalInstanceNodeService.update(nodeUpdateWrapper);
LambdaUpdateWrapper taskUpdateWrapper = Wrappers.lambdaUpdate();
taskUpdateWrapper.in(ApprovalTask::getInstanceId, ids)
.eq(ApprovalTask::getDeleted, 0)
.set(ApprovalTask::getDeleted, (byte) 1);
approvalTaskService.update(taskUpdateWrapper);
LambdaUpdateWrapper recordUpdateWrapper = Wrappers.lambdaUpdate();
recordUpdateWrapper.in(ApprovalRecord::getInstanceId, ids)
.eq(ApprovalRecord::getDeleted, 0)
.set(ApprovalRecord::getDeleted, (byte) 1);
approvalRecordService.update(recordUpdateWrapper);
return instanceRows > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public R approve(ApprovalInstanceDto approvalInstanceDto) {
if (approvalInstanceDto == null || approvalInstanceDto.getId() == null) {
return R.fail("审批实例 ID 不能为空");
}
String approveAction = normalizeApproveAction(approvalInstanceDto.getApproveAction());
if (approveAction == null) {
return R.fail("审批动作只支持 APPROVED 或 REJECTED");
}
ApprovalInstance instance = getPendingApprovalInstance(approvalInstanceDto.getId());
if (instance == null) {
return R.fail("审批实例不存在");
}
ApprovalInstanceNode currentNode = approveProcessConfigNodeUtils.getCurrentNode(instance.getId());
if (currentNode == null) {
return R.fail("当前没有待处理的审批节点");
}
Long currentUserId = SecurityUtils.getUserId();
ApprovalTask currentTask = approveProcessConfigNodeUtils.getCurrentUserTask(instance.getId(), currentUserId);
if (currentTask == null) {
return R.fail("当前用户没有可审批任务");
}
LoginUser loginUser = SecurityUtils.getLoginUser();
String operatorName = loginUser.getUser() != null ? loginUser.getUser().getNickName() : SecurityUtils.getUsername();
LocalDateTime now = LocalDateTime.now();
if (!updateCurrentTask(approvalInstanceDto, approveAction, currentTask, now)) {
return R.fail("当前任务已被处理,请刷新后重试");
}
saveApprovalRecord(
instance.getId(),
currentNode.getId(),
currentTask.getId(),
currentUserId,
operatorName,
approveAction,
approvalInstanceDto.getApproveComment()
);
if ("REJECTED".equals(approveAction)) {
return rejectCurrentNode(instance, currentNode, now);
}
if (!approveProcessConfigNodeUtils.canProceedToNextLevel(instance.getId(), currentNode.getApproveType())) {
return R.ok("审批成功,等待其他审批人处理");
}
return approveAndMoveNext(instance, currentNode, approvalInstanceDto, now);
}
private String normalizeApproveAction(String approveAction) {
if (!StringUtils.hasText(approveAction)) {
return null;
}
String normalizedAction = approveAction.trim().toUpperCase(Locale.ROOT);
return "APPROVED".equals(normalizedAction) || "REJECTED".equals(normalizedAction)
? normalizedAction
: null;
}
private ApprovalInstance getPendingApprovalInstance(Long instanceId) {
return this.getOne(
new LambdaQueryWrapper()
.eq(ApprovalInstance::getId, instanceId)
.eq(ApprovalInstance::getDeleted, 0)
.last("LIMIT 1")
);
}
private boolean updateCurrentTask(ApprovalInstanceDto approvalInstanceDto,
String approveAction,
ApprovalTask currentTask,
LocalDateTime now) {
// 仅允许待审批任务被成功处理一次,避免并发下重复审批成功。
return approvalTaskService.update(
Wrappers.lambdaUpdate()
.eq(ApprovalTask::getId, currentTask.getId())
.eq(ApprovalTask::getTaskStatus, "PENDING")
.eq(ApprovalTask::getDeleted, 0)
.set(ApprovalTask::getTaskStatus, approveAction)
.set(ApprovalTask::getComment, approvalInstanceDto.getApproveComment())
.set(ApprovalTask::getApproveTime, now)
.set(ApprovalTask::getIsRead, (byte) 1)
);
}
private R rejectCurrentNode(ApprovalInstance instance, ApprovalInstanceNode currentNode, LocalDateTime now) {
if (!updateCurrentNodeStatus(currentNode.getId(), "REJECTED", now)) {
return R.ok("当前节点已处理完成");
}
closePendingTasks(instance.getId(), currentNode.getId());
instance.setStatus("REJECTED");
instance.setFinishTime(now);
this.updateById(instance);
return R.ok("审批已驳回");
}
private R approveAndMoveNext(ApprovalInstance instance,
ApprovalInstanceNode currentNode,
ApprovalInstanceDto approvalInstanceDto,
LocalDateTime now) {
if (!updateCurrentNodeStatus(currentNode.getId(), "APPROVED", now)) {
return R.ok("当前节点已处理完成");
}
closePendingTasks(instance.getId(), currentNode.getId());
int nextLevel = currentNode.getLevelNo() + 1;
ApprovalTemplateNode nextTemplateNode = approvalTemplateNodeService.getOne(
new LambdaQueryWrapper()
.eq(ApprovalTemplateNode::getTemplateId, instance.getTemplateId())
.eq(ApprovalTemplateNode::getLevelNo, nextLevel)
.orderByAsc(ApprovalTemplateNode::getId)
.last("LIMIT 1")
);
if (nextTemplateNode == null) {
instance.setStatus("APPROVED");
instance.setFinishTime(now);
this.updateById(instance);
handleBusinessAfterApprovalFinished(instance);
return R.ok("审批已完成");
}
instance.setCurrentLevel(nextLevel);
instance.setStatus("PENDING");
this.updateById(instance);
approveProcessConfigNodeUtils.createCurrentNodeAndTasks(instance, false);
sendApproveNotice(instance, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
return R.ok("审批成功,已流转到下一节点");
}
private boolean updateCurrentNodeStatus(Long nodeId, String targetStatus, LocalDateTime now) {
// 仅允许一个请求将当前节点从待处理推进到目标状态,避免重复流转。
return approvalInstanceNodeService.update(
Wrappers.lambdaUpdate()
.eq(ApprovalInstanceNode::getId, nodeId)
.eq(ApprovalInstanceNode::getStatus, "PENDING")
.eq(ApprovalInstanceNode::getDeleted, 0)
.set(ApprovalInstanceNode::getStatus, targetStatus)
.set(ApprovalInstanceNode::getFinishTime, now)
);
}
private void handleBusinessAfterApprovalFinished(ApprovalInstance instance) {
String status = instance.getStatus();
Long businessType = instance.getBusinessType();
if (TypeEnums.PURCHASE_APPROVAL.getCode().equals(businessType)) {
handlePurchaseApprovalFinished(instance, status);
return;
}
if (TypeEnums.QUOTATION_APPROVAL.getCode().equals(businessType)) {
handleSalesQuotationApprovalFinished(instance, status);
return;
}
if (TypeEnums.SHIPPING_APPROVAL.getCode().equals(businessType)) {
handleShippingApprovalFinished(instance, status);
}
}
private void handlePurchaseApprovalFinished(ApprovalInstance instance, String status) {
PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(
new LambdaQueryWrapper()
.eq(PurchaseLedger::getPurchaseContractNumber, instance.getTitle())
.last("limit 1")
);
if (purchaseLedger == null) {
return;
}
if ("APPROVED".equals(status)) {
purchaseLedger.setApprovalStatus(ApprovalStatusEnum.APPROVED.getCode());
List salesLedgerProducts = salesLedgerProductMapper.selectList(
new QueryWrapper().lambda()
.eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
.eq(SalesLedgerProduct::getType, 2)
);
for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
if (salesLedgerProduct.getIsChecked()) {
qualityInspectHelper.addQualityInspect(purchaseLedger, salesLedgerProduct);
} else {
stockUtils.addStockWithBatchNo(
salesLedgerProduct.getProductModelId(),
salesLedgerProduct.getQuantity(),
StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
purchaseLedger.getId(),
purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId()
);
}
}
} else if ("REJECTED".equals(status)) {
purchaseLedger.setApprovalStatus(ApprovalStatusEnum.REJECTED.getCode());
} else if ("PENDING".equals(status)) {
purchaseLedger.setApprovalStatus(ApprovalStatusEnum.IN_PROGRESS.getCode());
}
purchaseLedgerMapper.updateById(purchaseLedger);
}
private void handleSalesQuotationApprovalFinished(ApprovalInstance instance, String status) {
SalesQuotation salesQuote = salesQuotationMapper.selectOne(
new LambdaQueryWrapper()
.eq(SalesQuotation::getQuotationNo, instance.getTitle())
.last("limit 1")
);
if (salesQuote == null) {
return;
}
if ("APPROVED".equals(status)) {
salesQuote.setStatus(SalesQuotationStatusEnum.APPROVED.getCode());
} else if ("REJECTED".equals(status)) {
salesQuote.setStatus(SalesQuotationStatusEnum.REJECTED.getCode());
} else if ("PENDING".equals(status)) {
salesQuote.setStatus(SalesQuotationStatusEnum.IN_PROGRESS.getCode());
}
salesQuotationMapper.updateById(salesQuote);
}
private void handleShippingApprovalFinished(ApprovalInstance instance, String status) {
ShippingInfo shippingInfo = shippingInfoMapper.selectOne(
new LambdaQueryWrapper()
.eq(ShippingInfo::getShippingNo, instance.getTitle())
.orderByDesc(ShippingInfo::getCreateTime)
.last("limit 1")
);
if (shippingInfo == null) {
return;
}
if ("APPROVED".equals(status)) {
shippingInfo.setStatus(ShippingStatusEnum.APPROVED.getCode());
shippingInfo.setShippingDate(new Date());
stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
} else if ("REJECTED".equals(status)) {
stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
shippingInfo.setStatus(ShippingStatusEnum.REJECTED.getCode());
} else if ("PENDING".equals(status)) {
shippingInfo.setStatus(ShippingStatusEnum.IN_PROGRESS.getCode());
}
shippingInfoMapper.updateById(shippingInfo);
}
private List createNodeAndTasks(ApprovalInstance instance, ApprovalTemplateNode templateNode) {
List approvers = approvalTemplateNodeApproverService.list(
new LambdaQueryWrapper()
.eq(ApprovalTemplateNodeApprover::getTemplateId, instance.getTemplateId())
.eq(ApprovalTemplateNodeApprover::getNodeId, templateNode.getId())
.eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
.orderByAsc(ApprovalTemplateNodeApprover::getSortNo)
);
if (approvers == null || approvers.isEmpty()) {
throw new RuntimeException("下一审批节点未配置审批人");
}
ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
instanceNode.setInstanceId(instance.getId());
instanceNode.setLevelNo(templateNode.getLevelNo());
instanceNode.setApproveType(templateNode.getApproveType());
instanceNode.setStatus("PENDING");
instanceNode.setStartTime(LocalDateTime.now());
instanceNode.setDeleted((byte) 0);
approvalInstanceNodeService.save(instanceNode);
List taskList = new ArrayList<>(approvers.size());
for (ApprovalTemplateNodeApprover approver : approvers) {
ApprovalTask task = new ApprovalTask();
task.setInstanceId(instance.getId());
task.setNodeId(instanceNode.getId());
task.setLevelNo(instanceNode.getLevelNo());
task.setApproverId(approver.getApproverId());
task.setApproverName(approver.getApproverName());
task.setTaskStatus("PENDING");
task.setIsRead((byte) 0);
task.setDeleted((byte) 0);
taskList.add(task);
}
approvalTaskService.saveBatch(taskList);
return taskList;
}
private void sendApproveNotice(ApprovalInstance instance, List tasks) {
if (instance == null || tasks == null || tasks.isEmpty()) {
return;
}
List approverIds = tasks.stream()
.map(ApprovalTask::getApproverId)
.filter(id -> id != null && id > 0)
.distinct()
.collect(Collectors.toList());
if (approverIds.isEmpty()) {
return;
}
String title = StringUtils.hasText(instance.getTemplateName()) ? instance.getTemplateName() : "审批提醒";
String message = "审批单号 " + instance.getInstanceNo() + " 需要您审批";
String jumpPath = "/approvalInstance?id=" + instance.getId();
sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
}
private void closePendingTasks(Long instanceId, Long nodeId) {
LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(ApprovalTask::getInstanceId, instanceId)
.eq(ApprovalTask::getNodeId, nodeId)
.eq(ApprovalTask::getTaskStatus, "PENDING")
.eq(ApprovalTask::getDeleted, 0)
.set(ApprovalTask::getDeleted, (byte) 1);
approvalTaskService.update(updateWrapper);
}
private void saveApprovalRecord(Long instanceId,
Long nodeId,
Long taskId,
Long operatorId,
String operatorName,
String action,
String comment) {
ApprovalRecord record = new ApprovalRecord();
record.setInstanceId(instanceId);
record.setNodeId(nodeId);
record.setTaskId(taskId);
record.setOperatorId(operatorId);
record.setOperatorName(operatorName);
record.setAction(action);
record.setComment(comment);
record.setDeleted((byte) 0);
approvalRecordService.save(record);
}
}