From bc0ec8f3547812d79e6c104d47b9549fdb7c05a6 Mon Sep 17 00:00:00 2001 From: ye1023 <12588209+ye1023@user.noreply.gitee.com> Date: Mon, 27 Jul 2026 15:06:10 +0800 Subject: [PATCH] =?UTF-8?q?yxk-20260727=20=E6=94=B9=E5=96=84=E6=8F=90?= =?UTF-8?q?=E6=A1=88=20=E9=87=8D=E6=9E=84=20ImprovementProposalBPMForm.vue?= =?UTF-8?q?=20=E4=B8=BA=E2=80=9C=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E2=80=9D=E5=92=8C=E2=80=9C=E6=B5=81=E7=A8=8B=E5=8A=9E=E7=90=86?= =?UTF-8?q?=E2=80=9D=E4=B8=A4=E4=B8=AA=E5=8C=BA=E5=9F=9F=E3=80=82=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E8=AF=86=E5=88=AB=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=20formData.taskDefKey=EF=BC=8C=E4=B8=8D=E9=80=9A=E8=BF=87=20PC?= =?UTF-8?q?=20=E8=A1=A8=E5=8D=95=20URL=20=E5=8F=82=E6=95=B0=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E8=8A=82=E7=82=B9=EF=BC=9BURL=20=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E4=BB=85=E4=BF=9D=E7=95=99=E7=BB=99=E5=B0=86=E6=9D=A5=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E5=BC=80=E5=85=B3=E7=B1=BB=E9=9C=80=E6=B1=82=E3=80=82?= =?UTF-8?q?=20=E5=A5=96=E9=A1=B9=E5=AD=97=E6=AE=B5=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=8C=89=20=E9=87=91=E5=A5=96=3D0=E3=80=81=E9=93=B6=E5=A5=96?= =?UTF-8?q?=3D1=E3=80=81=E9=93=9C=E5=A5=96=3D2=E3=80=81=E5=8F=82=E4=B8=8E?= =?UTF-8?q?=E5=A5=96=3D3=20=E5=AD=98=E5=82=A8=EF=BC=8C=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E4=B8=AD=E6=96=87=E3=80=82need=5Fdept=5Fscor?= =?UTF-8?q?e=E3=80=81isneedvote=20=E5=9D=87=E4=BD=BF=E7=94=A8=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=201/0=E3=80=82=20Key=20Changes=20=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E4=BF=A1=E6=81=AF=E5=8C=BA=E5=9F=9F=E5=9C=A8=E6=89=80?= =?UTF-8?q?=E6=9C=89=E6=B5=81=E7=A8=8B=E8=8A=82=E7=82=B9=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=EF=BC=8C=E4=BD=BF=E7=94=A8=E5=8F=AA=E8=AF=BB?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=B1=95=E7=A4=BA=E6=8F=90=E6=A1=88=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E5=AD=97=E6=AE=B5=E3=80=81=E6=94=B9=E5=96=84=E5=89=8D?= =?UTF-8?q?=E5=90=8E=E8=AF=B4=E6=98=8E=E5=8F=8A=E4=B8=89=E4=B8=AA=E9=99=84?= =?UTF-8?q?=E4=BB=B6=E5=88=86=E7=BB=84=EF=BC=9B=E4=B8=8D=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=BC=8F=E7=BC=96=E8=BE=91=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=EF=BC=8C=E4=B9=9F=E4=B8=8D=E5=85=81=E8=AE=B8=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=8A=9E=E7=90=86=E4=BA=BA=E4=BF=AE=E6=94=B9=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 流程办理区域仅在运行中的待办显示:formBpm=true、PROCESS_TAB_TYPE='run' 且存在 taskId。提取统一 processInfo,包含 taskId、taskDefKey、processInstanceId、mainId。 继续使用 TaskHandleInnerContent 的 beforeHandle 钩子;按当前 taskDefKey 完成节点专属数据校验、保存和流程变量写入后,再调用通用提交办理。 Task_improve001: 提供初步评定奖项 initial_award 必填选择。 金奖、银奖自动保存 need_dept_score='1';铜奖、参与奖保存 '0'。 后端在同一节点保存接口中更新主表,并通过 Flowable runtimeService.setVariable 写入全局流程变量 need_dept_score。 BPMN 排他网关配置:${need_dept_score == '1'} 流向 Task_improve002,${need_dept_score == '0'} 流向 Task_improve003。 Task_improve002: 展示部门评分记录及“打分”入口,入口按当前登录评委定位其 improvement_dept_score 记录。 本轮仅建立评分弹窗/页面入口和子表查询展示;四维度录入、加权计算、全部评委完成校验、平均分和建议奖项计算留待下一轮实现。 Task_improve003: 只读展示初步奖项;存在评分结果时展示 dept_avg_score、suggested_award。 部门负责人必选 leader_final_award;金奖写入 isneedvote='1',其他奖项写入 '0',并同步为全局流程变量。 BPMN 排他网关配置:${isneedvote == '1'} 流向 Task_improve004,${isneedvote == '0'} 流向 Task_improve006。 Task_improve004: 本轮预留“所级评议人员”办理区域和投票名单展示,不开发专用选人组件及投票记录初始化。 后续选人功能负责创建每位评委一条 improvement_institute_vote 待投票记录,并设置 institute_total_voters;流程需同时配置所级投票任务的多实例人员集合。 Task_improve005: 提供“同意”“不同意”操作,按当前登录用户更新其已有的待投票记录,写入 vote_result、vote_status='voted'、vote_time 与当前 process_task_id。 不信任前端传入的投票人 ID;后端以登录用户定位记录,并禁止非名单人员、重复投票或已完成记录再次修改。 提交办理前校验当前评委已投票。投票统计和三分之二结果计算留待选人及多实例规则完成后统一实现。 Task_improve006: 展示部门负责人确认奖项;如经过所级投票,同时展示投票统计和投票结果。 经办人必须选择 final_award。若金奖所级投票未通过,同时将 manual_confirm_result 固定保存为 confirmed,表示已由经办人手动确认最终奖项。 后端新增流程节点专用 DTO 与接口,分别处理初审奖项、负责人奖项、归档最终奖项和个人投票;接口校验主表、流程实例、活动任务定义键及当前办理人,避免通过普通 /edit 篡改流程数据。 不使用生成代码的 updateMain 保存流程数据,避免删除并重建评分、投票子表,也避免草稿状态校验阻断流程办理。 补齐数据库定义:在 improvement_proposal 增加 isneedvote char(1),同步建表 SQL、数据库 Markdown 与实体注释;将 need_dept_score 的说明统一改为 1/0。为已建库环境提供单独的增量 ALTER TABLE 脚本。 --- .../ImprovementProposalController.java | 43 ++++++ .../dto/ImprovementProposalBpmActionDTO.java | 38 ++++++ .../entity/ImprovementProposal.java | 4 +- .../service/IImprovementProposalService.java | 16 ++- .../impl/ImprovementProposalServiceImpl.java | 128 +++++++++++++++++- 5 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementProposalBpmActionDTO.java diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java index cfdc25a..f70d08d 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/controller/ImprovementProposalController.java @@ -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.ImprovementProposalBpmActionDTO; import org.jeecg.modules.improvementproposal.vo.ImprovementProposalPage; import org.jeecg.modules.improvementproposal.service.IImprovementProposalService; import org.jeecg.modules.improvementproposal.service.IImprovementDeptScoreService; @@ -149,6 +150,48 @@ public class ImprovementProposalController { return Result.error(e.getMessage()); } } + + @PostMapping(value = "/bpm/initialReview") + @Operation(summary = "保存部门精益专员初审结果") + public Result saveInitialReview(@RequestBody ImprovementProposalBpmActionDTO action) { + return handleBpmAction(() -> improvementProposalService.saveInitialReview(action, currentUser())); + } + + @PostMapping(value = "/bpm/leaderAward") + @Operation(summary = "保存部门负责人确认奖项") + public Result saveLeaderAward(@RequestBody ImprovementProposalBpmActionDTO action) { + return handleBpmAction(() -> improvementProposalService.saveLeaderAward(action, currentUser())); + } + + @PostMapping(value = "/bpm/archiveAward") + @Operation(summary = "保存归档最终奖项") + public Result saveArchiveAward(@RequestBody ImprovementProposalBpmActionDTO action) { + return handleBpmAction(() -> improvementProposalService.saveArchiveAward(action, currentUser())); + } + + @PostMapping(value = "/bpm/instituteVote") + @Operation(summary = "记录所级评委投票") + public Result castInstituteVote(@RequestBody ImprovementProposalBpmActionDTO action) { + return handleBpmAction(() -> improvementProposalService.castInstituteVote(action, currentUser())); + } + + private LoginUser currentUser() { + return (LoginUser) SecurityUtils.getSubject().getPrincipal(); + } + + private Result handleBpmAction(BpmAction action) { + try { + action.run(); + return Result.OK("保存成功"); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } + + @FunctionalInterface + private interface BpmAction { + void run(); + } /** * 通过id删除 diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementProposalBpmActionDTO.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementProposalBpmActionDTO.java new file mode 100644 index 0000000..be088d3 --- /dev/null +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementProposalBpmActionDTO.java @@ -0,0 +1,38 @@ +package org.jeecg.modules.improvementproposal.dto; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 改善提案流程节点办理数据。 + * + *

节点由后端根据 taskId 校验,不允许客户端通过该对象指定节点或办理人。

+ */ +@Data +@Schema(description = "改善提案流程节点办理数据") +public class ImprovementProposalBpmActionDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "改善提案主表ID") + private String mainId; + + @Schema(description = "流程实例ID") + private String processInstanceId; + + @Schema(description = "当前流程任务ID") + private String taskId; + + @Schema(description = "初步评定奖项:0金奖、1银奖、2铜奖、3参与奖") + private String initialAward; + + @Schema(description = "部门负责人确认奖项:0金奖、1银奖、2铜奖、3参与奖") + private String leaderFinalAward; + + @Schema(description = "归档最终奖项:0金奖、1银奖、2铜奖、3参与奖") + private String finalAward; + + @Schema(description = "投票结果:agree/disagree") + private String voteResult; +} diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java index 650146a..cf536df 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementProposal.java @@ -100,11 +100,11 @@ public class ImprovementProposal implements Serializable { @Excel(name = "部门精益专员初步评定奖项", width = 15) @Schema(description = "部门精益专员初步评定奖项") private String initialAward; - /**是否需要部门评议打分:Y/N*/ + /**是否需要部门评议打分:1需要,0不需要*/ @Excel(name = "是否需要部门评议打分:1/0", width = 15) @Schema(description = "是否需要部门评议打分:1/0") private String needDeptScore; - /**是否需要部门评议打分:Y/N*/ + /**是否需要所级评议投票:1需要,0不需要*/ @Excel(name = "是否需要所级评议小组投票:1/0", width = 15) @Schema(description = "是否需要所级评议小组投票:1/0") private String isneedvote; diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java index c3b4deb..33b1031 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/IImprovementProposalService.java @@ -3,6 +3,8 @@ 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.ImprovementProposalBpmActionDTO; +import org.jeecg.common.system.vo.LoginUser; import com.baomidou.mybatisplus.extension.service.IService; import java.io.Serializable; import java.util.Collection; @@ -57,6 +59,18 @@ public interface IImprovementProposalService extends IService idList); + /** 保存部门精益专员初审结果,并写入部门评分网关变量。 */ + void saveInitialReview(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + + /** 保存部门负责人确认奖项,并写入所级投票网关变量。 */ + void saveLeaderAward(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + + /** 保存经办人归档确认的最终奖项。 */ + void saveArchiveAward(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + + /** 记录当前所级评委的投票结果。 */ + void castInstituteVote(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + /** * 获取指定年度最新提案编号中的XXXX部分 * @@ -64,5 +78,5 @@ public interface IImprovementProposalService extends IService loginUser.getId().equals(item.getVoterId())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("当前用户不在所级评议投票名单中")); + if ("voted".equals(vote.getVoteStatus())) { + throw new IllegalStateException("您已完成投票,不能重复修改"); + } + int updated = improvementInstituteVoteMapper.update(null, new LambdaUpdateWrapper() + .eq(ImprovementInstituteVote::getId, vote.getId()) + .eq(ImprovementInstituteVote::getVoteStatus, "pending") + .set(ImprovementInstituteVote::getVoteResult, action.getVoteResult()) + .set(ImprovementInstituteVote::getVoteStatus, "voted") + .set(ImprovementInstituteVote::getVoteTime, new Date()) + .set(ImprovementInstituteVote::getProcessTaskId, action.getTaskId())); + if (updated != 1) { + throw new IllegalStateException("投票状态已变化,请刷新后重试"); + } + } + + private ImprovementProposal validateRunningTask(ImprovementProposalBpmActionDTO action, LoginUser loginUser, String expectedTaskDefKey) { + if (action == null || oConvertUtils.isEmpty(action.getMainId()) || oConvertUtils.isEmpty(action.getProcessInstanceId()) + || oConvertUtils.isEmpty(action.getTaskId())) { + throw new IllegalStateException("流程办理参数不完整"); + } + ImprovementProposal proposal = improvementProposalMapper.selectById(action.getMainId()); + if (proposal == null || !action.getProcessInstanceId().equals(proposal.getProcessInstanceId())) { + throw new IllegalStateException("提案与流程实例不匹配"); + } + Task task = taskService.createTaskQuery().taskId(action.getTaskId()).singleResult(); + if (task == null || !action.getProcessInstanceId().equals(task.getProcessInstanceId()) || !expectedTaskDefKey.equals(task.getTaskDefinitionKey())) { + throw new IllegalStateException("当前任务节点已变化,请刷新后重试"); + } + // 已签收任务必须由签收人操作;候选任务仍由通用办理接口在提交时执行权限校验。 + if (oConvertUtils.isNotEmpty(task.getAssignee()) && !loginUser.getUsername().equals(task.getAssignee())) { + throw new IllegalStateException("当前任务不属于登录用户"); + } + return proposal; + } + + private void validateAward(String award) { + if (!AWARD_GOLD.equals(award) && !AWARD_SILVER.equals(award) && !AWARD_BRONZE.equals(award) && !AWARD_PARTICIPATION.equals(award)) { + throw new IllegalStateException("请选择有效的奖项"); + } + } + + private boolean needsDeptScore(String award) { + return AWARD_GOLD.equals(award) || AWARD_SILVER.equals(award); + } + /** * 任何会改变主表生命周期的操作都必须先锁定并确认仍是未发起草稿, * 这样并发的“发起流程”和“编辑/删除”不会让流程与业务数据脱节。 @@ -178,11 +298,11 @@ public class ImprovementProposalServiceImpl extends ServiceImpl