diff --git a/src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java b/src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java index 5a2da56..9093a32 100644 --- a/src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java +++ b/src/main/java/com/budwk/app/flow/service/SchoolOaTodoService.java @@ -2,6 +2,8 @@ package com.budwk.app.flow.service; import com.budwk.app.flow.entity.ProcessTask; +import java.util.List; + /** * 学校 OA 待办同步服务。 */ @@ -41,4 +43,24 @@ public interface SchoolOaTodoService { * @param processInstanceId 本系统流程实例 ID,用于定位最后一个已完成审批任务并把学校 OA 节点名称改为完结 */ void updateFlowFinishedStepName(Long processInstanceId); + + /** + * 创建福利选择提醒待办。 + * + * @param projectId 福利项目ID,用于构造学校OA业务标识 + * @param title 待办标题,学校OA接口没有独立正文时用于展示提醒内容 + * @param pcUrl PC端福利选择地址 + * @param appUrl 手机端福利选择地址 + * @param creatorId 待办创建人ID + * @param receiverIds 待办接收人ID集合 + */ + void createWelfareReminderTodos(String projectId, String title, String pcUrl, String appUrl, String creatorId, List receiverIds); + + /** + * 删除用户已完成福利选择对应的学校OA提醒待办。 + * + * @param projectId 福利项目ID + * @param userId 完成选择的用户ID + */ + void deleteWelfareReminderTodo(String projectId, String userId); } 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 index b7ae09c..11f46f0 100644 --- a/src/main/java/com/budwk/app/flow/service/impl/SchoolOaTodoServiceImpl.java +++ b/src/main/java/com/budwk/app/flow/service/impl/SchoolOaTodoServiceImpl.java @@ -200,6 +200,41 @@ public class SchoolOaTodoServiceImpl implements SchoolOaTodoService { throw new BaseException("学校OA待办修改失败:未找到可修改的已完成流程任务,instanceId={}", processInstanceId); } + @Override + public void createWelfareReminderTodos(String projectId, String title, String pcUrl, String appUrl, String creatorId, List receiverIds) { + if (!isSchoolOaEnabled("创建福利选择提醒待办", null, null)) { + return; + } + if (StrUtil.hasBlank(projectId, title, pcUrl, appUrl, creatorId) || receiverIds == null || receiverIds.isEmpty()) { + throw new BaseException("学校OA福利选择提醒待办参数不完整"); + } + + Sys_user creator = dao.fetch(Sys_user.class, creatorId); + if (creator == null || StrUtil.hasBlank(creator.getLoginname(), creator.getUsername())) { + throw new BaseException("学校OA福利选择提醒待办创建人工号或姓名为空,userId={}", creatorId); + } + + String creatorTime = formatTime(System.currentTimeMillis()); + for (String receiverId : receiverIds) { + Sys_user receiver = dao.fetch(Sys_user.class, receiverId); + if (receiver == null || StrUtil.hasBlank(receiver.getLoginname(), receiver.getUsername())) { + throw new BaseException("学校OA福利选择提醒待办接收人工号或姓名为空,userId={}", receiverId); + } + sendWelfareReminderTodo(projectId, title, pcUrl, appUrl, creator, receiver, creatorTime); + } + } + + @Override + public void deleteWelfareReminderTodo(String projectId, String userId) { + if (!isSchoolOaEnabled("删除福利选择提醒待办", null, null) || StrUtil.hasBlank(projectId, userId)) { + return; + } + String uniqueId = buildWelfareReminderUniqueId(projectId, userId); + String deleteUrl = requireConfig("school-oa.todo.delete-url").replace("{uniqueId}", uniqueId); + sendTodoRequest(deleteUrl, "学校OA福利选择提醒待办删除失败", "学校OA福利选择提醒待办删除异常"); + log.info("学校OA福利选择提醒待办删除成功,projectId={},userId={},uniqueId={}", projectId, userId, uniqueId); + } + /** * 调用学校 OA 创建待办接口,接口失败时直接抛出异常,让本系统事务回滚。 */ @@ -354,6 +389,59 @@ public class SchoolOaTodoServiceImpl implements SchoolOaTodoService { return baseUrl + separator + "taskId=" + task.getId() + "&instanceId=" + task.getProcessInstanceId(); } + /** + * 按学校OA固定待办格式创建福利选择提醒,标题承载自定义提醒内容。 + */ + private void sendWelfareReminderTodo(String projectId, String title, String pcUrl, String appUrl, + Sys_user creator, Sys_user receiver, String creatorTime) { + JSONObject body = new JSONObject(); + body.set("flowTypeName", "福利选择"); + body.set("flowId", "welfare_reminder_" + projectId); + 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("pcPreviewUrl", pcUrl); + body.set("appPreviewUrl", appUrl); + body.set("uniqueId", buildWelfareReminderUniqueId(projectId, receiver.getId())); + body.set("receiveCode", receiver.getLoginname()); + body.set("receiveName", receiver.getUsername()); + body.set("receiveTime", creatorTime); + + String insertUrl = requireConfig("school-oa.todo.insert-url"); + String requestBody = JSONUtil.toJsonStr(body); + log.info("学校OA福利选择提醒待办创建请求,url={},projectId={},receiveCode={},body={}", + insertUrl, projectId, receiver.getLoginname(), requestBody); + try { + HttpRequest request = HttpUtil.createPost(insertUrl); + setTodoRequestHeaders(request); + request.body(requestBody); + 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福利选择提醒待办创建成功,projectId={},receiveCode={},response={}", + projectId, receiver.getLoginname(), responseBody); + } catch (BaseException e) { + throw e; + } catch (Exception e) { + log.error("学校OA福利选择提醒待办创建异常,url={},projectId={},receiveCode={},body={}", + insertUrl, projectId, receiver.getLoginname(), requestBody, e); + throw new BaseException("学校OA福利选择提醒待办创建异常:{}", e.getMessage()); + } + } + + /** + * 福利提醒待办按项目和接收人保持唯一;用户完成选择后可据此精确删除待办。 + */ + private String buildWelfareReminderUniqueId(String projectId, String userId) { + return "welfare_reminder_" + projectId + "_" + userId; + } + private String buildAppTaskUrl(String h5FormKey, ProcessTask task) { // 部分流程没有配置 H5 审核页,学校 OA 的移动端地址为空时按要求传固定域名兜底。 if (StrUtil.isBlank(h5FormKey)) { diff --git a/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java b/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java index c66815b..c7fb232 100644 --- a/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java +++ b/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareSelectionSituationController.java @@ -94,9 +94,32 @@ public class WelfareSelectionSituationController { welfareUserSelection.setSelectTime(new Date()); } dao.insert(selections); + situationService.clearReminderTodo(projectId, userId); return Result.success("选择成功"); } + @At + @SaCheckPermission("welfare.selection.situation") + @ApiOperation("预览福利选择提醒接收人数") + public Result previewReminder(@Valid @Param("pageForm") WelfareSelectionSituationPageForm pageForm, @Param("userIds") String[] userIds) { + return Result.success(situationService.countReminderRecipients(pageForm, userIds)); + } + + @At + @SLog(type = "welfare", tag = "选择情况", msg = "发送福利选择提醒") + @SaCheckPermission("welfare.selection.situation") + @ApiOperation("发送福利选择提醒") + public Result sendReminder(@Valid @Param("pageForm") WelfareSelectionSituationPageForm pageForm, @Param("userIds") String[] userIds, String content) { + if (StrUtil.isBlank(content)) { + return Result.error("提醒内容不能为空"); + } + int recipientCount = situationService.sendReminder(pageForm, userIds, content.trim()); + if (recipientCount == 0) { + return Result.error("当前筛选条件下没有可提醒的人员"); + } + return Result.success("提醒已发送给" + recipientCount + "人"); + } + @At @SaCheckPermission("welfare.selection.situation") diff --git a/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareUserSelectController.java b/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareUserSelectController.java index 4bc86b9..f6c7d81 100644 --- a/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareUserSelectController.java +++ b/src/main/java/com/budwk/app/zhgh/welfare/controller/WelfareUserSelectController.java @@ -10,6 +10,7 @@ import com.budwk.app.zhgh.welfare.model.WelfareList; import com.budwk.app.zhgh.welfare.model.WelfareProject; import com.budwk.app.zhgh.welfare.model.WelfareUserSelection; import com.budwk.app.zhgh.welfare.service.WelfareProjectService; +import com.budwk.app.zhgh.welfare.service.WelfareSelectionSituationService; import io.swagger.annotations.ApiOperation; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; @@ -37,6 +38,8 @@ public class WelfareUserSelectController { private Dao dao; @Inject private WelfareProjectService welfareProjectService; + @Inject + private WelfareSelectionSituationService situationService; @At("") @Ok("beetl:/platform/zhgh/welfare/select/index.html") @@ -103,6 +106,7 @@ public class WelfareUserSelectController { welfareUserSelection.setSelectTime(new Date()); } dao.insert(selections); + situationService.clearReminderTodo(projectId, SecurityUtil.getUserId()); return Result.success("选择成功"); } diff --git a/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java b/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java index 21aefde..398007d 100644 --- a/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java +++ b/src/main/java/com/budwk/app/zhgh/welfare/service/WelfareSelectionSituationService.java @@ -14,4 +14,31 @@ public interface WelfareSelectionSituationService extends BaseService recipientIds = getReminderRecipientIds(pageForm, userIds); + if (recipientIds.isEmpty()) { + return 0; + } + String title = "福利选择提醒"; + String pcUrl = buildWelfareSelectionUrl("/platform/welfare/userSelect"); + String appUrl = buildWelfareSelectionUrl("/platform/h5/welfare/userSelect/list"); + + // 本地消息始终发送,确保外部渠道关闭时仍可在系统内查看提醒。 + localMessageSendStrategy.send(title, content, 2, recipientIds, null); + + List receiverLoginNames = dao().query(Sys_user.class, Cnd.where(Sys_user::getId, "in", recipientIds)).stream() + .map(Sys_user::getLoginname) + .filter(StrUtil::isNotBlank) + .distinct() + .toList(); + // AppSms 控制学校消息平台发送,消息链接跳转到手机端福利选择列表。 + if (Boolean.TRUE.equals(Globals.MyConfig.getBoolean("AppSms")) && !receiverLoginNames.isEmpty()) { + smsService.massSend(receiverLoginNames, title, content, appUrl); + } + // AppSchoolOa 控制学校OA待办;学校OA没有正文栏位,标题使用自定义提醒内容。 + if (Boolean.TRUE.equals(Globals.MyConfig.getBoolean("AppSchoolOa"))) { + schoolOaTodoService.createWelfareReminderTodos(pageForm.getProjectId(), title + ":" + content, + pcUrl, appUrl, SecurityUtil.getUserId(), recipientIds); + } + return recipientIds.size(); + } + + @Override + public void clearReminderTodo(String projectId, String userId) { + try { + schoolOaTodoService.deleteWelfareReminderTodo(projectId, userId); + } catch (Exception e) { + // 学校OA删除失败不能影响用户完成福利选择,后续可根据日志人工处理。 + log.error("删除学校OA福利选择提醒待办失败,projectId={},userId={}", projectId, userId, e); + } + } + + /** + * 使用系统配置的绝对域名构造学校外部平台可访问的福利选择地址。 + */ + private String buildWelfareSelectionUrl(String path) { + return StrUtil.removeSuffix(Globals.AppDomain, "/") + path; + } + + /** + * 根据当前筛选条件和数据权限获取提醒接收人,避免前端传入范围外人员。 + */ + private List getReminderRecipientIds(WelfareSelectionSituationPageForm pageForm, String[] userIds) { + if (StrUtil.isBlank(pageForm.getProjectId())) { + return Collections.emptyList(); + } + + Sql sql = Sqls.create(""" + SELECT DISTINCT + t1.userId + FROM + welfare_list t1 + LEFT JOIN welfare_project_user_selection t2 ON t2.welfareId = t1.projectId AND t2.selectUserId = t1.userId + LEFT JOIN sys_user t4 ON t4.id = t1.userId + $condition + """); + Cnd cnd = Cnd.NEW(); + if (AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name()) || AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name()) || AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WENTI_SPORTS.name())) { + cnd.and("t1.welfareUnionId", "=", SecurityUtil.getUnionId()); + } + + cnd.and("t1.projectId", "=", pageForm.getProjectId()); + cnd.andEX("t2.selectOptionId", "=", pageForm.getWelfareOptionId()); + if (StrUtil.isNotBlank(pageForm.getUserName())) { + cnd.where().andLike("t4.username", pageForm.getUserName()); + } + if (StrUtil.isNotBlank(pageForm.getLoginName())) { + cnd.where().andLike("t4.loginname", pageForm.getLoginName()); + } + cnd.andEX("t1.welfareUnitId", "in", pageForm.getUnitIds()); + cnd.andEX("t1.welfareUnionId", "=", pageForm.getUnionId()); + if (pageForm.getIsSelect() != null) { + cnd.and("t2.id", pageForm.getIsSelect() ? "IS NOT" : "IS", null); + } + + List selectedUserIds = Arrays.stream(userIds == null ? new String[0] : userIds) + .filter(StrUtil::isNotBlank) + .distinct() + .toList(); + cnd.andEX("t1.userId", "in", selectedUserIds.toArray(new String[0])); + sql.setCondition(cnd); + return listMap(sql).stream() + .map(map -> map.getString("userId")) + .filter(StrUtil::isNotBlank) + .distinct() + .toList(); + } + @Override public void exportXlsx(WelfareSelectionSituationPageForm pageForm, HttpServletResponse response) { Sql sql = Sqls.create(""" @@ -124,6 +236,7 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl t2.selectUserId THEN CONCAT(t5.username, '代选') END) AS remark, t4.username AS userName, t4.loginname AS loginName, t4.sex @@ -132,6 +245,7 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl>> listMap = safeList.stream() - .collect(Collectors.groupingBy(n -> (String) n.get("welfareUnionName"))); + .collect(Collectors.groupingBy(n -> StrUtil.blankToDefault((String) n.get("welfareUnionName"), "未分配工会"))); // 构建 Excel 列 List entities = new ArrayList<>(); @@ -268,10 +384,8 @@ public class WelfareSelectionSituationServiceImpl extends BaseServiceImpl { entities.add(new ExcelExportEntity(option.getOptionName(), option.getOptionName(), 20)); }); - ExcelExportEntity userSignEntity = new ExcelExportEntity("签字", "userSign", 20); - userSignEntity.setType(2); - userSignEntity.setExportImageType(2); - entities.add(userSignEntity); + // 签字功能已取消,保留空白文本列供线下签字,不能按图片类型导出。 + entities.add(new ExcelExportEntity("签字", "userSign", 20)); // 导出 Workbook workbook = new HSSFWorkbook(); diff --git a/src/main/java/com/budwk/app/zhgh/welfare/service/impl/WelfareStatisticsServiceImpl.java b/src/main/java/com/budwk/app/zhgh/welfare/service/impl/WelfareStatisticsServiceImpl.java index 476f5a8..aeb22b4 100644 --- a/src/main/java/com/budwk/app/zhgh/welfare/service/impl/WelfareStatisticsServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/welfare/service/impl/WelfareStatisticsServiceImpl.java @@ -387,6 +387,8 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl wpus.selectUserId THEN CONCAT(selectOperator.username, '代选') END) AS remark FROM welfare_project_user_selection wpus LEFT JOIN welfare_list wl ON wl.userId = wpus.selectUserId AND wl.projectId = wpus.welfareId LEFT JOIN sys_user u ON u.id = wpus.selectUserId LEFT JOIN welfare_project_subject_option wpso ON wpso.id = wpus.selectOptionId + LEFT JOIN sys_user selectOperator ON selectOperator.id = wpus.createdBy WHERE wpus.welfareId = @projectId GROUP BY @@ -498,11 +502,15 @@ public class WelfareStatisticsServiceImpl extends BaseServiceImpl v.getString("welfareUnionId").equals(union.getString("id"))).count(); + long teacherSum = welfareSelectionList.stream() + .filter(v -> StrUtil.isNotBlank(v.getString("welfareUnionId")) && v.getString("welfareUnionId").equals(union.getString("id"))) + .count(); union.put("teacherSum", teacherSum); // 已选人数 - long selectedNum = welfareSelectionList.stream().filter(v -> v.getString("welfareUnionId").equals(union.getString("id")) && v.getBoolean("has_selected")).count(); + long selectedNum = welfareSelectionList.stream() + .filter(v -> StrUtil.isNotBlank(v.getString("welfareUnionId")) && v.getString("welfareUnionId").equals(union.getString("id")) && v.getBoolean("has_selected")) + .count(); union.put("selectedNum", selectedNum); // 未选人数 diff --git a/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html b/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html index f79f32f..16e86de 100644 --- a/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html +++ b/src/main/resources/views/platform/zhgh/welfare/selectionSituation/index.html @@ -66,11 +66,9 @@ layout("/layouts/platform.html"){ - - 全部 - 已选择 - 未选择 - + + 一键提醒 + 导出选择情况表 @@ -79,17 +77,25 @@ layout("/layouts/platform.html"){ 导出领取表 + + + 全部 + 已选择 + 未选择 + + + + + + + + + + + + 取消 + 确认发送 + + @@ -150,7 +169,12 @@ layout("/layouts/platform.html"){ { prop: "selectedOptions", label: "所选福利", sortable: true }, { prop: "mobile", label: "联系电话", sortable: true } ], - optionSelectVisible: false + optionSelectVisible: false, + selectedRows: [], + reminderVisible: false, + reminderContent: "", + reminderCount: 0, + reminderSending: false } }, computed: { @@ -204,6 +228,75 @@ layout("/layouts/platform.html"){ this.$downLoad("/platform/welfare/selection/situation/receiveXlsx", { pageForm: JSON.stringify(this.pageForm) }) }, + // 记录当前表格勾选人员;未勾选时提醒当前筛选范围内的全部人员。 + handleSelectionChange(rows) { + this.$set(this, "selectedRows", rows) + }, + + // 构建提醒请求参数,数组参数使用表单格式,保证后端可以绑定为 String[]。 + buildReminderRequest() { + const params = ["pageForm=" + encodeURIComponent(JSON.stringify(this.pageForm))] + this.selectedRows.map((row) => row.userId).forEach((userId) => { + params.push("userIds=" + encodeURIComponent(userId)) + }) + return params.join("&") + }, + + // 发送前由后端按当前筛选条件计算实际接收人数。 + async openReminder() { + if (!this.pageForm.projectId) { + this.$message.warning("请先选择福利项目") + return + } + const response = await this.$axios.post( + "/platform/welfare/selection/situation/previewReminder", + this.buildReminderRequest(), + { headers: { "Content-Type": "application/x-www-form-urlencoded" } } + ) + if (response.code !== 0) { + this.$message.error(response.msg) + return + } + if (response.data === 0) { + this.$message.warning("当前筛选条件下没有可提醒的人员") + return + } + this.$set(this, "reminderCount", response.data) + this.$set(this, "reminderContent", "福利选择提醒:请及时完成本次福利选择。") + this.$set(this, "reminderVisible", true) + }, + + // 后端会在发送前再次计算接收人,确保发送范围符合当前权限和筛选条件。 + async sendReminder() { + if (!this.reminderContent || !this.reminderContent.trim()) { + this.$message.warning("请输入提醒内容") + return + } + this.$set(this, "reminderSending", true) + const request = this.buildReminderRequest() + "&content=" + encodeURIComponent(this.reminderContent.trim()) + try { + const response = await this.$axios.post( + "/platform/welfare/selection/situation/sendReminder", + request, + { headers: { "Content-Type": "application/x-www-form-urlencoded" } } + ) + if (response.code === 0) { + this.$message.success(response.msg) + this.closeReminder() + } else { + this.$message.error(response.msg) + } + } finally { + this.$set(this, "reminderSending", false) + } + }, + + // 关闭提醒弹窗并清空输入内容,避免下次发送误用上次的消息。 + closeReminder() { + this.$set(this, "reminderVisible", false) + this.$set(this, "reminderContent", "") + }, + // 管理员待选 proxySelect(row) { this.optionSelectVisible = true