From d37105f03bfe59b163e49539475bbeeb1520adbc Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Tue, 12 May 2026 17:40:32 +0800 Subject: [PATCH] commit --- .../ProcessInstanceEndEventListener.java | 35 ++ .../ProcessTaskEndEventListener.java | 4 + .../ProcessTaskRevokeEventListener.java | 4 + .../ProcessTaskStartEventListener.java | 2 + .../app/flow/service/SchoolOaTodoService.java | 44 ++ .../impl/ProcessInstanceServiceImpl.java | 4 + .../service/impl/ProcessTaskServiceImpl.java | 5 + .../service/impl/SchoolOaTodoServiceImpl.java | 389 ++++++++++++++++++ src/main/resources/application-dev.yaml | 22 +- .../staffbenefit/condolence/apply/index.html | 24 +- .../condolence/branchUnionApproval/index.html | 19 +- .../schoolPrincipalApproval/index.html | 19 +- .../condolence/schoolUnionApproval/index.html | 19 +- .../staffbenefit/condolence/apply/index.html | 24 +- .../condolence/branchUnionApproval/index.html | 19 +- .../schoolPrincipalApproval/index.html | 19 +- .../condolence/schoolUnionApproval/index.html | 19 +- 17 files changed, 636 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/budwk/app/flow/listenter/ProcessInstanceEndEventListener.java create mode 100644 src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java create mode 100644 src/main/java/com/budwk/app/flow/service/impl/SchoolOaTodoServiceImpl.java diff --git a/src/main/java/com/budwk/app/flow/listenter/ProcessInstanceEndEventListener.java b/src/main/java/com/budwk/app/flow/listenter/ProcessInstanceEndEventListener.java new file mode 100644 index 0000000..64901c7 --- /dev/null +++ b/src/main/java/com/budwk/app/flow/listenter/ProcessInstanceEndEventListener.java @@ -0,0 +1,35 @@ +package com.budwk.app.flow.listenter; + +import com.budwk.app.flow.engine.event.ProcessEvent; +import com.budwk.app.flow.engine.event.ProcessEventListener; +import com.budwk.app.flow.entity.ProcessInstance; +import com.budwk.app.flow.enums.ProcessEventTypeEnum; +import com.budwk.app.flow.enums.ProcessInstanceStateEnum; +import com.budwk.app.flow.service.SchoolOaTodoService; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; + +/** + * 流程实例结束事件监听器。 + */ +@IocBean +public class ProcessInstanceEndEventListener implements ProcessEventListener { + + @Inject + private Dao dao; + @Inject + private SchoolOaTodoService schoolOaTodoService; + + @Override + public void onEvent(ProcessEvent event) { + if (event.getEventType() == ProcessEventTypeEnum.PROCESS_INSTANCE_END) { + Long instanceId = event.getSourceId(); + ProcessInstance instance = dao.fetch(ProcessInstance.class, instanceId); + // 只在流程正常完成时把学校OA节点名称改为完结,拒绝结束等状态不做完结同步。 + if (instance != null && ProcessInstanceStateEnum.FINISHED.getCode().equals(instance.getState())) { + schoolOaTodoService.updateFlowFinishedStepName(instanceId); + } + } + } +} diff --git a/src/main/java/com/budwk/app/flow/listenter/ProcessTaskEndEventListener.java b/src/main/java/com/budwk/app/flow/listenter/ProcessTaskEndEventListener.java index 553c49f..d36502b 100644 --- a/src/main/java/com/budwk/app/flow/listenter/ProcessTaskEndEventListener.java +++ b/src/main/java/com/budwk/app/flow/listenter/ProcessTaskEndEventListener.java @@ -4,6 +4,7 @@ import com.budwk.app.flow.engine.event.ProcessEvent; import com.budwk.app.flow.engine.event.ProcessEventListener; import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.enums.ProcessEventTypeEnum; +import com.budwk.app.flow.service.SchoolOaTodoService; import org.nutz.dao.Dao; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -16,12 +17,15 @@ public class ProcessTaskEndEventListener implements ProcessEventListener { @Inject private Dao dao; + @Inject + private SchoolOaTodoService schoolOaTodoService; @Override public void onEvent(ProcessEvent event) { if (event.getEventType() == ProcessEventTypeEnum.PROCESS_TASK_END) { Long sourceId = event.getSourceId(); ProcessTask task = dao.fetch(ProcessTask.class, sourceId); + schoolOaTodoService.completeTodo(task); System.out.println("流程任务完成事件:" + task.getDisplayName()); } } diff --git a/src/main/java/com/budwk/app/flow/listenter/ProcessTaskRevokeEventListener.java b/src/main/java/com/budwk/app/flow/listenter/ProcessTaskRevokeEventListener.java index 2e47045..426cc2c 100644 --- a/src/main/java/com/budwk/app/flow/listenter/ProcessTaskRevokeEventListener.java +++ b/src/main/java/com/budwk/app/flow/listenter/ProcessTaskRevokeEventListener.java @@ -9,6 +9,7 @@ import com.budwk.app.flow.engine.event.ProcessEventListener; import com.budwk.app.flow.entity.ProcessInstance; import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.enums.ProcessEventTypeEnum; +import com.budwk.app.flow.service.SchoolOaTodoService; import org.nutz.dao.Dao; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -21,12 +22,15 @@ public class ProcessTaskRevokeEventListener implements ProcessEventListener { @Inject private Dao dao; + @Inject + private SchoolOaTodoService schoolOaTodoService; @Override public void onEvent(ProcessEvent event) { if(event.getEventType()== ProcessEventTypeEnum.PROCESS_TASK_REVOKE){ Long taskId = event.getSourceId(); ProcessTask task = dao.fetch(ProcessTask.class, taskId); + schoolOaTodoService.deleteTodo(task); String taskDisplayName = task.getDisplayName(); diff --git a/src/main/java/com/budwk/app/flow/listenter/ProcessTaskStartEventListener.java b/src/main/java/com/budwk/app/flow/listenter/ProcessTaskStartEventListener.java index d030783..f5b33c6 100644 --- a/src/main/java/com/budwk/app/flow/listenter/ProcessTaskStartEventListener.java +++ b/src/main/java/com/budwk/app/flow/listenter/ProcessTaskStartEventListener.java @@ -8,6 +8,7 @@ import com.budwk.app.flow.entity.ProcessInstance; import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.entity.ProcessTaskActor; import com.budwk.app.flow.enums.ProcessEventTypeEnum; +import com.budwk.app.web.commons.base.Globals; import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService; import lombok.extern.slf4j.Slf4j; import org.nutz.dao.Cnd; @@ -37,6 +38,7 @@ public class ProcessTaskStartEventListener implements ProcessEventListener { Long sourceId = event.getSourceId(); ProcessTask task = dao.fetch(ProcessTask.class, sourceId); sendMessage(task); + } } diff --git a/src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java b/src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java new file mode 100644 index 0000000..5a2da56 --- /dev/null +++ b/src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java @@ -0,0 +1,44 @@ +package com.budwk.app.flow.service; + +import com.budwk.app.flow.entity.ProcessTask; + +/** + * 学校 OA 待办同步服务。 + */ +public interface SchoolOaTodoService { + + /** + * 根据本系统流程任务创建学校 OA 待办。 + * + * @param processTask 本系统已创建的流程任务,需包含任务 ID、流程实例 ID、节点名称和 PC/H5 表单地址 + */ + void createTodo(ProcessTask processTask); + + /** + * 根据本系统流程任务完成学校 OA 待办。 + * + * @param processTask 本系统已完成的流程任务,需包含任务 ID,用于拼接学校 OA 待办唯一值 + */ + void completeTodo(ProcessTask processTask); + + /** + * 根据本系统流程任务删除学校 OA 单条待办。 + * + * @param processTask 本系统待删除待办对应的流程任务,需包含任务 ID,用于拼接学校 OA 待办唯一值 + */ + void deleteTodo(ProcessTask processTask); + + /** + * 根据本系统流程实例 ID 删除学校 OA 流程。 + * + * @param flowId 创建学校 OA 待办时传入的流程实例 ID + */ + void deleteFlow(Long flowId); + + /** + * 流程最终完成后修改学校 OA 已办信息。 + * + * @param processInstanceId 本系统流程实例 ID,用于定位最后一个已完成审批任务并把学校 OA 节点名称改为完结 + */ + void updateFlowFinishedStepName(Long processInstanceId); +} diff --git a/src/main/java/com/budwk/app/flow/service/impl/ProcessInstanceServiceImpl.java b/src/main/java/com/budwk/app/flow/service/impl/ProcessInstanceServiceImpl.java index cbb831c..1d2f701 100644 --- a/src/main/java/com/budwk/app/flow/service/impl/ProcessInstanceServiceImpl.java +++ b/src/main/java/com/budwk/app/flow/service/impl/ProcessInstanceServiceImpl.java @@ -29,6 +29,7 @@ import com.budwk.app.flow.enums.ProcessTaskStateEnum; import com.budwk.app.flow.service.ProcessDefineService; import com.budwk.app.flow.service.ProcessInstanceService; import com.budwk.app.flow.service.ProcessTaskService; +import com.budwk.app.flow.service.SchoolOaTodoService; import com.budwk.app.flow.vo.HighLightVO; import com.budwk.app.flow.vo.ProcessInstanceVO; import com.budwk.app.flow.vo.ProcessTaskVO; @@ -55,6 +56,8 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl @Inject private ProcessDefineService processDefineService; + @Inject + private SchoolOaTodoService schoolOaTodoService; @Override @Aop(TransAop.READ_COMMITTED) @@ -556,6 +559,7 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl public void deleteProcessInstanceById(Long processInstanceId) { List taskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)); List taskIds = taskList.stream().map(ProcessTask::getId).toList(); + schoolOaTodoService.deleteFlow(processInstanceId); dao().clear(ProcessTask.class, Cnd.where(ProcessTask::getId, "in", taskIds)); dao().clear(ProcessInstance.class, Cnd.where(ProcessInstance::getId, "=", processInstanceId)); } diff --git a/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java b/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java index 943a009..f57a071 100644 --- a/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java +++ b/src/main/java/com/budwk/app/flow/service/impl/ProcessTaskServiceImpl.java @@ -29,6 +29,7 @@ import com.budwk.app.flow.enums.*; import com.budwk.app.flow.service.ProcessDefineService; import com.budwk.app.flow.service.ProcessInstanceService; import com.budwk.app.flow.service.ProcessTaskService; +import com.budwk.app.flow.service.SchoolOaTodoService; import com.budwk.app.flow.vo.ProcessTaskVO; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import org.nutz.aop.interceptor.ioc.TransAop; @@ -55,6 +56,8 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl impleme @Inject private ProcessDefineService processDefineService; + @Inject + private SchoolOaTodoService schoolOaTodoService; @Override public boolean save(ProcessTask param) { @@ -198,6 +201,7 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl impleme System.out.println("创建任务:" + processTask.getTaskName() + "," + processTask.getDisplayName()); processTaskList.add(processTask); addTaskActor(processTask.getId(), getTaskActors(taskModel, execution)); + schoolOaTodoService.createTodo(processTask); return processTaskList; } @@ -409,6 +413,7 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl impleme System.out.println("创建会签任务:" + processTask.getTaskName() + "," + processTask.getDisplayName()); processTaskList.add(processTask); addTaskActor(processTask.getId(), CollectionUtil.newArrayList(taskActor)); + schoolOaTodoService.createTodo(processTask); }); ProcessInstanceService processInstanceService = ServiceContext.find(ProcessInstanceService.class); // 更新会签变量到流程实例参数中 diff --git a/src/main/java/com/budwk/app/flow/service/impl/SchoolOaTodoServiceImpl.java b/src/main/java/com/budwk/app/flow/service/impl/SchoolOaTodoServiceImpl.java new file mode 100644 index 0000000..d7dbb4a --- /dev/null +++ b/src/main/java/com/budwk/app/flow/service/impl/SchoolOaTodoServiceImpl.java @@ -0,0 +1,389 @@ +package com.budwk.app.flow.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.budwk.app.base.exception.BaseException; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.entity.ProcessCategory; +import com.budwk.app.flow.entity.ProcessDefine; +import com.budwk.app.flow.entity.ProcessInstance; +import com.budwk.app.flow.entity.ProcessTask; +import com.budwk.app.flow.entity.ProcessTaskActor; +import com.budwk.app.flow.enums.ProcessTaskStateEnum; +import com.budwk.app.flow.service.SchoolOaTodoService; +import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.web.commons.base.Globals; +import lombok.extern.slf4j.Slf4j; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.impl.PropertiesProxy; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; + +import java.util.Date; +import java.util.List; + +/** + * 学校 OA 待办同步服务实现。 + */ +@Slf4j +@IocBean +public class SchoolOaTodoServiceImpl implements SchoolOaTodoService { + + private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + @Inject + private Dao dao; + @Inject + private PropertiesProxy conf; + + @Override + public void createTodo(ProcessTask processTask) { + if (processTask == null || processTask.getId() == null) { + throw new BaseException("学校OA待办同步失败:流程任务为空"); + } + if (Long.valueOf(0L).equals(processTask.getTaskParentId()) && "startTask".equals(processTask.getTaskName())) { + return; + } + + ProcessInstance instance = dao.fetch(ProcessInstance.class, processTask.getProcessInstanceId()); + if (instance == null) { + throw new BaseException("学校OA待办同步失败:流程实例不存在,taskId={}", processTask.getId()); + } + + ProcessDefine define = dao.fetch(ProcessDefine.class, instance.getProcessDefineId()); + if (define == null) { + throw new BaseException("学校OA待办同步失败:流程定义不存在,instanceId={}", instance.getId()); + } + + Sys_user creator = dao.fetch(Sys_user.class, instance.getOperator()); + if (creator == null || StrUtil.isBlank(creator.getLoginname()) || StrUtil.isBlank(creator.getUsername())) { + throw new BaseException("学校OA待办同步失败:流程发起人工号或姓名为空,userId={}", instance.getOperator()); + } + + List actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTask.getId())); + if (actors.isEmpty()) { + throw new BaseException("学校OA待办同步失败:流程任务接收人为空,taskId={}", processTask.getId()); + } + + String pcUrl = buildTaskUrl(processTask.getFormKey(), processTask); + String appUrl = buildTaskUrl(processTask.getH5FormKey(), processTask); + String flowTypeName = getFlowTypeName(define); + String title = getInstanceTitle(instance, define); + String creatorTime = formatTime(instance.getCreatedAt()); + String receiveTime = formatTime(processTask.getCreatedAt()); + + for (ProcessTaskActor actor : actors) { + sendCreateTodo(processTask, instance, creator, actor, flowTypeName, title, creatorTime, receiveTime, pcUrl, appUrl); + } + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void completeTodo(ProcessTask processTask) { + if (processTask == null || processTask.getId() == null) { + throw new BaseException("学校OA待办完成失败:流程任务为空"); + } + if (Long.valueOf(0L).equals(processTask.getTaskParentId()) && "startTask".equals(processTask.getTaskName())) { + return; + } + + List actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTask.getId())); + if (actors.isEmpty()) { + throw new BaseException("学校OA待办完成失败:流程任务接收人为空,taskId={}", processTask.getId()); + } + + for (ProcessTaskActor actor : actors) { + sendCompleteTodo(processTask, actor); + } + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void deleteTodo(ProcessTask processTask) { + if (processTask == null || processTask.getId() == null) { + throw new BaseException("学校OA待办删除失败:流程任务为空"); + } + if (Long.valueOf(0L).equals(processTask.getTaskParentId()) && "startTask".equals(processTask.getTaskName())) { + return; + } + // 仅处理仍处于进行中的任务,避免已完成、已撤回等历史任务重复触发学校OA待办删除。 + if (!ProcessTaskStateEnum.DOING.getCode().equals(processTask.getTaskState())) { + return; + } + + List actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTask.getId())); + if (actors.isEmpty()) { + throw new BaseException("学校OA待办删除失败:流程任务接收人为空,taskId={}", processTask.getId()); + } + + for (ProcessTaskActor actor : actors) { + sendDeleteTodo(processTask, actor); + } + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void deleteFlow(Long flowId) { + if (flowId == null) { + throw new BaseException("学校OA流程删除失败:流程实例ID为空"); + } + + String deleteFlowUrl = requireConfig("school-oa.todo.delete-flow-url").replace("{flowId}", String.valueOf(flowId)); + sendTodoRequest(deleteFlowUrl, "学校OA流程删除失败", "学校OA流程删除异常"); + log.info("学校OA流程删除成功,flowId={}", flowId); + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void updateFlowFinishedStepName(Long processInstanceId) { + if (processInstanceId == null) { + throw new BaseException("学校OA待办修改失败:流程实例ID为空"); + } + + ProcessInstance instance = dao.fetch(ProcessInstance.class, processInstanceId); + if (instance == null) { + throw new BaseException("学校OA待办修改失败:流程实例不存在,instanceId={}", processInstanceId); + } + + ProcessDefine define = dao.fetch(ProcessDefine.class, instance.getProcessDefineId()); + if (define == null) { + throw new BaseException("学校OA待办修改失败:流程定义不存在,instanceId={}", instance.getId()); + } + + Sys_user creator = dao.fetch(Sys_user.class, instance.getOperator()); + if (creator == null || StrUtil.isBlank(creator.getLoginname()) || StrUtil.isBlank(creator.getUsername())) { + throw new BaseException("学校OA待办修改失败:流程发起人工号或姓名为空,userId={}", instance.getOperator()); + } + + List finishedTaskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId) + .and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()) + .desc(ProcessTask::getId)); + for (ProcessTask task : finishedTaskList) { + List actors = dao.query(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", task.getId())); + if (!actors.isEmpty()) { + String pcUrl = buildTaskUrl(task.getFormKey(), task); + String appUrl = buildTaskUrl(task.getH5FormKey(), task); + String flowTypeName = getFlowTypeName(define); + String title = getInstanceTitle(instance, define); + String creatorTime = formatTime(instance.getCreatedAt()); + String receiveTime = formatTime(task.getCreatedAt()); + + for (ProcessTaskActor actor : actors) { + sendUpdateFinishedTodo(task, instance, creator, actor, flowTypeName, title, creatorTime, receiveTime, pcUrl, appUrl); + } + return; + } + } + throw new BaseException("学校OA待办修改失败:未找到可修改的已完成流程任务,instanceId={}", processInstanceId); + } + + /** + * 调用学校 OA 创建待办接口,接口失败时直接抛出异常,让本系统事务回滚。 + */ + private void sendCreateTodo(ProcessTask task, ProcessInstance instance, Sys_user creator, ProcessTaskActor actor, + String flowTypeName, String title, String creatorTime, String receiveTime, + String pcUrl, String appUrl) { + if (StrUtil.isBlank(actor.getActorAccount()) || StrUtil.isBlank(actor.getActorName())) { + throw new BaseException("学校OA待办同步失败:接收人工号或姓名为空,taskId={},actorId={}", task.getId(), actor.getActorId()); + } + + JSONObject body = new JSONObject(); + body.set("flowTypeName", flowTypeName); + body.set("flowId", String.valueOf(instance.getId())); + body.set("title", title); + body.set("stepName", task.getDisplayName()); + body.set("creatorCode", creator.getLoginname()); + body.set("creatorName", creator.getUsername()); + body.set("creatorTime", creatorTime); + body.set("pcUrl", pcUrl); + body.set("appUrl", appUrl); + body.set("pcPreviewUrl", pcUrl); + body.set("appPreviewUrl", appUrl); + body.set("uniqueId", task.getId() + "_" + actor.getActorId()); + body.set("receiveCode", actor.getActorAccount()); + body.set("receiveName", actor.getActorName()); + body.set("receiveTime", receiveTime); + + String insertUrl = requireConfig("school-oa.todo.insert-url"); + try { + HttpRequest request = HttpUtil.createPost(insertUrl); + setTodoRequestHeaders(request); + request.body(JSONUtil.toJsonStr(body)); + + String responseBody = request.execute().body(); + JSONObject response = JSONUtil.parseObj(responseBody); + if (response.getInt("state", 0) != 200) { + throw new BaseException("学校OA待办创建失败:{}", response.getStr("message", responseBody)); + } + log.info("学校OA待办创建成功,taskId={},receiveCode={},response={}", task.getId(), actor.getActorAccount(), responseBody); + } catch (BaseException e) { + throw e; + } catch (Exception e) { + throw new BaseException("学校OA待办创建异常:{}", e.getMessage()); + } + } + + /** + * 调用学校 OA 完成待办接口。 + * 参数使用创建待办时约定的 uniqueId,格式为“流程任务ID_参与人ID”; + * 返回值为学校 OA JSON 结果,state=200 表示已成功把该待办改为已办。 + */ + private void sendCompleteTodo(ProcessTask task, ProcessTaskActor actor) { + if (StrUtil.isBlank(actor.getActorId()) || StrUtil.isBlank(actor.getActorAccount())) { + throw new BaseException("学校OA待办完成失败:接收人ID或工号为空,taskId={},actorId={}", task.getId(), actor.getActorId()); + } + + String uniqueId = task.getId() + "_" + actor.getActorId(); + String updateUrl = requireConfig("school-oa.todo.update-url").replace("{uniqueId}", uniqueId); + String responseBody = sendTodoRequest(updateUrl, "学校OA待办完成失败", "学校OA待办完成异常"); + log.info("学校OA待办完成成功,taskId={},receiveCode={},uniqueId={},response={}", task.getId(), actor.getActorAccount(), uniqueId, responseBody); + } + + /** + * 调用学校 OA 删除单条待办接口。 + * 参数使用创建待办时约定的 uniqueId,格式为“流程任务ID_参与人ID”; + * 返回值为学校 OA JSON 结果,state=200 表示已成功删除该待办数据。 + */ + private void sendDeleteTodo(ProcessTask task, ProcessTaskActor actor) { + if (StrUtil.isBlank(actor.getActorId()) || StrUtil.isBlank(actor.getActorAccount())) { + throw new BaseException("学校OA待办删除失败:接收人ID或工号为空,taskId={},actorId={}", task.getId(), actor.getActorId()); + } + + String uniqueId = task.getId() + "_" + actor.getActorId(); + String deleteUrl = requireConfig("school-oa.todo.delete-url").replace("{uniqueId}", uniqueId); + String responseBody = sendTodoRequest(deleteUrl, "学校OA待办删除失败", "学校OA待办删除异常"); + log.info("学校OA待办删除成功,taskId={},receiveCode={},uniqueId={},response={}", task.getId(), actor.getActorAccount(), uniqueId, responseBody); + } + + /** + * 调用学校 OA 修改待办信息接口。 + * 参数为学校 OA 要求的完整流程信息,uniqueId 使用“流程任务ID_参与人ID”; + * 流程最终完成后将 stepName 固定改为“完结”,isRemark 固定为 2 表示已办。 + */ + private void sendUpdateFinishedTodo(ProcessTask task, ProcessInstance instance, Sys_user creator, ProcessTaskActor actor, + String flowTypeName, String title, String creatorTime, String receiveTime, + String pcUrl, String appUrl) { + if (StrUtil.isBlank(actor.getActorId()) || StrUtil.isBlank(actor.getActorAccount()) || StrUtil.isBlank(actor.getActorName())) { + throw new BaseException("学校OA待办修改失败:接收人ID、工号或姓名为空,taskId={},actorId={}", task.getId(), actor.getActorId()); + } + + String uniqueId = task.getId() + "_" + actor.getActorId(); + JSONObject body = new JSONObject(); + body.set("flowTypeName", flowTypeName); + body.set("flowId", String.valueOf(instance.getId())); + body.set("title", title); + body.set("stepName", "完结"); + body.set("creatorCode", creator.getLoginname()); + body.set("creatorName", creator.getUsername()); + body.set("creatorTime", creatorTime); + body.set("pcUrl", pcUrl); + body.set("appUrl", appUrl); + body.set("uniqueId", uniqueId); + body.set("receiveCode", actor.getActorAccount()); + body.set("receiveName", actor.getActorName()); + body.set("receiveTime", receiveTime); + body.set("isRemark", "2"); + + String updateHandleUrl = requireConfig("school-oa.todo.update-handle-url").replace("{uniqueId}", uniqueId); + String responseBody = sendTodoRequest(updateHandleUrl, body, "学校OA待办修改失败", "学校OA待办修改异常"); + log.info("学校OA待办修改成功,taskId={},receiveCode={},uniqueId={},response={}", task.getId(), actor.getActorAccount(), uniqueId, responseBody); + } + + private String getFlowTypeName(ProcessDefine define) { + if (StrUtil.isNotBlank(define.getCategory())) { + ProcessCategory category = dao.fetch(ProcessCategory.class, define.getCategory()); + if (category != null && StrUtil.isNotBlank(category.getName())) { + return category.getName(); + } + } + if (StrUtil.isNotBlank(define.getDisplayName())) { + return define.getDisplayName(); + } + throw new BaseException("学校OA待办同步失败:流程类型名称为空,processDefineId={}", define.getId()); + } + + private String getInstanceTitle(ProcessInstance instance, ProcessDefine define) { + if (JSONUtil.isTypeJSON(instance.getVariable())) { + String instanceName = JSONUtil.parseObj(instance.getVariable()).getStr(FlowConst.PROCESS_INSTANCE_NAME); + if (StrUtil.isNotBlank(instanceName)) { + return instanceName; + } + } + if (StrUtil.isNotBlank(define.getDisplayName())) { + return define.getDisplayName(); + } + throw new BaseException("学校OA待办同步失败:流程标题为空,instanceId={}", instance.getId()); + } + + private String buildTaskUrl(String formKey, ProcessTask task) { + if (StrUtil.isBlank(formKey)) { + throw new BaseException("学校OA待办同步失败:任务审核地址为空,taskId={}", task.getId()); + } + String baseUrl = StrUtil.removeSuffix(Globals.AppDomain, "/") + "/" + StrUtil.removePrefix(formKey, "/"); + String separator = baseUrl.contains("?") ? "&" : "?"; + return baseUrl + separator + "taskId=" + task.getId() + "&instanceId=" + task.getProcessInstanceId(); + } + + private String formatTime(Long timestamp) { + if (timestamp == null) { + throw new BaseException("学校OA待办同步失败:待办时间为空"); + } + return DateUtil.format(new Date(timestamp), DATE_PATTERN); + } + + private String requireConfig(String key) { + String value = conf.get(key); + if (StrUtil.isBlank(value)) { + throw new BaseException("学校OA待办同步失败:配置{}为空", key); + } + return value; + } + + /** + * 统一设置学校 OA 待办接口请求头。 + * 请求头参数包含 app-code、secret-key、domain-name 和可选 intercept-domain-name; + * 学校 OA 返回 JSON 字符串,由各调用方法按业务接口分别解析 state、message、data。 + */ + private void setTodoRequestHeaders(HttpRequest request) { + request.header("Content-Type", "application/json"); + request.header("app-code", requireConfig("school-oa.todo.app-code")); + request.header("secret-key", requireConfig("school-oa.todo.secret-key")); + request.header("domain-name", requireConfig("school-oa.todo.domain-name")); + String interceptDomainName = conf.get("school-oa.todo.intercept-domain-name", ""); + if (StrUtil.isNotBlank(interceptDomainName)) { + request.header("intercept-domain-name", interceptDomainName); + } + } + + private String sendTodoRequest(String url, String failMessage, String errorMessage) { + return sendTodoRequest(url, null, failMessage, errorMessage); + } + + private String sendTodoRequest(String url, JSONObject body, String failMessage, String errorMessage) { + try { + HttpRequest request = HttpUtil.createPost(url); + setTodoRequestHeaders(request); + if (body != null) { + request.body(JSONUtil.toJsonStr(body)); + } + + String responseBody = request.execute().body(); + JSONObject response = JSONUtil.parseObj(responseBody); + if (response.getInt("state", 0) != 200) { + throw new BaseException("{}:{}", failMessage, response.getStr("message", responseBody)); + } + return responseBody; + } catch (BaseException e) { + throw e; + } catch (Exception e) { + throw new BaseException("{}:{}", errorMessage, e.getMessage()); + } + } +} diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 3ffd73b..9a2db65 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -1,11 +1,11 @@ nutz: application: - name: zhgh_whcp_new + name: zhgh_ypi mvc: ignore: ^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|html|mp3|mp4|ico|svg|vue)$ exclusions: /favicon/*,/assets/*,/druid/*,/upload/*,/components/* server: - port: 8080 + port: 80 host: 0.0.0.0 jetty: @@ -21,7 +21,7 @@ jetty: #结合ftp使用,或用nginx代理ftp路径 #staticPath: /Users/wizzer/temp/files #开发模式静态资源 - staticPathLocal: D:/myProject/zhgh_whcp_new/src/main/resources/static + staticPathLocal: E:/projects/zhgh_ypi/src/main/resources/static security: tokenName: saToken @@ -70,7 +70,7 @@ redis: #nodes=192.168.6.31:6377,192.168.6.31:6378,192.168.6.28:6377,192.168.6.28:6378,192.168.6.34:6377,192.168.6.34:6378 jdbc: - url: jdbc:mysql://192.168.21.203/zhgh_whcp_new?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://192.168.21.203/zhgh_ypi?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root password: root validationQuery: select 1 @@ -86,7 +86,7 @@ xsssql: beetl: RESOURCE: #本地路径,开发时设置本地路径,便于调试 - rootLocal: D:/myProject/zhgh_whcp_new/src/main/resources/views/ + rootLocal: E:/projects/zhgh_ypi/src/main/resources/views/ root: views/ DELIMITER_STATEMENT_START: "" @@ -164,3 +164,15 @@ minio: endPoint: http://192.168.21.214:9000 bucket: jshvc +school-oa: + todo: + insert-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/insert + update-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{uniqueId}/updateUniqueId + update-handle-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{uniqueId}/updateHandle + delete-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{uniqueId}/deleteUniqueId + delete-flow-url: https://i.ypi.edu.cn/string_unified_handle_service/handle/{flowId}/deleteFlowId + app-code: 1553e2f0c1c54b5d854ffee1eae181eb + secret-key: f8a1da28b66644238659bce33f7ead6e + domain-name: i.ypi.edu.cn + intercept-domain-name: + diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/apply/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/apply/index.html index 845f05a..0e7a5d3 100644 --- a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/apply/index.html +++ b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/apply/index.html @@ -240,9 +240,15 @@ layout("/layouts/platform.html"){ - 保存 - 提交 - 提交 + + {{ submitLoading ? "提交中..." : "保存" }} + + + {{ submitLoading ? "提交中..." : "提交" }} + + + {{ submitLoading ? "提交中..." : "提交" }} + @@ -256,6 +262,9 @@ layout("/layouts/platform.html"){ bizId: GetQueryString("bizId"), taskId: GetQueryString("taskId"), formData: {}, + // 保存和提交请求期间控制按钮转圈,避免用户重复点击。 + saveLoading: false, + submitLoading: false, formRules: { helpUserId: [{required: true, message: "必填", trigger: ["change", "blur"]}], payUserId: [{required: true, message: "必填", trigger: ["change", "blur"]}], @@ -363,11 +372,14 @@ layout("/layouts/platform.html"){ cancelButtonText: "取消", type: "warning" }).then(() => { + this.submitLoading = true this.$axios.post("/platform/condolence/apply/save", {data: JSON.stringify(this.formData)}).then(res => { if (res.code === 0) { this.$message.success(res.msg) commonUtil.pjaxPush("/platform/condolence/mine") } + }).finally(() => { + this.submitLoading = false }) }) }, @@ -379,6 +391,7 @@ layout("/layouts/platform.html"){ cancelButtonText: "取消", type: "warning" }).then(() => { + this.submitLoading = true this.$axios.post("/platform/condolence/apply/submit", { data: JSON.stringify(this.formData) }).then(res => { @@ -386,6 +399,8 @@ layout("/layouts/platform.html"){ this.$message.success(res.msg) commonUtil.pjaxPush("/platform/condolence/mine") } + }).finally(() => { + this.submitLoading = false }) }) } @@ -399,6 +414,7 @@ layout("/layouts/platform.html"){ cancelButtonText: "取消", type: "warning" }).then(() => { + this.submitLoading = true this.$axios.post("/platform/condolence/apply/submitAgain", { data: JSON.stringify(this.formData), taskId: GetQueryString("taskId") @@ -407,6 +423,8 @@ layout("/layouts/platform.html"){ this.$message.success(res.msg) commonUtil.pjaxPush("/platform/condolence/mine") } + }).finally(() => { + this.submitLoading = false }) }) } diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/branchUnionApproval/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/branchUnionApproval/index.html index f2b4e47..51ea55e 100644 --- a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/branchUnionApproval/index.html +++ b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/branchUnionApproval/index.html @@ -88,9 +88,15 @@ layout("/layouts/platform.html"){ 取消 - 退回到发起人 - 拒绝申请 - 同意申请 + + {{ submitLoading ? "提交中..." : "退回到发起人" }} + + + {{ submitLoading ? "提交中..." : "拒绝申请" }} + + + {{ submitLoading ? "提交中..." : "同意申请" }} + @@ -124,7 +130,9 @@ layout("/layouts/platform.html"){ {prop: 'instanceState', label: '流程状态'}, ], formData: {}, - showApprovalForm: false + showApprovalForm: false, + // 审核提交请求期间控制按钮转圈,避免重复提交审批意见。 + submitLoading: false } }, methods: { @@ -150,6 +158,7 @@ layout("/layouts/platform.html"){ cancelButtonText: "取消", type: "warning" }).then(() => { + this.submitLoading = true this.$axios.post("/flow/common/executeTask", { data: JSON.stringify({ ...this.formData, @@ -161,6 +170,8 @@ layout("/layouts/platform.html"){ this.$message.success(res.msg) this.doSearch() } + }).finally(() => { + this.submitLoading = false }) }) }, diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolPrincipalApproval/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolPrincipalApproval/index.html index f72e4a9..e02c936 100644 --- a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolPrincipalApproval/index.html +++ b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolPrincipalApproval/index.html @@ -108,9 +108,15 @@ layout("/layouts/platform.html"){ 取消 - 退回到发起人 - 拒绝申请 - 同意申请 + + {{ submitLoading ? "提交中..." : "退回到发起人" }} + + + {{ submitLoading ? "提交中..." : "拒绝申请" }} + + + {{ submitLoading ? "提交中..." : "同意申请" }} + @@ -147,7 +153,9 @@ layout("/layouts/platform.html"){ unionList: [], unitList: [], formData: {}, - showApprovalForm: false + showApprovalForm: false, + // 审核提交请求期间控制按钮转圈,避免重复提交审批意见。 + submitLoading: false } }, methods: { @@ -173,6 +181,7 @@ layout("/layouts/platform.html"){ cancelButtonText: "取消", type: "warning" }).then(() => { + this.submitLoading = true this.$axios.post("/flow/common/executeTask", { data: JSON.stringify({ ...this.formData, @@ -184,6 +193,8 @@ layout("/layouts/platform.html"){ this.$message.success(res.msg) this.doSearch() } + }).finally(() => { + this.submitLoading = false }) }) }, diff --git a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolUnionApproval/index.html b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolUnionApproval/index.html index f72e4a9..e02c936 100644 --- a/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolUnionApproval/index.html +++ b/src/main/resources/views/platform/zhgh/staffbenefit/condolence/schoolUnionApproval/index.html @@ -108,9 +108,15 @@ layout("/layouts/platform.html"){ 取消 - 退回到发起人 - 拒绝申请 - 同意申请 + + {{ submitLoading ? "提交中..." : "退回到发起人" }} + + + {{ submitLoading ? "提交中..." : "拒绝申请" }} + + + {{ submitLoading ? "提交中..." : "同意申请" }} + @@ -147,7 +153,9 @@ layout("/layouts/platform.html"){ unionList: [], unitList: [], formData: {}, - showApprovalForm: false + showApprovalForm: false, + // 审核提交请求期间控制按钮转圈,避免重复提交审批意见。 + submitLoading: false } }, methods: { @@ -173,6 +181,7 @@ layout("/layouts/platform.html"){ cancelButtonText: "取消", type: "warning" }).then(() => { + this.submitLoading = true this.$axios.post("/flow/common/executeTask", { data: JSON.stringify({ ...this.formData, @@ -184,6 +193,8 @@ layout("/layouts/platform.html"){ this.$message.success(res.msg) this.doSearch() } + }).finally(() => { + this.submitLoading = false }) }) }, diff --git a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/apply/index.html b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/apply/index.html index 04ff03a..4a803c9 100644 --- a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/apply/index.html +++ b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/apply/index.html @@ -377,9 +377,15 @@ layout("/layouts/platform_h5.html"){
- 保存申请 - 提交申请 - 提交申请 + + {{ saveLoading ? "保存中..." : "保存申请" }} + + + {{ submitLoading ? "提交中..." : "提交申请" }} + + + {{ submitLoading ? "提交中..." : "提交申请" }} +
@@ -394,6 +400,9 @@ layout("/layouts/platform_h5.html"){ bizId: GetQueryString("bizId"), taskId: GetQueryString("taskId"), formData: {}, + // 保存和提交请求期间控制按钮转圈,避免用户重复点击。 + saveLoading: false, + submitLoading: false, chooseType: {}, userOptions: [], @@ -540,11 +549,14 @@ layout("/layouts/platform_h5.html"){ title: "提示", message: "您确定保存吗?" }).then(() => { + this.saveLoading = true this.$axios.post("/platform/condolence/apply/save", { data: JSON.stringify(this.formData) }).then(res => { if (res.code === 0) { this.$toast(res.msg) this.$pjaxReplace("/platform/condolence/mine/h5") } + }).finally(() => { + this.saveLoading = false }) }) }, @@ -558,11 +570,14 @@ layout("/layouts/platform_h5.html"){ title: "提示", message: "您确定要提交申请吗?" }).then(() => { + this.submitLoading = true this.$axios.post("/platform/condolence/apply/submit", { data: JSON.stringify(this.formData) }).then(res => { if (res.code === 0) { this.$toast(res.msg) this.$pjaxReplace("/platform/condolence/mine/h5") } + }).finally(() => { + this.submitLoading = false }) }) }) @@ -577,6 +592,7 @@ layout("/layouts/platform_h5.html"){ title: "提示", message: "您确定要提交申请吗?" }).then(() => { + this.submitLoading = true this.$axios.post("/platform/condolence/apply/submitAgain", { data: JSON.stringify(this.formData), taskId: GetQueryString("taskId") @@ -585,6 +601,8 @@ layout("/layouts/platform_h5.html"){ this.$toast(res.msg) this.$pjaxReplace("/platform/condolence/mine/h5") } + }).finally(() => { + this.submitLoading = false }) }) }) diff --git a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/branchUnionApproval/index.html b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/branchUnionApproval/index.html index 10b5dbf..a052e49 100644 --- a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/branchUnionApproval/index.html +++ b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/branchUnionApproval/index.html @@ -79,9 +79,15 @@ layout("/layouts/platform_h5.html"){
- 退回 - 不同意 - 同意 + + {{ submitLoading ? "提交中..." : "退回" }} + + + {{ submitLoading ? "提交中..." : "不同意" }} + + + {{ submitLoading ? "提交中..." : "同意" }} +
@@ -109,7 +115,9 @@ layout("/layouts/platform_h5.html"){ }, typeOptions: [], formData: {}, - showApprovalForm: false + showApprovalForm: false, + // 审核提交请求期间控制按钮转圈,避免重复提交审批意见。 + submitLoading: false } }, methods: { @@ -145,6 +153,7 @@ layout("/layouts/platform_h5.html"){ title: "提示", message: "您确定要提交吗?" }).then(() => { + this.submitLoading = true this.$axios.post("/flow/common/executeTask", { data: JSON.stringify({ ...this.formData, @@ -156,6 +165,8 @@ layout("/layouts/platform_h5.html"){ this.$toast.success(res.msg) this.doSearch() } + }).finally(() => { + this.submitLoading = false }) }).catch(() => { diff --git a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolPrincipalApproval/index.html b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolPrincipalApproval/index.html index dfb14a8..e73ab36 100644 --- a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolPrincipalApproval/index.html +++ b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolPrincipalApproval/index.html @@ -79,9 +79,15 @@ layout("/layouts/platform_h5.html"){
- 退回 - 不同意 - 同意 + + {{ submitLoading ? "提交中..." : "退回" }} + + + {{ submitLoading ? "提交中..." : "不同意" }} + + + {{ submitLoading ? "提交中..." : "同意" }} +
@@ -109,7 +115,9 @@ layout("/layouts/platform_h5.html"){ }, typeOptions: [], formData: {}, - showApprovalForm: false + showApprovalForm: false, + // 审核提交请求期间控制按钮转圈,避免重复提交审批意见。 + submitLoading: false } }, methods: { @@ -145,6 +153,7 @@ layout("/layouts/platform_h5.html"){ title: "提示", message: "您确定要提交吗?" }).then(() => { + this.submitLoading = true this.$axios.post("/flow/common/executeTask", { data: JSON.stringify({ ...this.formData, @@ -156,6 +165,8 @@ layout("/layouts/platform_h5.html"){ this.$toast.success(res.msg) this.doSearch() } + }).finally(() => { + this.submitLoading = false }) }).catch(() => { diff --git a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolUnionApproval/index.html b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolUnionApproval/index.html index 0d26f62..99c6a39 100644 --- a/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolUnionApproval/index.html +++ b/src/main/resources/views/platform/zhghh5/staffbenefit/condolence/schoolUnionApproval/index.html @@ -79,9 +79,15 @@ layout("/layouts/platform_h5.html"){
- 退回 - 不同意 - 同意 + + {{ submitLoading ? "提交中..." : "退回" }} + + + {{ submitLoading ? "提交中..." : "不同意" }} + + + {{ submitLoading ? "提交中..." : "同意" }} +
@@ -109,7 +115,9 @@ layout("/layouts/platform_h5.html"){ }, typeOptions: [], formData: {}, - showApprovalForm: false + showApprovalForm: false, + // 审核提交请求期间控制按钮转圈,避免重复提交审批意见。 + submitLoading: false } }, methods: { @@ -145,6 +153,7 @@ layout("/layouts/platform_h5.html"){ title: "提示", message: "您确定要提交吗?" }).then(() => { + this.submitLoading = true this.$axios.post("/flow/common/executeTask", { data: JSON.stringify({ ...this.formData, @@ -156,6 +165,8 @@ layout("/layouts/platform_h5.html"){ this.$toast.success(res.msg) this.doSearch() } + }).finally(() => { + this.submitLoading = false }) }).catch(() => {