diff --git a/src/main/java/com/budwk/app/base/sms/SmsService.java b/src/main/java/com/budwk/app/base/sms/SmsService.java index df7cd4da..e01131e1 100644 --- a/src/main/java/com/budwk/app/base/sms/SmsService.java +++ b/src/main/java/com/budwk/app/base/sms/SmsService.java @@ -1,39 +1,56 @@ package com.budwk.app.base.sms; +import com.budwk.app.base.sms.model.MsgPlatformResponse; +import com.budwk.app.base.sms.model.MsgPlatformSmsRequest; +import com.budwk.app.base.sms.model.MsgPlatformWechatRequest; + import java.util.List; public interface SmsService { /** - * 单发 - * @param loginName 工号 - * @param content 内容 + * 发送短信消息。 + * + * @param request 短信请求参数,需传接收人、发送人、模板ID或消息内容等字段 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} */ - void send(String loginName, String content); + MsgPlatformResponse sendSms(MsgPlatformSmsRequest request); /** - * 单发 - * @param loginName 工号 - * @param title 标题 - * @param content 内容 + * 通过工号发送短信文本消息的便捷方法。 + * controller 只需要传接收人工号,以及短信内容即可。 + * + * @param account 接收人工号 + * @param content 短信内容 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} */ - void send(String loginName, String title, String content); + MsgPlatformResponse sendSmsByAccount(String account, String content); /** - * 单发 - * @param loginName 工号 - * @param title 标题 - * @param content 内容 - * @param link 链接 + * 通过手机号发送短信文本消息的便捷方法。 + * controller 只需要传接收人手机号,以及短信内容即可。 + * + * @param mobile 接收人手机号 + * @param content 短信内容 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} */ - void send(String loginName, String title, String content, String link); + MsgPlatformResponse sendSmsByMobile(String mobile, String content); /** - * 群发 多人接收内容相同时使用该方法 - * @param loginNames 工号 - * @param title 标题 - * @param content 内容 - * @param link 链接 + * 发送微信消息。 + * + * @param request 微信请求参数,需传接收人、发送人、消息类型、模板ID或消息内容等字段 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} */ - void massSend(List loginNames, String title, String content, String link); + MsgPlatformResponse sendWechat(MsgPlatformWechatRequest request); + + /** + * 发送微信文本消息的便捷方法。 + * controller 只需要传接收人工号或手机号,以及微信文本内容即可。 + * + * @param accountOrMobile 接收人工号或手机号 + * @param content 微信文本内容 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} + */ + MsgPlatformResponse sendWechatTextByAccountOrMobile(String accountOrMobile, String content); } diff --git a/src/main/java/com/budwk/app/base/sms/impl/SmsServiceImpl.java b/src/main/java/com/budwk/app/base/sms/impl/SmsServiceImpl.java new file mode 100644 index 00000000..184a58de --- /dev/null +++ b/src/main/java/com/budwk/app/base/sms/impl/SmsServiceImpl.java @@ -0,0 +1,544 @@ +package com.budwk.app.base.sms.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.budwk.app.base.exception.BaseException; +import com.budwk.app.base.sms.SmsService; +import com.budwk.app.base.sms.model.*; +import com.budwk.app.web.commons.base.Globals; +import lombok.extern.slf4j.Slf4j; +import org.nutz.integration.jedis.RedisService; +import org.nutz.ioc.impl.PropertiesProxy; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.json.Json; +import org.nutz.lang.Lang; +import org.nutz.lang.Strings; +import org.nutz.lang.util.NutMap; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @ClassName SmsJshvcServiceImpl + * @Author JyuHsin + * @Date 2025/11/26 19:32 + * @Version 1.0 + * @Description TODO + */ +@Slf4j +@IocBean +public class SmsServiceImpl implements SmsService { + private static final Log log = Logs.get(); + private static final String SMS_API_PATH = "/tp_mp/api/SmsService/saveSmsInfo"; + private static final String WECHAT_API_PATH = "/tp_mp/api/WechatService/saveWechatInfo"; + private static final int MAX_RECEIVER_COUNT = 500; + + @Inject + private PropertiesProxy conf; + + /** + * 发送短信消息。 + * + * @param request 短信请求参数,需传接收人、发送人、模板ID或消息内容等字段 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} + */ + @Override + public MsgPlatformResponse sendSms(MsgPlatformSmsRequest request) { + validateCommonRequest(request); + JSONObject payload = buildCommonPayload(request, true); + return doPost(SMS_API_PATH, payload); + } + + /** + * 通过工号发送短信文本消息的便捷方法。 + * 该方法适合 controller 里只传“工号 + 内容”的简单场景,发送人信息从配置中读取。 + * + * @param account 接收人工号 + * @param content 短信内容 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} + */ + @Override + public MsgPlatformResponse sendSmsByAccount(String account, String content) { + MsgPlatformSmsRequest request = new MsgPlatformSmsRequest(); + request.setInfo(content); + request.setRecipients(buildAccountRecipients(account)); + fillDefaultSenderInfo(request); + return sendSms(request); + } + + /** + * 通过手机号发送短信文本消息的便捷方法。 + * 该方法适合 controller 里只传“手机号 + 内容”的简单场景,发送人信息从配置中读取。 + * + * @param mobile 接收人手机号 + * @param content 短信内容 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} + */ + @Override + public MsgPlatformResponse sendSmsByMobile(String mobile, String content) { + MsgPlatformSmsRequest request = new MsgPlatformSmsRequest(); + request.setInfo(content); + request.setRecipients(buildMobileRecipients(mobile)); + request.setCustomVar(Map.of("1","","2","","3",content)); + fillDefaultSenderInfo(request); + return sendSms(request); + } + + /** + * 发送微信消息。 + * + * @param request 微信请求参数,需传接收人、发送人、消息类型、模板ID或消息内容等字段 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} + */ + @Override + public MsgPlatformResponse sendWechat(MsgPlatformWechatRequest request) { + validateCommonRequest(request); + if (Strings.isBlank(request.getWechatType())) { + request.setWechatType("text"); + } + if ("news".equalsIgnoreCase(request.getWechatType()) && Strings.isBlank(request.getInfo())) { + if (request.getNewsItems() == null || request.getNewsItems().isEmpty()) { + throw new BaseException("微信图文消息必须传 info 或 newsItems"); + } + request.setInfo(JSONUtil.toJsonStr(request.getNewsItems())); + } + JSONObject payload = buildCommonPayload(request, false); + payload.set("wechat_type", request.getWechatType()); + return doPost(WECHAT_API_PATH, payload); + } + + /** + * 发送微信文本消息的便捷方法。 + * 该方法适合 controller 里只传“工号/手机号 + 内容”的简单场景,发送人信息从配置中读取。 + * + * @param accountOrMobile 接收人工号或手机号 + * @param content 微信文本内容 + * @return 通讯平台返回结果,返回类型为 {@link MsgPlatformResponse} + */ + @Override + public MsgPlatformResponse sendWechatTextByAccountOrMobile(String accountOrMobile, String content) { + MsgPlatformWechatRequest request = new MsgPlatformWechatRequest(); + request.setInfo(content); + request.setWechatType("text"); + request.setRecipients(buildWechatRecipients(accountOrMobile)); + fillDefaultSenderInfo(request); + return sendWechat(request); + } + + /** + * 组装平台公共请求体。 + * + * @param request 平台请求参数 + * @param includeMobile 是否在 person_info 第 5 段写入手机号 + * @return 返回发送给第三方平台的 JSON 对象 + */ + private JSONObject buildCommonPayload(MsgPlatformBaseRequest request, boolean includeMobile) { + JSONObject payload = new JSONObject(); + payload.set("tp_name", getRequiredConfig("msg.platform.tp-name", "请先配置 msg.platform.tp-name")); + payload.set("secret_key", getSecretKey()); + payload.set("person_info", resolvePersonInfo(request, includeMobile)); + payload.set("template_id", Strings.sNull(request.getTemplateId())); + payload.set("info", Strings.sNull(request.getInfo())); + payload.set("send_priority", Strings.isBlank(request.getSendPriority()) ? "3" : request.getSendPriority()); + payload.set("send_user_id", Strings.sNull(request.getSendUserId())); + payload.set("send_user_name", Strings.sNull(request.getSendUserName())); + payload.set("send_time", Strings.sNull(request.getSendTime())); + payload.set("send_unit_id", Strings.sNull(request.getSendUnitId())); + payload.set("send_unit_name", Strings.sNull(request.getSendUnitName())); + payload.set("send_user_sign", Strings.sNull(request.getSendUserSign())); + payload.set("receipt_id", Strings.isBlank(request.getReceiptId()) ? "0" : request.getReceiptId()); + if (request.getCustomVar() != null && !request.getCustomVar().isEmpty()) { + payload.set("custom_var", JSONUtil.toJsonStr(request.getCustomVar())); + } + return payload; + } + + /** + * 校验公共请求参数,避免 controller 传错参数后才到第三方接口报错。 + * + * @param request 平台请求参数 + */ + private void validateCommonRequest(MsgPlatformBaseRequest request) { + if (request == null) { + throw new BaseException("请求参数不能为空"); + } + if (Strings.isBlank(request.getPersonInfo()) && (request.getRecipients() == null || request.getRecipients().isEmpty())) { + throw new BaseException("personInfo 和 recipients 不能同时为空"); + } + if (Strings.isBlank(request.getTemplateId()) && Strings.isBlank(request.getInfo())) { + throw new BaseException("templateId 和 info 至少需要传一个"); + } + if (Strings.isBlank(request.getSendUserId())) { + throw new BaseException("sendUserId 不能为空"); + } + if (Strings.isBlank(request.getSendUserName())) { + throw new BaseException("sendUserName 不能为空"); + } + if (Strings.isBlank(request.getSendPriority())) { + request.setSendPriority("3"); + } + if ("4".equals(request.getSendPriority()) && Strings.isBlank(request.getSendTime())) { + throw new BaseException("sendPriority=4 时必须传 sendTime"); + } + validateReceiverCount(request.getPersonInfo(), request.getRecipients()); + } + + /** + * 解析 person_info。 + * 已传原始 personInfo 时直接使用;否则根据 recipients 自动拼接平台要求的格式。 + * + * @param request 平台请求参数 + * @param includeMobile 是否在第 5 段写入手机号 + * @return 平台要求的 person_info 字符串 + */ + private String resolvePersonInfo(MsgPlatformBaseRequest request, boolean includeMobile) { + if (Strings.isNotBlank(request.getPersonInfo())) { + return request.getPersonInfo(); + } + List recipients = request.getRecipients(); + if (recipients == null || recipients.isEmpty()) { + throw new BaseException("接收人列表不能为空"); + } + return recipients.stream().map(recipient -> buildReceiverLine(recipient, includeMobile)).collect(Collectors.joining("^@^")); + } + + /** + * 将单个接收人转成平台要求的分隔符格式。 + * + * @param recipient 接收人对象 + * @param includeMobile 是否在最后一段补手机号 + * @return 单个接收人的 person_info 片段 + */ + private String buildReceiverLine(MsgPlatformRecipient recipient, boolean includeMobile) { + if (recipient == null) { + throw new BaseException("接收人信息中存在空对象"); + } + List segments = new ArrayList<>(5); + segments.add(Strings.sNull(recipient.getName())); + segments.add(Strings.sNull(recipient.getAccount())); + segments.add(Strings.sNull(recipient.getUnitId())); + segments.add(Strings.sNull(recipient.getUnitName())); + if (includeMobile) { + segments.add(resolveSmsMobile(recipient)); + } else { + segments.add(""); + } + return String.join("|", segments); + } + + /** + * 解析短信接收人手机号。 + * 如果 controller 传的是手机号,则校验手机号格式; + * 如果传的是工号,则允许手机号为空,按“姓名|工号|部门ID|部门名称|”格式发送。 + * + * @param recipient 接收人信息 + * @return 短信 person_info 第 5 段内容 + */ + private String resolveSmsMobile(MsgPlatformRecipient recipient) { + String mobile = recipient.getMobile(); + if (Strings.isBlank(mobile)) { + if (Strings.isBlank(recipient.getAccount())) { + throw new BaseException("短信接收人手机号和工号不能同时为空"); + } + return ""; + } + if (!mobile.matches("^\\d{11}$")) { + throw new BaseException("短信接收人手机号必须为11位数字"); + } + return mobile; + } + + /** + * 校验接收人数量,避免超过第三方平台单次上限。 + * + * @param personInfo 原始 person_info + * @param recipients 接收人列表 + */ + private void validateReceiverCount(String personInfo, List recipients) { + int receiverCount; + if (Strings.isNotBlank(personInfo)) { + receiverCount = personInfo.split("\\^@\\^", -1).length; + } else { + receiverCount = recipients == null ? 0 : recipients.size(); + } + if (receiverCount > MAX_RECEIVER_COUNT) { + throw new BaseException("单次发送人数不能超过" + MAX_RECEIVER_COUNT + "人"); + } + } + + /** + * 构建工号发送专用接收人列表。 + * + * @param account controller 传入的工号 + * @return 仅包含一个接收人的列表 + */ + private List buildAccountRecipients(String account) { + if (Strings.isBlank(account)) { + throw new BaseException("接收人工号不能为空"); + } + MsgPlatformRecipient recipient = new MsgPlatformRecipient(); + recipient.setAccount(account); + List recipients = new ArrayList<>(1); + recipients.add(recipient); + return recipients; + } + + /** + * 构建手机号发送专用接收人列表。 + * + * @param mobile controller 传入的手机号 + * @return 仅包含一个接收人的列表 + */ + private List buildMobileRecipients(String mobile) { + if (Strings.isBlank(mobile)) { + throw new BaseException("接收人手机号不能为空"); + } + if (!mobile.matches("^\\d{11}$")) { + throw new BaseException("接收人手机号必须为11位数字"); + } + MsgPlatformRecipient recipient = new MsgPlatformRecipient(); + recipient.setMobile(mobile); + List recipients = new ArrayList<>(1); + recipients.add(recipient); + return recipients; + } + + /** + * 构建微信便捷发送专用接收人列表。 + * + * @param accountOrMobile controller 传入的企业微信成员ID、工号或手机号 + * @return 仅包含一个接收人的列表 + */ + private List buildWechatRecipients(String accountOrMobile) { + if (Strings.isBlank(accountOrMobile)) { + throw new BaseException("微信接收人工号或手机号不能为空"); + } + MsgPlatformRecipient recipient = new MsgPlatformRecipient(); + recipient.setAccount(accountOrMobile); + List recipients = new ArrayList<>(1); + recipients.add(recipient); + return recipients; + } + + /** + * 为便捷发送方法补默认发送人信息。 + * 这些字段从配置中读取,避免 controller 每次重复传固定参数。 + * + * @param request 平台请求参数 + */ + private void fillDefaultSenderInfo(MsgPlatformBaseRequest request) { + request.setSendUserId(getRequiredConfig("msg.platform.default-send-user-id", "请先配置 msg.platform.default-send-user-id")); + request.setSendUserName(getRequiredConfig("msg.platform.default-send-user-name", "请先配置 msg.platform.default-send-user-name")); + request.setSendUnitId(conf.get("msg.platform.default-send-unit-id", "")); + request.setSendUnitName(conf.get("msg.platform.default-send-unit-name", "")); + request.setSendUserSign(conf.get("msg.platform.default-send-user-sign", "")); + request.setTemplateId(conf.get("msg.platform.default-template-id", "")); + } + + /** + * 调用地大通讯平台 REST 接口。 + * + * @param apiPath 具体接口路径 + * @param payload 发送报文 + * @return 平台响应结果 + */ + private MsgPlatformResponse doPost(String apiPath, JSONObject payload) { + + if (!Globals.sso){ + log.error("【log】开发模式不允许发送消息通知"); + MsgPlatformResponse response = new MsgPlatformResponse(); + response.setHttpStatus(200); + response.setCode(0); + response.setResult(true); + response.setMsg("开发模式不允许发送消息通知"); + response.setRawBody(JSONUtil.toJsonStr(payload)); + return response; + } + if (!Globals.MyConfig.getBoolean("AppSms", false)) { + log.error("【log】短信配置未开启"); + MsgPlatformResponse response = new MsgPlatformResponse(); + response.setHttpStatus(200); + response.setCode(0); + response.setResult(true); + response.setMsg("短信配置未开启"); + response.setRawBody(JSONUtil.toJsonStr(payload)); + return response; + } + if (!conf.getBoolean("msg.platform.enabled", false)) { + MsgPlatformResponse response = new MsgPlatformResponse(); + response.setHttpStatus(200); + response.setCode(0); + response.setResult(true); + response.setMsg("通讯平台未启用,已跳过实际发送"); + response.setRawBody(JSONUtil.toJsonStr(payload)); + return response; + } + HttpURLConnection connection = null; + try { + String requestBody = JSONUtil.toJsonStr(payload); + log.error("发送内容:=================================================="+requestBody); + String requestUrl = buildRequestUrl(apiPath); + log.warnf("发送地址:=================================================="+requestUrl); + connection = (HttpURLConnection) new URL(requestUrl).openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setDoInput(true); + connection.setUseCaches(false); + connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); + connection.setConnectTimeout(conf.getInt("msg.platform.connect-timeout", 5000)); + connection.setReadTimeout(conf.getInt("msg.platform.read-timeout", 10000)); + try (OutputStream outputStream = connection.getOutputStream()) { + outputStream.write(requestBody.getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + } + int httpStatus = connection.getResponseCode(); + String responseBody = readResponseBody(connection, httpStatus); + MsgPlatformResponse response = parseResponse(httpStatus, responseBody); + log.warnf("接口调用返回结果=============================="+responseBody); + if (response.getCode() != null && response.getCode() != 0) { + log.warnf("通讯平台调用失败, httpStatus=%s, body=%s", httpStatus, responseBody); + } + return response; + } catch (IOException e) { + log.error("调用通讯平台接口异常", e); + throw new BaseException("调用通讯平台接口失败: " + e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + /** + * 构建完整请求地址,支持配置带或不带结尾斜杠的 base-url。 + * + * @param apiPath 接口路径 + * @return 完整请求地址 + */ + private String buildRequestUrl(String apiPath) { + String baseUrl = getRequiredConfig("msg.platform.base-url", "请先配置 msg.platform.base-url"); + if (baseUrl.endsWith("/")) { + return baseUrl.substring(0, baseUrl.length() - 1) + apiPath; + } + return baseUrl + apiPath; + } + + /** + * 读取第三方接口响应报文。 + * + * @param connection HttpURLConnection + * @param httpStatus HTTP 状态码 + * @return 原始响应字符串 + * @throws IOException 读取流失败时抛出 + */ + private String readResponseBody(HttpURLConnection connection, int httpStatus) throws IOException { + InputStream inputStream = httpStatus >= 200 && httpStatus < 400 ? connection.getInputStream() : connection.getErrorStream(); + if (inputStream == null) { + return ""; + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + StringBuilder builder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + builder.append(line); + } + return builder.toString(); + } + } + + /** + * 解析平台响应体。 + * + * @param httpStatus HTTP 状态码 + * @param responseBody 原始响应字符串 + * @return 解析后的平台响应对象 + */ + private MsgPlatformResponse parseResponse(int httpStatus, String responseBody) { + MsgPlatformResponse response = new MsgPlatformResponse(); + response.setHttpStatus(httpStatus); + response.setRawBody(responseBody); + if (Strings.isBlank(responseBody)) { + response.setCode(httpStatus); + response.setResult(false); + response.setMsg("通讯平台未返回响应内容"); + return response; + } + if (!JSONUtil.isTypeJSON(responseBody)) { + response.setCode(httpStatus); + response.setResult(false); + response.setMsg("通讯平台返回内容不是合法JSON"); + return response; + } + JSONObject jsonObject = JSONUtil.parseObj(responseBody); + response.setCode(jsonObject.getInt("code")); + response.setMsg(jsonObject.getStr("msg")); + response.setResult(jsonObject.getBool("result")); + response.setMsgId(jsonObject.getStr("msg_id")); + response.setRemainingQuota(jsonObject.getInt("remaining_quota")); + response.setDescription(jsonObject.getStr("description")); + return response; + } + + /** + * 获取平台密钥。 + * 优先读取已加密密钥;未配置时再根据原始密钥按“Base64 后 SHA”规则自动计算。 + * + * @return 平台要求的 secret_key + */ + private String getSecretKey() { + String encryptedSecretKey = conf.get("msg.platform.encrypted-secret-key", ""); + if (Strings.isNotBlank(encryptedSecretKey)) { + return encryptedSecretKey; + } + String rawSecret = getRequiredConfig("msg.platform.raw-secret-key", "请先配置 msg.platform.raw-secret-key 或 msg.platform.encrypted-secret-key"); + return shaHex(rawSecret); + } + + /** + * 读取必填配置项。 + * + * @param key 配置 key + * @param errorMessage 配置缺失时抛出的异常信息 + * @return 配置值 + */ + private String getRequiredConfig(String key, String errorMessage) { + String value = conf.get(key, ""); + if (Strings.isBlank(value)) { + throw new BaseException(errorMessage); + } + return value; + } + + /** + * 生成 SHA 十六进制摘要。 + * + * @param content 待加密内容 + * @return SHA 十六进制字符串 + */ + private String shaHex(String content) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA"); + messageDigest.update(content.getBytes()); + byte[] hash = messageDigest.digest(); + return Base64.getEncoder().encodeToString(hash); + } catch (Exception e) { + throw new BaseException("生成通讯平台 secret_key 失败: " + e.getMessage()); + } + } + +} diff --git a/src/main/java/com/budwk/app/base/sms/impl/jshvc/SmsJshvcServiceImpl.java b/src/main/java/com/budwk/app/base/sms/impl/jshvc/SmsJshvcServiceImpl.java deleted file mode 100644 index 1b0408b5..00000000 --- a/src/main/java/com/budwk/app/base/sms/impl/jshvc/SmsJshvcServiceImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.budwk.app.base.sms.impl.jshvc; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.digest.DigestUtil; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; -import com.budwk.app.base.sms.SmsService; -import lombok.extern.slf4j.Slf4j; -import org.nutz.integration.jedis.RedisService; -import org.nutz.ioc.loader.annotation.Inject; -import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.json.Json; -import org.nutz.lang.Lang; -import org.nutz.lang.util.NutMap; - -import java.util.List; -import java.util.Map; - -/** - * @ClassName SmsJshvcServiceImpl - * @Author JyuHsin - * @Date 2025/11/26 19:32 - * @Version 1.0 - * @Description TODO - */ -@Slf4j -@IocBean -public class SmsJshvcServiceImpl implements SmsService { - - private static final String APPID = ""; - private static final String APP_SECRET = ""; - private static final String TOKEN_URL = ""; - private static final String MSG_URL = ""; - private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "msg:token:"; - - @Inject - private RedisService redisService; - - @Override - public void send(String loginName, String content) { -// send(loginName, "智慧工会", content); - } - - @Override - public void send(String loginName, String title, String content) { - send(loginName, "智慧工会", content, null); - } - - @Override - public void send(String loginName, String title, String content, String link) { -// Map paramMap = Map.of("userId", loginName); -// doSend(title, content, List.of(paramMap), link); - } - - @Override - public void massSend(List loginNames, String title, String content, String link) { -// List> receivers = loginNames.stream() -// .map(name -> Map.of("userId", name)) -// .toList(); -// doSend(title, content, receivers, link); - } - - /** - * sign: 请求签名:(accessToken + 第一个receivers 的userID )的32位小写的MD5加密值,其中如果相应部分没有则忽略 - * msgType: 0: 普通消息(默认) 1: 必读消息 2: 验证码(为验证码时消息一定不入收件箱) - * expiredTime: 当msgType 为1必读消息 ,该字段为必填字段 格式:yyyy-MM-dd HH:mm:ss - * sendType: 1.只发送PC门户 2.只发送移动校园 3.邮件 4.短信 5.微信企业号 6.钉钉企业内部应用工作通知 7.微信服务号 8.welink - * wxSendType: 当发送类型为5时此字段才会生效:text(文本消息)、textcard(文本卡片)、nes(图文卡片,如果图文,则qyWeChatImgUrl必填)、button(按钮卡片详见示例),不传默认为text - * receiverType: 1:用户 2:用户组 3:部门,默认为1 - * - * @param title - * @param content - * @param receivers - */ - private void doSend(String title, String content, List> receivers, String link) { - - if (Lang.isEmpty(receivers)) { - log.error("send fail by receivers is null"); - return; - } - - // 获取token - String accessToken = buildAccessToken(); - // 获取第一个接收人的userID - String firstUserId = receivers.get(0).get("userId"); - // md5小写加密生成签名 - String sign = DigestUtil.md5Hex(accessToken + firstUserId); - - NutMap paramsMap = new NutMap(); - paramsMap.put("sign", sign); - paramsMap.put("msgType", "0"); - paramsMap.put("subject", title); - paramsMap.put("content", content); - paramsMap.put("sendType", "5"); - paramsMap.put("receivers", receivers); - if (StrUtil.isBlank(link)) { - paramsMap.put("wxSendType", "text"); - } else { - paramsMap.put("wxSendType", "textcard"); - paramsMap.put("mobileUrl", link); - // 这是图文卡片,后面用到再对接吧 - // paramsMap.put("qyWeChatImgUrl", ""); - } - - log.debug("send params: {}", Json.toJson(paramsMap)); - - HttpRequest request = HttpRequest.post(MSG_URL) - .header("appId", APPID) - .header("accessToken", accessToken) - .body(Json.toJson(paramsMap)); - - try { - HttpResponse response = request.execute(); - log.info("send response body: {}", response.body()); - - NutMap bodyMap = Json.fromJson(NutMap.class, response.body()); - if (bodyMap.getInt("status") == 200) { - log.info("send success code: {}, msg: {}", bodyMap.getInt("status"), bodyMap.getString("msg")); - } else { - log.error("send error code: {}, msg: {}", bodyMap.getInt("status"), bodyMap.getString("msg")); - } - } catch (Exception e) { - log.error("Failed to send: {}", e.getMessage(), e); - } - } - - private String buildAccessToken() { - String token = redisService.get(REDIS_KEY_MSG_ACCESS_TOKEN); - if (StrUtil.isNotBlank(token)) { - return token; - } - HttpRequest request = HttpRequest.get(TOKEN_URL); - request.header("appId", APPID); - request.header("appSecret", APP_SECRET); - - HttpResponse response = request.execute(); - - log.info("accessToken response: {}", Json.toJson(response.body())); - - NutMap bodyMap = Json.fromJson(NutMap.class, response.body()); - if (bodyMap.getInt("errcode") == 0) { - log.info("accessToken status: {}", bodyMap.getInt("errorcode")); - redisService.setex(REDIS_KEY_MSG_ACCESS_TOKEN, 60 * 120, bodyMap.getString("data")); - return bodyMap.getString("data"); - } else { - log.error("accessToken status: {}", bodyMap.getInt("errorcode")); - } - return ""; - } -} diff --git a/src/main/java/com/budwk/app/base/sms/model/MsgPlatformBaseRequest.java b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformBaseRequest.java new file mode 100644 index 00000000..d7ecb8da --- /dev/null +++ b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformBaseRequest.java @@ -0,0 +1,87 @@ +package com.budwk.app.base.sms.model; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 通讯平台公共请求参数。 + */ +@Data +public class MsgPlatformBaseRequest { + /** + * 接收人信息原始字符串。 + * 如果 controller 已经按平台要求拼好了 person_info,可以直接传该字段; + * 否则可只传 recipients,由 service 自动组装。 + */ + private String personInfo; + + /** + * 接收人列表。 + * 当 personInfo 为空时,service 会根据该列表自动拼接平台要求的 person_info 字符串。 + */ + private List recipients; + + /** + * 模板ID。 + * templateId 和 info 至少需要传一个,使用模板发送时优先传该字段。 + */ + private String templateId; + + /** + * 消息内容。 + * templateId 和 info 至少需要传一个;不使用模板时,需要传完整消息内容。 + */ + private String info; + + /** + * 发送优先级。 + * 3 表示立即发送,4 表示定时发送。 + */ + private String sendPriority = "3"; + + /** + * 发送人账号。 + */ + private String sendUserId; + + /** + * 发送人姓名。 + */ + private String sendUserName; + + /** + * 模板变量。 + * 使用模板发送时可传该字段,service 会自动转成平台要求的 JSON 字符串。 + */ + private Map customVar; + + /** + * 定时发送时间,格式必须为 yyyy-MM-dd HH:mm:ss。 + * 当 sendPriority=4 时该字段必填。 + */ + private String sendTime; + + /** + * 发送机构ID。 + */ + private String sendUnitId; + + /** + * 发送机构名称。 + */ + private String sendUnitName; + + /** + * 发送签名。 + * 旧通用模板或特定签名场景可传该字段。 + */ + private String sendUserSign; + + /** + * 回执ID。 + * 不需要回执时可保持默认值 0。 + */ + private String receiptId = "0"; +} diff --git a/src/main/java/com/budwk/app/base/sms/model/MsgPlatformNewsItem.java b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformNewsItem.java new file mode 100644 index 00000000..29d103af --- /dev/null +++ b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformNewsItem.java @@ -0,0 +1,29 @@ +package com.budwk.app.base.sms.model; + +import lombok.Data; + +/** + * 微信图文消息单条数据。 + */ +@Data +public class MsgPlatformNewsItem { + /** + * 图文标题。 + */ + private String title; + + /** + * 图文描述。 + */ + private String description; + + /** + * 点击跳转链接。 + */ + private String url; + + /** + * 图文封面图片链接。 + */ + private String picurl; +} diff --git a/src/main/java/com/budwk/app/base/sms/model/MsgPlatformRecipient.java b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformRecipient.java new file mode 100644 index 00000000..e12214b1 --- /dev/null +++ b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformRecipient.java @@ -0,0 +1,36 @@ +package com.budwk.app.base.sms.model; + +import lombok.Data; + +/** + * 通讯平台接收人信息。 + */ +@Data +public class MsgPlatformRecipient { + /** + * 接收人姓名,对应 person_info 中的第 1 段。 + */ + private String name; + + /** + * 接收人账号,对应 person_info 中的第 2 段。 + * 短信场景一般传学工号;微信场景可传企业微信成员ID、学工号或手机号。 + */ + private String account; + + /** + * 接收人部门ID,对应 person_info 中的第 3 段。 + */ + private String unitId; + + /** + * 接收人部门名称,对应 person_info 中的第 4 段。 + */ + private String unitName; + + /** + * 接收人手机号,对应短信 person_info 中的第 5 段。 + * 微信接口不会使用该字段,微信接口会自动补空串以保留分隔符格式。 + */ + private String mobile; +} diff --git a/src/main/java/com/budwk/app/base/sms/model/MsgPlatformResponse.java b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformResponse.java new file mode 100644 index 00000000..2a5f8e21 --- /dev/null +++ b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformResponse.java @@ -0,0 +1,49 @@ +package com.budwk.app.base.sms.model; + +import lombok.Data; + +/** + * 通讯平台响应结果。 + */ +@Data +public class MsgPlatformResponse { + /** + * HTTP 状态码,便于 controller 定位网络层问题。 + */ + private Integer httpStatus; + + /** + * 平台返回业务状态码,0 表示成功。 + */ + private Integer code; + + /** + * 平台返回消息提示。 + */ + private String msg; + + /** + * 平台返回业务是否成功。 + */ + private Boolean result; + + /** + * 发送成功后的消息ID。 + */ + private String msgId; + + /** + * 短信剩余额度,仅短信接口返回。 + */ + private Integer remainingQuota; + + /** + * 平台失败描述。 + */ + private String description; + + /** + * 平台原始响应报文,排查问题时可直接查看。 + */ + private String rawBody; +} diff --git a/src/main/java/com/budwk/app/base/sms/model/MsgPlatformSmsRequest.java b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformSmsRequest.java new file mode 100644 index 00000000..8f086daf --- /dev/null +++ b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformSmsRequest.java @@ -0,0 +1,7 @@ +package com.budwk.app.base.sms.model; + +/** + * 短信发送请求参数。 + */ +public class MsgPlatformSmsRequest extends MsgPlatformBaseRequest { +} diff --git a/src/main/java/com/budwk/app/base/sms/model/MsgPlatformWechatRequest.java b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformWechatRequest.java new file mode 100644 index 00000000..a6ccbff6 --- /dev/null +++ b/src/main/java/com/budwk/app/base/sms/model/MsgPlatformWechatRequest.java @@ -0,0 +1,25 @@ +package com.budwk.app.base.sms.model; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 微信发送请求参数。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class MsgPlatformWechatRequest extends MsgPlatformBaseRequest { + /** + * 微信消息类型。 + * 可传 text、news、image、file、video、voice,默认 text。 + */ + private String wechatType = "text"; + + /** + * 图文消息列表。 + * 当 wechatType=news 且未直接传 info 时,service 会将该列表转成平台要求的 JSON 数组字符串。 + */ + private List newsItems; +} diff --git a/src/main/java/com/budwk/app/flow/handler/FlowUnitPartySecretaryHandler.java b/src/main/java/com/budwk/app/flow/handler/FlowUnitPartySecretaryHandler.java index 9b71421c..7c2a3fdb 100644 --- a/src/main/java/com/budwk/app/flow/handler/FlowUnitPartySecretaryHandler.java +++ b/src/main/java/com/budwk/app/flow/handler/FlowUnitPartySecretaryHandler.java @@ -26,44 +26,45 @@ import java.util.List; */ public class FlowUnitPartySecretaryHandler implements AssignmentHandler { - @Override - public List assign(TaskModel model, Execution execution) { - String unitId = SecurityUtil.getUnitId(); + @Override + public List assign(TaskModel model, Execution execution) { + String unitId = SecurityUtil.getUnitId(); - SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class); - Sys_role role = sysRoleService.getByCode(RoleConstant.UNIT_PARTY_SECRETARY); + SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class); + Sys_role role = sysRoleService.getByCode(RoleConstant.UNIT_PARTY_SECRETARY); - View_user user = ServiceContext.find(Dao.class).fetch(View_user.class, Cnd.where(View_user::getId, "=", SecurityUtil.getUserId())); - List roles; - if(user.getUnionName().contains("行政")){ - List unitList = ServiceContext.find(Dao.class).query(Sys_unit.class, Cnd.where(Sys_unit::getUnionId, "=", user.getUnionId())); - List unitIds = unitList.stream().map(Sys_unit::getId).toList(); - roles = ServiceContext.find(Dao.class).query( - Sys_user_role.class, - Cnd.where(Sys_user_role::getRoleId, "=", role.getId()) - .and(Sys_user_role::getUnitId, "in", unitIds) - ); - }else{ - roles = ServiceContext.find(Dao.class).query( - Sys_user_role.class, - Cnd.where(Sys_user_role::getRoleId, "=", role.getId()) - .and(Sys_user_role::getUnitId, "=", unitId) - ); - } + View_user user = ServiceContext.find(Dao.class).fetch(View_user.class, Cnd.where(View_user::getId, "=", SecurityUtil.getUserId())); + List roles; - if (Lang.isEmpty(roles)) { - throw new RuntimeException("当前登录用户所在单位未设置单位党委书记,请联系校工会进行设置。"); - } - return roles.stream().map(Sys_user_role::getUserId).toList(); - } + roles = ServiceContext.find(Dao.class).query( + Sys_user_role.class, + Cnd.where(Sys_user_role::getRoleId, "=", role.getId()) + .and(Sys_user_role::getUnitId, "=", unitId) + ); - @Override - public String getMessage() { - return "获取当前登录用户所在单位党委书记,若是行政就找出行政分下的党委书记。因为行政只有一个党委书记"; - } + if (Lang.isEmpty(roles)) { + List unitList = ServiceContext.find(Dao.class).query(Sys_unit.class, Cnd.where(Sys_unit::getUnionId, "=", user.getUnionId())); + List unitIds = unitList.stream().map(Sys_unit::getId).toList(); + roles = ServiceContext.find(Dao.class).query( + Sys_user_role.class, + Cnd.where(Sys_user_role::getRoleId, "=", role.getId()) + .and(Sys_user_role::getUnitId, "in", unitIds) + ); + } - @Override - public int getOrder() { - return AssignmentHandler.super.getOrder(); - } + if (Lang.isEmpty(roles)) { + throw new RuntimeException("当前登录用户所在单位未设置单位党委书记,请联系校工会进行设置。"); + } + return roles.stream().map(Sys_user_role::getUserId).toList(); + } + + @Override + public String getMessage() { + return "获取当前登录用户所在单位党委书记"; + } + + @Override + public int getOrder() { + return AssignmentHandler.super.getOrder(); + } } diff --git a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java index 49b0fca3..1f862dcb 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java @@ -25,12 +25,15 @@ import com.budwk.app.sys.services.SysUserService; import com.budwk.app.sys.views.View_user; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig; import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain; import org.nutz.dao.Cnd; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.Lang; @@ -146,10 +149,20 @@ public class SysUnitController { @At @ApiOperation("按关键字查询单位下的人员") @Ok("json:{ignoreNull:true}") - public Result listUserSelect(@Valid String unitId, @Valid String keyWord) { + public Result listUserSelect(@Valid String unitId, @Valid String keyWord, @Valid String roleCode) { Sql sql = Sqls.create("select id,loginname,username,unitname from vw_user $condition"); Cnd cnd = Cnd.NEW(); - cnd.and(View_user::getUnitId, "=", unitId); + // 选择校领导角色时,人员范围来自提案配置中的校领导单位;其他角色仍限定当前单位。 + if ("UNIT_SCHOOL_LEADER".equals(roleCode)) { + ProposalConfig proposalConfig = sysUnitService.dao().fetch(ProposalConfig.class, Cnd.NEW()); + if (proposalConfig == null || Lang.isEmpty(proposalConfig.getSchoolLeaderUnitIds())) { + return Result.error("请在提案基础设置里配置校领导单位!"); + } + cnd.and(View_user::getUnitId, "in", proposalConfig.getSchoolLeaderUnitIds()); + } else { + cnd.and(View_user::getUnitId, "=", unitId); + } + cnd.and(View_user::getMember, "=", 1); SqlExpressionGroup seg = new SqlExpressionGroup(); seg.orLike(View_user::getLoginname, keyWord, true); seg.orLike(View_user::getUsername, keyWord, true); @@ -165,13 +178,14 @@ public class SysUnitController { @At @Ok("json") + @Aop(TransAop.READ_COMMITTED) public Result insertUnitUserRole(String userId, String roleCode, String unitId) { Sys_role role = sysRoleService.fetch(Cnd.where("code", "=", roleCode)); if (Lang.isEmpty(role)) { throw new BaseException("无法找到{}对应编码的角色", roleCode); } - sysRoleService.clear("sys_user_role", Cnd.where("roleId", "=", role.getId()).and("userId", "=", userId).and("unitId", "=", unitId)); - sysRoleService.insert("sys_user_role", Chain.make("roleId", role.getId()).add("userId", userId).add("unitId", unitId)); + sysRoleService.clear("sys_user_role", Cnd.where("roleId", "=", role.getId()).and("userId", "=", userId).and("unitId", "=", unitId).and("underTakeId", "=", unitId)); + sysRoleService.insert("sys_user_role", Chain.make("roleId", role.getId()).add("userId", userId).add("unitId", unitId).add("underTakeId", unitId)); sysRoleService.clearCache(); sysUserService.clearCache(); return Result.success(); @@ -179,12 +193,13 @@ public class SysUnitController { @At @Ok("json") + @Aop(TransAop.READ_COMMITTED) public Result deleteUnitUserRole(String userId, String roleCode, String unitId) { Sys_role role = sysRoleService.fetch(Cnd.where("code", "=", roleCode)); if (Lang.isEmpty(role)) { throw new BaseException("无法找到{}对应编码的角色", roleCode); } - sysRoleService.clear("sys_user_role", Cnd.where("roleId", "=", role.getId()).and("userId", "=", userId).and("unitId", "=", unitId)); + sysRoleService.clear("sys_user_role", Cnd.where("roleId", "=", role.getId()).and("userId", "=", userId).and("unitId", "=", unitId).and("underTakeId", "=", unitId)); sysRoleService.clearCache(); sysUserService.clearCache(); return Result.success(); diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysMsgServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysMsgServiceImpl.java index cf38b46c..0b0ac403 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysMsgServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysMsgServiceImpl.java @@ -6,6 +6,7 @@ import cn.hutool.http.HtmlUtil; import com.budwk.app.base.constant.RedisConstant; import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.base.sms.SmsService; +import com.budwk.app.base.sms.model.MsgPlatformWechatRequest; import com.budwk.app.base.utils.ManyAddOrRenewUtil; import com.budwk.app.sys.models.Sys_msg; import com.budwk.app.sys.models.Sys_msg_user; @@ -102,11 +103,12 @@ public class SysMsgServiceImpl extends BaseServiceImpl implements SysMs }); //发送学校平台消息 -// for (String loginName : loginNames) { -// smsService.send(loginName, sysMsg.getTitle(), sysMsg.getNote()); -// } ThreadUtil.execute(() -> { - //smsService.massSend(loginNames, sysMsg.getTitle(), HtmlUtil.cleanHtmlTag(StrUtil.blankToDefault(sysMsg.getNote(),"")), sysMsg.getUrl()); + for (String loginName : loginNames) { + smsService.sendSmsByAccount(loginName,sysMsg.getNote()); + smsService.sendWechatTextByAccountOrMobile(loginName,sysMsg.getNote()); + } + }); return sysMsg; diff --git a/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicScopeController.java b/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicScopeController.java index fb4fcd8b..76936836 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicScopeController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/basic/controller/ActivityBasicScopeController.java @@ -24,6 +24,8 @@ import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope; import com.budwk.app.zhgh.activity.basic.param.ActivityUserScopePageParam; import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService; import com.budwk.app.zhgh.activity.basic.template.UserTemp; +import com.budwk.app.zhgh.club.model.SysClub; +import com.budwk.app.zhgh.club.service.SysClubService; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; @@ -84,6 +86,9 @@ public class ActivityBasicScopeController { @Inject private RedisService redisService; + @Inject + private SysClubService sysClubService; + @At("") @Ok("beetl:platform/zhgh/activity/basic/userScope/index.html") @SaCheckPermission("activity.basic.scope") @@ -97,7 +102,6 @@ public class ActivityBasicScopeController { * @return {@link Result} */ @At - @SaCheckPermission("activity.basic") public Result pageData(@Param("data") ActivityUserScopePageParam pageForm) { Sql sql = Sqls.create(""" SELECT @@ -216,6 +220,8 @@ public class ActivityBasicScopeController { return Result.success(new NutMap() {{ addv("is_A06", StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name())); addv("is_H04", StpUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())); + addv("is_H02", StpUtil.hasRole(RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())); + addv("is_CLUB_PRESIDENT", StpUtil.hasRole(RoleConstant.CLUB_PRESIDENT.name())); addv("is_sysadmin", StpUtil.hasRole(RoleConstant.SYSADMIN.name())); addv("unionid", SecurityUtil.getUnionId()); }}); @@ -344,9 +350,30 @@ public class ActivityBasicScopeController { } try { - - if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())){ - cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId()); + if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { + boolean isBranchUnionChairman = AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name()); + boolean isClubPresident = AuthUtil.hasRole(RoleConstant.CLUB_PRESIDENT.name()); + if (isBranchUnionChairman && isClubPresident) { + // 同时具备分工会主席和社团会长身份时,未额外筛选前默认查看两类权限范围的并集。 + List manageClubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList(); + SqlExpressionGroup scopeGroup = new SqlExpressionGroup(); + scopeGroup.or("u.unionid", "=", SecurityUtil.getUnionId()); + if (!manageClubIdList.isEmpty()) { + scopeGroup.or("clubuser.clubid", "in", manageClubIdList); + } + cnd.and(scopeGroup); + } else if (isClubPresident) { + // 社团会长设置活动人员范围时,只允许查看自己可管理社团下的成员。 + List manageClubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList(); + if (manageClubIdList.isEmpty()) { + cnd.and("clubuser.clubid", "in", List.of("__EMPTY_CLUB_ID__")); + } else { + cnd.and("clubuser.clubid", "in", manageClubIdList); + } + } else { + // 分工会主席等分工会侧角色,继续只允许查看本分工会人员。 + cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId()); + } } String[] unionIds = getUnionIds(activityUserScopePageParam); if (Lang.isNotEmpty(unionIds)) { diff --git a/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureApplyUserController.java b/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureApplyUserController.java index c80cb5f0..6184bdb7 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureApplyUserController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureApplyUserController.java @@ -21,6 +21,7 @@ import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.json.Json; @@ -125,8 +126,17 @@ public class ActivityCultureApplyUserController { public Result queryTeammate(@Valid String keyword, Integer signUpMethod, String tissueId) { Sql sql = Sqls.create("select id userId,username userName,loginname loginName,sex,mobile,unitName from vw_user $condition limit 0,10"); Cnd cnd = Cnd.NEW(); - cnd.or(Cnd.likeEX("username", keyword)); - cnd.or(Cnd.likeEX("loginname", keyword)); + SqlExpressionGroup keywordGroup = new SqlExpressionGroup(); + keywordGroup.andLike("username", keyword); + keywordGroup.orLike("loginname", keyword); + cnd.and(keywordGroup); + if (StrUtil.isNotBlank(tissueId)) { + ActivityTissue tissue = dao.fetch(ActivityTissue.class, tissueId); + // 组队/分工会报名选人时,候选人必须落在活动新建时配置的参加人员范围内。 + if (ObjectUtil.isNotNull(tissue) && ObjectUtil.isNotNull(tissue.getGroupId())) { + cnd.and("id", "in", activityBasicScopeService.buildGroupUserIdSubSql(tissue.getGroupId())); + } + } if (signUpMethod == 3) { cnd.and("unionid", "=", SecurityUtil.getUnionId()); } diff --git a/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureAuditActivityController.java b/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureAuditActivityController.java index 805e0366..d27e639c 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureAuditActivityController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureAuditActivityController.java @@ -112,7 +112,7 @@ public class ActivityCultureAuditActivityController { cnd.andEX("tissue.unionId", "=", unionId); cnd.andEX("tissue.activity_type", "=", activity_type); if (approval) { - cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode())); + cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode())); } else { cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode()); } diff --git a/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureUserStatisticsController.java b/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureUserStatisticsController.java index 79cb27de..23ea1190 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureUserStatisticsController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/culture/controller/ActivityCultureUserStatisticsController.java @@ -161,6 +161,11 @@ public class ActivityCultureUserStatisticsController { cnd.and("tissue.signUpMethod", "in", List.of(1, 2, 3)); if (activity_type == 40002) { cnd.and("tissue.unionId", "=", SecurityUtil.getUnionId()); + } else if (activity_type == 40003) { + // 协会会长进入社团报名统计时,只允许查看自己管理社团下的活动名称。 + List myManageClub = sysClubService.getMyManageClub(); + List clubIdList = myManageClub.stream().map(SysClub::getId).collect(Collectors.toList()); + cnd.and("tissue.clubId", "in", clubIdList); } } // 只查询流程实例状态为20的数据(已完成状态) diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivityPrizeListController.java b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivityPrizeListController.java index feaa887f..8d7dbe52 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivityPrizeListController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivityPrizeListController.java @@ -169,34 +169,35 @@ public class ActivityPrizeListController { Map fourMap = NutMap.NEW(); Map fiveMap = NutMap.NEW(); + // 部分历史数据可能未配置竞赛类别,分组汇总时需要做空值保护,避免筛选时空指针 //甲组 - List oneList = allList.stream().filter(a -> a.getString("bszbName").equals("甲组")).collect(toList()); + List oneList = allList.stream().filter(a -> Objects.equals("甲组", a.getString("bszbName"))).collect(toList()); if (Lang.isNotEmpty(oneList)) { oneMap = oneList.stream().collect(Collectors.groupingBy(v -> v.getString("allName"))); } //乙组 - List twoList = allList.stream().filter(a -> a.getString("bszbName").equals("乙组")).collect(toList()); + List twoList = allList.stream().filter(a -> Objects.equals("乙组", a.getString("bszbName"))).collect(toList()); if (Lang.isNotEmpty(twoList)) { twoMap = twoList.stream().collect(Collectors.groupingBy(v -> v.getString("allName"))); } //丙组 - List threeList = allList.stream().filter(a -> a.getString("bszbName").equals("丙组")).collect(toList()); + List threeList = allList.stream().filter(a -> Objects.equals("丙组", a.getString("bszbName"))).collect(toList()); if (Lang.isNotEmpty(threeList)) { threeMap = threeList.stream().collect(Collectors.groupingBy(v -> v.getString("allName"))); } //丁组 - List fourList = allList.stream().filter(a -> a.getString("bszbName").equals("丁组")).collect(toList()); + List fourList = allList.stream().filter(a -> Objects.equals("丁组", a.getString("bszbName"))).collect(toList()); if (Lang.isNotEmpty(fourList)) { fourMap = fourList.stream().collect(Collectors.groupingBy(v -> v.getString("allName"))); } //团体 - List fiveList = allList.stream().filter(a -> a.getString("bszbName").equals("团体")).collect(toList()); + List fiveList = allList.stream().filter(a -> Objects.equals("团体", a.getString("bszbName"))).collect(toList()); if (Lang.isNotEmpty(fiveList)) { fiveMap = fiveList.stream().collect(Collectors.groupingBy(v -> v.getString("allName"))); } diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsReadingController.java b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsReadingController.java index 6219deb0..7314850d 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsReadingController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/controller/ActivitySportsReadingController.java @@ -2,6 +2,7 @@ package com.budwk.app.zhgh.activity.sports.controller; import cn.dev33.satoken.annotation.SaCheckPermission; import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.result.Result; import com.budwk.app.base.service.BaseService; import com.budwk.app.web.commons.auth.utils.AuthUtil; @@ -9,6 +10,8 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion; import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit; import com.budwk.app.zhgh.activity.sports.models.ActivitySchool; +import com.budwk.app.zhgh.activity.sports.service.ActivitySportsApplyUserService; +import com.budwk.app.zhgh.activity.sports.service.ActivitySportsService; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; @@ -19,7 +22,12 @@ import org.nutz.lang.Lang; import org.nutz.lang.util.NutMap; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; +import org.nutz.mvc.annotation.AdaptBy; +import org.nutz.mvc.upload.TempFile; +import org.nutz.mvc.upload.UploadAdaptor; +import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -45,6 +53,10 @@ public class ActivitySportsReadingController { public Dao dao; @Inject public BaseService baseService; + @Inject + private ActivitySportsService activitySportsService; + @Inject + private ActivitySportsApplyUserService activitySportsApplyUserService; @At @SaCheckPermission("activity.reading") @@ -150,5 +162,38 @@ public class ActivitySportsReadingController { return Result.success(apply); } + @At + @Ok("void") + @SaCheckPermission("activity.reading") + public void downloadImportTemplate(HttpServletResponse response) { + checkManageRole(); + activitySportsApplyUserService.downloadImportTemplate(response); + } + + @At + @SaCheckPermission("activity.reading") + @AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"}) + public Result importUserActivitys(@Param("file") TempFile file, @Param("businessId") String activityId) { + checkManageRole(); + return Result.success(activitySportsApplyUserService.importUserActivitys(file, activityId)); + } + + @At + @Ok("void") + @SaCheckPermission("activity.reading") + public void doExportByEnroll(String id, String unionId, HttpServletResponse response) { + checkManageRole(); + activitySportsService.exportXlsx(id, unionId, response); + } + + /** + * 参考原项目,导入和整表导出只开放给校工会管理员和系统管理员,避免普通阅览权限误操作。 + */ + private void checkManageRole() { + if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { + throw new BaseException("您没有操作权限"); + } + } + } diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsApplyUserService.java b/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsApplyUserService.java index 694d2e1a..91995f46 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsApplyUserService.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/service/ActivitySportsApplyUserService.java @@ -6,7 +6,9 @@ import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolApply; import com.budwk.app.zhgh.activity.sports.param.ActivitySportsApplyUserPageParam; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.upload.TempFile; +import javax.servlet.http.HttpServletResponse; import java.util.List; public interface ActivitySportsApplyUserService extends BaseService { @@ -31,4 +33,20 @@ public interface ActivitySportsApplyUserService extends BaseService getUserUnion(String query); + /** + * 下载报名人员导入模板。 + * + * @param response 响应流 + */ + void downloadImportTemplate(HttpServletResponse response); + + /** + * 导入报名人员并返回错误明细,全部成功时返回 null。 + * + * @param file 导入文件 + * @param activityId 活动ID + * @return 导入错误统计 + */ + NutMap importUserActivitys(TempFile file, String activityId); + } diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsApplyUserServiceImpl.java b/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsApplyUserServiceImpl.java index 748018ff..dbbb3e1e 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsApplyUserServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/service/impl/ActivitySportsApplyUserServiceImpl.java @@ -1,22 +1,33 @@ package com.budwk.app.zhgh.activity.sports.service.impl; +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.service.impl.BaseServiceImpl; +import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.zhgh.activity.basic.models.ActivityEvent; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; -import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService; import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion; +import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService; import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit; import com.budwk.app.zhgh.activity.sports.models.ActivitySchool; import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolApply; +import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolEvent; import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolTeam; import com.budwk.app.zhgh.activity.sports.param.ActivitySportsApplyUserPageParam; import com.budwk.app.zhgh.activity.sports.service.ActivitySportsApplyUserService; +import com.budwk.app.zhgh.activity.sports.template.ActivitySportsApplyImportTemp; +import org.apache.poi.ss.usermodel.Workbook; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain; import org.nutz.dao.Cnd; @@ -31,7 +42,9 @@ import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.Lang; import org.nutz.lang.Strings; import org.nutz.lang.util.NutMap; +import org.nutz.mvc.upload.TempFile; +import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -487,4 +500,224 @@ public class ActivitySportsApplyUserServiceImpl extends BaseServiceImpl()); + CommonDownloadUtil.download("报名人员导入模板.xlsx", workbook, response); + } catch (Exception e) { + throw new BaseException("下载导入模板失败"); + } + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public NutMap importUserActivitys(TempFile file, String activityId) { + if (file == null) { + throw new BaseException("请选择导入文件"); + } + if (StrUtil.isBlank(activityId)) { + throw new BaseException("请先选择活动名称"); + } + ActivitySchool activitySchool = dao().fetch(ActivitySchool.class, activityId); + if (activitySchool == null) { + throw new BaseException("活动不存在"); + } + try { + List importList = ExcelImportUtil.importExcel(file.getFile(), ActivitySportsApplyImportTemp.class, new ImportParams()); + if (Lang.isEmpty(importList)) { + return null; + } + List errorList = new ArrayList<>(); + int totalCount = 0; + int successCount = 0; + String currentUnionId = getUnionId(); + for (ActivitySportsApplyImportTemp importTemp : importList) { + totalCount++; + String loginName = normalizeLoginName(importTemp.getLoginName()); + if (StrUtil.isBlank(loginName)) { + importTemp.setNotes("工号不能为空"); + errorList.add(importTemp); + continue; + } + Sys_user sysUser = dao().fetch(Sys_user.class, Cnd.where("loginname", "=", loginName)); + if (sysUser == null) { + importTemp.setNotes("此用户不存在,请检查工号"); + errorList.add(importTemp); + continue; + } + ActivityBasicUnit basicUnit = dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", sysUser.getUnitId())); + if (basicUnit == null) { + importTemp.setNotes("请检查该人员活动单位是否配置正确"); + errorList.add(importTemp); + continue; + } + ActivityBasicUnion basicUnion = dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionId())); + if (basicUnion == null) { + importTemp.setNotes("请检查该人员所属活动工会是否配置正确"); + errorList.add(importTemp); + continue; + } + if (!hasImportAuth() && !basicUnion.getId().equals(currentUnionId)) { + importTemp.setNotes("此用户不是当前工会人员"); + errorList.add(importTemp); + continue; + } + String identityName = normalizeIdentityName(importTemp.getIdentity()); + String projectCode = normalizeText(importTemp.getProjectCode()); + if (StrUtil.isBlank(projectCode) && StrUtil.isBlank(identityName)) { + importTemp.setNotes("项目编号和身份不能同时为空"); + errorList.add(importTemp); + continue; + } + + ActivitySchoolApply activitySchoolApply = buildBaseApply(activityId, sysUser, basicUnit, basicUnion); + if (StrUtil.isNotBlank(identityName)) { + fillIdentityInfo(activitySchoolApply, identityName); + } + + if (StrUtil.isBlank(projectCode) && StrUtil.isNotBlank(identityName)) { + replaceUnionRoleUser(activitySchoolApply, identityName); + insert(activitySchoolApply); + successCount++; + continue; + } + + ActivityEvent activityEvent = dao().fetch(ActivityEvent.class, Cnd.where("projectCode", "=", projectCode)); + if (activityEvent == null) { + importTemp.setNotes("项目编号不存在,请检查后重试"); + errorList.add(importTemp); + continue; + } + ActivitySchoolEvent schoolEvent = dao().fetch(ActivitySchoolEvent.class, Cnd.where("activityId", "=", activityId).and("eventId", "=", activityEvent.getId())); + if (schoolEvent == null) { + importTemp.setNotes("该项目不属于当前活动,请检查项目编号"); + errorList.add(importTemp); + continue; + } + + activitySchoolApply.setEventId(schoolEvent.getEventId()); + activitySchoolApply.setAwardsMode(activityEvent.getProjectType()); + if ("2".equals(activityEvent.getProjectType())) { + ActivitySchoolTeam schoolTeam = dao().fetch(ActivitySchoolTeam.class, Cnd.where("activityId", "=", activityId).and("eventId", "=", schoolEvent.getEventId()).asc("location")); + if (schoolTeam == null) { + importTemp.setNotes("团体项目未配置队伍信息,请先维护小队"); + errorList.add(importTemp); + continue; + } + activitySchoolApply.setTeamId(schoolTeam.getId()); + activitySchoolApply.setTeam(schoolTeam.getName()); + } + // 兼容参考项目导入逻辑:带项目编号的记录按运动员处理。 + activitySchoolApply.setIdentity(List.of("1")); + clear(Cnd.where("activityId", "=", activityId).and("userId", "=", sysUser.getId()).and("eventId", "=", schoolEvent.getEventId())); + insert(activitySchoolApply); + successCount++; + } + + if (Lang.isNotEmpty(errorList)) { + NutMap result = NutMap.NEW(); + result.setv("totalCount", totalCount); + result.setv("successCount", successCount); + result.setv("errorCount", errorList.size()); + result.setv("errorList", errorList.stream().map(v -> NutMap.NEW() + .addv("工会", normalizeText(v.getUnionName())) + .addv("工号", normalizeText(v.getLoginName())) + .addv("姓名", normalizeText(v.getUserName())) + .addv("性别", normalizeText(v.getSex())) + .addv("项目编号", normalizeText(v.getProjectCode())) + .addv("身份", normalizeText(v.getIdentity())) + .addv("错误信息", normalizeText(v.getNotes()))).collect(Collectors.toList())); + return result; + } + return null; + } catch (Exception e) { + throw new BaseException("导入报名人员失败:" + e.getMessage()); + } + } + + /** + * 导入记录统一补齐报名基础字段,避免多入口写入时字段不一致。 + */ + private ActivitySchoolApply buildBaseApply(String activityId, Sys_user sysUser, ActivityBasicUnit basicUnit, ActivityBasicUnion basicUnion) { + ActivitySchoolApply activitySchoolApply = new ActivitySchoolApply(); + activitySchoolApply.setActivityId(activityId); + activitySchoolApply.setUnitId(basicUnit.getId()); + activitySchoolApply.setUnionId(basicUnion.getId()); + activitySchoolApply.setApplyUser(SecurityUtil.getUserId()); + activitySchoolApply.setUserId(sysUser.getId()); + activitySchoolApply.setLoginname(sysUser.getLoginname()); + activitySchoolApply.setUsername(sysUser.getUsername()); + activitySchoolApply.setSex(sysUser.getSex()); + activitySchoolApply.setMobile(sysUser.getMobile()); + activitySchoolApply.setProfessionalLevel(sysUser.getProfessionalLevel()); + activitySchoolApply.setBirthday(sysUser.getBirthday() == null ? null : DateUtil.formatDate(sysUser.getBirthday())); + activitySchoolApply.setApplyDate(DateUtil.now()); + activitySchoolApply.setStatus(2); + activitySchoolApply.setUnitname(basicUnit.getName()); + activitySchoolApply.setActivityUnionId(basicUnion.getId()); + activitySchoolApply.setActivityUnionName(basicUnion.getName()); + return activitySchoolApply; + } + + /** + * 角色类导入只保留当前活动当前工会当前人员的一条对应身份记录,防止重复导入产生脏数据。 + */ + private void replaceUnionRoleUser(ActivitySchoolApply activitySchoolApply, String identityName) { + Cnd cnd = Cnd.where("activityId", "=", activitySchoolApply.getActivityId()) + .and("activityUnionId", "=", activitySchoolApply.getActivityUnionId()) + .and("userId", "=", activitySchoolApply.getUserId()); + if ("教练".equals(identityName)) { + cnd.and("unionCoach", "=", true); + } else if ("领队".equals(identityName)) { + cnd.and("unionLeader", "=", true); + } else if ("团长".equals(identityName)) { + cnd.and("unionHead", "=", true); + } else if ("工作人员".equals(identityName)) { + cnd.and("unionStaff", "=", true); + } + clear(cnd); + } + + /** + * 统一处理导入身份映射,保证页面展示、导出与后续查询读取到一致的身份字段。 + */ + private void fillIdentityInfo(ActivitySchoolApply activitySchoolApply, String identityName) { + if ("教练".equals(identityName)) { + activitySchoolApply.setUnionCoach(true); + activitySchoolApply.setIdentity(List.of("2")); + } else if ("领队".equals(identityName)) { + activitySchoolApply.setUnionLeader(true); + activitySchoolApply.setIdentity(List.of("3")); + } else if ("团长".equals(identityName)) { + activitySchoolApply.setUnionHead(true); + activitySchoolApply.setIdentity(List.of("6")); + } else if ("工作人员".equals(identityName)) { + activitySchoolApply.setUnionStaff(true); + activitySchoolApply.setIdentity(List.of("7")); + } + } + + private boolean hasImportAuth() { + return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name()); + } + + private String normalizeLoginName(String loginName) { + return StrUtil.isBlank(loginName) ? "" : loginName.replaceAll("\\s+", ""); + } + + private String normalizeText(String text) { + return StrUtil.trimToEmpty(text); + } + + private String normalizeIdentityName(String identity) { + String identityName = normalizeText(identity); + if ("运动员".equals(identityName)) { + return ""; + } + return identityName; + } } diff --git a/src/main/java/com/budwk/app/zhgh/activity/sports/template/ActivitySportsApplyImportTemp.java b/src/main/java/com/budwk/app/zhgh/activity/sports/template/ActivitySportsApplyImportTemp.java new file mode 100644 index 00000000..0915cce7 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/activity/sports/template/ActivitySportsApplyImportTemp.java @@ -0,0 +1,32 @@ +package com.budwk.app.zhgh.activity.sports.template; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import lombok.Data; + +/** + * 体育活动报名导入模板 + */ +@Data +public class ActivitySportsApplyImportTemp { + + @Excel(name = "工会", width = 20) + private String unionName; + + @Excel(name = "工号", width = 20) + private String loginName; + + @Excel(name = "姓名", width = 20) + private String userName; + + @Excel(name = "性别", width = 12) + private String sex; + + @Excel(name = "项目编号", width = 20) + private String projectCode; + + @Excel(name = "身份", width = 20) + private String identity; + + @Excel(name = "备注", width = 30) + private String notes; +} diff --git a/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionManageController.java b/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionManageController.java index ffa90e4c..2b4885b2 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionManageController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionManageController.java @@ -9,6 +9,7 @@ import com.budwk.app.base.page.Pagination; import com.budwk.app.base.param.PageForm; import com.budwk.app.base.result.Result; import com.budwk.app.base.service.BaseService; +import com.budwk.app.sys.models.Sys_home_activity; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.sys.services.SysMsgService; import com.budwk.app.web.commons.auth.utils.SecurityUtil; @@ -81,17 +82,20 @@ public class ActivityWorksCollectionManageController { @At @SaCheckPermission("activity.workscollection.manage") + @Aop(TransAop.READ_COMMITTED) public Result insert(@Param("data") @Valid Activity_works_collection worksCollection) { worksCollection.setIsSubmit(true); dao.insertWith(worksCollection, "subjectTypes"); worksCollection.getSubjectTypes().forEach(item -> { dao.insertLinks(item, "worksTypes"); }); + syncHomeActivity(worksCollection); return Result.success(); } @At @SaCheckPermission("activity.workscollection.manage") + @Aop(TransAop.READ_COMMITTED) public Result save(@Param("data") @Valid Activity_works_collection worksCollection) { worksCollection.setIsSubmit(false); dao.insertWith(worksCollection, "subjectTypes"); @@ -122,6 +126,7 @@ public class ActivityWorksCollectionManageController { Cnd.where(Activity_works_worksType::getId, "not in ", workIds) .and(Activity_works_worksType::getSubjectId,"=",item.getId())); }); + syncHomeActivity(worksCollection); return Result.success(); } @@ -132,6 +137,7 @@ public class ActivityWorksCollectionManageController { dao.delete(Activity_works_collection.class, id); dao.clear(Activity_works_collection_upload.class, Cnd.where(Activity_works_collection_upload::getActivityId, "=", id)); dao.clear(Activity_works_subjectType.class, Cnd.where(Activity_works_subjectType::getActivityId, "=", id)); + dao.delete(Sys_home_activity.class, id); return Result.success(); } @@ -148,11 +154,26 @@ public class ActivityWorksCollectionManageController { @At @SaCheckPermission("activity.workscollection.manage") + @Aop(TransAop.READ_COMMITTED) public Result enableChange(@Valid String id, @Valid Boolean enable) { dao.update(Activity_works_collection.class, Chain.make("enable", enable), Cnd.where("id", "=", id)); + Activity_works_collection worksCollection = dao.fetch(Activity_works_collection.class, id); + worksCollection.setEnable(enable); + syncHomeActivity(worksCollection); return Result.success(); } + /** + * 同步作品征集到首页活动表:开启的活动写入首页,关闭的活动移除首页入口。 + */ + private void syncHomeActivity(Activity_works_collection worksCollection) { + if (Boolean.TRUE.equals(worksCollection.getEnable())) { + dao.insertOrUpdate(worksCollection.covertToSysHomeActivity()); + } else { + dao.delete(Sys_home_activity.class, worksCollection.getId()); + } + } + @At @SaCheckPermission("activity.workscollection.manage") public Result sendNotice(@Valid String id) { diff --git a/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionUploadController.java b/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionUploadController.java index 07bd3bf0..3a16dc8c 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionUploadController.java +++ b/src/main/java/com/budwk/app/zhgh/activity/workscollection/controller/ActivityWorksCollectionUploadController.java @@ -11,6 +11,7 @@ import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService; import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection; import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection_upload; import io.swagger.annotations.ApiOperation; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.FieldFilter; @@ -18,6 +19,7 @@ import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.dao.util.Daos; import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.mvc.annotation.At; @@ -94,11 +96,17 @@ public class ActivityWorksCollectionUploadController { */ @At @SaCheckPermission("activity.workscollection.upload") + @Aop(TransAop.READ_COMMITTED) public Result insert(@Param("data") @Valid Activity_works_collection_upload upload) { String activityId = upload.getActivityId(); Activity_works_collection activity = dao.fetch(Activity_works_collection.class, activityId); - if (activity.getEndDateTime().getTime() < System.currentTimeMillis()){ + long now = System.currentTimeMillis(); + // 作品上传只允许在活动开始时间和结束时间之间提交,防止活动未开始或已结束后继续上传。 + if (activity.getStartDateTime().getTime() > now) { + return Result.error("活动未开始,不能上传!"); + } + if (activity.getEndDateTime().getTime() < now){ return Result.error("活动已结束!"); } if (activity.getActivityGroupId() != null) { @@ -124,9 +132,15 @@ public class ActivityWorksCollectionUploadController { @At @SaCheckPermission("activity.workscollection.upload") + @Aop(TransAop.READ_COMMITTED) public Result update(@Param("data") @Valid Activity_works_collection_upload upload) { Activity_works_collection activity = dao.fetch(Activity_works_collection.class, upload.getActivityId()); - if (activity.getEndDateTime().getTime() < System.currentTimeMillis()){ + long now = System.currentTimeMillis(); + // 编辑作品同样按活动时间窗口控制,避免过期或未开始活动被继续提交作品信息。 + if (activity.getStartDateTime().getTime() > now) { + return Result.error("活动未开始,不能上传!"); + } + if (activity.getEndDateTime().getTime() < now){ return Result.error("活动已结束!"); } dao.updateIgnoreNull(upload); @@ -155,12 +169,19 @@ public class ActivityWorksCollectionUploadController { @At @SaCheckPermission("activity.workscollection.upload") + /** + * 查询当前登录人有权限上传作品的活动主题。 + * + * @param year 活动年度,前端传年度选择框的 yyyy 值,按活动开始时间所在年份过滤。 + * @return Result,data 为活动列表,仅返回 id、name,且保留已开启和面向对象权限过滤。 + */ @ApiOperation("获取有权限的活动列表") @Ok("json:{ignoreNull:true}") - public Result listPerMissionActivity() { + public Result listPerMissionActivity(Long year) { Dao extDao = Daos.ext(dao, FieldFilter.create(Activity_works_collection.class, "id|name|")); Cnd cnd = Cnd.NEW(); cnd.and(Activity_works_collection::getEnable,"=",1); + cnd.andEX("YEAR(startDateTime)", "=", year); cnd.desc(Activity_works_collection::getStartDateTime); List list = extDao.query(Activity_works_collection.class, cnd); list = list.stream().filter(item -> { diff --git a/src/main/java/com/budwk/app/zhgh/activity/workscollection/models/Activity_works_collection.java b/src/main/java/com/budwk/app/zhgh/activity/workscollection/models/Activity_works_collection.java index 28d85ca0..b3e50ca9 100644 --- a/src/main/java/com/budwk/app/zhgh/activity/workscollection/models/Activity_works_collection.java +++ b/src/main/java/com/budwk/app/zhgh/activity/workscollection/models/Activity_works_collection.java @@ -1,6 +1,8 @@ package com.budwk.app.zhgh.activity.workscollection.models; import com.budwk.app.base.model.BaseModel; +import com.budwk.app.sys.models.Sys_home_activity; +import com.budwk.app.sys.services.SysHomeConvert; import lombok.Data; import lombok.EqualsAndHashCode; import org.nutz.dao.entity.annotation.*; @@ -17,7 +19,7 @@ import java.util.List; @Table("activity_works_collection") @TableMeta("{'mysql-charset':'utf8mb4'}") @Comment("作品征集活动") -public class Activity_works_collection extends BaseModel { +public class Activity_works_collection extends BaseModel implements SysHomeConvert { @Name @Comment("ID") @@ -123,4 +125,21 @@ public class Activity_works_collection extends BaseModel { @Many(field = "activityId") private List uploads; + @Override + public Sys_home_activity covertToSysHomeActivity() { + Sys_home_activity sysHomeActivity = new Sys_home_activity(); + sysHomeActivity.setId(this.getId()); + sysHomeActivity.setName(this.getName()); + sysHomeActivity.setCover(this.getCover()); + sysHomeActivity.setContent(this.getContent()); + sysHomeActivity.setUrl("/platform/activity/worksCollection/upload"); + sysHomeActivity.setH5Url("/platform/activity/worksCollection/upload/h5"); + sysHomeActivity.setStartDate(this.getStartDateTime()); + sysHomeActivity.setEndDate(this.getEndDateTime()); + sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId()); + sysHomeActivity.setEnable(Boolean.TRUE.equals(this.getEnable())); + sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName()); + return sysHomeActivity; + } + } diff --git a/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApplyController.java b/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApplyController.java index c412c035..3f15b467 100644 --- a/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApplyController.java +++ b/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApplyController.java @@ -68,7 +68,7 @@ public class ClubUserJoinApplyController { @Inject private ClubUserJoinService clubUserJoinService; - @At("/") + @At("") @SaCheckPermission("club.join.apply") @Ok("beetl:/platform/zhgh/club/join/apply/index.html") public void index() { @@ -128,6 +128,12 @@ public class ClubUserJoinApplyController { @SLog(tag = "社团管理系统-申请入/退会", msg = "申请社团入/退会") public Result submit(@Param("data") ClubUserApply clubUserApply, @Param("mode") Boolean mode) { + if (Boolean.TRUE.equals(mode)) { + String qualificationMsg = clubUserJoinService.checkJoinQualification(SecurityUtil.getUserId()); + if (StrUtil.isNotBlank(qualificationMsg)) { + return Result.error(99, qualificationMsg); + } + } if(mode == false) { clubUserApply = clubUserJoinService.buildExitApply(clubUserApply.getClubId(), SecurityUtil.getUserId()); if (ObjectUtil.isEmpty(clubUserApply)) { @@ -192,6 +198,10 @@ public class ClubUserJoinApplyController { @ApiOperation("校验是否申请过社团") @SaCheckPermission(value = {"club.join.apply", "h5.club.join.apply"}, mode = SaMode.OR) public Result checkApplyClub(@Valid String clubId) { + String qualificationMsg = clubUserJoinService.checkJoinQualification(SecurityUtil.getUserId()); + if (StrUtil.isNotBlank(qualificationMsg)) { + return Result.error(99, qualificationMsg); + } View_user user = dao.fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId())); if (user.getMember() != 1) { return Result.error(99, "抱歉,您不满足入会的条件!"); diff --git a/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApprovalController.java b/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApprovalController.java index 7bb72a64..b99e6389 100644 --- a/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApprovalController.java +++ b/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinApprovalController.java @@ -48,7 +48,7 @@ public class ClubUserJoinApprovalController { @Inject private ClubUserJoinService clubUserJoinService; - @At("/") + @At("") @SaCheckPermission("club.join.clubApproval") @Ok("beetl:/platform/zhgh/club/join/clubApproval/index.html") public void index() {} diff --git a/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinMineController.java b/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinMineController.java index 92d611af..76a53a7e 100644 --- a/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinMineController.java +++ b/src/main/java/com/budwk/app/zhgh/club/controller/apply/ClubUserJoinMineController.java @@ -45,7 +45,7 @@ public class ClubUserJoinMineController { @Inject private ClubUserJoinService clubUserJoinService; - @At("/") + @At("") @SaCheckPermission("club.join.mine") @Ok("beetl:/platform/zhgh/club/join/mine/index.html") public void index() {} diff --git a/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java b/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java index 5708655a..3175236b 100644 --- a/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java +++ b/src/main/java/com/budwk/app/zhgh/club/controller/common/ClubCommonController.java @@ -57,7 +57,7 @@ public class ClubCommonController { @SaCheckLogin public Result listClubByRole() { Cnd cnd = Cnd.NEW(); - if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { + if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) { // 报销申请只允许选择当前用户已加入的协会,避免看到未加入的协会数据。 List clubUsers = sysClubService.dao().query(ClubUser.class, Cnd.where("userId", "=", SecurityUtil.getUserId())); List myClubId = clubUsers.stream().map(ClubUser::getClubId).distinct().toList(); @@ -78,4 +78,12 @@ public class ClubCommonController { return Result.success(list); } + @At + @SaCheckLogin + public Result listManageClubByRole() { + // 活动人员范围设置只允许按“可管理的社团”选择,避免把仅作为成员加入的社团也展示出来。 + List clubList = sysClubService.getMyManageClub(); + return Result.success(clubList); + } + } diff --git a/src/main/java/com/budwk/app/zhgh/club/service/ClubUserJoinService.java b/src/main/java/com/budwk/app/zhgh/club/service/ClubUserJoinService.java index 1168e586..bf195fa3 100644 --- a/src/main/java/com/budwk/app/zhgh/club/service/ClubUserJoinService.java +++ b/src/main/java/com/budwk/app/zhgh/club/service/ClubUserJoinService.java @@ -25,4 +25,12 @@ public interface ClubUserJoinService extends BaseService { * @return 入会时间 */ java.util.Date resolveJoinTime(String clubId, String userId); + + /** + * 校验当前用户是否满足申请加入社团的资格。 + * + * @param userId 用户ID + * @return 校验不通过时返回提示语,通过时返回null + */ + String checkJoinQualification(String userId); } diff --git a/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubUserJoinServiceImpl.java b/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubUserJoinServiceImpl.java index dc76f774..befd1811 100644 --- a/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubUserJoinServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/club/service/impl/ClubUserJoinServiceImpl.java @@ -1,6 +1,9 @@ +package com.budwk.app.zhgh.club.service.impl; + import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.service.impl.BaseServiceImpl; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import com.budwk.app.sys.views.View_user; import com.budwk.app.zhgh.club.model.ClubUser; import com.budwk.app.zhgh.club.model.ClubUserApply; @@ -15,10 +18,37 @@ import java.util.List; @IocBean(args = {"refer:dao"}) public class ClubUserJoinServiceImpl extends BaseServiceImpl implements ClubUserJoinService { + private static final List ALLOW_JOIN_PERSON_TYPES = List.of( + "教职工", + "未起薪人员", + "教职工新入会", + "教职工其他", + "劳动服务公司", + "后勤聘用人员", + "退休人员[一]", + "退休人员[二]", + "校聘劳务派遣全额" + ); + public ClubUserJoinServiceImpl(Dao dao) { super(dao); } + @Override + public String checkJoinQualification(String userId) { + View_user user = dao().fetch(View_user.class, Cnd.where("id", "=", userId)); + if (user == null) { + return "抱歉,您不满足入会的条件!"; + } + if (user.getMember() == null || user.getMember() != 1) { + return "抱歉,您不满足入会的条件!"; + } + if (StrUtil.isBlank(user.getPersonType()) || !ALLOW_JOIN_PERSON_TYPES.contains(user.getPersonType())) { + return "抱歉,您不满足入会的条件!"; + } + return null; + } + @Override public ClubUserApply buildExitApply(String clubId, String userId) { // 优先复用最近一次申请记录中的补充信息,避免用户重复填写基础资料。 diff --git a/src/main/java/com/budwk/app/zhgh/club/service/impl/SysClubServiceImpl.java b/src/main/java/com/budwk/app/zhgh/club/service/impl/SysClubServiceImpl.java index 16a9faa1..846a0a8f 100644 --- a/src/main/java/com/budwk/app/zhgh/club/service/impl/SysClubServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/club/service/impl/SysClubServiceImpl.java @@ -275,7 +275,14 @@ public class SysClubServiceImpl extends BaseServiceImpl implements SysC } cnd.andEX("year(info.createTime)", "=", pageForm.getYear()); cnd.and("info.userId", "=", SecurityUtil.getUserId()); - cnd.desc("createTime"); + // 我的注册列表默认按社团编码升序展示,点击表头时按用户选择的方向排序。 + if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) { + cnd.asc("info.clubCode"); + } else if ("clubCode".equals(pageForm.getPageOrderName())) { + cnd.orderBy("info.clubCode", PageUtil.getOrder(pageForm.getPageOrderBy())); + } else { + cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy())); + } cnd.groupBy("info.id"); sql.setCondition(cnd); return listPageVO(pageForm, sql, ClubRegisterPageVo.class); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/message/strategy/impl/SmsMessageSendStrategy.java b/src/main/java/com/budwk/app/zhgh/dayofficework/message/strategy/impl/SmsMessageSendStrategy.java index 78e38342..dee3355d 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/message/strategy/impl/SmsMessageSendStrategy.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/message/strategy/impl/SmsMessageSendStrategy.java @@ -1,6 +1,7 @@ package com.budwk.app.zhgh.dayofficework.message.strategy.impl; import cn.hutool.json.JSONObject; +import com.budwk.app.base.sms.SmsService; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.zhgh.dayofficework.message.enums.GlobalMessageChannel; import com.budwk.app.zhgh.dayofficework.message.strategy.GlobalMessageSendStrategy; @@ -21,6 +22,8 @@ public class SmsMessageSendStrategy implements GlobalMessageSendStrategy { @Inject private Dao dao; + @Inject + private SmsService smsService; @Override public GlobalMessageChannel getChannel() { @@ -70,29 +73,24 @@ public class SmsMessageSendStrategy implements GlobalMessageSendStrategy { } /** - * 发送短信消息 + * 发送短信消息。 + * 接收人由全局消息服务传入的用户ID或登录名查询得到,发送时使用用户工号对接通讯平台; + * 返回结果由 SmsService 内部解析并记录,当前策略只负责逐个用户触发发送并记录异常。 */ private void sendSmsMessage(String title, String content, Integer type, List users, JSONObject config) { for (Sys_user user : users) { try { - String mobile = user.getMobile(); - if (mobile == null || mobile.trim().isEmpty()) { - log.warn("用户{}没有手机号,跳过短信发送", user.getUsername()); + String loginName = user.getLoginname(); + if (loginName == null || loginName.trim().isEmpty()) { + log.warn("用户{}没有工号,跳过短信发送", user.getUsername()); continue; } // 构建短信内容 String smsContent = buildSmsContent(title, content, type); - // 执行发送 - - boolean success = true; - - if (success) { - log.info("短信发送成功:用户{},手机号:{}", user.getUsername(), mobile); - } else { - log.warn("短信发送失败:用户{},手机号:{}", user.getUsername(), mobile); - } + smsService.sendSmsByAccount(loginName, smsContent); + log.info("短信发送完成:用户{},工号:{}", user.getUsername(), loginName); } catch (Exception e) { log.error("发送短信给用户{}时出错:{}", user.getUsername(), e.getMessage(), e); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/message/strategy/impl/WechatMessageSendStrategy.java b/src/main/java/com/budwk/app/zhgh/dayofficework/message/strategy/impl/WechatMessageSendStrategy.java new file mode 100644 index 00000000..a4b0e19e --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/message/strategy/impl/WechatMessageSendStrategy.java @@ -0,0 +1,113 @@ +package com.budwk.app.zhgh.dayofficework.message.strategy.impl; + +import cn.hutool.json.JSONObject; +import com.budwk.app.base.sms.SmsService; +import com.budwk.app.sys.models.Sys_user; +import com.budwk.app.zhgh.dayofficework.message.enums.GlobalMessageChannel; +import com.budwk.app.zhgh.dayofficework.message.strategy.GlobalMessageSendStrategy; +import lombok.extern.slf4j.Slf4j; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; + +import java.util.List; + +/** + * 微信发送策略 + */ +@IocBean +@Slf4j +public class WechatMessageSendStrategy implements GlobalMessageSendStrategy { + + @Inject + private Dao dao; + @Inject + private SmsService smsService; + + @Override + public GlobalMessageChannel getChannel() { + return GlobalMessageChannel.WECHAT; + } + + @Override + public boolean supportAsync() { + return true; + } + + @Override + public void send(String title, String content, Integer type, List receiverIds, JSONObject config) { + try { + log.info("开始发送微信消息,标题:{},接收人数量:{}", title, receiverIds.size()); + + // receiverIds 为 sys_user.id,按用户ID查询后使用工号对接通讯平台微信文本消息接口。 + List users = dao.query(Sys_user.class, Cnd.where(Sys_user::getId, "in", receiverIds)); + sendWechatMessage(title, content, type, users, config); + + log.info("微信消息发送完成,标题:{},接收人数量:{}", title, users.size()); + } catch (Exception e) { + log.error("微信消息发送失败,标题:{},错误:{}", title, e.getMessage(), e); + throw new RuntimeException("微信消息发送失败:" + e.getMessage(), e); + } + } + + @Override + public void sendByLoginName(String title, String content, Integer type, List receiverLoginNames, JSONObject config) { + try { + log.info("开始发送微信消息(按登录名),标题:{},接收人数量:{}", title, receiverLoginNames.size()); + + // receiverLoginNames 为 sys_user.loginname,查询用户后仍使用工号作为通讯平台接收人标识。 + List users = dao.query(Sys_user.class, Cnd.where(Sys_user::getLoginname, "in", receiverLoginNames)); + sendWechatMessage(title, content, type, users, config); + + log.info("微信消息发送完成(按登录名),标题:{},接收人数量:{}", title, users.size()); + } catch (Exception e) { + log.error("微信消息发送失败(按登录名),标题:{},错误:{}", title, e.getMessage(), e); + throw new RuntimeException("微信消息发送失败:" + e.getMessage(), e); + } + } + + /** + * 发送微信文本消息。 + * 接收人来自全局消息服务传入的用户ID或登录名,方法内部转换为 Sys_user 后取工号发送; + * SmsService 返回 MsgPlatformResponse,当前策略按单人发送记录日志,单个用户失败不影响其他接收人。 + */ + private void sendWechatMessage(String title, String content, Integer type, List users, JSONObject config) { + for (Sys_user user : users) { + try { + String loginName = user.getLoginname(); + if (loginName == null || loginName.trim().isEmpty()) { + log.warn("用户{}没有工号,跳过微信发送", user.getUsername()); + continue; + } + + String wechatContent = buildWechatContent(title, content, type); + smsService.sendWechatTextByAccountOrMobile(loginName, wechatContent); + log.info("微信发送完成:用户{},工号:{}", user.getUsername(), loginName); + } catch (Exception e) { + log.error("发送微信给用户{}时出错:{}", user.getUsername(), e.getMessage(), e); + } + } + } + + /** + * 构建微信文本内容。 + * type=2 表示待办消息,其余类型按系统通知处理,内容格式保持与短信策略一致。 + */ + private String buildWechatContent(String title, String content, Integer type) { + StringBuilder sb = new StringBuilder(); + + if (type != null && type == 2) { + sb.append("【待办通知】"); + } else { + sb.append("【系统通知】"); + } + + sb.append(title); + if (content != null && !content.trim().isEmpty()) { + sb.append(":").append(content); + } + + return sb.toString(); + } +} diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java index a7923c08..f95e3eef 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/siteCug/controller/SiteCugApplyController.java @@ -457,9 +457,13 @@ public class SiteCugApplyController { return result; } + /** + * 当预约日期就是今天时,已经过了开始时间的时间块直接标记为不可预约, + * 这样全天候和分段预约在前端展示时都会统一变成禁用状态。 + */ private NutMap buildAvailabilityBlock(String day, int startMinute, int endMinute, List reservedRanges, List disabledRanges, boolean siteClosed, int groupIndex, String groupLabel) { String status = "available"; - if (siteClosed || intersectsAny(startMinute, endMinute, disabledRanges)) { + if (siteClosed || isExpiredAvailabilityBlock(day, startMinute) || intersectsAny(startMinute, endMinute, disabledRanges)) { status = "closed"; } else if (intersectsAny(startMinute, endMinute, reservedRanges)) { status = "reserved"; @@ -478,6 +482,19 @@ public class SiteCugApplyController { .addv("endDateTime", day + " " + formatMinutes(endMinute) + ":00"); } + /** + * 仅在当天按“时间块开始时间是否已经过去”判断是否失效, + * 避免用户继续选中已经开始或已经结束的预约时段。 + */ + private boolean isExpiredAvailabilityBlock(String day, int startMinute) { + String today = DateUtil.today(); + if (!StrUtil.equals(today, day)) { + return false; + } + DateTime blockStart = DateUtil.parseDateTime(day + " " + formatMinutes(startMinute) + ":00"); + return !blockStart.isAfter(DateUtil.date()); + } + private boolean isSiteClosed(SiteCugInfo siteInfo, String day, Set holidaySet) { if (isWeekend(day)) { return true; diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java index 7a799ff1..dc5ea19a 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseApplyController.java @@ -113,22 +113,23 @@ public class UnionReimburseApplyController { return Result.success(); } -// @At -// @ApiOperation("重新提交申请") -// @Aop(TransAop.READ_COMMITTED) -// @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR) -// public Result submitAgain(@Param("data") UnionReimburse unionReimburse, @Param("taskId") Long taskId) { -// if (StrUtil.isBlank(unionReimburse.getId())) unionReimburse.setCreateTime(new Date()); -// -// dao.insertOrUpdate(unionReimburse); -// -// Dict dict = Dict.create(); -// dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId); -// dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode()); -// dict.set("userId", unionReimburse.getCondolenceUserId()); -// flowCommonService.executeTask(dict); -// return Result.success(); -// } + @At + @ApiOperation("重新提交申请") + @Aop(TransAop.READ_COMMITTED) + @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR) + public Result submitAgain(@Param("data") UnionReimburse unionReimburse, @Param("taskId") Long taskId) { + // 退回重提也必须复用当前提交保存逻辑,避免绕过发票明细落库和提交校验。 + Result result = unionReimburseService.saveApply(unionReimburse, 2); + if (result.getCode() != 0) { + return result; + } + + Dict dict = Dict.create(); + dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId); + dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode()); + flowCommonService.executeTask(dict); + return Result.success(); + } @At @SaCheckLogin @@ -179,6 +180,14 @@ public class UnionReimburseApplyController { return Result.success(pagination.getList()); } + @At + @ApiOperation("查询付款人") + @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR) + @SLog(type = "unionReimburse", tag = "查询付款人", msg = "按经费来源查询付款人") + public Result listPayerUser(String keyword, String reimburseFundSource, String clubId) { + return Result.success(unionReimburseService.listPayerUser(keyword, reimburseFundSource, clubId)); + } + @At @ApiOperation("查询职工慰问类型") @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR) @@ -267,6 +276,6 @@ public class UnionReimburseApplyController { @ApiOperation("统一查询经费余额") @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR) public Result getBudgetBalance(String reimburseFundSource, String clubId) { - return unionReimburseService.getBudgetBalance(reimburseFundSource, clubId); + return unionReimburseService.getBudgetBalance(reimburseFundSource, clubId, null); } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseCollectController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseCollectController.java index 7c2dda77..766f6aff 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseCollectController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseCollectController.java @@ -7,6 +7,7 @@ import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaMode; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.page.Pagination; @@ -26,12 +27,14 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Workbook; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; @@ -117,12 +120,10 @@ public class UnionReimburseCollectController { } @At + @Aop(TransAop.READ_COMMITTED) @SaCheckPermission(value = {"unionReimburse.collect", "h5.unionReimburse.collect"}, mode = SaMode.OR) public Result updateActuallyAmount(String id,Double realMoney) { - UnionReimburse unionReimburse = unionReimburseService.fetch(id); - unionReimburse.setRealMoney(realMoney); - unionReimburseService.updateIgnoreNull(unionReimburse); - return Result.success(); + return unionReimburseService.updateActuallyAmount(id, realMoney); } @At @@ -237,6 +238,9 @@ public class UnionReimburseCollectController { item.setReimburseFundSource(typeDict.getName()); } } + if (StrUtil.isNotBlank(item.getCreateTime())) { + item.setCreateTime(DateUtil.format(DateUtil.parse(item.getCreateTime()), "yyyy-MM-dd HH:mm:ss")); + } // 拼接备注字段(仿照系统代码逻辑,使用activityName和condolenceName字段) if (cn.hutool.core.util.StrUtil.isNotBlank(item.getActivityName())) { diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java index 779b5b30..da4e2f07 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseMineController.java @@ -189,7 +189,7 @@ public class UnionReimburseMineController { @SaCheckPermission(value = {"unionReimburse.mine", "h5.unionReimburse.mine"}, mode = SaMode.OR) @SLog( tag = "删除工会报销", msg = "删除工会报销") public Result delete(@Param("id") String id) { - unionReimburseService.delete(id); + unionReimburseService.deleteApply(id); return Result.success(); } @@ -227,7 +227,7 @@ public class UnionReimburseMineController { tempFile.deleteOnExit(); - docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd")); + docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); docData.put("unitName", unionReimburse.getUnitName()); docData.put("userName", unionReimburse.getUserName()); docData.put("loginName", unionReimburse.getLoginName()); @@ -309,7 +309,7 @@ public class UnionReimburseMineController { .create(); docData.put("qrcode", pictureRenderData); - docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd")); + docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); docData.put("unitName", unionReimburse.getUnitName()); docData.put("userName", unionReimburse.getUserName()); docData.put("loginName", unionReimburse.getLoginName()); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java index a6d6de41..3965528d 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/controller/UnionReimburseReviewController.java @@ -12,7 +12,6 @@ import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse; import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Sqls; @@ -32,7 +31,6 @@ import java.util.List; @At("/platform/unionReimburse/review") @Ok("json:full") @Api("工会报销审核") -@Slf4j public class UnionReimburseReviewController { @Inject @@ -110,19 +108,7 @@ public class UnionReimburseReviewController { @Aop(TransAop.READ_COMMITTED) @SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR) @SLog(tag = "工会报销审核", msg = "一键审核工会报销") - public Result allReview() { - try { - List reimbursements = unionReimburseService.query(Cnd.where("stateId", "=", 2)); - for (UnionReimburse reimbursement : reimbursements) { - Result result = unionReimburseService.reviewApply(reimbursement.getId(), "通过", 3); - if (result.getCode() != 0) { - return result; - } - } - return Result.success("一键审核完成,共处理" + reimbursements.size() + "条记录"); - } catch (Exception e) { - log.error("一键审核失败", e); - return Result.error("一键审核失败: " + e.getMessage()); - } + public Result allReview(@Param("ids[]") String[] ids) { + return unionReimburseService.batchReviewApply(ids); } } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java index db5efe78..6fef206a 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/UnionReimburseService.java @@ -6,6 +6,7 @@ import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse; import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm; import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO; import org.nutz.dao.sql.Sql; +import org.nutz.lang.util.NutMap; import java.util.List; @@ -23,11 +24,26 @@ public interface UnionReimburseService extends BaseService { */ UnionReimburse getApplyForm(String id); + /** + * 删除报销申请,并同步删除关联发票明细。 + */ + void deleteApply(String id); + /** * 查询付款人的历史收款信息。 */ List getUserBankHistory(String payer); + /** + * 查询付款人候选人;协会经费按所选协会过滤,只返回该协会成员。 + * + * @param keyword 姓名或工号关键字 + * @param reimburseFundSource 当前选择的经费来源 + * @param clubId 协会经费对应的协会ID + * @return 可选付款人列表 + */ + List listPayerUser(String keyword, String reimburseFundSource, String clubId); + /** * 校验发票号码是否与历史报销记录重复。 */ @@ -41,10 +57,23 @@ public interface UnionReimburseService extends BaseService { /** * 查询报销可用经费余额,同时校验是否已分配以及协会归属权限。 */ - Result getBudgetBalance(String reimburseFundSource, String clubId); + Result getBudgetBalance(String reimburseFundSource, String clubId, String unionId); /** * 审核报销,审核通过时扣减对应预算并新增预算使用详情。 */ Result reviewApply(String reimburseId, String reviewOpinion, int submitType); + + /** + * 管理员修改实际报销金额,已报销成功的数据需要同步调整经费预算管理台账。 + */ + Result updateActuallyAmount(String id, Double realMoney); + + /** + * 批量审核选中的待审核报销申请。 + * + * @param reimburseIds 前端表格勾选的报销申请ID数组,只处理状态为待审核确认的数据 + * @return Result,成功时返回本次实际审核通过的记录数量,失败时返回具体错误原因 + */ + Result batchReviewApply(String[] reimburseIds); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java index 6d42fe24..ea0718ae 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/unionReimburse/service/impl/UnionReimburseServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.sys.models.Sys_file; @@ -122,7 +123,10 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i normalizeInvoiceDetails(unionReimburse); if (needInvoiceDetails(unionReimburse)) { fillInvoiceSummary(unionReimburse); + } else { + fillCondolenceInvoiceSummary(unionReimburse); } + fillDefaultRealMoney(unionReimburse); if (stateId == 2) { Result validateResult = validateBeforeSubmit(unionReimburse); if (validateResult != null) { @@ -139,23 +143,29 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i } if (oldRecord == null) { - unionReimburse.setCreateTime(DateUtil.date()); + unionReimburse.setCreateTime(toSecondPrecision(DateUtil.date())); unionReimburse.setDocumentNo(generateDocumentNo()); } else { - unionReimburse.setCreateTime(oldRecord.getCreateTime()); + unionReimburse.setCreateTime(toSecondPrecision(oldRecord.getCreateTime())); unionReimburse.setDocumentNo(oldRecord.getDocumentNo()); } unionReimburse.setStateId(stateId); - if ("UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject())) { - unionReimburse.setRealMoney(unionReimburse.getCondolenceMoney()); - } else { - unionReimburse.setRealMoney(unionReimburse.getMoney()); - } + fillDefaultRealMoney(unionReimburse); this.dao().insertOrUpdate(unionReimburse); saveInvoiceDetails(unionReimburse.getId(), unionReimburse.getInvoiceDetails()); return Result.success(stateId == 2 ? "提交成功" : "保存成功"); } + /** + * 申请时间统一保存到秒,避免数据库和页面出现毫秒级时间。 + */ + private Date toSecondPrecision(Date date) { + if (date == null) { + return null; + } + return new Date((date.getTime() / 1000) * 1000); + } + @Override public UnionReimburse getApplyForm(String id) { UnionReimburse unionReimburse = this.fetch(id); @@ -170,6 +180,16 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i return unionReimburse; } + @Override + public void deleteApply(String id) { + if (StrUtil.isBlank(id)) { + return; + } + // 删除申请主记录时同步清理发票明细,避免遗留无归属的发票关联数据。 + this.dao().clear(UnionReimburseInvoiceDetail.class, Cnd.where("reimburseId", "=", id)); + this.delete(id); + } + @Override public List getUserBankHistory(String payer) { if (StrUtil.isBlank(payer)) { @@ -199,6 +219,55 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i return this.listVO(sql, UnionReimburseBankHistoryVO.class); } + /** + * 付款人查询要跟随经费来源;选择协会经费时,只允许从当前协会会员中选择付款人。 + */ + @Override + public List listPayerUser(String keyword, String reimburseFundSource, String clubId) { + boolean clubFundSource = "UNION_REIMBURSE_FUND_SOURCE_3".equals(reimburseFundSource); + if (clubFundSource && StrUtil.isBlank(clubId)) { + return new ArrayList<>(); + } + String clubJoinSql = clubFundSource ? "INNER JOIN club_user cu ON cu.userId = u.id" : ""; + Sql sql = Sqls.create(""" + SELECT + u.id, + u.username AS userName, + u.loginname AS loginName, + u.sex, + u.mobile, + u.technicalTitle, + IFNULL(u.unitname, '暂无') AS unitName, + u.unitid AS unitId, + u.unionid AS unionId, + u.unionname AS unionName, + DATE(u.birthday) AS birthday, + u.idCard, + u.unionCode + FROM + vw_user u + %s + $condition + """.formatted(clubJoinSql)); + Cnd cnd = Cnd.NEW(); + if (StrUtil.isNotBlank(keyword)) { + SqlExpressionGroup seg = new SqlExpressionGroup(); + seg.or("u.loginname", "like", "%" + keyword + "%"); + seg.or("u.username", "like", "%" + keyword + "%"); + cnd.and(seg); + } + cnd.and("u.id", "!=", SecurityUtil.getUserId()); + if (clubFundSource) { + cnd.and("cu.clubId", "=", clubId); + } else if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { + cnd.and("u.unionid", "=", SecurityUtil.getUnionId()); + } + cnd.groupBy("u.id"); + sql.setCondition(cnd); + Pagination pagination = this.listPageMap(1, 50, sql); + return pagination.getList(); + } + @Override public Result checkInvoiceDuplicate(String invoiceNo, String reimburseId) { String currentInvoiceNo = StrUtil.blankToDefault(StrUtil.trim(invoiceNo), ""); @@ -244,7 +313,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i * 按经费来源统一查询当前可用余额,未分配或权限不匹配时直接拦截。 */ @Override - public Result getBudgetBalance(String reimburseFundSource, String clubId) { + public Result getBudgetBalance(String reimburseFundSource, String clubId, String unionId) { if (StrUtil.isBlank(reimburseFundSource)) { return Result.error("请选择经费来源"); } @@ -257,8 +326,9 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i return Result.success(formatMoney(outlayManageSchool.getTotalQuota().subtract(outlayManageSchool.getUsedQuota()))); } if ("UNION_REIMBURSE_FUND_SOURCE_2".equals(reimburseFundSource)) { + String queryUnionId = StrUtil.blankToDefault(unionId, SecurityUtil.getUnionId()); OutlayManageUnion outlayManageUnion = dao().fetch(OutlayManageUnion.class, - Cnd.where("unionId", "=", SecurityUtil.getUnionId()).and("year", "=", DateUtil.thisYear())); + Cnd.where("unionId", "=", queryUnionId).and("year", "=", DateUtil.thisYear())); if (outlayManageUnion == null) { return Result.error("分工会经费未分配"); } @@ -315,6 +385,58 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i return Result.success(); } + /** + * 批量审核前只查询前端勾选且仍处于待审核确认状态的数据,避免误处理未勾选或状态已变化的申请。 + */ + @Override + public Result batchReviewApply(String[] reimburseIds) { + if (reimburseIds == null || reimburseIds.length == 0) { + return Result.error("请选择需要审核的报销申请"); + } + List reimbursements = this.query(Cnd.where("id", "in", reimburseIds).and("stateId", "=", 2)); + if (reimbursements.isEmpty()) { + return Result.error("所选记录中没有待审核的数据"); + } + for (UnionReimburse reimbursement : reimbursements) { + Result result = this.reviewApply(reimbursement.getId(), "通过", 3); + if (result.getCode() != 0) { + return result; + } + } + return Result.success("一键审核完成,共处理" + reimbursements.size() + "条记录"); + } + + /** + * 管理员修改实际金额时,报销成功的数据已经写入预算台账,需要按差额同步已使用额度和使用明细。 + */ + @Override + public Result updateActuallyAmount(String id, Double realMoney) { + if (StrUtil.isBlank(id)) { + return Result.error("报销记录ID不能为空"); + } + BigDecimal newMoney = normalizeActualMoney(realMoney); + if (newMoney == null || newMoney.compareTo(BigDecimal.ZERO) <= 0) { + return Result.error("实际报销金额必须大于0"); + } + UnionReimburse dbRecord = this.fetch(id); + if (dbRecord == null) { + return Result.error("未找到对应的报销记录"); + } + if (Integer.valueOf(3).equals(dbRecord.getStateId())) { + OutlayUseDetail oldDetail = dao().fetch(OutlayUseDetail.class, Cnd.where("outlayReimburseId", "=", dbRecord.getId())); + BigDecimal oldMoney = oldDetail != null && oldDetail.getAdjustMoney() != null + ? oldDetail.getAdjustMoney() + : getApplyMoney(dbRecord); + Result syncResult = syncApprovedActualAmount(dbRecord, oldDetail, oldMoney, newMoney); + if (syncResult != null && syncResult.getCode() != 0) { + return syncResult; + } + } + dbRecord.setRealMoney(newMoney.doubleValue()); + this.updateIgnoreNull(dbRecord); + return Result.success(); + } + private void normalizeInvoiceDetails(UnionReimburse unionReimburse) { if (unionReimburse.getInvoiceDetails() == null) { unionReimburse.setInvoiceDetails(new ArrayList<>()); @@ -360,6 +482,36 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i unionReimburse.setInvoiceNumber(String.valueOf(invoiceFileCount)); } + /** + * 慰问类报销允许不填发票;有发票时按发票汇总金额申请,没有发票时按慰问金额申请。 + */ + private void fillCondolenceInvoiceSummary(UnionReimburse unionReimburse) { + double totalMoney = 0D; + int invoiceFileCount = 0; + for (UnionReimburseInvoiceDetail detail : unionReimburse.getInvoiceDetails()) { + if (detail.getInvoiceAmount() != null) { + totalMoney = BigDecimal.valueOf(totalMoney) + .add(BigDecimal.valueOf(detail.getInvoiceAmount())) + .doubleValue(); + } + if (Lang.isNotEmpty(detail.getInvoiceFiles())) { + invoiceFileCount++; + } + } + Double applyMoney = invoiceFileCount > 0 + ? BigDecimal.valueOf(totalMoney).setScale(2, RoundingMode.HALF_UP).doubleValue() + : unionReimburse.getCondolenceMoney(); + unionReimburse.setMoney(applyMoney); + unionReimburse.setInvoiceNumber(String.valueOf(invoiceFileCount)); + } + + /** + * 默认实际金额跟随申请金额,后续管理员可在汇总页按最终报销金额调整。 + */ + private void fillDefaultRealMoney(UnionReimburse unionReimburse) { + unionReimburse.setRealMoney(getDeclaredApplyMoney(unionReimburse).doubleValue()); + } + /** * 提交前补一层后端校验,防止前端绕过校验直接提交。 */ @@ -379,11 +531,8 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i // if (StrUtil.isBlank(unionReimburse.getBankOfDeposit())) { // return Result.error("请填写开户行"); // } - if (StrUtil.isBlank(unionReimburse.getPaymentNotes())) { - return Result.error("请填写支付内容"); - } if (!needInvoiceDetails(unionReimburse)) { - return null; + return validateInvoiceDetails(unionReimburse, false); } if (StrUtil.isBlank(unionReimburse.getActivityName())) { return Result.error("请填写活动名称"); @@ -397,7 +546,17 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i if (Lang.isEmpty(unionReimburse.getFiles())) { return Result.error("请上传附件"); } + return validateInvoiceDetails(unionReimburse, true); + } + + /** + * 发票明细提交校验:活动类必须至少一条,慰问类允许为空但填写后必须完整。 + */ + private Result validateInvoiceDetails(UnionReimburse unionReimburse, boolean required) { if (Lang.isEmpty(unionReimburse.getInvoiceDetails())) { + if (!required) { + return null; + } return Result.error("请至少维护一条发票明细"); } for (int i = 0; i < unionReimburse.getInvoiceDetails().size(); i++) { @@ -426,7 +585,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i * 提交和审核前统一校验预算是否已分配、余额是否充足。 */ private Result validateFundSourceBeforeSubmit(UnionReimburse unionReimburse) { - Result balanceResult = getBudgetBalance(unionReimburse.getReimburseFundSource(), unionReimburse.getClubId()); + Result balanceResult = getBudgetBalance(unionReimburse.getReimburseFundSource(), unionReimburse.getClubId(), unionReimburse.getUnionId()); if (balanceResult == null || balanceResult.getCode() != 0) { return balanceResult == null ? Result.error("经费余额校验失败") : balanceResult; } @@ -493,6 +652,117 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i return Result.error("不支持的经费来源"); } + /** + * 已通过报销单修改实际金额时,用差额同步预算主表和使用明细,避免重复全量扣减。 + */ + private Result syncApprovedActualAmount(UnionReimburse unionReimburse, OutlayUseDetail oldDetail, + BigDecimal oldMoney, BigDecimal newMoney) { + BigDecimal diffMoney = newMoney.subtract(safeMoney(oldMoney)).setScale(2, RoundingMode.HALF_UP); + String reimburseFundSource = unionReimburse.getReimburseFundSource(); + if ("UNION_REIMBURSE_FUND_SOURCE_1".equals(reimburseFundSource)) { + OutlayManageSchool outlayManageSchool = dao().fetch(OutlayManageSchool.class, + Cnd.where("year", "=", DateUtil.thisYear())); + if (outlayManageSchool == null) { + return Result.error("校工会经费未分配"); + } + Result quotaResult = updateSchoolUsedQuota(outlayManageSchool, diffMoney); + if (quotaResult.getCode() != 0) { + return quotaResult; + } + saveOrUpdateUseDetail(unionReimburse, oldDetail, outlayManageSchool.getId(), newMoney); + return Result.success(); + } + if ("UNION_REIMBURSE_FUND_SOURCE_2".equals(reimburseFundSource)) { + OutlayManageUnion outlayManageUnion = dao().fetch(OutlayManageUnion.class, + Cnd.where("unionId", "=", unionReimburse.getUnionId()).and("year", "=", DateUtil.thisYear())); + if (outlayManageUnion == null) { + return Result.error("分工会经费未分配"); + } + Result quotaResult = updateUnionUsedQuota(outlayManageUnion, diffMoney); + if (quotaResult.getCode() != 0) { + return quotaResult; + } + saveOrUpdateUseDetail(unionReimburse, oldDetail, outlayManageUnion.getId(), newMoney); + return Result.success(); + } + if ("UNION_REIMBURSE_FUND_SOURCE_3".equals(reimburseFundSource)) { + OutlayManageClub outlayManageClub = dao().fetch(OutlayManageClub.class, + Cnd.where("clubId", "=", unionReimburse.getClubId()).and("year", "=", DateUtil.thisYear())); + if (outlayManageClub == null) { + return Result.error("该协会本年度未生成预算记录,请先在协会预算分配中补分配"); + } + Result quotaResult = updateClubUsedQuota(outlayManageClub, diffMoney); + if (quotaResult.getCode() != 0) { + return quotaResult; + } + saveOrUpdateUseDetail(unionReimburse, oldDetail, outlayManageClub.getId(), newMoney); + return Result.success(); + } + return Result.error("不支持的经费来源"); + } + + private Result updateSchoolUsedQuota(OutlayManageSchool outlayManageSchool, BigDecimal diffMoney) { + BigDecimal newUsedQuota = buildNewUsedQuota(outlayManageSchool.getTotalQuota(), outlayManageSchool.getUsedQuota(), diffMoney, "校工会经费"); + if (newUsedQuota == null) { + return Result.error("校工会经费余额不足"); + } + outlayManageSchool.setUsedQuota(newUsedQuota); + dao().updateIgnoreNull(outlayManageSchool); + return Result.success(); + } + + private Result updateUnionUsedQuota(OutlayManageUnion outlayManageUnion, BigDecimal diffMoney) { + BigDecimal newUsedQuota = buildNewUsedQuota(outlayManageUnion.getTotalQuota(), outlayManageUnion.getUsedQuota(), diffMoney, "分工会经费"); + if (newUsedQuota == null) { + return Result.error("分工会经费余额不足"); + } + outlayManageUnion.setUsedQuota(newUsedQuota); + dao().updateIgnoreNull(outlayManageUnion); + return Result.success(); + } + + private Result updateClubUsedQuota(OutlayManageClub outlayManageClub, BigDecimal diffMoney) { + BigDecimal newUsedQuota = buildNewUsedQuota(outlayManageClub.getTotalQuota(), outlayManageClub.getUsedQuota(), diffMoney, "协会经费"); + if (newUsedQuota == null) { + return Result.error("协会经费余额不足"); + } + outlayManageClub.setUsedQuota(newUsedQuota); + dao().updateIgnoreNull(outlayManageClub); + return Result.success(); + } + + /** + * 计算调整后的已使用额度,调增时不能超过总额度,调减时不能小于0。 + */ + private BigDecimal buildNewUsedQuota(BigDecimal totalQuota, BigDecimal usedQuota, BigDecimal diffMoney, String outlayName) { + BigDecimal safeTotalQuota = safeMoney(totalQuota); + BigDecimal safeUsedQuota = safeMoney(usedQuota); + BigDecimal safeDiffMoney = safeMoney(diffMoney); + BigDecimal newUsedQuota = safeUsedQuota.add(safeDiffMoney).setScale(2, RoundingMode.HALF_UP); + if (safeDiffMoney.compareTo(BigDecimal.ZERO) == 0) { + return newUsedQuota; + } + if (newUsedQuota.compareTo(BigDecimal.ZERO) < 0) { + return null; + } + if (newUsedQuota.compareTo(safeTotalQuota) > 0) { + log.warn("{}实际金额调增后超出预算,总额度:{}, 已使用:{}, 调整差额:{}", outlayName, safeTotalQuota, safeUsedQuota, diffMoney); + return null; + } + return newUsedQuota; + } + + private void saveOrUpdateUseDetail(UnionReimburse unionReimburse, OutlayUseDetail oldDetail, + String outlayManageId, BigDecimal newMoney) { + if (oldDetail == null) { + insertUseDetail(unionReimburse, outlayManageId, newMoney); + return; + } + oldDetail.setOutlayManageId(outlayManageId); + oldDetail.setAdjustMoney(newMoney); + dao().updateIgnoreNull(oldDetail); + } + /** * 一张报销单只写入一条预算使用详情,避免重复通过或重复点击造成重复明细。 */ @@ -516,11 +786,31 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i } private BigDecimal getApplyMoney(UnionReimburse unionReimburse) { - Double money = "UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject()) - ? unionReimburse.getCondolenceMoney() : unionReimburse.getMoney(); + if (unionReimburse.getRealMoney() != null) { + return BigDecimal.valueOf(unionReimburse.getRealMoney()).setScale(2, RoundingMode.HALF_UP); + } + return getDeclaredApplyMoney(unionReimburse); + } + + private BigDecimal getDeclaredApplyMoney(UnionReimburse unionReimburse) { + Double money = unionReimburse.getMoney(); + if (money == null && "UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject())) { + money = unionReimburse.getCondolenceMoney(); + } return BigDecimal.valueOf(money == null ? 0D : money).setScale(2, RoundingMode.HALF_UP); } + private BigDecimal normalizeActualMoney(Double realMoney) { + if (realMoney == null || realMoney.isNaN() || realMoney.isInfinite()) { + return null; + } + return realMoney == null ? null : BigDecimal.valueOf(realMoney).setScale(2, RoundingMode.HALF_UP); + } + + private BigDecimal safeMoney(BigDecimal money) { + return money == null ? BigDecimal.ZERO : money.setScale(2, RoundingMode.HALF_UP); + } + private String buildUseDetailProjectName(UnionReimburse unionReimburse) { if (StrUtil.isNotBlank(unionReimburse.getActivityName())) { return unionReimburse.getActivityName(); @@ -851,7 +1141,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl i } /** - * 当前仅活动类报销维护发票明细,慰问类仍按原有逻辑处理金额。 + * 非慰问类报销必须维护发票明细,慰问类发票明细为可选。 */ private boolean needInvoiceDetails(UnionReimburse unionReimburse) { return unionReimburse != null && !"UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject()); diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java index a75e2f3c..de4450fc 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/dashboard/ProposalDashboardController.java @@ -59,7 +59,7 @@ public class ProposalDashboardController { nodes.add(NutMap.NEW().addv("name", "提案总数").addv("id", "total").addv("type", "total").addv("count", 0)); // 节点 - ProcessDefine define = processDefineService.getLastByName("JDHTA"); + ProcessDefine define = processDefineService.getLastByName("JDHTA_NC"); ProcessModel processModel = processDefineService.processDefineToModel(define); List taskModels = processModel.getTasks(); diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/query/ProposalQueryChartController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/query/ProposalQueryChartController.java index e5d4a209..6c8c3700 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/query/ProposalQueryChartController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/query/ProposalQueryChartController.java @@ -141,7 +141,7 @@ public class ProposalQueryChartController { FROM wf_process_design WHERE - NAME = 'JDHTA' + NAME = 'JDHTA_NC' """); sql.setCallback(Sqls.callback.map()); dao.execute(sql); diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java index cedcf17b..326cf7a7 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSecondedController.java @@ -120,6 +120,7 @@ public class ProposalSecondedController { Cnd cnd = Cnd.NEW(); cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId())); + cnd.and("info.createUserId", "!=", SecurityUtil.getUserId()); if (approval) { // 已附议页签只展示当前登录附议人已经处理过的记录。 diff --git a/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/delegate/controller/TeacherCongressDelegatePushController.java b/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/delegate/controller/TeacherCongressDelegatePushController.java index b597d30c..97405523 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/delegate/controller/TeacherCongressDelegatePushController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/delegate/controller/TeacherCongressDelegatePushController.java @@ -179,7 +179,7 @@ public class TeacherCongressDelegatePushController { """); sql.setParam("sessionId", sessionId); Cnd cnd = Cnd.NEW(); -// cnd.and("u.member", "=", 1); + cnd.and("u.member", "=", 1); cnd.and("u.username", "is not", null); cnd.andEX("u.id", "not in", userIds); cnd.and("tcd.userId", "is", null); diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeMineController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeMineController.java index 8dbe211e..c365445d 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeMineController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeMineController.java @@ -12,6 +12,8 @@ import com.budwk.app.bpm.service.BpmService; import com.budwk.app.flow.engine.FlowEngine; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; +import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeOrigin; +import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType; import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord; import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberChangePageForm; import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService; @@ -22,6 +24,7 @@ import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.cri.Static; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; @@ -111,6 +114,12 @@ public class MemberChangeMineController { cnd.and("info.userId", "=", SecurityUtil.getUserId()); } } + // Filter manual input records created by member info input + cnd.and(new Static( + "(info.changeOrigin <> '" + MemberChangeOrigin.HAND_MOVEMENT.name() + + "' OR info.changeOrigin IS NULL OR info.changeType <> '" + MemberChangeType.NEW.name() + + "' OR info.changeType IS NULL)" + )); if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) { cnd.desc("info.applyDateTime"); } else { diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java index b83a7b43..03828356 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java @@ -46,6 +46,8 @@ import org.nutz.lang.Lang; import org.nutz.lang.random.R; import org.nutz.lang.util.NutMap; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -298,6 +300,7 @@ public class MemberManageServiceImpl extends BaseServiceImpl implement @Override public void compareChangeInfoAndUpdateMember(MemberChangeRecord record) { + normalizeArrivalAtSchoolDate(record); List changeList = getChangeInfos(record); // 如果有工会关系人员,这个user用来存储数据 @@ -503,6 +506,7 @@ public class MemberManageServiceImpl extends BaseServiceImpl implement @Override public void validateChangeAndSetBasicData(MemberChangeRecord record, MemberChangeOrigin origin) { + normalizeArrivalAtSchoolDate(record); // 校验有没有发生变更 List changeInfos = getChangeInfos(record); if (Lang.isEmpty(changeInfos)) { @@ -521,6 +525,39 @@ public class MemberManageServiceImpl extends BaseServiceImpl implement } + /** + * 统一规范变更记录中的到校日期,避免 Date 对象复制后以英文字符串形式参与比较或直接落库。 + */ + private void normalizeArrivalAtSchoolDate(MemberChangeRecord record) { + if (record == null || StrUtil.isBlank(record.getArrivalAtSchoolDate())) { + return; + } + String arrivalAtSchoolDate = StrUtil.trim(record.getArrivalAtSchoolDate()); + try { + Date parsedDate = parseArrivalAtSchoolDate(arrivalAtSchoolDate); + record.setArrivalAtSchoolDate(DateUtil.format(parsedDate, "yyyy-MM-dd")); + } catch (Exception e) { + throw new BaseException("到校日期格式不正确,请使用yyyy-MM-dd格式"); + } + } + + /** + * 兼容页面手工变更时从视图对象复制出的英文日期字符串。 + */ + private Date parseArrivalAtSchoolDate(String arrivalAtSchoolDate) throws ParseException { + if (arrivalAtSchoolDate.matches("\\d{4}-\\d{2}-\\d{2}")) { + return DateUtil.parseDate(arrivalAtSchoolDate); + } + try { + return DateUtil.parse(arrivalAtSchoolDate); + } catch (Exception ignore) { + SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH); + format.setLenient(false); + return format.parse(arrivalAtSchoolDate); + } + } + + /** * 验证申请或变更是否正在进行 * @param userId 用户id diff --git a/src/main/java/com/budwk/app/zhgh/welfare/model/WelfareProject.java b/src/main/java/com/budwk/app/zhgh/welfare/model/WelfareProject.java index 23c73483..fd7fa498 100644 --- a/src/main/java/com/budwk/app/zhgh/welfare/model/WelfareProject.java +++ b/src/main/java/com/budwk/app/zhgh/welfare/model/WelfareProject.java @@ -214,7 +214,7 @@ public class WelfareProject extends BaseModel implements SysHomeConvert { sysHomeActivity.setH5Url("/platform/h5/welfare/userSelect/index?id=" + this.getId()); sysHomeActivity.setStartDate(this.getChoiceTimeStart()); sysHomeActivity.setEndDate(this.getChoiceTimeEnd()); - sysHomeActivity.setAllowUserSql("select userId from welfare_list where projectId = '" + this.getId() + "' and userId = @userId"); + sysHomeActivity.setAllowUserSql("select userId from welfare_list where projectId = (select id from welfare_project where id='" + this.getId() + "' and provideMode!=1) and userId = @userId "); sysHomeActivity.setEnable(true); sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName()); return sysHomeActivity; diff --git a/src/main/resources/static/assets/platform/js/tool/businessTool.js b/src/main/resources/static/assets/platform/js/tool/businessTool.js index 1972f9fb..f96cd476 100644 --- a/src/main/resources/static/assets/platform/js/tool/businessTool.js +++ b/src/main/resources/static/assets/platform/js/tool/businessTool.js @@ -48,6 +48,18 @@ const businessTool = { } }) }, + listManageClubByRole() { + return commonUtil + .axiosService() + .post("/platform/club/common/listManageClubByRole", {}) + .then((resp) => { + if (resp.code === 0) { + return resp.data + } else { + return [] + } + }) + }, /** * 获取字典数据,不包含禁用 * @param code diff --git a/src/main/resources/static/components/module/activity/UserScope.vue b/src/main/resources/static/components/module/activity/UserScope.vue index c1a609fa..75b84032 100644 --- a/src/main/resources/static/components/module/activity/UserScope.vue +++ b/src/main/resources/static/components/module/activity/UserScope.vue @@ -15,6 +15,7 @@ placeholder="输入工号或者姓名查询" :remote-method="queryUser" @change="doSearch" + @clear="doSearch" style="width: 100%" > - + 社团: 0) { - this.$set(this.pageForm, "clubId", this.clubOptions[0].id) - } await this.getActivityGroup() this.personTypeOptions = await this.$businessTool.getDictOptions("USER_PERSON_TYPE") this.userStateOptions = await this.$businessTool.getDictOptions("USER_STATE") diff --git a/src/main/resources/static/components/plugins/sysSignature/h5Index.vue b/src/main/resources/static/components/plugins/sysSignature/h5Index.vue index 62726682..6c78cdbf 100644 --- a/src/main/resources/static/components/plugins/sysSignature/h5Index.vue +++ b/src/main/resources/static/components/plugins/sysSignature/h5Index.vue @@ -15,7 +15,7 @@ diff --git a/src/main/resources/views/platform/zhghh5/dayofficework/unionReimburse/collect/index.html b/src/main/resources/views/platform/zhghh5/dayofficework/unionReimburse/collect/index.html index 41127a5a..be53964a 100644 --- a/src/main/resources/views/platform/zhghh5/dayofficework/unionReimburse/collect/index.html +++ b/src/main/resources/views/platform/zhghh5/dayofficework/unionReimburse/collect/index.html @@ -2,6 +2,19 @@ layout("/layouts/platform_h5.html"){ #--> +
@@ -22,31 +35,26 @@ layout("/layouts/platform_h5.html"){