diff --git a/db/clear_all_data.sql b/db/clear_all_data.sql new file mode 100644 index 0000000..6ff04f3 --- /dev/null +++ b/db/clear_all_data.sql @@ -0,0 +1,321 @@ +-- ============================================================ +-- 数据库全量清理脚本 +-- 目标:清除所有业务数据与流程运行时数据, +-- 保留用户/部门/角色/菜单/权限/字典等系统配置 +-- 保留已设计的流程定义(可重新发布) +-- 效果:登录后可看到菜单,点进去是空的业务界面 +-- +-- 表来源:代码 @TableName 注解 + 数据库已存在 +-- 部分表可能在某些环境中尚未创建,执行时若"doesn't exist" +-- 错误可忽略(该表当前环境无数据需清理) +-- +-- ============================================================ +-- 执行后必须执行的恢复操作: +-- 1. 同步用户到 Flowable 引擎: +-- PUT /act/process/extActProcess/doSyncUser +-- (act_id_* 表已清空,不执行此步则按角色/部门分配任务会失败) +-- 2. 重新发布流程定义(如有需要): +-- 进入流程设计器 → 发布已设计好的流程 +-- (ext_act_process.process_status 已重置为 0) +-- ============================================================ + +SET FOREIGN_KEY_CHECKS = 0; + +-- ============================================================ +-- 1. Flowable 引擎运行时表(当前运行中的流程实例数据) +-- ============================================================ +TRUNCATE TABLE act_ru_actinst; +TRUNCATE TABLE act_ru_deadletter_job; +TRUNCATE TABLE act_ru_entitylink; +TRUNCATE TABLE act_ru_event_subscr; +TRUNCATE TABLE act_ru_execution; +TRUNCATE TABLE act_ru_external_job; +TRUNCATE TABLE act_ru_history_job; +TRUNCATE TABLE act_ru_identitylink; +TRUNCATE TABLE act_ru_job; +TRUNCATE TABLE act_ru_suspended_job; +TRUNCATE TABLE act_ru_task; +TRUNCATE TABLE act_ru_timer_job; +TRUNCATE TABLE act_ru_variable; + +-- ============================================================ +-- 2. Flowable 引擎历史表(已完成的流程实例归档数据) +-- ============================================================ +TRUNCATE TABLE act_hi_actinst; +TRUNCATE TABLE act_hi_attachment; +TRUNCATE TABLE act_hi_comment; +TRUNCATE TABLE act_hi_detail; +TRUNCATE TABLE act_hi_entitylink; +TRUNCATE TABLE act_hi_identitylink; +TRUNCATE TABLE act_hi_procinst; +TRUNCATE TABLE act_hi_taskinst; +TRUNCATE TABLE act_hi_tsk_log; +TRUNCATE TABLE act_hi_varinst; +TRUNCATE TABLE act_evt_log; + +-- ============================================================ +-- 3. Flowable 引擎部署表(已发布的流程定义快照,重新发布会自动重建) +-- ============================================================ +TRUNCATE TABLE act_re_procdef; +TRUNCATE TABLE act_re_deployment; +TRUNCATE TABLE act_re_model; +TRUNCATE TABLE act_ge_bytearray; +TRUNCATE TABLE act_procdef_info; + +-- ============================================================ +-- 4. Flowable 事件注册表运行时数据 +-- flw_channel_definition / flw_event_* / flw_ev_* 保留 +-- ============================================================ +TRUNCATE TABLE flw_ru_batch; +TRUNCATE TABLE flw_ru_batch_part; + +-- ============================================================ +-- 5. Flowable 身份表(从 sys_user/sys_role 同步的缓存,清空后需重新同步) +-- act_id_property 保留(引擎配置元数据) +-- ============================================================ +TRUNCATE TABLE act_id_user; +TRUNCATE TABLE act_id_group; +TRUNCATE TABLE act_id_membership; +TRUNCATE TABLE act_id_info; +TRUNCATE TABLE act_id_bytearray; +TRUNCATE TABLE act_id_priv; +TRUNCATE TABLE act_id_priv_mapping; +TRUNCATE TABLE act_id_token; + +-- ============================================================ +-- 6. 自定义 BPM 运行时数据 +-- ============================================================ +TRUNCATE TABLE ext_act_bpm_log; +TRUNCATE TABLE ext_act_bpm_file; +TRUNCATE TABLE ext_act_flow_data; +TRUNCATE TABLE ext_act_task_cc; +TRUNCATE TABLE ext_act_task_notification; +TRUNCATE TABLE ext_act_mutil_flow_data; +TRUNCATE TABLE task_approval_opinion; + +-- ============================================================ +-- 7. 流程部署快照(与 act_re_* 对应,重新发布流程时自动重建) +-- ============================================================ +TRUNCATE TABLE ext_act_process_node_deploy; + +-- ============================================================ +-- 8. 表单设计器运行时数据 +-- design_form 及其他 design_form_* 表保留(表单定义) +-- ============================================================ +TRUNCATE TABLE ext_act_design_flow_data; +TRUNCATE TABLE design_form_data; + +-- ============================================================ +-- 9. 流程定义状态重置为"未发布" +-- 以下流程定义配置表保留不动: +-- ext_act_process / ext_act_process_node / ext_act_process_form +-- ext_act_process_node_auth / ext_act_process_node_reject_target +-- ext_act_listener / ext_act_expression +-- ============================================================ +UPDATE ext_act_process SET process_status = 0; + +-- ============================================================ +-- 10. 监督模块业务表(代码中有 @TableName 定义) +-- ============================================================ +-- 习语讲话 +TRUNCATE TABLE bg_xi_speak; +TRUNCATE TABLE bg_xi_speak_feedback; +-- 把脉问诊 +TRUNCATE TABLE bg_takepulse; +TRUNCATE TABLE bg_takepulse_feedback; +-- 党务管理 +TRUNCATE TABLE bg_partymatter; +TRUNCATE TABLE bg_partymatter_feedback; +-- 党建检查与整改 +TRUNCATE TABLE dj_inspect_improve; +TRUNCATE TABLE dj_inspection_rectify_list; +TRUNCATE TABLE dj_check; +TRUNCATE TABLE dj_meeting_record; +TRUNCATE TABLE dj_demo_life_meeting; +TRUNCATE TABLE dj_people_service; +TRUNCATE TABLE dj_officialdoc_distribute; +-- 纪律检查 +TRUNCATE TABLE dq_inspect_task; +TRUNCATE TABLE dq_inspect_progress; +TRUNCATE TABLE dq_inspect_problem; +-- 改善提案 +TRUNCATE TABLE improvement_proposal; +TRUNCATE TABLE improvement_institute_vote; +TRUNCATE TABLE improvement_dept_score; +-- 任务清单 +TRUNCATE TABLE task_task; +TRUNCATE TABLE task_list; +TRUNCATE TABLE task_list_detial; +TRUNCATE TABLE task_list_favorite; +TRUNCATE TABLE task_list_permission; +-- 监督测试 +TRUNCATE TABLE supervision_test; + +-- ============================================================ +-- 11. JOA 流程业务演示表 +-- ============================================================ +TRUNCATE TABLE ext_biz_leave; +TRUNCATE TABLE joa_busines_strip; +TRUNCATE TABLE joa_doc_receiving; +TRUNCATE TABLE joa_doc_sending; +TRUNCATE TABLE joa_employee_leave; +TRUNCATE TABLE joa_loan; +TRUNCATE TABLE joa_overtime; +TRUNCATE TABLE joa_overtime_detail; +TRUNCATE TABLE joa_overtime_leave; +TRUNCATE TABLE joa_demo; + +-- ============================================================ +-- 12. Demo 模块测试表 +-- ============================================================ +TRUNCATE TABLE demo; +TRUNCATE TABLE demo_staff; +TRUNCATE TABLE demo_staff_group; +TRUNCATE TABLE demo_field_def_val_main; +TRUNCATE TABLE demo_field_def_val_sub; +TRUNCATE TABLE demo_sub_flow_hq; +TRUNCATE TABLE jeecg_order_main; +TRUNCATE TABLE jeecg_order_customer; +TRUNCATE TABLE jeecg_order_ticket; +TRUNCATE TABLE jeecg_tree_demo; + +-- ============================================================ +-- 13. OA 办公业务表 +-- ============================================================ +-- WPS +TRUNCATE TABLE oa_wps_file; +TRUNCATE TABLE oa_wps_file_version; +TRUNCATE TABLE oa_wps_file_watermark; +TRUNCATE TABLE oa_wps_user_acl; +-- 人事 +TRUNCATE TABLE oa_become_regular; +TRUNCATE TABLE oa_dimission; +TRUNCATE TABLE oa_employee_award; +TRUNCATE TABLE oa_employee_certificate; +TRUNCATE TABLE oa_employee_education; +TRUNCATE TABLE oa_employee_experience; +TRUNCATE TABLE oa_employee_family; +TRUNCATE TABLE oa_employee_info; +TRUNCATE TABLE oa_employee_relations; +TRUNCATE TABLE oa_employment; +TRUNCATE TABLE oa_employment_apply; +TRUNCATE TABLE oa_interview; +TRUNCATE TABLE oa_promotion; +-- 出差/用车/请假/加班 +TRUNCATE TABLE oa_business_travel; +TRUNCATE TABLE oa_business_travel_accompanied; +TRUNCATE TABLE oa_car_info; +TRUNCATE TABLE oa_car_use; +TRUNCATE TABLE oa_leave; +TRUNCATE TABLE oa_overtime; +TRUNCATE TABLE oa_security_vehicle; +-- 签到 +TRUNCATE TABLE oa_sign_info; +TRUNCATE TABLE oa_sign_out; +TRUNCATE TABLE oa_sign_patch; +TRUNCATE TABLE oa_sign_rule; +-- 用品 +TRUNCATE TABLE oa_supplies_detail; +TRUNCATE TABLE oa_supplies_use; +-- 公文 +TRUNCATE TABLE oa_officialdoc_depart_user; +TRUNCATE TABLE oa_officialdoc_distribute; +TRUNCATE TABLE oa_officialdoc_issued; +TRUNCATE TABLE oa_officialdoc_organcode; +TRUNCATE TABLE oa_officialdoc_received; +TRUNCATE TABLE oa_officialdoc_seal; +TRUNCATE TABLE oa_officialdoc_temp; + +-- ============================================================ +-- 14. EOA 在线办公表 +-- ============================================================ +-- 聊天 +TRUNCATE TABLE eoa_chat_group; +TRUNCATE TABLE eoa_chat_off_message; +TRUNCATE TABLE eoa_chat_on_message; +TRUNCATE TABLE eoa_chat_on_message_his; +TRUNCATE TABLE eoa_chat_session; +TRUNCATE TABLE eoa_chat_user; +TRUNCATE TABLE eoa_chat_user_group; +-- CMS +TRUNCATE TABLE eoa_cms_article; +TRUNCATE TABLE eoa_cms_article_read; +TRUNCATE TABLE eoa_cms_menu; +TRUNCATE TABLE eoa_cms_site; +-- 文件 +TRUNCATE TABLE eoa_file; +TRUNCATE TABLE eoa_file_log; +-- 邮箱 +TRUNCATE TABLE eoa_mailbox_attach; +TRUNCATE TABLE eoa_mailbox_category; +TRUNCATE TABLE eoa_mailbox_info; +TRUNCATE TABLE eoa_mailbox_receiver; +-- 会议 +TRUNCATE TABLE eoa_metting; +TRUNCATE TABLE eoa_metting_device; +TRUNCATE TABLE eoa_metting_room; +TRUNCATE TABLE eoa_metting_sign; +-- 计划/门户 +TRUNCATE TABLE eoa_plan; +TRUNCATE TABLE eoa_portal_site; +TRUNCATE TABLE eoa_portal_site_auth; + +-- ============================================================ +-- 15. 测试表(代码 @TableName + DB 中存在) +-- ============================================================ +TRUNCATE TABLE ceshi_des_qingjia; +TRUNCATE TABLE ceshi_nongmin; +TRUNCATE TABLE ceshi_qingjia_dan; +TRUNCATE TABLE ceshi_ruzhi; +TRUNCATE TABLE ceshi_subqt; +TRUNCATE TABLE ce_fenzhi_zx; +TRUNCATE TABLE ce_paita_wangguan; +TRUNCATE TABLE joa_onl_leave; +TRUNCATE TABLE joa_onl_bthuiq; +TRUNCATE TABLE joa_onl_sxhuiq; +TRUNCATE TABLE oa_demo_bhan_wguan; +TRUNCATE TABLE rep_demo_dxtj; +TRUNCATE TABLE rep_demo_employee; +TRUNCATE TABLE rep_demo_gongsi; +TRUNCATE TABLE rep_demo_huizong; +TRUNCATE TABLE rep_demo_jianpiao; +TRUNCATE TABLE rep_demo_order_main; +TRUNCATE TABLE rep_demo_order_product; +TRUNCATE TABLE test_demo; +TRUNCATE TABLE test_enhance_select; +TRUNCATE TABLE test_main_table; +TRUNCATE TABLE test_note; +TRUNCATE TABLE test_online_link; +TRUNCATE TABLE test_order_customer; +TRUNCATE TABLE test_order_main; +TRUNCATE TABLE test_order_product; +TRUNCATE TABLE test_order_user; +TRUNCATE TABLE test_person; +TRUNCATE TABLE test_shoptype_tree; +TRUNCATE TABLE test_son_table; +TRUNCATE TABLE test_third_link; +TRUNCATE TABLE test_tree; + +-- ============================================================ +-- 16. 系统运行日志 / 消息 / API 调用记录 +-- sys_files / sys_file_attachment 保留(头像、系统图标等文件) +-- jimu_report_export_log 保留(可选清除,见下方) +-- ============================================================ +TRUNCATE TABLE sys_log; +TRUNCATE TABLE sys_data_log; +TRUNCATE TABLE sys_announcement; +TRUNCATE TABLE sys_announcement_send; +TRUNCATE TABLE sys_sms; +TRUNCATE TABLE sys_comment; +TRUNCATE TABLE sys_files_operate; +TRUNCATE TABLE open_api_log; + +-- ============================================================ +-- 17. (可选)取消注释以清除更多内容 +-- ============================================================ +-- TRUNCATE TABLE jimu_report_export_log; +-- TRUNCATE TABLE sys_file_attachment; +-- TRUNCATE TABLE sys_files; +-- TRUNCATE TABLE flyway_schema_history_1; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java index d73a871..e1cc756 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java @@ -105,9 +105,7 @@ public class KsimTestController { if (msgRespBody != null) { int code = msgRespBody.getIntValue("code"); result.put("success", code == 0 || code == 200); - if (msgRespBody.getJSONObject("dataBody") != null) { - result.put("messageId", msgRespBody.getJSONObject("dataBody").getString("messageId")); - } + result.put("requestId", msgRespBody.getString("requestId")); result.put("responseCode", code); } else { result.put("success", false); diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java index bf620bd..aaa0d2b 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java @@ -66,10 +66,9 @@ public class KsimMessageServiceImpl implements IKsimMessageService { return Result.error(apiResult.getCode(), "即时通发送失败: " + apiResult.getMessage()); } - String messageId = apiResult.getDataBody() != null - ? apiResult.getDataBody().getString("messageId") : ""; - log.info("【即时通推送-sendBatch】成功, messageId: {}, toUserCount: {}, title: {}", messageId, + String requestId = apiResult.getRequestId() != null ? apiResult.getRequestId() : ""; + log.info("【即时通推送-sendBatch】成功, requestId: {}, toUserCount: {}, title: {}", requestId, toUserValueList != null ? toUserValueList.size() : 0, title); - return Result.OK(messageId); + return Result.OK(requestId); } } diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/listener/task/TaskCreatedAutoSubmitListener.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/listener/task/TaskCreatedAutoSubmitListener.java index 8f010ac..72277d3 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/listener/task/TaskCreatedAutoSubmitListener.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/listener/task/TaskCreatedAutoSubmitListener.java @@ -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; diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java index fce908a..26f11b9 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; +import org.jeecg.common.exception.JeecgBootException; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.runtime.Execution; @@ -276,6 +277,28 @@ public class FlowNodeExpression { return sysbase.getUserByRoleIdLocalApi(roleId); } + /** + * Get required usernames for a Flowable multi-instance task. + * An empty collection would otherwise make Flowable skip the task, so fail fast instead. + * Example: ${flowNodeExpression.getRequiredUsersListByRole('roleIdValue')} + * + * @param roleId system role id + * @return non-empty username list + */ + public List getRequiredUsersListByRole(String roleId) { + if (StringUtils.isBlank(roleId)) { + throw new JeecgBootException("流程角色ID不能为空"); + } + + List usernames = ISysBaseAPI.getUsernamesByRoleIdLocalApi(roleId); + log.info("流程多实例按角色取人,roleId={}, usernames={}, count={}", roleId, usernames, + usernames == null ? 0 : usernames.size()); + if (CollectionUtils.isEmpty(usernames)) { + throw new JeecgBootException("角色[" + roleId + "]未配置有效用户,流程无法继续"); + } + return usernames; + } + /** * Get subprocess variable. * Example: ${flowNodeExpression.getSonProcessVariable(execution, 'subApproveUser')} diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessController.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessController.java index 4b15ff5..6a989ed 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessController.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/controller/ExtActProcessController.java @@ -37,6 +37,8 @@ import org.jeecg.modules.extbpm.util.JSONHelper; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -60,6 +62,7 @@ import java.util.stream.Collectors; * @date: 2019-03-15 * @version: V1.0 */ +@Tag(name = "流程定义") @RestController("extActProcessController") @RequestMapping("/act/process/extActProcess") @Slf4j @@ -570,6 +573,7 @@ public class ExtActProcessController { * @param extActProcess * @return */ + @Operation(summary = "同步用户到Flowable引擎") @PutMapping(value = "/doSyncUser") public Result doSyncUser(HttpServletRequest request) { Result result = new Result(); 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 f70d08d..8bfe805 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,8 @@ 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.ImprovementInstituteVoteCandidateDTO; import org.jeecg.modules.improvementproposal.dto.ImprovementProposalBpmActionDTO; import org.jeecg.modules.improvementproposal.vo.ImprovementProposalPage; import org.jeecg.modules.improvementproposal.service.IImprovementProposalService; @@ -157,12 +159,60 @@ public class ImprovementProposalController { return handleBpmAction(() -> improvementProposalService.saveInitialReview(action, currentUser())); } + @GetMapping(value = "/bpm/myDeptScore") + @Operation(summary = "查询当前部门评委个人评分") + public Result 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 saveDeptScore(@RequestBody ImprovementDeptScoreBpmDTO score) { + return handleBpmAction(() -> improvementProposalService.saveDeptScore(score, currentUser())); + } + + @PostMapping(value = "/bpm/submitDeptScore") + @Operation(summary = "锁定当前部门评委个人评分") + public Result submitDeptScore(@RequestBody ImprovementProposalBpmActionDTO action) { + return handleBpmAction(() -> improvementProposalService.submitDeptScore(action, currentUser())); + } + @PostMapping(value = "/bpm/leaderAward") @Operation(summary = "保存部门负责人确认奖项") public Result saveLeaderAward(@RequestBody ImprovementProposalBpmActionDTO action) { return handleBpmAction(() -> improvementProposalService.saveLeaderAward(action, currentUser())); } + @GetMapping(value = "/bpm/instituteVoteCandidates") + @Operation(summary = "查询所级评议投票候选人") + public Result> getInstituteVoteCandidates(@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.getInstituteVoteCandidates(action, currentUser())); + } catch (IllegalStateException e) { + return Result.error(e.getMessage()); + } + } + + @PostMapping(value = "/bpm/instituteVoteMembers") + @Operation(summary = "保存所级评议投票人员") + public Result saveInstituteVoteMembers(@RequestBody ImprovementProposalBpmActionDTO action) { + return handleBpmAction(() -> improvementProposalService.saveInstituteVoteMembers(action, currentUser())); + } + @PostMapping(value = "/bpm/archiveAward") @Operation(summary = "保存归档最终奖项") public Result saveArchiveAward(@RequestBody ImprovementProposalBpmActionDTO action) { @@ -175,6 +225,12 @@ public class ImprovementProposalController { return handleBpmAction(() -> improvementProposalService.castInstituteVote(action, currentUser())); } + @PostMapping(value = "/bpm/submitInstituteVote") + @Operation(summary = "确认所级评委投票并直接完成流程任务") + public Result submitInstituteVote(@RequestBody ImprovementProposalBpmActionDTO action, HttpServletRequest request) { + return handleBpmAction(() -> improvementProposalService.submitInstituteVoteAndComplete(action, currentUser(), request)); + } + private LoginUser currentUser() { return (LoginUser) SecurityUtils.getSubject().getPrincipal(); } diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementDeptScoreBpmDTO.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementDeptScoreBpmDTO.java new file mode 100644 index 0000000..a65feb2 --- /dev/null +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementDeptScoreBpmDTO.java @@ -0,0 +1,46 @@ +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 safetyScore; + + @Schema(description = "质量得分,0-100") + private BigDecimal qualityScore; + + @Schema(description = "效率得分,0-100") + private BigDecimal efficiencyScore; + + @Schema(description = "成本得分,0-100") + private BigDecimal costScore; + + @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; +} diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementInstituteVoteCandidateDTO.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementInstituteVoteCandidateDTO.java new file mode 100644 index 0000000..3b777c5 --- /dev/null +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/dto/ImprovementInstituteVoteCandidateDTO.java @@ -0,0 +1,25 @@ +package org.jeecg.modules.improvementproposal.dto; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 所级评议投票候选人。 + * 仅暴露名单选择和 Flowable 多实例所需的最小用户信息。 + */ +@Data +@Schema(description = "所级评议投票候选人") +public class ImprovementInstituteVoteCandidateDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "用户ID") + private String id; + + @Schema(description = "登录用户名") + private String username; + + @Schema(description = "真实姓名") + private String realname; +} 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 index be088d3..15373a5 100644 --- 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 @@ -1,6 +1,7 @@ package org.jeecg.modules.improvementproposal.dto; import java.io.Serializable; +import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -35,4 +36,11 @@ public class ImprovementProposalBpmActionDTO implements Serializable { @Schema(description = "投票结果:agree/disagree") private String voteResult; + + /** + * 精益管理部门经办人在所级评议节点选择的投票人 ID 列表。 + * 服务端会按固定角色重新校验该列表,不能将其作为投票人身份的信任来源。 + */ + @Schema(description = "所级评议投票人ID列表") + private List voterIds; } diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementDeptScore.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementDeptScore.java index 2b18d21..2edf323 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementDeptScore.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/entity/ImprovementDeptScore.java @@ -50,6 +50,22 @@ public class ImprovementDeptScore implements Serializable { @Excel(name = "评分状态:pending/completed", width = 15) @Schema(description = "评分状态:pending/completed") private String scoreStatus; + /**安全得分*/ + @Excel(name = "安全得分", width = 15) + @Schema(description = "安全得分") + private java.math.BigDecimal safetyScore; + /**质量得分*/ + @Excel(name = "质量得分", width = 15) + @Schema(description = "质量得分") + private java.math.BigDecimal qualityScore; + /**效率得分*/ + @Excel(name = "效率得分", width = 15) + @Schema(description = "效率得分") + private java.math.BigDecimal efficiencyScore; + /**成本得分*/ + @Excel(name = "成本得分", width = 15) + @Schema(description = "成本得分") + private java.math.BigDecimal costScore; /**改善效果得分*/ @Excel(name = "改善效果得分", width = 15) @Schema(description = "改善效果得分") 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 925338e..27d93dd 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 @@ -125,6 +125,10 @@ public class ImprovementProposal implements Serializable { @Schema(description = "最终奖项") @Dict(dicCode = "proposal_prize") private String finalAward; + /**所级评议领导名单*/ + @Excel(name = "所级评议领导名单", width = 15) + @Schema(description = "所级评议领导名单") + private Integer instituteVoters; /**所级评议应投票人数*/ @Excel(name = "所级评议应投票人数", width = 15) @Schema(description = "所级评议应投票人数") @@ -137,9 +141,9 @@ public class ImprovementProposal implements Serializable { @Excel(name = "所级评议不同意票数", width = 15) @Schema(description = "所级评议不同意票数") private Integer instituteDisagreeCount; - /**所级评议结果:passed/failed*/ - @Excel(name = "所级评议结果:passed/failed", width = 15) - @Schema(description = "所级评议结果:passed/failed") + /**所级评议结果:通过/不通过*/ + @Excel(name = "所级评议结果:通过/不通过", width = 15) + @Schema(description = "所级评议结果:通过/不通过") private String instituteVoteResult; /**金奖未通过后的经办人手动确认结果*/ @Excel(name = "金奖未通过后的经办人手动确认结果", width = 15) diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementInstituteVoteMapper.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementInstituteVoteMapper.java index fcf2b9c..93fc97d 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementInstituteVoteMapper.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementInstituteVoteMapper.java @@ -28,4 +28,7 @@ public interface ImprovementInstituteVoteMapper extends BaseMapper */ public List selectByMainId(@Param("mainId") String mainId); + + /** 任务尚未进入投票节点时,仅失效本轮待投票记录,保留已投票历史。 */ + int logicalDeletePendingByMainId(@Param("mainId") String mainId); } diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java index 335b2d5..6a633c9 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/ImprovementProposalMapper.java @@ -2,8 +2,10 @@ package org.jeecg.modules.improvementproposal.mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import org.jeecg.modules.improvementproposal.dto.ImprovementInstituteVoteCandidateDTO; import org.jeecg.modules.improvementproposal.entity.ImprovementProposal; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; /** * @Description: improvement_proposal @@ -20,4 +22,7 @@ public interface ImprovementProposalMapper extends BaseMapper selectInstituteVoteCandidates(@Param("roleCode") String roleCode); + } diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementInstituteVoteMapper.xml b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementInstituteVoteMapper.xml index c70bb9b..4335c78 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementInstituteVoteMapper.xml +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementInstituteVoteMapper.xml @@ -12,5 +12,16 @@ SELECT * FROM improvement_institute_vote WHERE - main_id = #{mainId} + main_id = #{mainId} + AND del_flag = '0' + + + + + UPDATE improvement_institute_vote + SET del_flag = '1' + WHERE main_id = #{mainId} + AND del_flag = '0' + AND vote_status = 'pending' + diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementProposalMapper.xml b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementProposalMapper.xml index f21cac3..1b7e498 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementProposalMapper.xml +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/mapper/xml/ImprovementProposalMapper.xml @@ -10,4 +10,17 @@ LIMIT 1 + + + 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 33b1031..b02b44c 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,9 +3,12 @@ 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.ImprovementInstituteVoteCandidateDTO; import org.jeecg.modules.improvementproposal.dto.ImprovementProposalBpmActionDTO; import org.jeecg.common.system.vo.LoginUser; import com.baomidou.mybatisplus.extension.service.IService; +import javax.servlet.http.HttpServletRequest; import java.io.Serializable; import java.util.Collection; import java.util.List; @@ -62,15 +65,36 @@ public interface IImprovementProposalService extends IService getInstituteVoteCandidates(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + + /** 保存所级评议名单、待投票记录及多实例流程变量。 */ + void saveInstituteVoteMembers(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + /** 保存经办人归档确认的最终奖项。 */ void saveArchiveAward(ImprovementProposalBpmActionDTO action, LoginUser loginUser); /** 记录当前所级评委的投票结果。 */ void castInstituteVote(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + /** 锁定当前所级评委的投票并在全员完成后汇总投票结果。 */ + void submitInstituteVote(ImprovementProposalBpmActionDTO action, LoginUser loginUser); + + /** 锁定投票后,使用通用流程办理能力直接完成当前所级评委任务。 */ + void submitInstituteVoteAndComplete(ImprovementProposalBpmActionDTO action, LoginUser loginUser, HttpServletRequest request); + /** * 获取指定年度最新提案编号中的XXXX部分 * diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java index 0952c3a..dadc51b 100644 --- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java +++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/improvementproposal/service/impl/ImprovementProposalServiceImpl.java @@ -3,6 +3,8 @@ 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.ImprovementInstituteVoteCandidateDTO; import org.jeecg.modules.improvementproposal.dto.ImprovementProposalBpmActionDTO; import org.jeecg.modules.improvementproposal.mapper.ImprovementDeptScoreMapper; import org.jeecg.modules.improvementproposal.mapper.ImprovementInstituteVoteMapper; @@ -12,22 +14,31 @@ import org.jeecg.common.api.vo.Result; import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.system.vo.LoginUser; import org.jeecg.modules.extbpm.process.service.impl.BpmBaseExtApiImpl; +import org.jeecg.modules.extbpm.process.service.IExtActTaskCcService; 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.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 org.jeecg.modules.system.entity.SysRole; +import org.jeecg.modules.system.service.ISysRoleService; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import javax.servlet.http.HttpServletRequest; /** * @Description: improvement_proposal @@ -43,13 +54,18 @@ public class ImprovementProposalServiceImpl extends ServiceImpl() + .eq(ImprovementDeptScore::getId, existingScore.getId()) + .eq(ImprovementDeptScore::getScoreStatus, "pending") + .set(ImprovementDeptScore::getProcessTaskId, scoreAction.getTaskId()) + .set(ImprovementDeptScore::getSafetyScore, scoreAction.getSafetyScore()) + .set(ImprovementDeptScore::getQualityScore, scoreAction.getQualityScore()) + .set(ImprovementDeptScore::getEfficiencyScore, scoreAction.getEfficiencyScore()) + .set(ImprovementDeptScore::getCostScore, scoreAction.getCostScore()) + .set(ImprovementDeptScore::getEffectScore, effectScore) + .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, new Date())); + 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() + .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) { validateAward(action.getLeaderFinalAward()); ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_LEADER_AWARD); + String awardBase = resolveLeaderAwardBase(proposal); + if (awardRank(action.getLeaderFinalAward()) < awardRank(awardBase)) { + throw new IllegalStateException("负责人确认奖项只能选择当前档位或降档奖项"); + } String needInstituteVote = AWARD_GOLD.equals(action.getLeaderFinalAward()) ? "1" : "0"; ImprovementProposal update = new ImprovementProposal(); update.setId(proposal.getId()); @@ -215,6 +330,75 @@ public class ImprovementProposalServiceImpl extends ServiceImpl getInstituteVoteCandidates(ImprovementProposalBpmActionDTO action, LoginUser loginUser) { + validateRunningTask(action, loginUser, TASK_INSTITUTE_REVIEW); + return loadInstituteVoteCandidates(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveInstituteVoteMembers(ImprovementProposalBpmActionDTO action, LoginUser loginUser) { + ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_INSTITUTE_REVIEW); + ImprovementProposal lockedProposal = improvementProposalMapper.selectByIdForUpdate(proposal.getId()); + if (lockedProposal == null || !action.getProcessInstanceId().equals(lockedProposal.getProcessInstanceId())) { + throw new IllegalStateException("提案流程状态已变化,请刷新后重试"); + } + + List candidates = loadInstituteVoteCandidates(); + Map candidateById = new LinkedHashMap<>(); + for (ImprovementInstituteVoteCandidateDTO candidate : candidates) { + candidateById.put(candidate.getId(), candidate); + } + if (action.getVoterIds() == null || action.getVoterIds().isEmpty()) { + throw new IllegalStateException("请至少选择一名所级评议人员"); + } + + LinkedHashSet selectedUserIds = new LinkedHashSet<>(); + for (String voterId : action.getVoterIds()) { + if (oConvertUtils.isEmpty(voterId) || !selectedUserIds.add(voterId)) { + throw new IllegalStateException("所级评议人员选择数据不合法"); + } + } + LinkedHashSet usernames = new LinkedHashSet<>(); + for (String voterId : selectedUserIds) { + ImprovementInstituteVoteCandidateDTO candidate = candidateById.get(voterId); + if (candidate == null) { + throw new IllegalStateException("所选人员不属于所级评议小组"); + } + if (!usernames.add(candidate.getUsername())) { + throw new IllegalStateException("所级评议人员用户名重复"); + } + } + + List activeVotes = improvementInstituteVoteMapper.selectByMainId(lockedProposal.getId()); + if (activeVotes.stream().anyMatch(item -> !"pending".equals(item.getVoteStatus()))) { + throw new IllegalStateException("所级评议已开始投票,不能重新选择人员"); + } + // beforeHandle 可能因网络重试重复调用;先失效未投票记录再重建,确保有效名单始终和流程变量一致。 + improvementInstituteVoteMapper.logicalDeletePendingByMainId(lockedProposal.getId()); + for (String voterId : selectedUserIds) { + ImprovementInstituteVoteCandidateDTO candidate = candidateById.get(voterId); + ImprovementInstituteVote vote = new ImprovementInstituteVote(); + vote.setMainId(lockedProposal.getId()); + vote.setVoterId(candidate.getId()); + vote.setVoterName(oConvertUtils.isEmpty(candidate.getRealname()) ? candidate.getUsername() : candidate.getRealname()); + vote.setVoteStatus("pending"); + vote.setSysOrgCode(lockedProposal.getSysOrgCode()); + vote.setDelFlag(NORMAL_DEL_FLAG); + improvementInstituteVoteMapper.insert(vote); + } + + ImprovementProposal update = new ImprovementProposal(); + update.setId(lockedProposal.getId()); + update.setInstituteTotalVoters(selectedUserIds.size()); + update.setInstituteAgreeCount(null); + update.setInstituteDisagreeCount(null); + update.setInstituteVoteResult(null); + improvementProposalMapper.updateById(update); + runtimeService.setVariable(action.getProcessInstanceId(), "suoleadervotelist", String.join(",", usernames)); + } + @Override @Transactional(rollbackFor = Exception.class) public void saveArchiveAward(ImprovementProposalBpmActionDTO action, LoginUser loginUser) { @@ -225,7 +409,7 @@ public class ImprovementProposalServiceImpl extends ServiceImpl loginUser.getId().equals(item.getVoterId())) - .findFirst() - .orElseThrow(() -> new IllegalStateException("当前用户不在所级评议投票名单中")); + ImprovementInstituteVote vote = findInstituteVote(proposal.getId(), loginUser.getId()); if ("voted".equals(vote.getVoteStatus())) { - throw new IllegalStateException("您已完成投票,不能重复修改"); + 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::getVoteTime, null) .set(ImprovementInstituteVote::getProcessTaskId, action.getTaskId())); if (updated != 1) { throw new IllegalStateException("投票状态已变化,请刷新后重试"); } } + @Override + @Transactional(rollbackFor = Exception.class) + public void submitInstituteVote(ImprovementProposalBpmActionDTO action, LoginUser loginUser) { + if (!"agree".equals(action.getVoteResult()) && !"disagree".equals(action.getVoteResult())) { + throw new IllegalStateException("投票结果只能是同意或不同意"); + } + ImprovementProposal proposal = validateRunningTask(action, loginUser, TASK_INSTITUTE_VOTE); + ImprovementProposal lockedProposal = improvementProposalMapper.selectByIdForUpdate(proposal.getId()); + if (lockedProposal == null || !action.getProcessInstanceId().equals(lockedProposal.getProcessInstanceId())) { + throw new IllegalStateException("提案流程状态已变化,请刷新后重试"); + } + ImprovementInstituteVote vote = findInstituteVote(lockedProposal.getId(), loginUser.getId()); + if ("voted".equals(vote.getVoteStatus())) { + // 网络异常导致流程任务未完成时,允许使用已确认的同一结果重试办理;不允许改票。 + if (!action.getVoteResult().equals(vote.getVoteResult())) { + throw new IllegalStateException("投票已确认,不能修改投票结果"); + } + } else { + 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("投票状态已变化,请刷新后重试"); + } + } + updateInstituteVoteSummaryIfComplete(lockedProposal, action.getProcessInstanceId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void submitInstituteVoteAndComplete(ImprovementProposalBpmActionDTO action, LoginUser loginUser, HttpServletRequest request) { + submitInstituteVote(action, loginUser); + + // 复用通用办理服务,确保审批日志、流程状态及多实例收敛逻辑与普通办理保持一致。 + HashMap processParams = new HashMap<>(4); + processParams.put("taskId", action.getTaskId()); + processParams.put("processModel", "1"); + processParams.put("nextCodeCount", "1"); + // Task5 只有正常流转分支;通用办理服务以 end + 单分支执行 taskService.complete。 + processParams.put("nextnode", "end"); + Result result = extActTaskCcService.processComplete(processParams, request); + if (result == null || !result.isSuccess()) { + throw new IllegalStateException(result == null || oConvertUtils.isEmpty(result.getMessage()) + ? "投票已确认,但流程办理失败" + : result.getMessage()); + } + } + + private ImprovementInstituteVote findInstituteVote(String mainId, String voterId) { + return improvementInstituteVoteMapper.selectByMainId(mainId).stream() + .filter(item -> voterId.equals(item.getVoterId())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("当前用户不在所级评议投票名单中")); + } + + /** + * 所级评议以名单确定时写入的应投票人数为分母。赞成票满足 agree * 3 >= total * 2 即通过,避免小数取整歧义。 + */ + private void updateInstituteVoteSummaryIfComplete(ImprovementProposal proposal, String processInstanceId) { + Integer totalVoters = proposal.getInstituteTotalVoters(); + if (totalVoters == null || totalVoters <= 0) { + throw new IllegalStateException("所级评议应投票人数未初始化"); + } + List votes = improvementInstituteVoteMapper.selectByMainId(proposal.getId()); + if (votes.size() != totalVoters) { + throw new IllegalStateException("所级评议投票名单与应投票人数不一致"); + } + if (votes.stream().anyMatch(item -> !"voted".equals(item.getVoteStatus()))) { + return; + } + long agreeCount = votes.stream().filter(item -> "agree".equals(item.getVoteResult())).count(); + long disagreeCount = votes.stream().filter(item -> "disagree".equals(item.getVoteResult())).count(); + if (agreeCount + disagreeCount != totalVoters) { + throw new IllegalStateException("所级评议投票结果不完整"); + } + String voteResult = agreeCount * 3 >= (long) totalVoters * 2 + ? INSTITUTE_VOTE_RESULT_PASSED + : INSTITUTE_VOTE_RESULT_FAILED; + ImprovementProposal update = new ImprovementProposal(); + update.setId(proposal.getId()); + update.setInstituteAgreeCount((int) agreeCount); + update.setInstituteDisagreeCount((int) disagreeCount); + update.setInstituteVoteResult(voteResult); + improvementProposalMapper.updateById(update); + runtimeService.setVariable(processInstanceId, "institute_vote_result", voteResult); + } + + /** + * 固定按角色编码读取名单,保存时再次查询以防前端伪造用户 ID 或角色配置已变更。 + */ + private List loadInstituteVoteCandidates() { + SysRole role = sysRoleService.getRoleNoTenant(INSTITUTE_VOTE_ROLE_CODE); + if (role == null) { + throw new IllegalStateException("未配置所级评议小组角色:" + INSTITUTE_VOTE_ROLE_CODE); + } + List candidates = improvementProposalMapper.selectInstituteVoteCandidates(INSTITUTE_VOTE_ROLE_CODE); + if (candidates == null || candidates.isEmpty()) { + throw new IllegalStateException("所级评议小组角色未配置有效用户"); + } + return candidates; + } + + 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.getSafetyScore(), "安全"); + validateScoreValue(scoreAction.getQualityScore(), "质量"); + validateScoreValue(scoreAction.getEfficiencyScore(), "效率"); + validateScoreValue(scoreAction.getCostScore(), "成本"); + 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 calculateEffectScore(ImprovementDeptScoreBpmDTO scoreAction) { + return calculateEffectScore(scoreAction.getSafetyScore(), scoreAction.getQualityScore(), scoreAction.getEfficiencyScore(), scoreAction.getCostScore()); + } + + private BigDecimal calculateEffectScore(BigDecimal safetyScore, BigDecimal qualityScore, BigDecimal efficiencyScore, BigDecimal costScore) { + return safetyScore.add(qualityScore).add(efficiencyScore).add(costScore).divide(BigDecimal.valueOf(4), 2, RoundingMode.HALF_UP); + } + + private BigDecimal calculateTotalScore(ImprovementDeptScoreBpmDTO scoreAction, BigDecimal effectScore) { + return effectScore.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 effectScore, BigDecimal totalScore) { + target.setSafetyScore(source.getSafetyScore()); + target.setQualityScore(source.getQualityScore()); + target.setEfficiencyScore(source.getEfficiencyScore()); + target.setCostScore(source.getCostScore()); + target.setEffectScore(effectScore); + 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.getSafetyScore(), "安全"); + validateScoreValue(score.getQualityScore(), "质量"); + validateScoreValue(score.getEfficiencyScore(), "效率"); + validateScoreValue(score.getCostScore(), "成本"); + validateScoreValue(score.getPromotionScore(), "可推广度"); + validateScoreValue(score.getOriginalityScore(), "独创性"); + validateScoreValue(score.getEffortScore(), "努力度"); + BigDecimal calculatedEffectScore = calculateEffectScore(score.getSafetyScore(), score.getQualityScore(), score.getEfficiencyScore(), score.getCostScore()); + if (score.getEffectScore() == null || score.getEffectScore().compareTo(calculatedEffectScore) != 0 || 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 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())) { @@ -278,11 +689,30 @@ public class ImprovementProposalServiceImpl extends ServiceImpl getUsernamesByRoleIdLocalApi(@RequestParam("roleId") String roleId); + /** * 7通过用户账号查询角色集合 * @param username diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java b/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java index 7b8a3d3..9336d11 100644 --- a/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java +++ b/jeecg-module-system/jeecg-system-api/jeecg-system-cloud-api/src/main/java/org/jeecg/common/system/api/fallback/SysBaseAPIFallback.java @@ -10,6 +10,7 @@ import org.jeecg.common.constant.enums.EmailTemplateEnum; import org.jeecg.common.system.api.ISysBaseAPI; import org.jeecg.common.system.vo.*; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -24,6 +25,12 @@ public class SysBaseAPIFallback implements ISysBaseAPI { @Setter private Throwable cause; + @Override + public List getUsernamesByRoleIdLocalApi(String roleId) { + log.error("Query usernames by role failed, roleId={}", roleId, cause); + return Collections.emptyList(); + } + @Override public void sendSysAnnouncement(MessageDTO message) { log.error("发送消息失败 {}", cause); diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java b/jeecg-module-system/jeecg-system-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java index 632c7c4..8a994ba 100644 --- a/jeecg-module-system/jeecg-system-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java +++ b/jeecg-module-system/jeecg-system-api/jeecg-system-local-api/src/main/java/org/jeecg/common/system/api/ISysBaseAPI.java @@ -575,4 +575,13 @@ public interface ISysBaseAPI extends CommonAPI { List getUsersListByDeptIdAndRoleIdLocalApi(String deptId, String roleId); List getUserByRoleIdLocalApi(String roleId); - } + + /** + * Query active usernames by role for Flowable multi-instance tasks. + * This method intentionally uses an independent query path so existing role queries remain unchanged. + * + * @param roleId system role id + * @return active username list + */ + List getUsernamesByRoleIdLocalApi(String roleId); +} diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/api/controller/SystemApiController.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/api/controller/SystemApiController.java index 4fffe7c..354bcd4 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/api/controller/SystemApiController.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/api/controller/SystemApiController.java @@ -131,6 +131,14 @@ public class SystemApiController { return loginUser; } + /** + * 流程多实例节点只接受 username 作为办理人标识,统一由系统服务按角色查询。 + */ + @GetMapping("/getUsernamesByRoleId") + List getUsernamesByRoleId(@RequestParam("roleId") String roleId) { + return sysBaseApi.getUsernamesByRoleIdLocalApi(roleId); + } + /** * 通过用户账号查询角色集合 * @param username diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/SysUserMapper.java b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/SysUserMapper.java index 4a359dc..dfe3411 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/SysUserMapper.java +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/SysUserMapper.java @@ -251,4 +251,13 @@ public interface SysUserMapper extends BaseMapper { * @return */ List getUserByRoleId(String roleId); + + /** + * Query active usernames by role through a dedicated statement. + * It must not reuse the overloaded getUserByRoleId statement used by the user management page. + * + * @param roleId role id + * @return username list + */ + List getUsernamesByRoleId(@Param("roleId") String roleId); } diff --git a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/xml/SysUserMapper.xml b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/xml/SysUserMapper.xml index 4efa813..248441f 100644 --- a/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/xml/SysUserMapper.xml +++ b/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/xml/SysUserMapper.xml @@ -59,6 +59,17 @@ + + +