yxk-20260728
改善提案 实现部门评议小组打分功能
This commit is contained in:
+1
-1
@@ -33,7 +33,7 @@ import java.util.Map;
|
||||
public class TaskCreatedAutoSubmitListener implements TaskListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String DEFAULT_START_APPROVAL_OPINION = "发起督办流程。";
|
||||
private static final String DEFAULT_START_APPROVAL_OPINION = "发起流程。";
|
||||
private static ISysBaseAPI sysBaseAPI;
|
||||
private static IExtActBpmLogService extActBpmLogService;
|
||||
protected static RuntimeService runtimeService;
|
||||
|
||||
+28
@@ -27,6 +27,7 @@ import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementDeptScore;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementInstituteVote;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementProposal;
|
||||
import org.jeecg.modules.improvementproposal.dto.ImprovementDeptScoreBpmDTO;
|
||||
import org.jeecg.modules.improvementproposal.dto.ImprovementProposalBpmActionDTO;
|
||||
import org.jeecg.modules.improvementproposal.vo.ImprovementProposalPage;
|
||||
import org.jeecg.modules.improvementproposal.service.IImprovementProposalService;
|
||||
@@ -157,6 +158,33 @@ public class ImprovementProposalController {
|
||||
return handleBpmAction(() -> improvementProposalService.saveInitialReview(action, currentUser()));
|
||||
}
|
||||
|
||||
@GetMapping(value = "/bpm/myDeptScore")
|
||||
@Operation(summary = "查询当前部门评委个人评分")
|
||||
public Result<ImprovementDeptScore> getMyDeptScore(@RequestParam String mainId, @RequestParam String processInstanceId,
|
||||
@RequestParam String taskId) {
|
||||
ImprovementProposalBpmActionDTO action = new ImprovementProposalBpmActionDTO();
|
||||
action.setMainId(mainId);
|
||||
action.setProcessInstanceId(processInstanceId);
|
||||
action.setTaskId(taskId);
|
||||
try {
|
||||
return Result.OK(improvementProposalService.getMyDeptScore(action, currentUser()));
|
||||
} catch (IllegalStateException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping(value = "/bpm/deptScore")
|
||||
@Operation(summary = "保存当前部门评委个人评分草稿")
|
||||
public Result<String> saveDeptScore(@RequestBody ImprovementDeptScoreBpmDTO score) {
|
||||
return handleBpmAction(() -> improvementProposalService.saveDeptScore(score, currentUser()));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/bpm/submitDeptScore")
|
||||
@Operation(summary = "锁定当前部门评委个人评分")
|
||||
public Result<String> submitDeptScore(@RequestBody ImprovementProposalBpmActionDTO action) {
|
||||
return handleBpmAction(() -> improvementProposalService.submitDeptScore(action, currentUser()));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/bpm/leaderAward")
|
||||
@Operation(summary = "保存部门负责人确认奖项")
|
||||
public Result<String> saveLeaderAward(@RequestBody ImprovementProposalBpmActionDTO action) {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package org.jeecg.modules.improvementproposal.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 部门评议评委提交个人评分时使用的流程节点参数。
|
||||
*/
|
||||
@Data
|
||||
public class ImprovementDeptScoreBpmDTO {
|
||||
|
||||
@Schema(description = "改善提案主表ID")
|
||||
private String mainId;
|
||||
|
||||
@Schema(description = "流程实例ID")
|
||||
private String processInstanceId;
|
||||
|
||||
@Schema(description = "当前流程任务ID")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "改善效果得分,0-100")
|
||||
private BigDecimal effectScore;
|
||||
|
||||
@Schema(description = "可推广度得分,0-100")
|
||||
private BigDecimal promotionScore;
|
||||
|
||||
@Schema(description = "独创性得分,0-100")
|
||||
private BigDecimal originalityScore;
|
||||
|
||||
@Schema(description = "努力度得分,0-100")
|
||||
private BigDecimal effortScore;
|
||||
|
||||
@Schema(description = "评分意见")
|
||||
private String scoreOpinion;
|
||||
}
|
||||
+10
@@ -3,6 +3,7 @@ package org.jeecg.modules.improvementproposal.service;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementDeptScore;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementInstituteVote;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementProposal;
|
||||
import org.jeecg.modules.improvementproposal.dto.ImprovementDeptScoreBpmDTO;
|
||||
import org.jeecg.modules.improvementproposal.dto.ImprovementProposalBpmActionDTO;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
@@ -62,6 +63,15 @@ public interface IImprovementProposalService extends IService<ImprovementProposa
|
||||
/** 保存部门精益专员初审结果,并写入部门评分网关变量。 */
|
||||
void saveInitialReview(ImprovementProposalBpmActionDTO action, LoginUser loginUser);
|
||||
|
||||
/** 获取当前部门评委在当前流程任务下的个人评分,不返回其他评委数据。 */
|
||||
ImprovementDeptScore getMyDeptScore(ImprovementProposalBpmActionDTO action, LoginUser loginUser);
|
||||
|
||||
/** 保存当前部门评委的四维度评分草稿,流程任务提交前允许修改。 */
|
||||
void saveDeptScore(ImprovementDeptScoreBpmDTO score, LoginUser loginUser);
|
||||
|
||||
/** 锁定当前评委的已保存评分,并在全部评委完成时汇总部门评议结果。 */
|
||||
void submitDeptScore(ImprovementProposalBpmActionDTO action, LoginUser loginUser);
|
||||
|
||||
/** 保存部门负责人确认奖项,并写入所级投票网关变量。 */
|
||||
void saveLeaderAward(ImprovementProposalBpmActionDTO action, LoginUser loginUser);
|
||||
|
||||
|
||||
+194
@@ -3,6 +3,7 @@ package org.jeecg.modules.improvementproposal.service.impl;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementProposal;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementDeptScore;
|
||||
import org.jeecg.modules.improvementproposal.entity.ImprovementInstituteVote;
|
||||
import org.jeecg.modules.improvementproposal.dto.ImprovementDeptScoreBpmDTO;
|
||||
import org.jeecg.modules.improvementproposal.dto.ImprovementProposalBpmActionDTO;
|
||||
import org.jeecg.modules.improvementproposal.mapper.ImprovementDeptScoreMapper;
|
||||
import org.jeecg.modules.improvementproposal.mapper.ImprovementInstituteVoteMapper;
|
||||
@@ -17,12 +18,15 @@ 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.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.task.api.Task;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
@@ -43,6 +47,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
||||
private static final String FLOW_NAME = "改善提案审批";
|
||||
private static final String BPM_FORM_URL = "gh/improvementProposal/components/ImprovementProposalBPMForm";
|
||||
private static final String TASK_INITIAL_REVIEW = "Task_improve001";
|
||||
private static final String TASK_DEPT_SCORE = "Task_improve002";
|
||||
private static final String TASK_LEADER_AWARD = "Task_improve003";
|
||||
private static final String TASK_INSTITUTE_VOTE = "Task_improve005";
|
||||
private static final String TASK_ARCHIVE = "Task_improve006";
|
||||
@@ -62,6 +67,8 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
||||
@Autowired
|
||||
private RuntimeService runtimeService;
|
||||
@Autowired
|
||||
private HistoryService historyService;
|
||||
@Autowired
|
||||
private TaskService taskService;
|
||||
|
||||
@Override
|
||||
@@ -201,6 +208,87 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
||||
runtimeService.setVariable(action.getProcessInstanceId(), "need_dept_score", needDeptScore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImprovementDeptScore getMyDeptScore(ImprovementProposalBpmActionDTO action, LoginUser loginUser) {
|
||||
ImprovementProposal proposal = validateDeptScoreTask(action, loginUser);
|
||||
return findDeptScore(proposal.getId(), loginUser.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveDeptScore(ImprovementDeptScoreBpmDTO scoreAction, LoginUser loginUser) {
|
||||
validateScoreValues(scoreAction);
|
||||
ImprovementProposalBpmActionDTO action = toBpmAction(scoreAction);
|
||||
ImprovementProposal proposal = validateDeptScoreTask(action, loginUser);
|
||||
|
||||
// 锁定主表后再查询个人记录,未设置数据库唯一约束时也能阻止同一评委并发生成重复评分。
|
||||
ImprovementProposal lockedProposal = improvementProposalMapper.selectByIdForUpdate(proposal.getId());
|
||||
if (lockedProposal == null || !scoreAction.getProcessInstanceId().equals(lockedProposal.getProcessInstanceId())) {
|
||||
throw new IllegalStateException("提案流程状态已变化,请刷新后重试");
|
||||
}
|
||||
ImprovementDeptScore existingScore = findDeptScore(lockedProposal.getId(), loginUser.getId());
|
||||
if (existingScore != null && "completed".equals(existingScore.getScoreStatus())) {
|
||||
throw new IllegalStateException("评分已锁定,请重新提交流程办理");
|
||||
}
|
||||
|
||||
BigDecimal totalScore = calculateTotalScore(scoreAction);
|
||||
if (existingScore == null) {
|
||||
ImprovementDeptScore newScore = new ImprovementDeptScore();
|
||||
newScore.setMainId(lockedProposal.getId());
|
||||
newScore.setProcessTaskId(scoreAction.getTaskId());
|
||||
newScore.setScorerId(loginUser.getId());
|
||||
newScore.setScorerName(loginUser.getRealname());
|
||||
fillDeptScoreDraft(newScore, scoreAction, totalScore);
|
||||
improvementDeptScoreMapper.insert(newScore);
|
||||
} else {
|
||||
// 草稿保存只能更新 pending 记录,避免流程已提交后通过接口修改最终评分。
|
||||
int updated = improvementDeptScoreMapper.update(null, new LambdaUpdateWrapper<ImprovementDeptScore>()
|
||||
.eq(ImprovementDeptScore::getId, existingScore.getId())
|
||||
.eq(ImprovementDeptScore::getScoreStatus, "pending")
|
||||
.set(ImprovementDeptScore::getProcessTaskId, scoreAction.getTaskId())
|
||||
.set(ImprovementDeptScore::getEffectScore, scoreAction.getEffectScore())
|
||||
.set(ImprovementDeptScore::getPromotionScore, scoreAction.getPromotionScore())
|
||||
.set(ImprovementDeptScore::getOriginalityScore, scoreAction.getOriginalityScore())
|
||||
.set(ImprovementDeptScore::getEffortScore, scoreAction.getEffortScore())
|
||||
.set(ImprovementDeptScore::getTotalScore, totalScore)
|
||||
.set(ImprovementDeptScore::getScoreOpinion, scoreAction.getScoreOpinion())
|
||||
.set(ImprovementDeptScore::getScoreStatus, "pending")
|
||||
.set(ImprovementDeptScore::getScoreTime, null));
|
||||
if (updated != 1) {
|
||||
throw new IllegalStateException("评分状态已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void submitDeptScore(ImprovementProposalBpmActionDTO action, LoginUser loginUser) {
|
||||
ImprovementProposal proposal = validateDeptScoreTask(action, loginUser);
|
||||
ImprovementProposal lockedProposal = improvementProposalMapper.selectByIdForUpdate(proposal.getId());
|
||||
if (lockedProposal == null || !action.getProcessInstanceId().equals(lockedProposal.getProcessInstanceId())) {
|
||||
throw new IllegalStateException("提案流程状态已变化,请刷新后重试");
|
||||
}
|
||||
ImprovementDeptScore score = findDeptScore(lockedProposal.getId(), loginUser.getId());
|
||||
if (score == null) {
|
||||
throw new IllegalStateException("请先保存个人评分");
|
||||
}
|
||||
validateSavedScore(score);
|
||||
|
||||
// 通用流程办理可能因网络重试再次调用 beforeHandle;已锁定时保持幂等,让任务能够再次提交完成。
|
||||
if (!"completed".equals(score.getScoreStatus())) {
|
||||
int updated = improvementDeptScoreMapper.update(null, new LambdaUpdateWrapper<ImprovementDeptScore>()
|
||||
.eq(ImprovementDeptScore::getId, score.getId())
|
||||
.eq(ImprovementDeptScore::getScoreStatus, "pending")
|
||||
.set(ImprovementDeptScore::getScoreStatus, "completed")
|
||||
.set(ImprovementDeptScore::getScoreTime, new Date())
|
||||
.set(ImprovementDeptScore::getProcessTaskId, action.getTaskId()));
|
||||
if (updated != 1) {
|
||||
throw new IllegalStateException("评分状态已变化,请刷新后重试");
|
||||
}
|
||||
}
|
||||
updateDeptScoreSummaryIfComplete(lockedProposal, action.getProcessInstanceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveLeaderAward(ImprovementProposalBpmActionDTO action, LoginUser loginUser) {
|
||||
@@ -257,6 +345,112 @@ public class ImprovementProposalServiceImpl extends ServiceImpl<ImprovementPropo
|
||||
}
|
||||
}
|
||||
|
||||
private ImprovementProposalBpmActionDTO toBpmAction(ImprovementDeptScoreBpmDTO scoreAction) {
|
||||
ImprovementProposalBpmActionDTO action = new ImprovementProposalBpmActionDTO();
|
||||
action.setMainId(scoreAction.getMainId());
|
||||
action.setProcessInstanceId(scoreAction.getProcessInstanceId());
|
||||
action.setTaskId(scoreAction.getTaskId());
|
||||
return action;
|
||||
}
|
||||
|
||||
private ImprovementProposal validateDeptScoreTask(ImprovementProposalBpmActionDTO action, LoginUser loginUser) {
|
||||
ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_DEPT_SCORE);
|
||||
Task task = taskService.createTaskQuery().taskId(action.getTaskId()).singleResult();
|
||||
// 部门评议必须使用并行多实例的直接办理人任务,避免候选组任务被未分配人员越权评分。
|
||||
if (task == null || !loginUser.getUsername().equals(task.getAssignee())) {
|
||||
throw new IllegalStateException("当前用户不是本评分任务的办理人");
|
||||
}
|
||||
return proposal;
|
||||
}
|
||||
|
||||
private ImprovementDeptScore findDeptScore(String mainId, String scorerId) {
|
||||
return improvementDeptScoreMapper.selectByMainId(mainId).stream()
|
||||
.filter(item -> scorerId.equals(item.getScorerId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private void validateScoreValues(ImprovementDeptScoreBpmDTO scoreAction) {
|
||||
if (scoreAction == null) {
|
||||
throw new IllegalStateException("评分参数不能为空");
|
||||
}
|
||||
validateScoreValue(scoreAction.getEffectScore(), "改善效果");
|
||||
validateScoreValue(scoreAction.getPromotionScore(), "可推广度");
|
||||
validateScoreValue(scoreAction.getOriginalityScore(), "独创性");
|
||||
validateScoreValue(scoreAction.getEffortScore(), "努力度");
|
||||
}
|
||||
|
||||
private void validateScoreValue(BigDecimal score, String dimensionName) {
|
||||
if (score == null || score.scale() > 2 || score.compareTo(BigDecimal.ZERO) < 0 || score.compareTo(BigDecimal.valueOf(100)) > 0) {
|
||||
throw new IllegalStateException(dimensionName + "得分必须是 0 到 100 之间且最多两位小数的数值");
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal calculateTotalScore(ImprovementDeptScoreBpmDTO scoreAction) {
|
||||
return scoreAction.getEffectScore().multiply(new BigDecimal("0.50"))
|
||||
.add(scoreAction.getPromotionScore().multiply(new BigDecimal("0.20")))
|
||||
.add(scoreAction.getOriginalityScore().multiply(new BigDecimal("0.20")))
|
||||
.add(scoreAction.getEffortScore().multiply(new BigDecimal("0.10")))
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private void fillDeptScoreDraft(ImprovementDeptScore target, ImprovementDeptScoreBpmDTO source, BigDecimal totalScore) {
|
||||
target.setEffectScore(source.getEffectScore());
|
||||
target.setPromotionScore(source.getPromotionScore());
|
||||
target.setOriginalityScore(source.getOriginalityScore());
|
||||
target.setEffortScore(source.getEffortScore());
|
||||
target.setTotalScore(totalScore);
|
||||
target.setScoreOpinion(source.getScoreOpinion());
|
||||
target.setScoreStatus("pending");
|
||||
target.setScoreTime(null);
|
||||
}
|
||||
|
||||
private void validateSavedScore(ImprovementDeptScore score) {
|
||||
validateScoreValue(score.getEffectScore(), "改善效果");
|
||||
validateScoreValue(score.getPromotionScore(), "可推广度");
|
||||
validateScoreValue(score.getOriginalityScore(), "独创性");
|
||||
validateScoreValue(score.getEffortScore(), "努力度");
|
||||
if (score.getTotalScore() == null) {
|
||||
throw new IllegalStateException("请先保存完整个人评分");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDeptScoreSummaryIfComplete(ImprovementProposal proposal, String processInstanceId) {
|
||||
long expectedScorerCount = historyService.createHistoricTaskInstanceQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.taskDefinitionKey(TASK_DEPT_SCORE)
|
||||
.count();
|
||||
List<ImprovementDeptScore> scoreList = improvementDeptScoreMapper.selectByMainId(proposal.getId());
|
||||
boolean allCompleted = expectedScorerCount > 0 && scoreList.size() == expectedScorerCount
|
||||
&& scoreList.stream().allMatch(item -> "completed".equals(item.getScoreStatus()) && item.getTotalScore() != null);
|
||||
if (!allCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
BigDecimal totalScore = scoreList.stream()
|
||||
.map(ImprovementDeptScore::getTotalScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal averageScore = totalScore.divide(BigDecimal.valueOf(expectedScorerCount), 2, RoundingMode.HALF_UP);
|
||||
ImprovementProposal summary = new ImprovementProposal();
|
||||
summary.setId(proposal.getId());
|
||||
summary.setDeptAvgScore(averageScore);
|
||||
summary.setSuggestedAward(resolveSuggestedAward(averageScore));
|
||||
improvementProposalMapper.updateById(summary);
|
||||
}
|
||||
|
||||
private String resolveSuggestedAward(BigDecimal averageScore) {
|
||||
if (averageScore.compareTo(BigDecimal.valueOf(80)) >= 0) {
|
||||
return AWARD_GOLD;
|
||||
}
|
||||
if (averageScore.compareTo(BigDecimal.valueOf(60)) >= 0) {
|
||||
return AWARD_SILVER;
|
||||
}
|
||||
if (averageScore.compareTo(BigDecimal.valueOf(40)) >= 0) {
|
||||
return AWARD_BRONZE;
|
||||
}
|
||||
return AWARD_PARTICIPATION;
|
||||
}
|
||||
|
||||
private ImprovementProposal validateRunningTask(ImprovementProposalBpmActionDTO action, LoginUser loginUser, String expectedTaskDefKey) {
|
||||
if (action == null || oConvertUtils.isEmpty(action.getMainId()) || oConvertUtils.isEmpty(action.getProcessInstanceId())
|
||||
|| oConvertUtils.isEmpty(action.getTaskId())) {
|
||||
|
||||
Reference in New Issue
Block a user