Merge branch 'main' of https://dd3skj.picp.vip/JyuHsin/zhgh_cug_v4
This commit is contained in:
@@ -1,17 +1,7 @@
|
||||
package com.budwk.app.base.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.constant.RedisConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
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;
|
||||
@@ -33,19 +23,17 @@ public class DataCenterProperties {
|
||||
|
||||
@Inject
|
||||
private PropertiesProxy conf;
|
||||
@Inject
|
||||
private RedisService redisService;
|
||||
|
||||
private String appId;
|
||||
private String tokenUrl;
|
||||
private Map<String, String> codes = new HashMap<>();
|
||||
private String key;
|
||||
private String secret;
|
||||
private Map<String, String> urls = new HashMap<>();
|
||||
|
||||
public void init() {
|
||||
Map<String, String> all = conf.toMap();
|
||||
appId = all.get("data-center.app-id");
|
||||
tokenUrl = all.get("data-center.token-url");
|
||||
extract(all, "data-center.codes.", codes);
|
||||
key = all.get("data-center.key");
|
||||
secret = all.get("data-center.secret");
|
||||
extract(all, "data-center.urls.", urls);
|
||||
}
|
||||
|
||||
@@ -61,59 +49,4 @@ public class DataCenterProperties {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取配置
|
||||
* @param key 接口类型枚举
|
||||
* @return 相关配置
|
||||
*/
|
||||
public Credential credential(String key) {
|
||||
String code = codes.get(key);
|
||||
String url = urls.get(key);
|
||||
if (code == null || url == null) {
|
||||
throw new BaseException("未知业务: " + key);
|
||||
}
|
||||
String token = getToken(Integer.parseInt(code), key);
|
||||
return new Credential(url, token);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class Credential {
|
||||
private String url;
|
||||
private String token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
* @param code
|
||||
* @param keyPrefix
|
||||
* @return token
|
||||
*/
|
||||
private String getToken(Integer code, String keyPrefix) {
|
||||
String dateCenterPrefix = RedisConstant.DATE_CENTER_PREFIX + keyPrefix;
|
||||
String token = redisService.get(dateCenterPrefix);
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
return token;
|
||||
} else {
|
||||
HttpRequest httpRequest = HttpUtil.createPost(tokenUrl);
|
||||
Map<String, Object> reqBody = Map.of(
|
||||
"appId", appId,
|
||||
"code", code
|
||||
);
|
||||
httpRequest.body(JSONUtil.toJsonStr(reqBody));
|
||||
JSONObject resp = JSONUtil.parseObj(httpRequest.execute().body());
|
||||
|
||||
if (resp.getInt("code") != 200) {
|
||||
throw new BaseException("获取token失败: " + resp.getStr("msg"));
|
||||
} else {
|
||||
JSONObject data = resp.getJSONObject("data");
|
||||
String tokenStr = data.getStr("token");
|
||||
Integer expire = data.getInt("expire");
|
||||
redisService.setex(dateCenterPrefix, expire, tokenStr);
|
||||
return tokenStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
@DataCenterColumn(name = "工号", key = "JGH")
|
||||
@DataCenterColumn(name = "工号", key = "ZGH")
|
||||
private String loginname;
|
||||
|
||||
@Column
|
||||
@@ -49,7 +49,7 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("性别")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
@DataCenterColumn(name = "姓别", key = "XBM", dict = "USER_SEX")
|
||||
@DataCenterColumn(name = "性别", key = "XB", dict = "USER_SEX")
|
||||
private String sex;
|
||||
|
||||
@Column
|
||||
@@ -108,13 +108,12 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("手机号码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@DataCenterColumn(name = "手机号码", key = "SJHM")
|
||||
@DataCenterColumn(name = "手机号码", key = "YDDH")
|
||||
private String mobile;
|
||||
|
||||
@Column
|
||||
@Comment("学历")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
@DataCenterColumn(name = "学历", key = "ZGXLM", dict = "USER_EDUCATION")
|
||||
private String education;
|
||||
|
||||
@Column
|
||||
@@ -131,19 +130,16 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("岗位类别")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
@DataCenterColumn(name = "岗位类别", key = "JRGWDJM",dict = "USER_JOB_CATEGORY")
|
||||
private String jobCategory;
|
||||
|
||||
@Column
|
||||
@Comment("职称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
@DataCenterColumn(name = "职称", key = "DQZC", dict = "USER_PROFESSIONAL_TITLE")
|
||||
private String professionalTitle;
|
||||
|
||||
@Column
|
||||
@Comment("职级")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
@DataCenterColumn(name = "取得职称等级码", key = "QDZCDJM", dict = "USER_PROFESSIONAL_LEVEL")
|
||||
private String professionalLevel;
|
||||
|
||||
@Column
|
||||
@@ -179,7 +175,7 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("来校时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
@DataCenterColumn(name = "来校时间", key = "LXRQ")
|
||||
@DataCenterColumn(name = "来校时间", key = "LXNY")
|
||||
private String arrivalAtSchoolDate;
|
||||
|
||||
@Column
|
||||
@@ -195,7 +191,7 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("在职状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
@DataCenterColumn(name = "当前状态码", key = "DQZTM", dict = "USER_STATE")
|
||||
@DataCenterColumn(name = "当前状态码", key = "DQZT", dict = "USER_STATE")
|
||||
private String userState;
|
||||
|
||||
@Column
|
||||
@@ -207,19 +203,17 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@Column
|
||||
@Comment("在岗情况")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
@DataCenterColumn(name = "在岗情况码", key = "ZGQKM", dict = "USER_DUTY_SITUATION")
|
||||
private String dutySituation;
|
||||
|
||||
@Column
|
||||
@Comment("教职工类别码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
@DataCenterColumn(name = "教职工类别码", key = "JZGLBM", dict = "USER_PERSON_TYPE")
|
||||
@DataCenterColumn(name = "教职工类别码", key = "RYLX", dict = "USER_PERSON_TYPE")
|
||||
private String personType;
|
||||
|
||||
@Column
|
||||
@Comment("编制类别码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
@DataCenterColumn(name = "编制类别码", key = "BZLBM", dict = "USER_PREPARED_BY_TYPE")
|
||||
private String preparedBy;
|
||||
|
||||
@Column
|
||||
@@ -269,7 +263,7 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@DataCenterColumn(name = "单位", key = "DWH")
|
||||
@DataCenterColumn(name = "单位", key = "SZDWH")
|
||||
private String unitId;
|
||||
|
||||
@Column
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
package com.budwk.app.sys.services.impl;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.config.DataCenterProperties;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.models.Sys_data_dict;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDataDictPullService;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
@@ -35,14 +21,6 @@ import java.util.Map;
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SysDataDictPullServiceImpl extends BaseServiceImpl<Sys_dict> implements SysDataDictPullService {
|
||||
|
||||
@Inject
|
||||
private DataCenterProperties dcPro;
|
||||
@Inject
|
||||
private SysDictService dictService;
|
||||
|
||||
// code
|
||||
private final static String KEY_PREFIX = "user_";
|
||||
|
||||
public SysDataDictPullServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -50,57 +28,7 @@ public class SysDataDictPullServiceImpl extends BaseServiceImpl<Sys_dict> implem
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void pullDataDict() {
|
||||
Map<String, String> urls = dcPro.getUrls();
|
||||
List<String> list = urls.keySet().stream().filter(o -> o.startsWith(KEY_PREFIX.toUpperCase())).toList();
|
||||
|
||||
List<Sys_data_dict> dataDictList = new ArrayList<>();
|
||||
|
||||
for (String key : list) {
|
||||
// 获取配置
|
||||
DataCenterProperties.Credential credential = dcPro.credential(key);
|
||||
String url = credential.getUrl();
|
||||
String token = credential.getToken();
|
||||
|
||||
// 请求数据
|
||||
HttpRequest httpRequest = HttpUtil.createPost(url);
|
||||
httpRequest.header("Content-Type", "application/json");
|
||||
httpRequest.header("X-H3C-TOKEN", token);
|
||||
httpRequest.body(JSONUtil.toJsonStr(Map.of()));
|
||||
|
||||
String resBody = httpRequest.execute().body();
|
||||
JSONObject jsonBody = JSONUtil.parseObj(resBody);
|
||||
|
||||
if (jsonBody.getInt("code") != 200) {
|
||||
log.error("获取{}字典数据失败,错误码: {};错误原因:{}", key, jsonBody.getInt("code"), jsonBody.getStr("msg"));
|
||||
}
|
||||
List<NutMap> data = Json.fromJsonAsList(NutMap.class, jsonBody.getStr("data"));
|
||||
|
||||
// 先删除
|
||||
Sys_dict sysDict = dao().fetch(Sys_dict.class, Cnd.where(Sys_dict::getCode, "=", key));
|
||||
dao().clear(Sys_dict.class, Cnd.where(Sys_dict::getParentId, "=", sysDict.getId()));
|
||||
|
||||
// 组合数据
|
||||
data.forEach(o -> {
|
||||
Sys_dict dict = new Sys_dict();
|
||||
dict.setParentId(sysDict.getId());
|
||||
|
||||
dict.setPath(dictService.getSubPath("sys_dict", "path", sysDict.getPath()));
|
||||
dict.setName(o.getString("xbname"));
|
||||
dict.setCode(o.getString("xbname"));
|
||||
dict.setDisabled(false);
|
||||
dict.setHasChildren(false);
|
||||
dao().insert(dict);
|
||||
|
||||
Sys_data_dict sysDataDict = new Sys_data_dict();
|
||||
sysDataDict.setParentCode(key);
|
||||
sysDataDict.setCode(o.getString("xbcode"));
|
||||
sysDataDict.setName(o.getString("xbname"));
|
||||
|
||||
dataDictList.add(sysDataDict);
|
||||
|
||||
});
|
||||
|
||||
dao().insert(dataDictList);
|
||||
}
|
||||
// 旧数据中心字典接口已停用,避免继续误调旧token/code模式接口。
|
||||
throw new BaseException("旧数据中心字典拉取接口已停用");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
package com.budwk.app.sys.services.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.config.DataCenterProperties;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.enums.Api2UnitFiledMap;
|
||||
import com.budwk.app.sys.models.Sys_unit;
|
||||
import com.budwk.app.sys.services.SysDataUnitPullService;
|
||||
import com.budwk.app.sys.utils.DataCenterUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -40,8 +41,11 @@ import java.util.stream.Collectors;
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implements SysDataUnitPullService {
|
||||
|
||||
private static final String DMP_UNIT_KEY = "unit";
|
||||
private static final int DMP_PAGE_SIZE = 1000;
|
||||
|
||||
@Inject
|
||||
private DataCenterProperties dcPro;
|
||||
private DataCenterProperties dataCenterProperties;
|
||||
|
||||
public SysDataUnitPullServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
@@ -51,11 +55,7 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void updateUnits() {
|
||||
Sql sql = Sqls.create("select id from sys_unit group by id");
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao().execute(sql);
|
||||
List<String> unitIds = sql.getList(String.class);
|
||||
updateUnits(unitIds);
|
||||
updateUnits(List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,65 +64,235 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl<Sys_unit> implem
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void updateUnits(List<String> unitIds) {
|
||||
// token url参数
|
||||
Map<String, String> tokenParams = Map.of(
|
||||
"grant_type", "password",
|
||||
"scope", "read",
|
||||
"username", "3848A3C738EFBB2C2E76E0D59B419DACA16A80F3AB0354CD",
|
||||
"password", "3848A3C738EFBB2C2E76E0D59B419DACA16A80F3AB0354CD"
|
||||
);
|
||||
String accessToken = getDmpAccessToken();
|
||||
List<JSONObject> rawDataList = distinctDmpUnits(pullDmpUnits(getDmpUnitUrl(), accessToken));
|
||||
Set<String> parentCodes = rawDataList.stream()
|
||||
.map(this::getNormalizedParentCode)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
String params = URLUtil.buildQuery(tokenParams, StandardCharsets.UTF_8);
|
||||
String tokenUrl = "https://bd.whcp.edu.cn/sjjkfwpt/api/bd-api/oauth/token" + (StrUtil.isBlank(params) ? "" : "?" + params);
|
||||
HttpRequest tokenHttpRequest = HttpUtil.createPost(tokenUrl);
|
||||
tokenHttpRequest.header("Content-Type", "application/json");
|
||||
tokenHttpRequest.header("Authorization", "Basic MTE0OjU5M2UwNGI4YTE4NWRlMzkyNjY4ZmY0ZDJhZGMyZTEw");
|
||||
rawDataList.stream()
|
||||
.sorted(Comparator.comparingInt(this::getUnitLevel))
|
||||
.forEach(raw -> saveOrUpdateUnit(raw, parentCodes));
|
||||
|
||||
log.info("请求token: {}", tokenHttpRequest);
|
||||
String tokenResBody = tokenHttpRequest.execute().body();
|
||||
log.info("信息中心单位数据同步完成,本次接口返回单位数量: {},待检查单位编码数量: {}", rawDataList.size(), unitIds == null ? 0 : unitIds.size());
|
||||
}
|
||||
|
||||
log.info("请求token结果: {}", tokenResBody);
|
||||
/**
|
||||
* 获取信息中心开放平台访问令牌。
|
||||
* 参数来自 data-center.token-url、data-center.key、data-center.secret;
|
||||
* 返回值为后续单位接口 body 中 access_token 使用的字符串。
|
||||
*/
|
||||
private String getDmpAccessToken() {
|
||||
String tokenUrl = dataCenterProperties.getTokenUrl();
|
||||
if (StrUtil.isBlank(tokenUrl)) {
|
||||
throw new BaseException("未配置信息中心token地址: data-center.token-url");
|
||||
}
|
||||
String key = dataCenterProperties.getKey();
|
||||
if (StrUtil.isBlank(key)) {
|
||||
throw new BaseException("未配置信息中心token key: data-center.key");
|
||||
}
|
||||
String secret = dataCenterProperties.getSecret();
|
||||
if (StrUtil.isBlank(secret)) {
|
||||
throw new BaseException("未配置信息中心token secret: data-center.secret");
|
||||
}
|
||||
|
||||
String params = URLUtil.buildQuery(Map.of("key", key, "secret", secret), StandardCharsets.UTF_8);
|
||||
String tokenResBody = HttpUtil.createGet(tokenUrl + "?" + params).execute().body();
|
||||
JSONObject tokenJsonBody = JSONUtil.parseObj(tokenResBody);
|
||||
|
||||
// 请求数据
|
||||
HttpRequest httpRequest = HttpUtil.createGet("https://bd.whcp.edu.cn/sjjkfwpt/api/bd-api/get/YXSDWJBSJZLB");
|
||||
httpRequest.form(Map.of("access_token", tokenJsonBody.getStr("access_token"), "pageSize", 100, "page", 1));
|
||||
log.info("请求单位数据: {}", httpRequest);
|
||||
String resBody = httpRequest.execute().body();
|
||||
log.info("请求单位数据结果: {}", resBody);
|
||||
JSONObject jsonBody = JSONUtil.parseObj(resBody);
|
||||
|
||||
if (jsonBody.getInt("code") != 200) {
|
||||
throw new BaseException("获取单位数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("msg"));
|
||||
String accessToken = tokenJsonBody.getStr("access_token");
|
||||
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("result") != null) {
|
||||
accessToken = tokenJsonBody.getJSONObject("result").getStr("access_token");
|
||||
}
|
||||
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("data") != null) {
|
||||
accessToken = tokenJsonBody.getJSONObject("data").getStr("access_token");
|
||||
}
|
||||
if (StrUtil.isBlank(accessToken)) {
|
||||
accessToken = tokenJsonBody.getStr("token");
|
||||
}
|
||||
if (StrUtil.isBlank(accessToken)) {
|
||||
throw new BaseException("获取信息中心token失败");
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
// 需要新增的单位
|
||||
List<Sys_unit> insertList = new ArrayList<>();
|
||||
// 需要更新的单位
|
||||
List<Sys_unit> updateList = new ArrayList<>();
|
||||
/**
|
||||
* 读取信息中心单位接口地址。
|
||||
* 配置项为 data-center.urls.unit,返回值为单位全量接口URL。
|
||||
*/
|
||||
private String getDmpUnitUrl() {
|
||||
String url = dataCenterProperties.getUrls().get(DMP_UNIT_KEY);
|
||||
if (StrUtil.isBlank(url)) {
|
||||
throw new BaseException("未配置信息中心单位接口地址: data-center.urls." + DMP_UNIT_KEY);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// 拉取过来的数据,全部的单位,一万四千多条,这里只保留 “部门类”
|
||||
List<JSONObject> data = jsonBody.getJSONArray("data").stream()
|
||||
.map(o -> (JSONObject) o)
|
||||
.toList();
|
||||
/**
|
||||
* 分页拉取单位接口数据。
|
||||
* 入参 url 为单位接口地址,accessToken 为信息中心令牌;
|
||||
* 请求 body 包含 access_token、per_page、page;
|
||||
* 返回值为 result.data 合并后的单位原始 JSON 列表。
|
||||
*/
|
||||
private List<JSONObject> pullDmpUnits(String url, String accessToken) {
|
||||
List<JSONObject> rawDataList = new ArrayList<>();
|
||||
int page = 1;
|
||||
int maxPage = 1;
|
||||
int total = 0;
|
||||
|
||||
Map<String, String> api2db = Arrays.stream(Api2UnitFiledMap.values())
|
||||
.collect(Collectors.toMap(e -> e.apiField, e -> e.dbColumn));
|
||||
for (JSONObject row : data) {
|
||||
Sys_unit unit = DataCenterUtil.mapJsonToBean(row, Sys_unit.class, api2db);
|
||||
unit.setUnitcode(unit.getId());
|
||||
unit.setUnitTypeCode(1);
|
||||
do {
|
||||
HttpRequest httpRequest = HttpUtil.createPost(url);
|
||||
httpRequest.header(Header.CONTENT_TYPE, "application/json");
|
||||
httpRequest.body(JSONUtil.toJsonStr(Map.of(
|
||||
"access_token", accessToken,
|
||||
"per_page", String.valueOf(DMP_PAGE_SIZE),
|
||||
"page", String.valueOf(page)
|
||||
)));
|
||||
|
||||
if (unitIds.contains(unit.getId())) {
|
||||
// 存在则更新
|
||||
updateList.add(unit);
|
||||
} else {
|
||||
// 不存在则新增
|
||||
insertList.add(unit);
|
||||
log.info("请求信息中心单位数据,第{}页: {}", page, httpRequest);
|
||||
String resBody = httpRequest.execute().body();
|
||||
log.info("请求信息中心单位数据第{}页结果: {}", page, resBody);
|
||||
JSONObject jsonBody = JSONUtil.parseObj(resBody);
|
||||
|
||||
if (jsonBody.getInt("code") != 10000) {
|
||||
throw new BaseException("获取信息中心单位数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("message"));
|
||||
}
|
||||
|
||||
JSONObject result = jsonBody.getJSONObject("result");
|
||||
if (result == null) {
|
||||
break;
|
||||
}
|
||||
total = result.getInt("total", total);
|
||||
maxPage = result.getInt("max_page", maxPage);
|
||||
JSONArray data = result.getJSONArray("data");
|
||||
if (CollUtil.isNotEmpty(data)) {
|
||||
rawDataList.addAll(data.stream().map(v -> (JSONObject) v).toList());
|
||||
}
|
||||
log.info("信息中心单位数据拉取进度: {}/{}", rawDataList.size(), total);
|
||||
page++;
|
||||
} while (page <= maxPage);
|
||||
|
||||
return rawDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按单位代码去重,避免接口分页或源数据重复导致同一 DWDM 重复写入。
|
||||
*/
|
||||
private List<JSONObject> distinctDmpUnits(List<JSONObject> rawDataList) {
|
||||
Map<String, JSONObject> unitMap = new LinkedHashMap<>();
|
||||
for (JSONObject raw : rawDataList) {
|
||||
String unitCode = raw.getStr("DWDM");
|
||||
if (StrUtil.isBlank(unitCode)) {
|
||||
continue;
|
||||
}
|
||||
unitMap.putIfAbsent(unitCode, raw);
|
||||
}
|
||||
log.info("本次拉取单位数据,新增{}条,更新{}条", insertList.size(), updateList.size());
|
||||
dao().insert(insertList);
|
||||
dao().update(updateList);
|
||||
return new ArrayList<>(unitMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或更新单位。
|
||||
* DWDM 对应系统单位 id/unitcode,DWMC 对应名称,LSDWH 对应父级单位号,DWCC 对应信息中心原始单位层级。
|
||||
*/
|
||||
private void saveOrUpdateUnit(JSONObject raw, Set<String> parentCodes) {
|
||||
String unitCode = raw.getStr("DWDM");
|
||||
if (StrUtil.isBlank(unitCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String parentId = getParentId(unitCode, getNormalizedParentCode(raw));
|
||||
Sys_unit unit = fetch(unitCode);
|
||||
if (unit == null) {
|
||||
unit = new Sys_unit();
|
||||
unit.setId(unitCode);
|
||||
unit.setUnitcode(unitCode);
|
||||
unit.setPath(getUnitPath(parentId));
|
||||
setUnitValue(unit, raw, parentId, parentCodes);
|
||||
dao().fastInsert(unit);
|
||||
} else {
|
||||
setUnitValue(unit, raw, parentId, parentCodes);
|
||||
if (StrUtil.isBlank(unit.getPath())) {
|
||||
unit.setPath(getUnitPath(parentId));
|
||||
}
|
||||
dao().updateIgnoreNull(unit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口字段给系统单位赋值。
|
||||
* DWYXBS 表示单位是否有效,DWCC 表示信息中心原始单位层次;
|
||||
* 系统内置根单位占用一级层级,因此保存时统一将 DWCC 加一。
|
||||
*/
|
||||
private void setUnitValue(Sys_unit unit, JSONObject raw, String parentId, Set<String> parentCodes) {
|
||||
String unitCode = raw.getStr("DWDM");
|
||||
String unitName = raw.getStr("DWMC");
|
||||
String aliasName = StrUtil.blankToDefault(raw.getStr("DWJC"), unitName);
|
||||
Integer rawUnitLevel = raw.getInt("DWCC");
|
||||
Integer unitLevel = getNormalizedUnitLevel(raw);
|
||||
boolean availableBusinessUnit = "是".equals(raw.getStr("DWYXBS")) && Integer.valueOf(2).equals(rawUnitLevel);
|
||||
|
||||
unit.setParentId(parentId);
|
||||
unit.setName(unitName);
|
||||
unit.setAliasName(aliasName);
|
||||
unit.setUnitcode(unitCode);
|
||||
unit.setAddress(raw.getStr("DWDZ"));
|
||||
unit.setUnitLevel(unitLevel);
|
||||
unit.setUnitType(raw.getStr("DWLB"));
|
||||
unit.setUnitTypeCode(availableBusinessUnit ? 1 : 0);
|
||||
unit.setHasChildren(parentCodes.contains(unitCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算父级单位号。
|
||||
* LSDWH 为空、等于自身或本地不存在该父级时按根级处理;系统根单位 0 允许直接作为父级。
|
||||
*/
|
||||
private String getParentId(String unitCode, String parentCode) {
|
||||
if (StrUtil.isBlank(parentCode) || unitCode.equals(parentCode)) {
|
||||
return "";
|
||||
}
|
||||
if ("0".equals(parentCode)) {
|
||||
return parentCode;
|
||||
}
|
||||
Sys_unit parentUnit = fetch(parentCode);
|
||||
return parentUnit == null ? "" : parentCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据父级 path 生成当前单位 path。
|
||||
* 父级为空时生成根节点 path,父级存在时在父级 path 下生成子节点 path。
|
||||
*/
|
||||
private String getUnitPath(String parentId) {
|
||||
String parentPath = "";
|
||||
if (StrUtil.isNotBlank(parentId)) {
|
||||
Sys_unit parentUnit = fetch(parentId);
|
||||
parentPath = parentUnit == null ? "" : parentUnit.getPath();
|
||||
}
|
||||
return getSubPath("sys_unit", "path", parentPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 信息中心一级单位应挂在系统根单位 0 下,其余单位沿用接口返回的 LSDWH。
|
||||
*/
|
||||
private String getNormalizedParentCode(JSONObject raw) {
|
||||
Integer rawUnitLevel = raw.getInt("DWCC");
|
||||
if (Integer.valueOf(1).equals(rawUnitLevel)) {
|
||||
return "0";
|
||||
}
|
||||
return raw.getStr("LSDWH");
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统内置根单位占用一级层级,信息中心单位层次入库时统一加一。
|
||||
*/
|
||||
private Integer getNormalizedUnitLevel(JSONObject raw) {
|
||||
Integer rawUnitLevel = raw.getInt("DWCC");
|
||||
return rawUnitLevel == null ? null : rawUnitLevel + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* DWCC 为信息中心单位层次,排序时用于尽量先处理父级单位,便于后续子级找到父节点。
|
||||
*/
|
||||
private int getUnitLevel(JSONObject raw) {
|
||||
return raw.getInt("DWCC", 0);
|
||||
}
|
||||
}
|
||||
|
||||
+96
-24
@@ -5,6 +5,7 @@ import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.date.DateField;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.event.role.RoleEventMsg;
|
||||
@@ -50,6 +51,8 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@IocBean
|
||||
public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService {
|
||||
private static final int BATCH_SIZE = 500;
|
||||
private static final int ROLE_DELETE_BATCH_SIZE = 50;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@@ -180,6 +183,21 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
List<Sys_user_history> histories = new CopyOnWriteArrayList<>();
|
||||
List<String> addMemberUserIds = Collections.synchronizedList(new ArrayList<>());
|
||||
List<String> removeMemberUserIds = Collections.synchronizedList(new ArrayList<>());
|
||||
Set<String> sourceLoginNames = sources.stream()
|
||||
.map(Sys_user_source::getLoginname)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
List<String> leaveUserIds = sysUsers.stream()
|
||||
.filter(user -> StrUtil.isNotBlank(user.getLoginname()))
|
||||
.filter(user -> !sourceLoginNames.contains(user.getLoginname()))
|
||||
.map(Sys_user::getId)
|
||||
.toList();
|
||||
List<String> leaveMemberUserIds = sysUsers.stream()
|
||||
.filter(user -> StrUtil.isNotBlank(user.getLoginname()))
|
||||
.filter(user -> !sourceLoginNames.contains(user.getLoginname()))
|
||||
.filter(user -> Boolean.TRUE.equals(user.getMember()))
|
||||
.map(Sys_user::getId)
|
||||
.toList();
|
||||
|
||||
// 处理每条数据
|
||||
for (Sys_user_source source : sources) {
|
||||
@@ -235,7 +253,7 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
CompletableFuture<Void> insertTask = CompletableFuture.runAsync(() -> {
|
||||
log.info("新增用户: {} 个", needInitUserList.size());
|
||||
// 分批处理,每批200条
|
||||
List<List<Sys_user>> batches = ListUtil.split(needInitUserList, 500);
|
||||
List<List<Sys_user>> batches = ListUtil.split(needInitUserList, BATCH_SIZE);
|
||||
batches.forEach(batch -> {
|
||||
try {
|
||||
dao.fastInsert(batch);
|
||||
@@ -260,7 +278,7 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
|
||||
if (!roleList.isEmpty()) {
|
||||
// 分批处理角色分配
|
||||
List<List<Sys_user_role>> roleBatches = ListUtil.split(roleList, 500);
|
||||
List<List<Sys_user_role>> roleBatches = ListUtil.split(roleList, BATCH_SIZE);
|
||||
roleBatches.forEach(batch -> {
|
||||
try {
|
||||
dao.fastInsert(batch);
|
||||
@@ -282,7 +300,7 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
CompletableFuture<Void> updateTask = CompletableFuture.runAsync(() -> {
|
||||
log.info("更新用户: {} 个", needDoUpdateList.size());
|
||||
// 分批处理,每批200条
|
||||
List<List<Sys_user>> batches = ListUtil.split(needDoUpdateList, 500);
|
||||
List<List<Sys_user>> batches = ListUtil.split(needDoUpdateList, BATCH_SIZE);
|
||||
batches.forEach(batch -> {
|
||||
try {
|
||||
dao.updateIgnoreNull(batch);
|
||||
@@ -294,6 +312,65 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
updateTasks.add(updateTask);
|
||||
}
|
||||
|
||||
if (Lang.isNotEmpty(leaveUserIds)) {
|
||||
CompletableFuture<Void> leaveTask = CompletableFuture.runAsync(() -> {
|
||||
log.info("数据源缺失用户转为不在岗并取消会员: {} 个", leaveUserIds.size());
|
||||
List<List<String>> batches = ListUtil.split(leaveUserIds, BATCH_SIZE);
|
||||
batches.forEach(batch -> {
|
||||
try {
|
||||
/*
|
||||
* 本次拉取批次中没有出现、但系统用户表仍存在的人员,按离岗处理:
|
||||
* 1. userState 写为“不在岗”,用于后续人员状态筛选和业务判断;
|
||||
* 2. member 写为 false,避免仍按会员身份参与后续业务判断。
|
||||
*/
|
||||
dao.update(Sys_user.class, Chain.make("userState", "不在岗").add("member", false), Cnd.where("id", "in", batch));
|
||||
} catch (Exception e) {
|
||||
log.error("批量更新数据源缺失用户状态异常", e);
|
||||
}
|
||||
});
|
||||
}, executorService);
|
||||
updateTasks.add(leaveTask);
|
||||
}
|
||||
|
||||
if (Lang.isNotEmpty(leaveMemberUserIds)) {
|
||||
CompletableFuture<Void> leaveMemberRoleTask = CompletableFuture.runAsync(() -> {
|
||||
log.info("数据源缺失会员移除会员角色: {} 个", leaveMemberUserIds.size());
|
||||
List<List<String>> batches = ListUtil.split(leaveMemberUserIds, ROLE_DELETE_BATCH_SIZE);
|
||||
batches.forEach(batch -> {
|
||||
try {
|
||||
/*
|
||||
* 只对原本是会员的缺失人员移除会员角色,并缩小删除批次,
|
||||
* 降低 sys_user_role 大批量 DELETE 时的锁等待概率。
|
||||
*/
|
||||
dao.clear(Sys_user_role.class, Cnd.where("userId", "in", batch).and("roleId", "=", memberRole.getId()));
|
||||
} catch (Exception e) {
|
||||
log.error("批量移除数据源缺失会员角色异常", e);
|
||||
}
|
||||
});
|
||||
}, executorService);
|
||||
updateTasks.add(leaveMemberRoleTask);
|
||||
}
|
||||
|
||||
if (Lang.isNotEmpty(histories)) {
|
||||
CompletableFuture<Void> historyTask = CompletableFuture.runAsync(() -> {
|
||||
log.info("添加历史记录: {} 条", histories.size());
|
||||
/*
|
||||
* 历史记录基于更新前的 sys_user 与 sys_user_source 生成,必须纳入本次更新等待范围。
|
||||
* 继续使用线程池和批量插入,避免主线程逐条写入拖慢更新接口。
|
||||
*/
|
||||
List<List<Sys_user_history>> batches = ListUtil.split(histories, 500);
|
||||
batches.forEach(batch -> {
|
||||
try {
|
||||
dao.fastInsert(batch);
|
||||
} catch (Exception e) {
|
||||
log.error("批量添加历史记录异常", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}, executorService);
|
||||
updateTasks.add(historyTask);
|
||||
}
|
||||
|
||||
// 等待用户数据更新完成
|
||||
try {
|
||||
// 设置超时时间,避免无限等待
|
||||
@@ -307,22 +384,6 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
return "更新失败: " + e.getMessage();
|
||||
}
|
||||
|
||||
// 3. 异步添加历史记录 - 不等待完成
|
||||
if (Lang.isNotEmpty(histories)) {
|
||||
executorService.execute(() -> {
|
||||
log.info("添加历史记录: {} 条", histories.size());
|
||||
// 分批处理历史记录
|
||||
List<List<Sys_user_history>> batches = ListUtil.split(histories, 500);
|
||||
batches.forEach(batch -> {
|
||||
try {
|
||||
dao.fastInsert(batch);
|
||||
} catch (Exception e) {
|
||||
log.error("批量添加历史记录异常", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* // 4. 异步添加会员角色 - 不等待完成
|
||||
if (Lang.isNotEmpty(addMemberUserIds)) {
|
||||
executorService.execute(() -> {
|
||||
@@ -384,7 +445,7 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
log.info("全量更新用户数据完成,耗时: {} 毫秒", (endTime - startTime));
|
||||
|
||||
return "更新完成: 新增用户 " + needInitUserList.size() + " 个, 更新用户 " + needDoUpdateList.size()
|
||||
+ " 个, 待添加会员 " + addMemberUserIds.size() + " 个, 待移除会员 " + removeMemberUserIds.size() + " 个";
|
||||
+ " 个, 数据源缺失转不在岗 " + leaveUserIds.size() + " 个, 待添加会员 " + addMemberUserIds.size() + " 个, 待移除会员 " + removeMemberUserIds.size() + " 个";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,8 +514,8 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
NutMap change = NutMap.NEW();
|
||||
change.put("fieldName", "单位");
|
||||
change.put("field", "unitId");
|
||||
change.put("sourceValue", unitIdNameMap.get(user.getUnitId()));
|
||||
change.put("newValue", unitIdNameMap.get(user.getUnitId()));
|
||||
change.put("sourceValue", getUnitChangeValue(user.getUnitId()));
|
||||
change.put("newValue", getUnitChangeValue(source.getUnitId()));
|
||||
changeList.add(change);
|
||||
|
||||
// 单位异动发送订阅消息,清空原单位的所有角色
|
||||
@@ -470,8 +531,8 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
// 生成变更信息描述
|
||||
String changeInfos = changeList.stream()
|
||||
.map(v -> v.getString("fieldName") + ":" +
|
||||
HtmlUtil.cleanHtmlTag(v.getString("sourceValue")) + "→" +
|
||||
HtmlUtil.cleanHtmlTag(v.getString("newValue")))
|
||||
HtmlUtil.cleanHtmlTag(StrUtil.blankToDefault(v.getString("sourceValue"), "")) + "→" +
|
||||
HtmlUtil.cleanHtmlTag(StrUtil.blankToDefault(v.getString("newValue"), "")))
|
||||
.collect(Collectors.joining(";"));
|
||||
|
||||
// 设置历史记录信息
|
||||
@@ -481,4 +542,15 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单位变更记录展示值。
|
||||
* 入参 unitId 为系统用户旧单位或数据源新单位ID;返回值优先使用单位名称,查不到名称时保留单位ID,避免变更记录为空导致更新中断。
|
||||
*/
|
||||
private String getUnitChangeValue(String unitId) {
|
||||
if (StrUtil.isBlank(unitId)) {
|
||||
return "";
|
||||
}
|
||||
return StrUtil.blankToDefault(unitIdNameMap.get(unitId), unitId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.budwk.app.sys.annotation.DataCenterColumn;
|
||||
import com.budwk.app.sys.enums.Api2FinanceFiledMap;
|
||||
import com.budwk.app.sys.models.Sys_data_dict;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.models.Sys_unit;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.sys.models.Sys_user_source;
|
||||
import com.budwk.app.sys.services.SysDataUnitPullService;
|
||||
@@ -50,6 +51,10 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source> implements SysDataUserPullService {
|
||||
|
||||
private static final String DMP_TEACHER_KEY = "teacher";
|
||||
private static final String DMP_DISPATCH_KEY = "dispatch";
|
||||
private static final int DMP_PAGE_SIZE = 1000;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
@Inject
|
||||
@@ -115,48 +120,14 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
// 字典码表Map,key为父级编码,value为子级字典列表
|
||||
Map<String, List<Sys_data_dict>> dataDictMap = dataDictList.stream().collect(Collectors.groupingBy(Sys_data_dict::getParentCode));
|
||||
|
||||
// token url参数
|
||||
Map<String, String> tokenParams = Map.of(
|
||||
"grant_type", "password",
|
||||
"scope", "read",
|
||||
"username", "3848A3C738EFBB2C2E76E0D59B419DACA16A80F3AB0354CD",
|
||||
"password", "3848A3C738EFBB2C2E76E0D59B419DACA16A80F3AB0354CD"
|
||||
);
|
||||
|
||||
String params = URLUtil.buildQuery(tokenParams, StandardCharsets.UTF_8);
|
||||
String tokenUrl = "https://bd.whcp.edu.cn/sjjkfwpt/api/bd-api/oauth/token" + (StrUtil.isBlank(params) ? "" : "?" + params);
|
||||
HttpRequest tokenHttpRequest = HttpUtil.createPost(tokenUrl);
|
||||
tokenHttpRequest.header("Content-Type", "application/json");
|
||||
tokenHttpRequest.header("Authorization", "Basic MTE0OjU5M2UwNGI4YTE4NWRlMzkyNjY4ZmY0ZDJhZGMyZTEw");
|
||||
|
||||
log.info("请求token: {}", tokenHttpRequest);
|
||||
String tokenResBody = tokenHttpRequest.execute().body();
|
||||
log.info("请求token结果: {}", tokenResBody);
|
||||
|
||||
JSONObject tokenJsonBody = JSONUtil.parseObj(tokenResBody);
|
||||
|
||||
// 请求数据
|
||||
HttpRequest httpRequest = HttpUtil.createGet("https://bd.whcp.edu.cn/sjjkfwpt/api/bd-api/get/JZGJCSJZLB");
|
||||
httpRequest.form(Map.of("access_token", tokenJsonBody.getStr("access_token"), "pageSize", 5000, "page", 1));
|
||||
|
||||
log.info("请求人员数据: {}", httpRequest);
|
||||
String resBody = httpRequest.execute().body();
|
||||
log.info("请求人员数据结果: {}", resBody);
|
||||
JSONObject jsonBody = JSONUtil.parseObj(resBody);
|
||||
|
||||
if (jsonBody.getInt("code") != 200) {
|
||||
throw new BaseException("获取人员数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("msg"));
|
||||
}
|
||||
|
||||
String accessToken = getDmpAccessToken();
|
||||
List<JSONObject> rawDataList = new ArrayList<>();
|
||||
JSONArray data = jsonBody.getJSONArray("data");
|
||||
rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_TEACHER_KEY), accessToken, "在岗教职工"));
|
||||
rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_DISPATCH_KEY), accessToken, "在岗劳务派遣"));
|
||||
rawDataList = distinctDmpUsers(rawDataList);
|
||||
|
||||
if (!data.isEmpty()) {
|
||||
// 先收集所有原始数据
|
||||
rawDataList = data.stream().map(v -> (JSONObject) v).toList();
|
||||
log.info("数据拉取进度: {}/{}", rawDataList.size(), jsonBody.getInt("total"));
|
||||
} else {
|
||||
log.warn("当前未获取到数据");
|
||||
if (rawDataList.isEmpty()) {
|
||||
log.warn("当前未获取到人员数据");
|
||||
}
|
||||
|
||||
Date nowDate = new Date();
|
||||
@@ -172,20 +143,24 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
if (mapping.field.getType() == String.class) {
|
||||
|
||||
if (StrUtil.isNotBlank(mapping.dict)) {
|
||||
Sys_data_dict sysDataDict = dataDictMap.get(mapping.dict).stream()
|
||||
.filter(v -> raw.getStr(mapping.key)
|
||||
.equals(v.getCode())).findFirst().orElse(new Sys_data_dict());
|
||||
mapping.field.set(sysUser, sysDataDict.getName());
|
||||
List<Sys_data_dict> sysDataDicts = dataDictMap.getOrDefault(mapping.dict, Collections.emptyList());
|
||||
Sys_data_dict sysDataDict = sysDataDicts.stream()
|
||||
.filter(v -> raw.getStr(mapping.key, "").equals(v.getCode()))
|
||||
.findFirst().orElse(new Sys_data_dict());
|
||||
mapping.field.set(sysUser, StrUtil.blankToDefault(sysDataDict.getName(), raw.getStr(mapping.key)));
|
||||
} else {
|
||||
// 特殊处理 根据身份证号获取性别和出生年月
|
||||
if ("SFZJH".equals(mapping.field.getName())) {
|
||||
if ("SFZJH".equals(mapping.key)) {
|
||||
String idCard = raw.getStr(mapping.key);
|
||||
mapping.field.set(sysUser, idCard);
|
||||
// 设置出生年月
|
||||
try {
|
||||
sysUser.setBirthday(IdcardUtil.getBirthDate(idCard));
|
||||
} catch (Exception e) {
|
||||
sysUser.setBirthday(null);
|
||||
}
|
||||
} else if ("LXNY".equals(mapping.key)) {
|
||||
mapping.field.set(sysUser, normalizeArrivalAtSchoolDate(raw.getStr(mapping.key)));
|
||||
} else {
|
||||
mapping.field.set(sysUser, raw.getStr(mapping.key));
|
||||
}
|
||||
@@ -198,6 +173,9 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
}
|
||||
}
|
||||
|
||||
sysUser.setUnitName(raw.getStr("SZDW"));
|
||||
sysUser.setUnitId(resolveSourceUnitId(raw.getStr("SZDWH")));
|
||||
|
||||
// 设置拉取时间
|
||||
sysUser.setPullTime(nowDate);
|
||||
|
||||
@@ -206,10 +184,12 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
|
||||
log.info("用户数据初始化完成,等待插入,当前数据条数{}", latestSourceList.size());
|
||||
// 插入到数据库
|
||||
dao().insert(latestSourceList);
|
||||
if (CollUtil.isNotEmpty(latestSourceList)) {
|
||||
dao().insert(latestSourceList);
|
||||
}
|
||||
|
||||
// 人员的单位数据和数据库的单位数据比较,如果人员里面有单位不存在,去更新单位数据
|
||||
List<String> sourceUnitIds = latestSourceList.stream().map(Sys_user_source::getUnitId).distinct().toList();
|
||||
List<String> sourceUnitIds = latestSourceList.stream().map(Sys_user_source::getUnitId).filter(StrUtil::isNotBlank).distinct().toList();
|
||||
Sql sql = Sqls.create("select id from sys_unit group by id");
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao().execute(sql);
|
||||
@@ -225,6 +205,159 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化信息中心返回的来校时间。
|
||||
* 入参 arrivalAtSchoolDate 对应信息中心 LXNY 字段,允许 yyyy-MM-dd 或 yyyy-MM;
|
||||
* 返回值用于 sys_user_source.arrivalAtSchoolDate 和后续 sys_user.arrivalAtSchoolDate,年月格式统一补为当月 01 日。
|
||||
*/
|
||||
private String normalizeArrivalAtSchoolDate(String arrivalAtSchoolDate) {
|
||||
String dateText = StrUtil.trim(arrivalAtSchoolDate);
|
||||
if (StrUtil.isBlank(dateText)) {
|
||||
return dateText;
|
||||
}
|
||||
if (dateText.matches("\\d{4}-\\d{1,2}-\\d{1,2}")) {
|
||||
String[] parts = dateText.split("-");
|
||||
return parts[0] + "-" + StrUtil.padPre(parts[1], 2, '0') + "-" + StrUtil.padPre(parts[2], 2, '0');
|
||||
}
|
||||
if (dateText.matches("\\d{4}-\\d{1,2}")) {
|
||||
String[] parts = dateText.split("-");
|
||||
return parts[0] + "-" + StrUtil.padPre(parts[1], 2, '0') + "-01";
|
||||
}
|
||||
return dateText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信息中心开放平台访问令牌。
|
||||
* 返回值为接口后续调用使用的 access_token 字符串。
|
||||
*/
|
||||
private String getDmpAccessToken() {
|
||||
String tokenUrl = dataCenterProperties.getTokenUrl();
|
||||
if (StrUtil.isBlank(tokenUrl)) {
|
||||
throw new BaseException("未配置信息中心token地址: data-center.token-url");
|
||||
}
|
||||
String key = dataCenterProperties.getKey();
|
||||
if (StrUtil.isBlank(key)) {
|
||||
throw new BaseException("未配置信息中心token key: data-center.key");
|
||||
}
|
||||
String secret = dataCenterProperties.getSecret();
|
||||
if (StrUtil.isBlank(secret)) {
|
||||
throw new BaseException("未配置信息中心token secret: data-center.secret");
|
||||
}
|
||||
// 信息中心token接口要求key和secret通过URL参数传入,配置中分开维护,调用时统一组装。
|
||||
String params = URLUtil.buildQuery(Map.of("key", key, "secret", secret), StandardCharsets.UTF_8);
|
||||
HttpRequest tokenHttpRequest = HttpUtil.createGet(tokenUrl + "?" + params);
|
||||
log.info("请求信息中心token: {}", tokenHttpRequest);
|
||||
String tokenResBody = tokenHttpRequest.execute().body();
|
||||
log.info("请求信息中心token结果: {}", tokenResBody);
|
||||
|
||||
JSONObject tokenJsonBody = JSONUtil.parseObj(tokenResBody);
|
||||
String accessToken = tokenJsonBody.getStr("access_token");
|
||||
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("result") != null) {
|
||||
accessToken = tokenJsonBody.getJSONObject("result").getStr("access_token");
|
||||
}
|
||||
if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("data") != null) {
|
||||
accessToken = tokenJsonBody.getJSONObject("data").getStr("access_token");
|
||||
}
|
||||
if (StrUtil.isBlank(accessToken)) {
|
||||
accessToken = tokenJsonBody.getStr("token");
|
||||
}
|
||||
if (StrUtil.isBlank(accessToken)) {
|
||||
throw new BaseException("获取信息中心token失败");
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取信息中心人员接口地址。
|
||||
* 入参 key 对应 data-center.urls 下的配置项,返回值为具体接口URL。
|
||||
*/
|
||||
private String getDmpUrl(String key) {
|
||||
String url = dataCenterProperties.getUrls().get(key);
|
||||
if (StrUtil.isBlank(url)) {
|
||||
throw new BaseException("未配置信息中心人员接口地址: data-center.urls." + key);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页拉取单个人员接口数据。
|
||||
* 入参 url 为人员接口地址,accessToken 为信息中心令牌,sourceName 用于日志区分接口来源。
|
||||
* 返回值为该接口所有分页合并后的原始人员 JSON 列表。
|
||||
*/
|
||||
private List<JSONObject> pullDmpUsers(String url, String accessToken, String sourceName) {
|
||||
List<JSONObject> rawDataList = new ArrayList<>();
|
||||
int page = 1;
|
||||
int maxPage = 1;
|
||||
int total = 0;
|
||||
|
||||
do {
|
||||
// 信息中心接口单页最多返回1000条,按page循环拉完当前接口全部数据。
|
||||
HttpRequest httpRequest = HttpUtil.createPost(url);
|
||||
httpRequest.header(Header.CONTENT_TYPE, "application/json");
|
||||
httpRequest.body(JSONUtil.toJsonStr(Map.of(
|
||||
"access_token", accessToken,
|
||||
"per_page", String.valueOf(DMP_PAGE_SIZE),
|
||||
"page", String.valueOf(page)
|
||||
)));
|
||||
|
||||
log.info("请求{}人员数据,第{}页: {}", sourceName, page, httpRequest);
|
||||
String resBody = httpRequest.execute().body();
|
||||
log.info("请求{}人员数据第{}页结果: {}", sourceName, page, resBody);
|
||||
JSONObject jsonBody = JSONUtil.parseObj(resBody);
|
||||
|
||||
if (jsonBody.getInt("code") != 10000) {
|
||||
throw new BaseException("获取" + sourceName + "人员数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("message"));
|
||||
}
|
||||
|
||||
JSONObject result = jsonBody.getJSONObject("result");
|
||||
if (result == null) {
|
||||
break;
|
||||
}
|
||||
total = result.getInt("total", total);
|
||||
maxPage = result.getInt("max_page", maxPage);
|
||||
JSONArray data = result.getJSONArray("data");
|
||||
if (CollUtil.isNotEmpty(data)) {
|
||||
rawDataList.addAll(data.stream().map(v -> (JSONObject) v).toList());
|
||||
}
|
||||
log.info("{}人员数据拉取进度: {}/{}", sourceName, rawDataList.size(), total);
|
||||
page++;
|
||||
} while (page <= maxPage);
|
||||
|
||||
return rawDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并两个接口数据时按职工号去重,避免同一批次出现重复人员。
|
||||
* 返回值保持第一次出现的数据,用于后续统一映射入库。
|
||||
*/
|
||||
private List<JSONObject> distinctDmpUsers(List<JSONObject> rawDataList) {
|
||||
Map<String, JSONObject> userMap = new LinkedHashMap<>();
|
||||
for (JSONObject raw : rawDataList) {
|
||||
String loginName = raw.getStr("ZGH");
|
||||
if (StrUtil.isBlank(loginName)) {
|
||||
loginName = raw.getStr("ID");
|
||||
}
|
||||
userMap.putIfAbsent(loginName, raw);
|
||||
}
|
||||
return new ArrayList<>(userMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口返回的所在单位号转换数据源用户单位。
|
||||
* 入参 rawUnitId 对应人员接口 SZDWH:5位表示三级单位,需要写入其二级父单位;其他长度保持接口原值。
|
||||
* 返回值为 sys_user_source.unitId 使用的单位ID,找不到三级单位或父级为空时保留接口原始单位号,避免人员数据丢失。
|
||||
*/
|
||||
private String resolveSourceUnitId(String rawUnitId) {
|
||||
if (StrUtil.isBlank(rawUnitId) || rawUnitId.length() != 5) {
|
||||
return rawUnitId;
|
||||
}
|
||||
Sys_unit unit = dao().fetch(Sys_unit.class, rawUnitId);
|
||||
if (unit == null || StrUtil.isBlank(unit.getParentId())) {
|
||||
return rawUnitId;
|
||||
}
|
||||
return unit.getParentId();
|
||||
}
|
||||
|
||||
@Async
|
||||
private void updateDict(List<Sys_user_source> userSources) {
|
||||
//判断是否要更新在职状态字典
|
||||
@@ -388,70 +521,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
|
||||
@Override
|
||||
public Map<String, NutMap> pullFinance() {
|
||||
// 获取配置文件 接口地址 和 token
|
||||
DataCenterProperties.Credential credential = dataCenterProperties.credential("finance");
|
||||
String url = credential.getUrl();
|
||||
String token = credential.getToken();
|
||||
|
||||
List<JSONObject> rawDataList = new ArrayList<>();
|
||||
String year = String.valueOf(DateUtil.thisYear());
|
||||
String month = String.valueOf(DateUtil.thisMonth() + 1);
|
||||
|
||||
int pageNum = 1;
|
||||
int pageSize = 1000;
|
||||
int totalPages = 1;
|
||||
int totalCount = 0;
|
||||
do {
|
||||
HttpRequest httpRequest = HttpUtil.createPost(url);
|
||||
httpRequest.header(Header.CONTENT_TYPE, "application/json");
|
||||
httpRequest.header("Accept-Encoding", "gzip, deflate, br");
|
||||
httpRequest.header("X-H3C-TOKEN", token);
|
||||
Map<String, Object> reqBody = Map.of(
|
||||
"nf", year,
|
||||
"yf", month,
|
||||
"pageNum", pageNum,
|
||||
"pageSize", pageSize
|
||||
);
|
||||
httpRequest.body(JSONUtil.toJsonStr(reqBody));
|
||||
|
||||
JSONObject resp = JSONUtil.parseObj(httpRequest.execute().body());
|
||||
if (resp.getInt("code") != 0) {
|
||||
throw new BaseException("获取财务数据失败,错误码: " + resp.getInt("code") + ";错误原因:" + resp.getStr("msg"));
|
||||
}
|
||||
|
||||
JSONObject data = resp.getJSONObject("data");
|
||||
if (pageNum == 1) {
|
||||
totalCount = data.getInt("total", 0);
|
||||
// totalPages = data.getInt("pages", 0);
|
||||
totalPages = (int) Math.ceil((double) totalCount / pageSize);
|
||||
}
|
||||
|
||||
JSONArray records = data.getJSONArray("records");
|
||||
if (CollUtil.isNotEmpty(records)) {
|
||||
rawDataList.addAll(records.stream().map(v -> (JSONObject) v).toList());
|
||||
log.info("数据拉取进度: {}/{}", rawDataList.size(), totalCount);
|
||||
}
|
||||
pageNum++; // 只加页码
|
||||
} while (pageNum <= totalPages);
|
||||
|
||||
// 接口数据输出
|
||||
log.info("数据拉取结果: {}", rawDataList);
|
||||
|
||||
// 最后结果数据
|
||||
Map<String, NutMap> result = new HashMap<>();
|
||||
|
||||
Map<String, String> api2db = Arrays.stream(Api2FinanceFiledMap.values())
|
||||
.collect(Collectors.toMap(e -> e.apiField, e -> e.dbColumn));
|
||||
|
||||
for (JSONObject row : rawDataList) {
|
||||
NutMap nutMap = DataCenterUtil.mapJsonToBean(row, NutMap.class, api2db);
|
||||
result.put(nutMap.getString("loginname"), nutMap);
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
log.info("财务数据拉取转换结果: {}", result);
|
||||
|
||||
log.info("数据拉取完成,共拉取 {} 条数据", result.size());
|
||||
return result;
|
||||
// 旧数据中心财务接口已停用,避免继续调用旧token/code模式接口。
|
||||
throw new BaseException("旧数据中心财务拉取接口已停用");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,9 +309,9 @@ public class SysUserServiceImpl extends BaseServiceImpl<Sys_user> implements Sys
|
||||
String decodePwd = Base64Decoder.decodeStr(passowrd);
|
||||
String hashedPassword = PwdUtil.getPassword(decodePwd, user.getSalt());
|
||||
if (!Strings.sNull(hashedPassword).equalsIgnoreCase(user.getPassword())) {
|
||||
// if (Globals.sso) {
|
||||
// throw new BaseException("用户名或者密码不正确");
|
||||
// }
|
||||
if (Globals.sso) {
|
||||
throw new BaseException("用户名或者密码不正确");
|
||||
}
|
||||
}
|
||||
user = this.fetchLinks(user, "unit");
|
||||
if (Lang.isNotEmpty(user.getUnit()) && StrUtil.isNotBlank(user.getUnit().getUnionId())) {
|
||||
|
||||
@@ -169,16 +169,16 @@ public class ActivitySchoolApply extends BaseModel implements Serializable {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String mobile;
|
||||
|
||||
// @Excel(name = "身份证号", width = 20)
|
||||
@Excel(name = "身份证", width = 25)
|
||||
private String idCard;
|
||||
|
||||
@Column
|
||||
@Comment("职级")
|
||||
@Excel(name = "职级", width = 20)
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String professionalLevel;
|
||||
|
||||
|
||||
// @Excel(name = "身份证号", width = 20)
|
||||
private String idCard;
|
||||
|
||||
@Column
|
||||
@Comment("性别")
|
||||
@Excel(name = "性别", width = 20)
|
||||
|
||||
+15
-1
@@ -10,6 +10,7 @@ import com.budwk.app.base.utils.DateUtil;
|
||||
import com.budwk.app.sys.models.Sys_home_activity;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
|
||||
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
|
||||
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchool;
|
||||
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolApply;
|
||||
@@ -200,6 +201,7 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
app.activityUnionName unionname,
|
||||
app.sex,
|
||||
app.mobile,
|
||||
u.idcard AS idCard,
|
||||
app.professionalLevel,
|
||||
u.userState,
|
||||
ev.allName,
|
||||
@@ -244,13 +246,25 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, ActivitySchoolApply.class, excels);
|
||||
CommonDownloadUtil.download("报名表.xlsx", workbook, response);
|
||||
CommonDownloadUtil.download(getUnionApplyExportFileName(unionId), workbook, response);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getUnionApplyExportFileName(String unionId) {
|
||||
String unionName = "分工会";
|
||||
if (Strings.isNotBlank(unionId)) {
|
||||
ActivityBasicUnion union = dao().fetch(ActivityBasicUnion.class, unionId);
|
||||
if (union != null && Strings.isNotBlank(union.getName())) {
|
||||
unionName = union.getName();
|
||||
}
|
||||
}
|
||||
// 分工会名称可能已经包含“分工会”后缀,避免导出文件名重复拼接。
|
||||
return unionName.endsWith("分工会") ? unionName + "报名表.xlsx" : unionName + "分工会报名表.xlsx";
|
||||
}
|
||||
|
||||
|
||||
private static class personType {
|
||||
|
||||
|
||||
+1
@@ -135,6 +135,7 @@ public class TrainSignUpApplyController {
|
||||
tsuc.courseLocationCoordinates,
|
||||
tsuc.courseReservedNumber,
|
||||
tsuc.courseInstructor,
|
||||
tsuc.courseInstructorMobile,
|
||||
tsuc.campus,
|
||||
tsuc.unionLimit,
|
||||
tsuc.isMobileSign,
|
||||
|
||||
@@ -68,6 +68,11 @@ public class TrainSignUpCourse extends BaseModel implements Serializable {
|
||||
@Comment("课程讲师")
|
||||
private String courseInstructor;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
@Comment("负责人手机号")
|
||||
private String courseInstructorMobile;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@Comment("校区")
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ public class HonorBasicSettingsController {
|
||||
} else {
|
||||
dao.updateIgnoreNull(honorBasicSettings);
|
||||
}
|
||||
dao.update(Chain.make("hasChildren", true), Cnd.where("id", "=", honorBasicSettings.getParentId()));
|
||||
dao.update(HonorBasicSettings.class, Chain.make("hasChildren", true), Cnd.where("id", "=", honorBasicSettings.getParentId()));
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
|
||||
+9
-1
@@ -103,7 +103,11 @@ public class HonorBatchController {
|
||||
u.unionId,
|
||||
u.unionName,
|
||||
u.unitId,
|
||||
u.unitName
|
||||
u.unitName,
|
||||
u.sex,
|
||||
u.birthday,
|
||||
u.userState,
|
||||
u.political
|
||||
FROM
|
||||
`vw_user` u
|
||||
""");
|
||||
@@ -127,6 +131,10 @@ public class HonorBatchController {
|
||||
user.ifPresent(sysUser -> v.setUnitId(sysUser.getString("unitId")));
|
||||
user.ifPresent(sysUser -> v.setUnitName(sysUser.getString("unitName")));
|
||||
user.ifPresent(sysUser -> v.setUserId(sysUser.getString("id")));
|
||||
user.ifPresent(sysUser -> v.setUserSex(sysUser.getString("sex")));
|
||||
user.ifPresent(sysUser -> v.setBirthday(sysUser.getString("birthday")));
|
||||
user.ifPresent(sysUser -> v.setJobStatus(sysUser.getString("userState")));
|
||||
user.ifPresent(sysUser -> v.setPolitical(sysUser.getString("political")));
|
||||
} else {
|
||||
v.setHonorType(getBasicIdByName("集体荣誉"));
|
||||
//设置分工会
|
||||
|
||||
+36
-23
@@ -11,41 +11,41 @@ import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分工会季度额度分配服务。
|
||||
* 分工会分配项目额度分配服务。
|
||||
*
|
||||
* <p>该服务统一处理季度额度的分配、批量分配和重置回滚,
|
||||
* 保证季度记录与年度预算表金额始终同步。</p>
|
||||
* <p>该服务统一处理会费、评优奖励等分配项目的额度分配、批量分配和重置回滚,
|
||||
* 保证分配项目记录与年度预算表金额始终同步。</p>
|
||||
*
|
||||
* @author zhf
|
||||
*/
|
||||
public interface OutlayManageUnionAllocateService extends BaseService<OutLayAllocateUnion> {
|
||||
|
||||
/**
|
||||
* 修改单个分工会的季度分配金额。
|
||||
* 修改单个分工会的分配项目金额。
|
||||
*
|
||||
* @param id outlay_allocate_union 表主键,用于定位当前编辑的季度分配记录
|
||||
* @param allocateMoney 本次要设置的季度分配金额,传入的是最终金额,不是增量金额
|
||||
* @param id outlay_allocate_union 表主键,用于定位当前编辑的分配项目记录
|
||||
* @param allocateMoney 本次要设置的分配项目金额,传入的是最终金额,不是增量金额
|
||||
* @return Result 成功时返回 success;失败时返回错误信息,前端据此提示用户
|
||||
*/
|
||||
Result doEdit(String id, BigDecimal allocateMoney);
|
||||
|
||||
/**
|
||||
* 批量设置某一季度所有分工会的分配金额。
|
||||
* 批量设置某一分配项目所有分工会的分配金额。
|
||||
*
|
||||
* @param allocateMoney 本次批量设置的季度分配金额,所有命中的分工会都会写入该金额
|
||||
* @param quarterly 要操作的季度,例如 1/2/3/4
|
||||
* @param year 要操作的年份,用于筛选对应季度的有效分配记录
|
||||
* @param allocateMoney 本次批量设置的分配项目金额,所有命中的分工会都会写入该金额
|
||||
* @param quarterly 要操作的分配项目编码,复用 OUTLAY_QUARTERLY 字典
|
||||
* @param year 要操作的年份,用于筛选对应分配项目的有效记录
|
||||
* @return Result 成功时返回 success;失败时返回错误信息
|
||||
*/
|
||||
Result doBatchAllocate(BigDecimal allocateMoney, Integer quarterly, Integer year);
|
||||
|
||||
/**
|
||||
* 判断某年某季度是否已经做过额度分配。
|
||||
* 判断某年某分配项目是否已经做过额度分配。
|
||||
*
|
||||
* <p>只要当前季度记录中存在分配额度大于 0 的有效记录,
|
||||
* 就认为该季度已经分配过额度,前端据此决定是否提示再次分配。</p>
|
||||
* <p>只要当前分配项目记录中存在分配额度大于 0 的有效记录,
|
||||
* 就认为该分配项目已经分配过额度,前端据此决定是否提示再次分配。</p>
|
||||
*
|
||||
* @param quarterly 要检查的季度,例如 1/2/3/4
|
||||
* @param quarterly 要检查的分配项目编码,取值由 OUTLAY_QUARTERLY 字典维护
|
||||
* @param year 要检查的年份
|
||||
* @return true 已分配过;false 未分配
|
||||
*/
|
||||
@@ -60,15 +60,28 @@ public interface OutlayManageUnionAllocateService extends BaseService<OutLayAllo
|
||||
List<OutlayUnionAllocateImportVo> readImportExcel(TempFile file);
|
||||
|
||||
/**
|
||||
* 根据导入数据按分工会逐条分配季度额度。
|
||||
* 根据导入数据按分工会逐条分配项目额度。
|
||||
*
|
||||
* @param importList 导入预览数据
|
||||
* @param quarterly 当前季度
|
||||
* @param quarterly 当前分配项目编码
|
||||
* @param year 当前年度
|
||||
* @return 导入分配结果
|
||||
*/
|
||||
Result doImportAllocate(List<OutlayUnionAllocateImportVo> importList, Integer quarterly, Integer year);
|
||||
|
||||
/**
|
||||
* 给指定分工会新增评优奖励分配记录。
|
||||
*
|
||||
* <p>评优奖励属于 OUTLAY_QUARTERLY 字典中的独立分配项目,只针对单个分工会新增;
|
||||
* 新增后同步累加年度预算表总额度,分工会经费分配详情会按分配项目展示该记录。</p>
|
||||
*
|
||||
* @param year 当前预算年度
|
||||
* @param unionId 要新增评优奖励的分工会 ID
|
||||
* @param allocateMoney 本次新增的评优奖励金额
|
||||
* @return Result 成功时返回 success;失败时返回校验原因
|
||||
*/
|
||||
Result doAddAwardAllocate(Integer year, String unionId, BigDecimal allocateMoney);
|
||||
|
||||
/**
|
||||
* 导出分工会额度导入模板。
|
||||
*
|
||||
@@ -77,24 +90,24 @@ public interface OutlayManageUnionAllocateService extends BaseService<OutLayAllo
|
||||
Workbook exportImportTemplate();
|
||||
|
||||
/**
|
||||
* 重置指定年度季度的分配记录。
|
||||
* 重置指定年度分配项目的分配记录。
|
||||
*
|
||||
* <p>页面支持切换年度和季度,所以重置动作必须严格使用前端当前选择值,
|
||||
* 不能再按系统当前自然季度处理,否则会出现页面筛选季度和实际重置季度不一致的问题。</p>
|
||||
* <p>页面支持切换年度和分配项目,所以重置动作必须严格使用前端当前选择值,
|
||||
* 不能再按系统当前默认项目处理,否则会出现页面筛选项目和实际重置项目不一致的问题。</p>
|
||||
*
|
||||
* @param quarterly 要重置的季度,例如 1/2/3/4
|
||||
* @param quarterly 要重置的分配项目编码
|
||||
* @param year 要重置的年份
|
||||
* @return Result 成功时返回 success;失败时返回错误信息
|
||||
*/
|
||||
Result deleteAllocateRecord(Integer quarterly, Integer year);
|
||||
|
||||
/**
|
||||
* 按指定年度季度生成分工会预算记录。
|
||||
* 按指定年度分配项目生成分工会预算记录。
|
||||
*
|
||||
* <p>生成前会先校验该年度季度是否已存在有效记录;
|
||||
* <p>生成前会先校验该年度分配项目是否已存在有效记录;
|
||||
* 若已存在,则直接提示前端需要先重置后再重新生成。</p>
|
||||
*
|
||||
* @param quarterly 要生成的季度,例如 1/2/3/4
|
||||
* @param quarterly 要生成的分配项目编码
|
||||
* @param year 要生成的年份
|
||||
* @return Result 成功时返回 success;失败时返回错误信息
|
||||
*/
|
||||
|
||||
+134
-37
@@ -33,13 +33,15 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 分工会季度额度分配实现。
|
||||
* 分工会分配项目额度分配实现。
|
||||
*
|
||||
* @author zhf
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLayAllocateUnion> implements OutlayManageUnionAllocateService {
|
||||
|
||||
private static final int AWARD_PROJECT_CODE = 4;
|
||||
|
||||
public OutlayManageUnionAllocateServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -50,9 +52,9 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
if (Lang.isEmpty(allocateUnion)) {
|
||||
return Result.error("分配记录不存在!");
|
||||
}
|
||||
int currentQuarter = getCurrentQuarter();
|
||||
if (allocateUnion.getQuarterly() < currentQuarter) {
|
||||
return Result.error("当前是第【" + currentQuarter + "季度】无法修改【第" + allocateUnion.getQuarterly() + "季度】的额度!");
|
||||
int currentProject = getCurrentFeeProject();
|
||||
if (allocateUnion.getQuarterly() < currentProject) {
|
||||
return Result.error("当前仅允许修改当前会费项目额度!");
|
||||
}
|
||||
applyAllocateMoney(allocateUnion, allocateMoney);
|
||||
return Result.success();
|
||||
@@ -60,7 +62,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
|
||||
@Override
|
||||
public Result doBatchAllocate(BigDecimal allocateMoney, Integer quarterly, Integer year) {
|
||||
Result validateResult = validateCurrentQuarterOperation(quarterly);
|
||||
Result validateResult = validateCurrentPeriodOperation(quarterly);
|
||||
if (validateResult != null) {
|
||||
return validateResult;
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
.and(OutLayAllocateUnion::getYear, "=", year)
|
||||
.and(OutLayAllocateUnion::getDelFlag, "=", false));
|
||||
if (allocateUnionList == null || allocateUnionList.isEmpty()) {
|
||||
return Result.error("当前季度没有分配记录!");
|
||||
return Result.error("当前分配项目没有分配记录!");
|
||||
}
|
||||
for (OutLayAllocateUnion allocateUnion : allocateUnionList) {
|
||||
applyAllocateMoney(allocateUnion, allocateMoney);
|
||||
@@ -143,7 +145,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
|
||||
@Override
|
||||
public Result doImportAllocate(List<OutlayUnionAllocateImportVo> importList, Integer quarterly, Integer year) {
|
||||
Result validateResult = validateCurrentQuarterOperation(quarterly);
|
||||
Result validateResult = validateCurrentPeriodOperation(quarterly);
|
||||
if (validateResult != null) {
|
||||
return validateResult;
|
||||
}
|
||||
@@ -155,7 +157,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
.and(OutLayAllocateUnion::getYear, "=", year)
|
||||
.and(OutLayAllocateUnion::getDelFlag, "=", false));
|
||||
if (allocateUnionList == null || allocateUnionList.isEmpty()) {
|
||||
return Result.error("当前季度没有分配记录!");
|
||||
return Result.error("当前分配项目没有分配记录!");
|
||||
}
|
||||
List<Sys_union> unionList = dao().query(Sys_union.class, Cnd.NEW());
|
||||
Map<String, Sys_union> unionMap = new HashMap<>();
|
||||
@@ -190,7 +192,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
continue;
|
||||
}
|
||||
if (!allocateUnionMap.containsKey(union.getId())) {
|
||||
errorList.add("第" + item.getRowNum() + "行:当前季度未生成该分工会分配记录");
|
||||
errorList.add("第" + item.getRowNum() + "行:当前分配项目未生成该分工会分配记录");
|
||||
}
|
||||
}
|
||||
if (!errorList.isEmpty()) {
|
||||
@@ -199,20 +201,62 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
for (OutlayUnionAllocateImportVo item : importList) {
|
||||
Sys_union union = unionMap.get(item.getUnionCode());
|
||||
OutLayAllocateUnion allocateUnion = allocateUnionMap.get(union.getId());
|
||||
// 导入分配与手工分配保持同一套金额同步规则,确保季度表和年度预算表数据一致。
|
||||
// 导入分配与手工分配保持同一套金额同步规则,确保分配项目记录和年度预算表数据一致。
|
||||
applyAllocateMoney(allocateUnion, item.getAllocateMoney());
|
||||
}
|
||||
return Result.success("导入分配成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result doAddAwardAllocate(Integer year, String unionId, BigDecimal allocateMoney) {
|
||||
if (year == null || StrUtil.isBlank(unionId) || allocateMoney == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
if (allocateMoney.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return Result.error("评优奖励金额必须大于0!");
|
||||
}
|
||||
Sys_union union = dao().fetch(Sys_union.class, unionId);
|
||||
if (Lang.isEmpty(union)) {
|
||||
return Result.error("分工会不存在!");
|
||||
}
|
||||
int count = dao().count(OutLayAllocateUnion.class,
|
||||
Cnd.where(OutLayAllocateUnion::getYear, "=", year)
|
||||
.and(OutLayAllocateUnion::getUnionId, "=", unionId)
|
||||
.and(OutLayAllocateUnion::getQuarterly, "=", AWARD_PROJECT_CODE)
|
||||
.and(OutLayAllocateUnion::getDelFlag, "=", false));
|
||||
if (count > 0) {
|
||||
return Result.error("该分工会本年度已存在评优奖励记录,请直接编辑已有记录!");
|
||||
}
|
||||
|
||||
OutLayAllocateUnion allocateUnion = new OutLayAllocateUnion();
|
||||
allocateUnion.setYear(year);
|
||||
allocateUnion.setUnionId(unionId);
|
||||
allocateUnion.setQuarterly(AWARD_PROJECT_CODE);
|
||||
allocateUnion.setAllocateHeadMoney(getCurrentYearSurplusMoney(year, unionId));
|
||||
allocateUnion.setAllocateMoney(BigDecimal.ZERO);
|
||||
insert(allocateUnion);
|
||||
// 新增评优奖励后复用统一金额同步规则,确保分配项目记录和年度预算表 totalQuota 同步。
|
||||
applyAllocateMoney(allocateUnion, allocateMoney);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Workbook exportImportTemplate() {
|
||||
List<ExcelExportEntity> entityList = new ArrayList<>();
|
||||
entityList.add(new ExcelExportEntity("工会名称", "unionName", 20));
|
||||
entityList.add(new ExcelExportEntity("工会编码", "unionCode", 20));
|
||||
entityList.add(new ExcelExportEntity("分配额度", "allocateMoney", 20));
|
||||
List<Sys_union> unionList = dao().query(Sys_union.class, Cnd.NEW().asc("unionCode"));
|
||||
List<OutlayUnionAllocateImportVo> templateList = new ArrayList<>();
|
||||
for (Sys_union union : unionList) {
|
||||
OutlayUnionAllocateImportVo item = new OutlayUnionAllocateImportVo();
|
||||
item.setUnionName(union.getName());
|
||||
item.setUnionCode(union.getUnionCode());
|
||||
templateList.add(item);
|
||||
}
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
return ExcelExportUtil.exportExcel(exportParams, entityList, new ArrayList<>());
|
||||
return ExcelExportUtil.exportExcel(exportParams, entityList, templateList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,7 +264,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
if (quarterly == null || year == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
Result validateResult = validateCurrentQuarterOperation(quarterly);
|
||||
Result validateResult = validateCurrentPeriodOperation(quarterly);
|
||||
if (validateResult != null) {
|
||||
return validateResult;
|
||||
}
|
||||
@@ -229,7 +273,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
.and(OutLayAllocateUnion::getYear, "=", year)
|
||||
.and(OutLayAllocateUnion::getDelFlag, "=", false));
|
||||
if (allocateUnionList == null || allocateUnionList.isEmpty()) {
|
||||
return Result.error("当前季度还未分配,无法重置!");
|
||||
return Result.error("当前分配项目还未分配,无法重置!");
|
||||
}
|
||||
|
||||
for (OutLayAllocateUnion allocateUnion : allocateUnionList) {
|
||||
@@ -248,7 +292,7 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
if (quarterly == null || year == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
Result validateResult = validateCurrentQuarterOperation(quarterly);
|
||||
Result validateResult = validateCurrentPeriodOperation(quarterly);
|
||||
if (validateResult != null) {
|
||||
return validateResult;
|
||||
}
|
||||
@@ -257,10 +301,10 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
.and(OutLayAllocateUnion::getYear, "=", year)
|
||||
.and(OutLayAllocateUnion::getDelFlag, "=", false));
|
||||
if (count > 0) {
|
||||
return Result.error("当前季度已分配,如需重新分配请点击重置分配记录!");
|
||||
return Result.error("当前分配项目已分配,如需重新分配请点击重置分配记录!");
|
||||
}
|
||||
|
||||
// 第一季度需要承接上一年度剩余额度,其余季度承接当年预算额度,确保生成记录时的分配前额度准确。
|
||||
// 第一个会费项目需要承接上一年度剩余额度,其余项目承接当年预算额度,确保生成记录时的分配前额度准确。
|
||||
Integer sourceYear = quarterly == 1 ? year - 1 : year;
|
||||
List<OutlayManageUnion> outlayManageUnionList = dao().query(OutlayManageUnion.class,
|
||||
Cnd.where(OutlayManageUnion::getYear, "=", sourceYear));
|
||||
@@ -270,10 +314,10 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
}
|
||||
|
||||
/**
|
||||
* 按“先回退旧额度,再写入新额度”的方式同步季度记录和年度预算表。
|
||||
* 按“先回退旧额度,再写入新额度”的方式同步分配项目记录和年度预算表。
|
||||
*
|
||||
* @param allocateUnion 当前季度分配记录,包含分工会、年份、旧分配额度等上下文
|
||||
* @param newAllocateMoney 本次最终要保存的季度分配金额
|
||||
* @param allocateUnion 当前分配项目记录,包含分工会、年份、旧分配额度等上下文
|
||||
* @param newAllocateMoney 本次最终要保存的分配项目金额
|
||||
*/
|
||||
private void applyAllocateMoney(OutLayAllocateUnion allocateUnion, BigDecimal newAllocateMoney) {
|
||||
BigDecimal oldAllocateMoney = defaultValue(allocateUnion.getAllocateMoney());
|
||||
@@ -285,10 +329,14 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
manageUnion = buildManageUnion(allocateUnion, newAllocateMoney);
|
||||
insert(manageUnion);
|
||||
} else {
|
||||
// 当前 totalQuota 里已经包含了旧季度额度,因此要先减旧值,再加新值,避免重复累加。
|
||||
// 当前 totalQuota 里已经包含了旧项目额度,因此要先减旧值,再加新值,避免重复累加。
|
||||
BigDecimal newTotalQuota = defaultValue(manageUnion.getTotalQuota())
|
||||
.subtract(oldAllocateMoney)
|
||||
.add(newAllocateMoney);
|
||||
BigDecimal lastYearSurplusMoney = getFirstProjectMissingHeadMoney(allocateUnion, manageUnion);
|
||||
if (lastYearSurplusMoney.compareTo(BigDecimal.ZERO) != 0) {
|
||||
newTotalQuota = newTotalQuota.add(lastYearSurplusMoney);
|
||||
}
|
||||
dao().update(OutlayManageUnion.class, Chain.make("totalQuota", newTotalQuota),
|
||||
Cnd.where(OutlayManageUnion::getId, "=", manageUnion.getId()));
|
||||
}
|
||||
@@ -298,9 +346,9 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚当前季度已分配到年度预算表中的金额。
|
||||
* 回滚当前分配项目已分配到年度预算表中的金额。
|
||||
*
|
||||
* @param allocateUnion 当前季度分配记录,allocateMoney 表示本次需要从年度额度中扣回的金额
|
||||
* @param allocateUnion 当前分配项目记录,allocateMoney 表示本次需要从年度额度中扣回的金额
|
||||
*/
|
||||
private void rollbackAllocateMoney(OutLayAllocateUnion allocateUnion) {
|
||||
OutlayManageUnion manageUnion = dao().fetch(OutlayManageUnion.class,
|
||||
@@ -315,11 +363,43 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
Cnd.where(OutlayManageUnion::getId, "=", manageUnion.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 第一段会费分配记录的 allocateHeadMoney 保存上一年度剩余额度。
|
||||
* 如果当前年度预算表已提前生成,导入/一键分配时 totalQuota 可能只包含各分配项目金额,
|
||||
* 此时需要补入这笔期初结余;已补入过的年度预算再次导入时不重复累加。
|
||||
*
|
||||
* @param allocateUnion 当前分配项目记录
|
||||
* @param manageUnion 当前年度分工会预算记录
|
||||
* @return BigDecimal 本次需要补入的上一年度剩余额度;无需补入时返回 0
|
||||
*/
|
||||
private BigDecimal getFirstProjectMissingHeadMoney(OutLayAllocateUnion allocateUnion, OutlayManageUnion manageUnion) {
|
||||
if (allocateUnion == null || manageUnion == null || !Integer.valueOf(1).equals(allocateUnion.getQuarterly())) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal headMoney = defaultValue(allocateUnion.getAllocateHeadMoney());
|
||||
if (headMoney.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
List<OutLayAllocateUnion> allocateUnionList = dao().query(OutLayAllocateUnion.class,
|
||||
Cnd.where(OutLayAllocateUnion::getUnionId, "=", allocateUnion.getUnionId())
|
||||
.and(OutLayAllocateUnion::getYear, "=", allocateUnion.getYear())
|
||||
.and(OutLayAllocateUnion::getDelFlag, "=", false));
|
||||
BigDecimal allocatedTotalMoney = allocateUnionList.stream()
|
||||
.map(OutLayAllocateUnion::getAllocateMoney)
|
||||
.map(this::defaultValue)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal nonAllocateMoney = defaultValue(manageUnion.getTotalQuota()).subtract(allocatedTotalMoney);
|
||||
if (nonAllocateMoney.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return headMoney;
|
||||
}
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前年份预算表不存在时,按“分配前额度 + 本次分配额度”创建年度预算记录。
|
||||
*
|
||||
* @param allocateUnion 当前季度分配记录,allocateHeadMoney 表示生成记录时的剩余额度快照
|
||||
* @param allocateMoney 本次要设置的季度分配额度
|
||||
* @param allocateUnion 当前分配项目记录,allocateHeadMoney 表示生成记录时的剩余额度快照
|
||||
* @param allocateMoney 本次要设置的分配项目额度
|
||||
* @return OutlayManageUnion 新建的年度预算实体
|
||||
*/
|
||||
private OutlayManageUnion buildManageUnion(OutLayAllocateUnion allocateUnion, BigDecimal allocateMoney) {
|
||||
@@ -347,38 +427,55 @@ public class OutlayManageUnionAllocateServiceImpl extends BaseServiceImpl<OutLay
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前自然季度。
|
||||
* 计算当前年度指定分工会的可用余额,作为新增评优奖励前的额度快照。
|
||||
*
|
||||
* @return int 当前季度,取值范围 1-4
|
||||
* @param year 当前预算年度
|
||||
* @param unionId 分工会 ID
|
||||
* @return BigDecimal 当前年度总额度减已使用额度;没有年度预算记录时返回 0
|
||||
*/
|
||||
private int getCurrentQuarter() {
|
||||
return DateUtil.month(DateUtil.date()) / 3 + 1;
|
||||
private BigDecimal getCurrentYearSurplusMoney(Integer year, String unionId) {
|
||||
OutlayManageUnion manageUnion = dao().fetch(OutlayManageUnion.class,
|
||||
Cnd.where(OutlayManageUnion::getYear, "=", year)
|
||||
.and(OutlayManageUnion::getUnionId, "=", unionId));
|
||||
if (Lang.isEmpty(manageUnion)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return defaultValue(manageUnion.getTotalQuota()).subtract(defaultValue(manageUnion.getUsedQuota()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 预算季度记录相关操作只能针对当前自然季度执行。
|
||||
* 计算当前会费项目。
|
||||
*
|
||||
* @param quarterly 前端传入的目标季度
|
||||
* @return int 当前会费项目,1 表示 1-4月会费,2 表示 5-8月会费,3 表示 9-12月会费
|
||||
*/
|
||||
private int getCurrentFeeProject() {
|
||||
return DateUtil.month(DateUtil.date()) / 4 + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预算分配项目记录相关操作只能针对当前会费项目执行。
|
||||
*
|
||||
* @param quarterly 前端传入的目标分配项目编码,取值由 OUTLAY_QUARTERLY 字典维护
|
||||
* @return Result 不允许操作时返回错误结果;允许操作时返回 null
|
||||
*/
|
||||
private Result validateCurrentQuarterOperation(Integer quarterly) {
|
||||
private Result validateCurrentPeriodOperation(Integer quarterly) {
|
||||
if (quarterly == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
int currentQuarter = getCurrentQuarter();
|
||||
if (!quarterly.equals(currentQuarter)) {
|
||||
return Result.error("当前仅允许操作第" + currentQuarter + "季度数据,请切换后再操作!");
|
||||
int currentProject = getCurrentFeeProject();
|
||||
if (!quarterly.equals(currentProject)) {
|
||||
return Result.error("当前仅允许操作当前会费项目数据,请切换后再操作!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按目标年度季度初始化要生成的分工会分配记录。
|
||||
* 按目标年度分配项目初始化要生成的分工会分配记录。
|
||||
*
|
||||
* @param outlayManageUnionList 用于计算“分配前额度”的年度预算数据
|
||||
* @param quarterly 目标季度
|
||||
* @param quarterly 目标分配项目编码
|
||||
* @param year 目标年度
|
||||
* @return List<OutLayAllocateUnion> 待插入的季度分配记录
|
||||
* @return List<OutLayAllocateUnion> 待插入的分配项目记录
|
||||
*/
|
||||
private List<OutLayAllocateUnion> buildAllocateUnionList(List<OutlayManageUnion> outlayManageUnionList, Integer quarterly, Integer year) {
|
||||
List<Sys_union> unionList = dao().query(Sys_union.class, Cnd.NEW());
|
||||
|
||||
+32
-9
@@ -38,13 +38,13 @@ import java.util.List;
|
||||
/**
|
||||
* @author zhf
|
||||
* @date 2026/2/3 11:12
|
||||
* @description 分工会经费分配
|
||||
* @description 分工会经费分配项目管理
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/outlay/outlayManage/unionAllocate")
|
||||
@Ok("json:full")
|
||||
@Slf4j
|
||||
@Api(tags = "分工会经费分配")
|
||||
@Api(tags = "分工会经费分配项目管理")
|
||||
public class OutlayManageUnionAllocateController {
|
||||
|
||||
@Inject
|
||||
@@ -100,11 +100,13 @@ public class OutlayManageUnionAllocateController {
|
||||
@At
|
||||
@ApiOperation("一键批量分配所有工会额度")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "分工会预算-季度预算分配", msg = "一键批量分配所有工会额度")
|
||||
@SLog(tag = "分工会预算-分配项目预算分配", msg = "一键批量分配所有工会额度")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result doBatchAllocate(@Param("allocateMoney") BigDecimal allocateMoney,
|
||||
@Param("quarterly") Integer quarterly,
|
||||
@Param("year") Integer year) {
|
||||
// 参数说明:allocateMoney 为本次写入的分配额度;quarterly 承载 OUTLAY_QUARTERLY 字典编码,对应会费、评优奖励等分配项目;year 为预算年度。
|
||||
// 返回 Result,成功时前端刷新列表,失败时 msg 字段用于页面提示。
|
||||
if (allocateMoney == null || quarterly == null || year == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
@@ -112,7 +114,7 @@ public class OutlayManageUnionAllocateController {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("校验当前季度是否已分配额度")
|
||||
@ApiOperation("校验当前分配项目是否已分配额度")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result hasAllocated(@Param("quarterly") Integer quarterly,
|
||||
@Param("year") Integer year) {
|
||||
@@ -139,11 +141,13 @@ public class OutlayManageUnionAllocateController {
|
||||
@At
|
||||
@ApiOperation("导入分配分工会额度")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "分工会预算-季度预算分配", msg = "导入分配分工会额度")
|
||||
@SLog(tag = "分工会预算-分配项目预算分配", msg = "导入分配分工会额度")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result doImportAllocate(String data,
|
||||
@Param("quarterly") Integer quarterly,
|
||||
@Param("year") Integer year) {
|
||||
// 参数说明:data 为导入预览行 JSON;quarterly 为分配项目字典编码;year 为预算年度。
|
||||
// 返回 Result,成功表示导入额度已同步到分配项目记录和年度预算表,失败时 msg 字段返回具体校验原因。
|
||||
if (StrUtil.isBlank(data) || quarterly == null || year == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
@@ -151,6 +155,22 @@ public class OutlayManageUnionAllocateController {
|
||||
return outlayManageUnionAllocateService.doImportAllocate(importList, quarterly, year);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("新增单个分工会评优奖励")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "分工会预算-分配项目预算分配", msg = "新增单个分工会评优奖励")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result doAddAwardAllocate(@Param("year") Integer year,
|
||||
@Param("unionId") String unionId,
|
||||
@Param("allocateMoney") BigDecimal allocateMoney) {
|
||||
// 参数说明:year 为预算年度;unionId 为分工会 ID;allocateMoney 为本次新增的评优奖励金额。
|
||||
// 返回 Result,成功后前端刷新分配列表;失败时 msg 字段返回参数或重复新增等校验原因。
|
||||
if (year == null || StrUtil.isBlank(unionId) || allocateMoney == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
return outlayManageUnionAllocateService.doAddAwardAllocate(year, unionId, allocateMoney);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("下载导入分配模板")
|
||||
@@ -166,12 +186,13 @@ public class OutlayManageUnionAllocateController {
|
||||
|
||||
|
||||
@At
|
||||
@ApiOperation("重置预算季度记录")
|
||||
@ApiOperation("重置预算分配记录")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "分工会预算-季度预算分配", msg = "重置预算季度记录")
|
||||
@SLog(tag = "分工会预算-分配项目预算分配", msg = "重置预算分配记录")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result deleteAllocateRecord(@Param("quarterly") Integer quarterly,
|
||||
@Param("year") Integer year) {
|
||||
// 参数说明:quarterly 为当前要重置的分配项目编码,year 为预算年度;返回 Result 用于提示重置是否成功。
|
||||
if (quarterly == null || year == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
@@ -179,11 +200,13 @@ public class OutlayManageUnionAllocateController {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("预算季度记录生成")
|
||||
@SLog(tag = "分工会预算-季度预算分配", msg = "生成了预算分配记录")
|
||||
@ApiOperation("预算分配记录生成")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "分工会预算-分配项目预算分配", msg = "生成了预算分配记录")
|
||||
@SaCheckPermission("outlay.outlayManage.union.allocate")
|
||||
public Result doAllocateRecord(@Param("quarterly") Integer quarterly,
|
||||
@Param("year") Integer year) {
|
||||
// 参数说明:quarterly 为本次生成记录的分配项目编码,year 为预算年度;返回 Result 用于前端提示生成结果。
|
||||
if (quarterly == null || year == null) {
|
||||
return Result.error("参数错误!");
|
||||
}
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
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.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.vo.OutlayUnionIncomeExpenseVO;
|
||||
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.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 二级分工会经费收支使用情况表。
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/outlay/outlayManage/unionIncomeExpense")
|
||||
@Ok("json:full")
|
||||
@Slf4j
|
||||
@Api(tags = "二级分工会经费收支使用情况表")
|
||||
public class OutlayManageUnionIncomeExpenseController {
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/outlay/outlayManage/union/incomeExpense/index.html")
|
||||
@SaCheckPermission("outlay.outlayManage.union.incomeExpense")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("分页查询")
|
||||
@SaCheckPermission("outlay.outlayManage.union.incomeExpense")
|
||||
public Result pageData(PageForm pageForm, Integer year, String unionId) {
|
||||
Sql sql = buildIncomeExpenseSql(year, unionId);
|
||||
Pagination pagination = baseService.listPageVO(pageForm, sql, OutlayUnionIncomeExpenseVO.class);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出")
|
||||
@SaCheckPermission("outlay.outlayManage.union.incomeExpense")
|
||||
public void doExport(Integer year, String unionId, HttpServletResponse response) {
|
||||
Integer queryYear = getQueryYear(year);
|
||||
Sql sql = buildIncomeExpenseSql(queryYear, unionId);
|
||||
List<OutlayUnionIncomeExpenseVO> list = baseService.listVO(sql, OutlayUnionIncomeExpenseVO.class);
|
||||
|
||||
ArrayList<ExcelExportEntity> entities = new ArrayList<>();
|
||||
entities.add(new ExcelExportEntity("单位", "unionName", 25));
|
||||
entities.add(new ExcelExportEntity("年初数", "beginMoney", 20));
|
||||
entities.add(new ExcelExportEntity("1-4月会费", "feeJanApr", 20));
|
||||
entities.add(new ExcelExportEntity("5-8月会费", "feeMayAug", 20));
|
||||
entities.add(new ExcelExportEntity("9-12月会费", "feeSepDec", 20));
|
||||
entities.add(new ExcelExportEntity("评优奖励", "awardMoney", 20));
|
||||
entities.add(new ExcelExportEntity("1-12月支出", "usedMoney", 20));
|
||||
entities.add(new ExcelExportEntity("经费余额", "remainMoney", 20));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list);
|
||||
CommonDownloadUtil.download(queryYear + "年二级分工会经费收支使用情况表.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计参数说明:
|
||||
* year 为统计年度,未传时默认当前年份;unionId 为二级分工会主键,管理员可为空查询全部。
|
||||
* 返回 VO 中 beginMoney 是年初数,feeJanApr/feeMayAug/feeSepDec 是三个会费收入段,
|
||||
* awardMoney 是评优奖励,usedMoney 是年度支出,remainMoney 是当前经费余额。
|
||||
*/
|
||||
private Sql buildIncomeExpenseSql(Integer year, String unionId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
mu.year AS year,
|
||||
mu.unionId AS unionId,
|
||||
COALESCE(mu.unionName, su.name) AS unionName,
|
||||
COALESCE(mu.unionCode, su.unionCode) AS unionCode,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateHeadMoney ELSE 0 END), 0) AS beginMoney,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 1 THEN au.allocateMoney ELSE 0 END), 0) AS feeJanApr,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 2 THEN au.allocateMoney ELSE 0 END), 0) AS feeMayAug,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 3 THEN au.allocateMoney ELSE 0 END), 0) AS feeSepDec,
|
||||
COALESCE(SUM(CASE WHEN au.quarterly = 4 THEN au.allocateMoney ELSE 0 END), 0) AS awardMoney,
|
||||
COALESCE(mu.usedQuota, 0) AS usedMoney,
|
||||
COALESCE(mu.totalQuota, 0) - COALESCE(mu.usedQuota, 0) AS remainMoney
|
||||
FROM outlay_manage_union mu
|
||||
LEFT JOIN sys_union su ON su.id = mu.unionId
|
||||
LEFT JOIN outlay_allocate_union au ON au.year = mu.year
|
||||
AND au.unionId = mu.unionId
|
||||
AND au.delFlag = 0
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("mu.delFlag", "=", false);
|
||||
cnd.and("mu.year", "=", getQueryYear(year));
|
||||
cnd.and("mu.totalQuota", "IS NOT", null);
|
||||
cnd.andEX("mu.unionId", "=", unionId);
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||
cnd.and("mu.unionId", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
cnd.groupBy("mu.year");
|
||||
cnd.groupBy("mu.unionId");
|
||||
cnd.groupBy("mu.unionName");
|
||||
cnd.groupBy("mu.unionCode");
|
||||
cnd.groupBy("su.name");
|
||||
cnd.groupBy("su.unionCode");
|
||||
cnd.groupBy("mu.usedQuota");
|
||||
cnd.groupBy("mu.totalQuota");
|
||||
cnd.asc("mu.unionCode");
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
}
|
||||
|
||||
private Integer getQueryYear(Integer year) {
|
||||
if (year != null) {
|
||||
return year;
|
||||
}
|
||||
return DateUtil.thisYear();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -116,7 +116,7 @@ public class OutlayManageUnionUseDetailController {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询分工会今年每个季度的分配情况")
|
||||
@ApiOperation("查询分工会今年每个分配项目的分配情况")
|
||||
@SaCheckPermission("outlay.outlayManage.union.useDetail")
|
||||
public Result queryQuarterlyList(Integer year, String unionId){
|
||||
Sql sql = Sqls.create("""
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ public class OutLayAllocateUnion extends BaseModel implements Serializable {
|
||||
private String unionId;
|
||||
|
||||
@Column
|
||||
@Comment("季度")
|
||||
@Comment("分配项目")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer quarterly;
|
||||
|
||||
|
||||
+4
-6
@@ -6,22 +6,20 @@ import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 分工会季度额度导入行数据。
|
||||
* 分工会分配项目额度导入行数据。
|
||||
*/
|
||||
@Data
|
||||
public class OutlayUnionAllocateImportVo {
|
||||
|
||||
@Excel(name = "工会名称")
|
||||
private String unionName;
|
||||
|
||||
@Excel(name = "工会编码")
|
||||
private String unionCode;
|
||||
|
||||
@Excel(name = "分配额度")
|
||||
private BigDecimal allocateMoney;
|
||||
|
||||
/**
|
||||
* 预览时展示匹配到的工会名称。
|
||||
*/
|
||||
private String unionName;
|
||||
|
||||
/**
|
||||
* 预览时展示当前行错误信息。
|
||||
*/
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.budwk.app.zhgh.dayofficework.outlay.outlayManage.union.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 二级分工会经费收支使用情况表数据。
|
||||
*/
|
||||
@Data
|
||||
public class OutlayUnionIncomeExpenseVO {
|
||||
|
||||
private Integer year;
|
||||
|
||||
private String unionId;
|
||||
|
||||
private String unionName;
|
||||
|
||||
private String unionCode;
|
||||
|
||||
private BigDecimal beginMoney;
|
||||
|
||||
private BigDecimal feeJanApr;
|
||||
|
||||
private BigDecimal feeMayAug;
|
||||
|
||||
private BigDecimal feeSepDec;
|
||||
|
||||
private BigDecimal awardMoney;
|
||||
|
||||
private BigDecimal usedMoney;
|
||||
|
||||
private BigDecimal remainMoney;
|
||||
}
|
||||
+32
-3
@@ -307,6 +307,9 @@ public class SiteCugApplyController {
|
||||
if (item == null || StrUtil.hasBlank(item.getReserveStartTime(), item.getReserveEndTime())) {
|
||||
continue;
|
||||
}
|
||||
if (isOccupiedByCurrentUser(item)) {
|
||||
continue;
|
||||
}
|
||||
DateTime reserveStart = DateUtil.parseDateTime(item.getReserveStartTime());
|
||||
DateTime reserveEnd = DateUtil.parseDateTime(item.getReserveEndTime());
|
||||
if (!reserveEnd.isAfter(reserveStart)) {
|
||||
@@ -389,6 +392,13 @@ public class SiteCugApplyController {
|
||||
}
|
||||
}
|
||||
|
||||
// 当前登录人占用了该预约时,该预约占用的时间块对当前登录人放开,对其他人仍保持锁定
|
||||
private boolean isOccupiedByCurrentUser(SiteCugApply apply) {
|
||||
return apply != null
|
||||
&& StrUtil.isNotBlank(apply.getOccupyUserId())
|
||||
&& StrUtil.equals(apply.getOccupyUserId(), SecurityUtil.getUserId());
|
||||
}
|
||||
|
||||
private boolean intersectsAny(int start, int end, List<int[]> ranges) {
|
||||
for (int[] range : ranges) {
|
||||
if (range != null && start < range[1] && end > range[0]) {
|
||||
@@ -693,11 +703,11 @@ public class SiteCugApplyController {
|
||||
@SaCheckPermission(value = {"siteCug.apply", "h5.siteCug.apply"}, mode = SaMode.OR)
|
||||
public Result timeLimitConfig(@Param("siteId") String siteId) {
|
||||
if (StrUtil.isBlank(siteId)) {
|
||||
return Result.success(NutMap.NEW().addv("filterHolidays", false).addv("holidayList", new ArrayList<>()).addv("notApplyTimeList", new ArrayList<>()));
|
||||
return Result.success(NutMap.NEW().addv("filterHolidays", false).addv("holidayList", new ArrayList<>()).addv("notApplyTimeList", new ArrayList<>()).addv("termStartDate", "").addv("termEndDate", ""));
|
||||
}
|
||||
SiteCugInfo info = infoService.fetch(siteId);
|
||||
if (info == null) {
|
||||
return Result.success(NutMap.NEW().addv("filterHolidays", false).addv("holidayList", new ArrayList<>()).addv("notApplyTimeList", new ArrayList<>()));
|
||||
return Result.success(NutMap.NEW().addv("filterHolidays", false).addv("holidayList", new ArrayList<>()).addv("notApplyTimeList", new ArrayList<>()).addv("termStartDate", "").addv("termEndDate", ""));
|
||||
}
|
||||
List<String> holidayList = new ArrayList<>();
|
||||
if (Boolean.TRUE.equals(info.getFilterHolidays())) {
|
||||
@@ -713,7 +723,9 @@ public class SiteCugApplyController {
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("filterHolidays", Boolean.TRUE.equals(info.getFilterHolidays()))
|
||||
.addv("holidayList", holidayList)
|
||||
.addv("notApplyTimeList", info.getNotApplyTimeList() == null ? new ArrayList<>() : info.getNotApplyTimeList()));
|
||||
.addv("notApplyTimeList", info.getNotApplyTimeList() == null ? new ArrayList<>() : info.getNotApplyTimeList())
|
||||
.addv("termStartDate", info.getTermStartDate() != null ? info.getTermStartDate() : "")
|
||||
.addv("termEndDate", info.getTermEndDate() != null ? info.getTermEndDate() : ""));
|
||||
}
|
||||
|
||||
private List<SiteCugApply> buildYearlyApplyList(SiteCugApply apply) {
|
||||
@@ -819,5 +831,22 @@ public class SiteCugApplyController {
|
||||
for (ProcessTask task : doingTaskList) {
|
||||
flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args);
|
||||
}
|
||||
if (applyService.shouldDirectFinishOccupiedApply(apply)) {
|
||||
directFinishOccupiedApply(instance, args);
|
||||
}
|
||||
}
|
||||
|
||||
// 占用人提交与占用时间交叉的预约时,复用已有流程引擎自动结束后续审核任务
|
||||
private void directFinishOccupiedApply(ProcessInstance instance, Dict args) {
|
||||
List<ProcessTask> doingTaskList = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
|
||||
if (doingTaskList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Dict directArgs = args.clone();
|
||||
directArgs.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.AGREE.getCode());
|
||||
directArgs.set(FlowConst.APPROVAL_COMMENT, "占用预约自动通过");
|
||||
for (ProcessTask task : doingTaskList) {
|
||||
flowEngine.executeAndJumpToEnd(task.getId(), FlowConst.AUTO_ID, directArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -105,6 +105,13 @@ public class SiteCugManageController {
|
||||
if ((fullDayOpenHour == null || fullDayOpenHour.isEmpty()) && info.getReserveTimeType() == 2 && info.getOpenHours() != null && !info.getOpenHours().isEmpty()) {
|
||||
fullDayOpenHour = info.getOpenHours().get(0);
|
||||
}
|
||||
// 场地预约次数限制作为后续提交校验的依据,保存场地时必须配置为正整数
|
||||
if (info.getUnionYearReserveLimit() == null || info.getUnionYearReserveLimit() <= 0) {
|
||||
return Result.error("分工会每年预约次数必须大于0");
|
||||
}
|
||||
if (info.getClubWeekReserveLimit() == null || info.getClubWeekReserveLimit() <= 0) {
|
||||
return Result.error("社团每周预约次数必须大于0");
|
||||
}
|
||||
if (info.getReserveTimeType() == 1) {
|
||||
if (segmentedOpenHours == null || segmentedOpenHours.isEmpty()) {
|
||||
return Result.error("Please add at least one booking slot");
|
||||
|
||||
+24
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.siteCug.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
@@ -24,6 +25,7 @@ import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@@ -87,6 +89,28 @@ public class SiteCugRecordController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("占用预约记录")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("siteCug.record")
|
||||
public Result occupy(@Param("id") String id, @Param("message") String message) {
|
||||
Map<Boolean, String> result = applyService.occupyRecord(id, message, false);
|
||||
return result.containsKey(false) ? Result.error(result.get(false)) : Result.success(result.get(true));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("批量占用预约记录")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("siteCug.record")
|
||||
public Result occupyBatch(@Param("ids") String ids, @Param("message") String message) {
|
||||
if (StrUtil.isBlank(ids)) {
|
||||
return Result.error("请选择需要占用的预约");
|
||||
}
|
||||
List<String> idList = StrUtil.split(ids, ",").stream().filter(StrUtil::isNotBlank).toList();
|
||||
Map<Boolean, String> result = applyService.occupyRecords(idList, message, false);
|
||||
return result.containsKey(false) ? Result.error(result.get(false)) : Result.success(result.get(true));
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出预约记录")
|
||||
|
||||
@@ -110,5 +110,15 @@ public class SiteCugApply extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 200)
|
||||
private String backOption;
|
||||
|
||||
@Column
|
||||
@Comment("占用人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String occupyUserId;
|
||||
|
||||
@Column
|
||||
@Comment("占用通知消息")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 1000)
|
||||
private String occupyMessage;
|
||||
|
||||
private String yearlyReserveEndDate;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,28 @@ public class SiteCugInfo extends BaseModel {
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sexLimit;
|
||||
|
||||
@Column
|
||||
@Comment("本学期开始时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String termStartDate;
|
||||
|
||||
@Column
|
||||
@Comment("本学期结束时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String termEndDate;
|
||||
|
||||
@Column
|
||||
@Comment("分工会每年预约次数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
@Default("2")
|
||||
private Integer unionYearReserveLimit;
|
||||
|
||||
@Column
|
||||
@Comment("社团每周预约次数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
@Default("2")
|
||||
private Integer clubWeekReserveLimit;
|
||||
|
||||
@Column
|
||||
@Comment("场地的禁用时间")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
|
||||
+10
@@ -7,6 +7,7 @@ import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SiteCugApplyService extends BaseService<SiteCugApply> {
|
||||
@@ -21,4 +22,13 @@ public interface SiteCugApplyService extends BaseService<SiteCugApply> {
|
||||
|
||||
// 场馆预约查询页的导出逻辑统一放在 service,controller 只负责接收请求
|
||||
void exportRecord(SiteCugRecordPageForm pageForm, HttpServletResponse response);
|
||||
|
||||
// 占用单条预约记录,保存占用人和通知内容;真实发送消息代码当前保留为注释,便于测试流程
|
||||
Map<Boolean, String> occupyRecord(String id, String message, boolean sendMsg);
|
||||
|
||||
// 批量占用预约记录,先统一校验再保存,避免出现部分记录已占用、部分记录失败的状态
|
||||
Map<Boolean, String> occupyRecords(List<String> ids, String message, boolean sendMsg);
|
||||
|
||||
// 判断当前预约是否命中当前登录人的占用时间段,命中后提交预约可自动通过审核
|
||||
boolean shouldDirectFinishOccupiedApply(SiteCugApply apply);
|
||||
}
|
||||
|
||||
+246
-7
@@ -10,10 +10,10 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.sys.models.SysHoliday;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.sys.services.SysMsgService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.club.model.SysClub;
|
||||
import com.budwk.app.zhgh.club.service.SysClubService;
|
||||
@@ -46,6 +46,8 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
|
||||
@Inject
|
||||
private SysClubService sysClubService;
|
||||
@Inject
|
||||
private SysMsgService sysMsgService;
|
||||
|
||||
public SiteCugApplyServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
@@ -78,6 +80,10 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
if (!Boolean.TRUE.equals(siteInfo.getState())) {
|
||||
return Map.of(false, "该场地未开启预约");
|
||||
}
|
||||
Map<Boolean, String> termDateRangeValidate = validateTermDateRange(apply, siteInfo);
|
||||
if (termDateRangeValidate.containsKey(false)) {
|
||||
return termDateRangeValidate;
|
||||
}
|
||||
if (apply.getJoinCount() == null || apply.getJoinCount() <= 0) {
|
||||
return Map.of(false, "预约人数必须大于0");
|
||||
}
|
||||
@@ -96,6 +102,10 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
if (intersectsDisabledTime(siteInfo, apply.getReserveStartTime(), apply.getReserveEndTime())) {
|
||||
return Map.of(false, "预约时间落在禁用时间内");
|
||||
}
|
||||
Map<Boolean, String> reserveLimitValidate = validateReserveLimit(apply, siteInfo);
|
||||
if (reserveLimitValidate.containsKey(false)) {
|
||||
return reserveLimitValidate;
|
||||
}
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
@@ -108,8 +118,7 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and(SiteCugApply::getSiteId, "=", apply.getSiteId());
|
||||
cnd.andEX("sa.id", "!=", apply.getId());
|
||||
List<Integer> stateList = List.of(ProcessInstanceStateEnum.DOING.getCode(), ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.PENDING.getCode());
|
||||
cnd.and(ProcessInstance::getState, "in", stateList);
|
||||
cnd.and("ins.state", "in", buildEffectiveProcessStateList());
|
||||
sql.setCondition(cnd);
|
||||
List<SiteCugApply> applyList = listEntity(sql);
|
||||
|
||||
@@ -118,6 +127,9 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
boolean overlap = DateUtil.parseDateTime(item.getReserveStartTime()).isBefore(DateUtil.parseDateTime(apply.getReserveEndTime()))
|
||||
&& DateUtil.parseDateTime(item.getReserveEndTime()).isAfter(DateUtil.parseDateTime(apply.getReserveStartTime()));
|
||||
if (overlap) {
|
||||
if (isOccupiedByCurrentUser(item)) {
|
||||
continue;
|
||||
}
|
||||
if (StrUtil.isBlank(apply.getId()) && StrUtil.equals(item.getApplyUserId(), SecurityUtil.getUserId())) {
|
||||
return Map.of(false, "该时间段您已存在预约");
|
||||
}
|
||||
@@ -127,6 +139,236 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
return Map.of(true, "");
|
||||
}
|
||||
|
||||
// 分工会预约必须落在后台配置的本学期时间内,批量预约逐条复用该校验防止接口越权提交学期外日期
|
||||
private Map<Boolean, String> validateTermDateRange(SiteCugApply apply, SiteCugInfo siteInfo) {
|
||||
if (!StrUtil.equals(apply.getReserveType(), "union")
|
||||
|| StrUtil.hasBlank(siteInfo.getTermStartDate(), siteInfo.getTermEndDate())) {
|
||||
return Map.of(true, "");
|
||||
}
|
||||
try {
|
||||
DateTime reserveStart = DateUtil.parseDateTime(apply.getReserveStartTime());
|
||||
DateTime reserveEnd = DateUtil.parseDateTime(apply.getReserveEndTime());
|
||||
Date termStart = DateUtil.beginOfDay(DateUtil.parseDate(siteInfo.getTermStartDate()));
|
||||
Date termEnd = DateUtil.endOfDay(DateUtil.parseDate(siteInfo.getTermEndDate()));
|
||||
if (reserveStart.isBefore(termStart) || reserveEnd.isAfter(termEnd)) {
|
||||
return Map.of(false, "预约时间必须在本学期时间范围内");
|
||||
}
|
||||
return Map.of(true, "");
|
||||
} catch (Exception e) {
|
||||
return Map.of(false, "本学期时间配置有误,请联系管理员处理");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Boolean, String> occupyRecord(String id, String message, boolean sendMsg) {
|
||||
if (StrUtil.isBlank(message)) {
|
||||
return Map.of(false, "请填写通知消息");
|
||||
}
|
||||
SiteCugApply apply = fetch(id);
|
||||
if (apply == null) {
|
||||
return Map.of(false, "记录不存在");
|
||||
}
|
||||
Map<Boolean, String> validate = validateOccupyApply(apply);
|
||||
if (validate.containsKey(false)) {
|
||||
return validate;
|
||||
}
|
||||
saveOccupyApply(apply, message, sendMsg);
|
||||
return Map.of(true, "占用成功");
|
||||
}
|
||||
|
||||
// 已通过校验后统一保存占用信息;测试阶段真实发送消息代码保留注释
|
||||
private void saveOccupyApply(SiteCugApply apply, String message, boolean sendMsg) {
|
||||
// 保存占用人和通知内容,后续预约校验据此只放行当前占用人
|
||||
apply.setOccupyUserId(SecurityUtil.getUserId());
|
||||
apply.setOccupyMessage(message);
|
||||
updateIgnoreNull(apply);
|
||||
// 测试占用流程时先不真实发送消息,保留日志用于确认保存和跳转链路是否正常
|
||||
log.info("场地预约占用测试消息,applyId={},receiverLoginName={},message={}", apply.getId(), apply.getApplyLoginName(), message);
|
||||
if (sendMsg) {
|
||||
// sysMsgService.sendMsgInSys(List.of(apply.getApplyLoginName()), "场地预约占用通知", message, SecurityUtil.getUserId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Boolean, String> occupyRecords(List<String> ids, String message, boolean sendMsg) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return Map.of(false, "请选择需要占用的预约");
|
||||
}
|
||||
if (StrUtil.isBlank(message)) {
|
||||
return Map.of(false, "请填写通知消息");
|
||||
}
|
||||
List<SiteCugApply> applyList = new ArrayList<>();
|
||||
for (String id : ids) {
|
||||
SiteCugApply apply = fetch(id);
|
||||
if (apply == null) {
|
||||
return Map.of(false, "记录不存在");
|
||||
}
|
||||
Map<Boolean, String> validate = validateOccupyApply(apply);
|
||||
if (validate.containsKey(false)) {
|
||||
return validate;
|
||||
}
|
||||
applyList.add(apply);
|
||||
}
|
||||
for (SiteCugApply apply : applyList) {
|
||||
saveOccupyApply(apply, message, sendMsg);
|
||||
}
|
||||
return Map.of(true, "占用成功");
|
||||
}
|
||||
|
||||
// 占用前统一校验预约状态,避免单条和批量接口出现不同判断
|
||||
private Map<Boolean, String> validateOccupyApply(SiteCugApply apply) {
|
||||
if (StrUtil.isBlank(apply.getApplyLoginName())) {
|
||||
return Map.of(false, "预约人工号为空,无法发送消息");
|
||||
}
|
||||
if (StrUtil.isBlank(apply.getReserveStartTime()) || !DateUtil.parseDateTime(apply.getReserveStartTime()).isAfter(DateUtil.date())) {
|
||||
return Map.of(false, "预约已开始,不能占用");
|
||||
}
|
||||
Integer state = queryInstanceState(apply.getId());
|
||||
if (state == null || state != ProcessInstanceStateEnum.FINISHED.getCode()) {
|
||||
return Map.of(false, "仅可占用已通过且未开始的预约记录");
|
||||
}
|
||||
if (StrUtil.isNotBlank(apply.getOccupyUserId()) && !StrUtil.equals(apply.getOccupyUserId(), SecurityUtil.getUserId())) {
|
||||
return Map.of(false, "该预约已被其他人占用");
|
||||
}
|
||||
return Map.of(true, "");
|
||||
}
|
||||
|
||||
private Integer queryInstanceState(String id) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
ins.state AS instanceState
|
||||
FROM
|
||||
site_cug_apply info
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||
WHERE info.id = @id
|
||||
""");
|
||||
sql.params().set("id", id);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
dao().execute(sql);
|
||||
List<NutMap> list = sql.getList(NutMap.class);
|
||||
return list != null && !list.isEmpty() ? list.get(0).getInt("instanceState") : null;
|
||||
}
|
||||
|
||||
// 当前登录人占用了该记录时,允许其再次提交与该记录有交叉的预约
|
||||
private boolean isOccupiedByCurrentUser(SiteCugApply apply) {
|
||||
return apply != null
|
||||
&& StrUtil.isNotBlank(apply.getOccupyUserId())
|
||||
&& StrUtil.equals(apply.getOccupyUserId(), SecurityUtil.getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDirectFinishOccupiedApply(SiteCugApply apply) {
|
||||
if (apply == null || StrUtil.hasBlank(apply.getSiteId(), apply.getReserveStartTime(), apply.getReserveEndTime())) {
|
||||
return false;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
sa.*
|
||||
from
|
||||
site_cug_apply sa
|
||||
left join wf_process_instance ins on ins.businessNo = sa.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("sa.siteId", "=", apply.getSiteId());
|
||||
cnd.andEX("sa.id", "!=", apply.getId());
|
||||
cnd.and("sa.occupyUserId", "=", SecurityUtil.getUserId());
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
sql.setCondition(cnd);
|
||||
List<SiteCugApply> applyList = listEntity(sql);
|
||||
|
||||
// 只要新预约时间和当前登录人占用的已通过预约有交叉,就允许本次预约免审通过
|
||||
return applyList.stream().anyMatch(item ->
|
||||
DateUtil.parseDateTime(item.getReserveStartTime()).isBefore(DateUtil.parseDateTime(apply.getReserveEndTime()))
|
||||
&& DateUtil.parseDateTime(item.getReserveEndTime()).isAfter(DateUtil.parseDateTime(apply.getReserveStartTime()))
|
||||
);
|
||||
}
|
||||
|
||||
// 按预约主体统计有效预约次数,批量预约同一个批次号只计一次,避免一次批量提交占用多次额度
|
||||
private Map<Boolean, String> validateReserveLimit(SiteCugApply apply, SiteCugInfo siteInfo) {
|
||||
if (StrUtil.equals(apply.getReserveType(), "union")) {
|
||||
if (siteInfo.getUnionYearReserveLimit() == null || siteInfo.getUnionYearReserveLimit() <= 0) {
|
||||
return Map.of(false, "请先配置分工会每年预约次数");
|
||||
}
|
||||
String[] range = buildYearRange(apply.getReserveStartTime());
|
||||
long count = countEffectiveReserveTimes(apply, "sa.applyUnionId", apply.getApplyUnionId(), range[0], range[1]);
|
||||
if (count >= siteInfo.getUnionYearReserveLimit()) {
|
||||
return Map.of(false, "该分工会本年度预约次数已达" + siteInfo.getUnionYearReserveLimit() + "次,不能继续预约");
|
||||
}
|
||||
}
|
||||
if (StrUtil.equals(apply.getReserveType(), "club")) {
|
||||
if (siteInfo.getClubWeekReserveLimit() == null || siteInfo.getClubWeekReserveLimit() <= 0) {
|
||||
return Map.of(false, "请先配置社团每周预约次数");
|
||||
}
|
||||
String[] range = buildWeekRange(apply.getReserveStartTime());
|
||||
long count = countEffectiveReserveTimes(apply, "sa.clubId", apply.getClubId(), range[0], range[1]);
|
||||
if (count >= siteInfo.getClubWeekReserveLimit()) {
|
||||
return Map.of(false, "该协会本周预约次数已达" + siteInfo.getClubWeekReserveLimit() + "次,不能继续预约");
|
||||
}
|
||||
}
|
||||
return Map.of(true, "");
|
||||
}
|
||||
|
||||
// 分工会每年次数按预约开始时间所在自然年统计
|
||||
private String[] buildYearRange(String reserveStartTime) {
|
||||
DateTime reserveStart = DateUtil.parseDateTime(reserveStartTime);
|
||||
String year = String.valueOf(reserveStart.year());
|
||||
return new String[]{year + "-01-01 00:00:00", year + "-12-31 23:59:59"};
|
||||
}
|
||||
|
||||
// 协会每周次数按自然周统计,周一为开始、周日为结束
|
||||
private String[] buildWeekRange(String reserveStartTime) {
|
||||
DateTime reserveStart = DateUtil.parseDateTime(reserveStartTime);
|
||||
int week = reserveStart.dayOfWeek() - 1;
|
||||
int offsetToMonday = week == 0 ? -6 : 1 - week;
|
||||
DateTime weekStart = DateUtil.offsetDay(reserveStart, offsetToMonday);
|
||||
DateTime weekEnd = DateUtil.offsetDay(weekStart, 6);
|
||||
return new String[]{DateUtil.formatDate(weekStart) + " 00:00:00", DateUtil.formatDate(weekEnd) + " 23:59:59"};
|
||||
}
|
||||
|
||||
// 统计预约主体在指定时间范围内的有效预约次数,所有场地合计,不按场地过滤
|
||||
private long countEffectiveReserveTimes(SiteCugApply apply, String targetField, String targetId, String rangeStart, String rangeEnd) {
|
||||
if (StrUtil.isBlank(targetId)) {
|
||||
return 0;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
select
|
||||
sa.*
|
||||
from
|
||||
site_cug_apply sa
|
||||
left join wf_process_instance ins on ins.businessNo = sa.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("sa.reserveType", "=", apply.getReserveType());
|
||||
cnd.andEX(targetField, "=", targetId);
|
||||
cnd.andEX("sa.reserveStartTime", ">=", rangeStart);
|
||||
cnd.andEX("sa.reserveStartTime", "<=", rangeEnd);
|
||||
if (StrUtil.isNotBlank(apply.getId())) {
|
||||
cnd.andEX("sa.id", "!=", apply.getId());
|
||||
}
|
||||
cnd.and("ins.state", "in", buildEffectiveProcessStateList());
|
||||
sql.setCondition(cnd);
|
||||
List<SiteCugApply> applyList = listEntity(sql);
|
||||
return applyList.stream()
|
||||
.map(this::buildReserveCountKey)
|
||||
.distinct()
|
||||
.count();
|
||||
}
|
||||
|
||||
// 只有待审核、办理中、已通过的流程占用预约次数,驳回记录不占额度
|
||||
private List<Integer> buildEffectiveProcessStateList() {
|
||||
return List.of(ProcessInstanceStateEnum.DOING.getCode(), ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.PENDING.getCode());
|
||||
}
|
||||
|
||||
// 批量预约同一批次号只算一次,普通预约按单条申请计算
|
||||
private String buildReserveCountKey(SiteCugApply apply) {
|
||||
if (apply != null && StrUtil.isNotBlank(apply.getBackOption()) && apply.getBackOption().startsWith("YEARLY_BATCH:")) {
|
||||
return apply.getBackOption();
|
||||
}
|
||||
return apply == null ? "" : apply.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sql buildRecordSql() {
|
||||
// 场馆预约查询页和导出页共用同一套返回字段,统一在 service 中维护,避免 controller 重复拼 SQL
|
||||
@@ -135,6 +377,7 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
info.*,
|
||||
si.name AS siteName,
|
||||
ins.id AS instanceId,
|
||||
ins.state AS instanceState,
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
CASE WHEN info.backOption LIKE 'YEARLY_BATCH:%' THEN 1 ELSE 0 END AS yearlyBatch,
|
||||
CASE WHEN info.reserveType = 'club' THEN '协会预约' ELSE '分工会预约' END AS reserveTypeName,
|
||||
@@ -315,7 +558,3 @@ public class SiteCugApplyServiceImpl extends BaseServiceImpl<SiteCugApply> imple
|
||||
return Map.of(false, "预约类型不合法");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+36
-22
@@ -111,8 +111,6 @@ public class UnionReimburseCollectController {
|
||||
cnd.and(seg);
|
||||
}
|
||||
|
||||
cnd.and("info.stateId", "in", List.of(3, 2));
|
||||
|
||||
applyListSort(cnd, pageForm);
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pagination = unionReimburseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
@@ -185,29 +183,26 @@ public class UnionReimburseCollectController {
|
||||
@Ok("void")
|
||||
@SaCheckPermission(value = {"unionReimburse.collect", "h5.unionReimburse.collect"}, mode = SaMode.OR)
|
||||
@ApiOperation("导出报销汇总表")
|
||||
public void onExport(@Param(value = "year") Integer year,
|
||||
public void onExport(PageForm pageForm,
|
||||
@Param(value = "year") Integer year,
|
||||
@Param(value = "unionId") String unionId,
|
||||
@Param(value = "unitId") String unitId,
|
||||
@Param(value = "reimburseProject") String reimburseProject,
|
||||
@Param(value = "userName") String userName,
|
||||
@Param(value = "searchKeyword") String searchKeyword,
|
||||
HttpServletResponse response) {
|
||||
//查询审核通过的数据
|
||||
// 导出复用收集列表筛选口径,保证下载结果与页面当前查询条件一致。
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t1.*,
|
||||
ins.state instanceState
|
||||
info.*
|
||||
FROM
|
||||
union_reimburse t1
|
||||
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = t1.id
|
||||
union_reimburse info
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = buildCondition(year, unionId, unitId, reimburseProject, userName, "t1.");
|
||||
// 只查询流程实例状态为20的数据(已完成状态)
|
||||
cnd.and("ins.state", "=", 20);
|
||||
Cnd cnd = buildCondition(year, unionId, unitId, reimburseProject, searchKeyword, "info.");
|
||||
if (!isAdmin()) {
|
||||
cnd.andEX("t1.unionId", "=", SecurityUtil.getUnionId());
|
||||
cnd.andEX("info.unionId", "=", SecurityUtil.getUnionId());
|
||||
}
|
||||
cnd.desc("t1.createTime");
|
||||
applyListSort(cnd, pageForm);
|
||||
|
||||
sql.setCondition(cnd);
|
||||
List<UnionReimburseCollectExcelVO> list = unionReimburseService.listVO(sql,UnionReimburseCollectExcelVO.class);
|
||||
@@ -242,11 +237,13 @@ public class UnionReimburseCollectController {
|
||||
item.setCreateTime(DateUtil.format(DateUtil.parse(item.getCreateTime()), "yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
// 拼接备注字段(仿照系统代码逻辑,使用activityName和condolenceName字段)
|
||||
item.setStateName(formatStateName(item.getStateId()));
|
||||
|
||||
// 备注字段与页面表格保持一致:慰问显示被慰问人,其他项目显示活动名称。
|
||||
if (cn.hutool.core.util.StrUtil.isNotBlank(item.getActivityName())) {
|
||||
item.setRemark("活动名称:" + item.getActivityName());
|
||||
} else if (cn.hutool.core.util.StrUtil.isNotBlank(item.getCondolenceName())) {
|
||||
item.setRemark("慰问对象:" + item.getCondolenceName());
|
||||
} else if (cn.hutool.core.util.StrUtil.isNotBlank(item.getCondolenceUserName())) {
|
||||
item.setRemark("被慰问人:" + item.getCondolenceUserName());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -263,12 +260,12 @@ public class UnionReimburseCollectController {
|
||||
* @param unionId 工会ID
|
||||
* @param unitId 单位ID
|
||||
* @param reimburseProject 报销项目
|
||||
* @param userName 经办人姓名
|
||||
* @param searchKeyword 经办人姓名或工号
|
||||
* @param prefix 表前缀
|
||||
* @return 查询条件
|
||||
*/
|
||||
private Cnd buildCondition(Integer year, String unionId, String unitId,
|
||||
String reimburseProject, String userName, String prefix) {
|
||||
String reimburseProject, String searchKeyword, String prefix) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
|
||||
// 年度查询条件
|
||||
@@ -281,9 +278,12 @@ public class UnionReimburseCollectController {
|
||||
// 报销项目查询条件
|
||||
cnd.andEX(prefix + "reimburseProject", "=", reimburseProject);
|
||||
|
||||
// 经办人姓名查询条件
|
||||
if (StrUtil.isNotBlank(userName)) {
|
||||
cnd.and(prefix + "userName", "like", "%" + userName + "%");
|
||||
// 经办人姓名或工号查询条件
|
||||
if (StrUtil.isNotBlank(searchKeyword)) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.orLike(prefix + "userName", searchKeyword);
|
||||
seg.orLike(prefix + "loginName", searchKeyword);
|
||||
cnd.and(seg);
|
||||
}
|
||||
|
||||
return cnd;
|
||||
@@ -318,4 +318,18 @@ public class UnionReimburseCollectController {
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private String formatStateName(Integer stateId) {
|
||||
if (stateId == null) {
|
||||
return "";
|
||||
}
|
||||
return switch (stateId) {
|
||||
case 1 -> "待提交";
|
||||
case 2 -> "待审核确认";
|
||||
case 3 -> "报销成功";
|
||||
case 4 -> "拒绝";
|
||||
case 5 -> "退回";
|
||||
default -> String.valueOf(stateId);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -175,6 +175,11 @@ public class UnionReimburse extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bankOfDeposit;
|
||||
|
||||
@Column
|
||||
@Comment("支付明细")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<JSONObject> paymentDetails;
|
||||
|
||||
/**
|
||||
* 慰问
|
||||
*/
|
||||
|
||||
+20
-8
@@ -369,13 +369,15 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
if (Integer.valueOf(3).equals(dbRecord.getStateId())) {
|
||||
return Result.success();
|
||||
}
|
||||
Result validateResult = validateFundSourceBeforeSubmit(dbRecord);
|
||||
if (validateResult != null) {
|
||||
return validateResult;
|
||||
}
|
||||
Result deductResult = deductBudgetAndSaveUseDetail(dbRecord);
|
||||
if (deductResult != null && deductResult.getCode() != 0) {
|
||||
return deductResult;
|
||||
if (!skipBudgetDeduct(dbRecord)) {
|
||||
Result validateResult = validateFundSourceBeforeSubmit(dbRecord);
|
||||
if (validateResult != null) {
|
||||
return validateResult;
|
||||
}
|
||||
Result deductResult = deductBudgetAndSaveUseDetail(dbRecord);
|
||||
if (deductResult != null && deductResult.getCode() != 0) {
|
||||
return deductResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
dbRecord.setReviewTime(new Date());
|
||||
@@ -422,7 +424,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
if (dbRecord == null) {
|
||||
return Result.error("未找到对应的报销记录");
|
||||
}
|
||||
if (Integer.valueOf(3).equals(dbRecord.getStateId())) {
|
||||
if (Integer.valueOf(3).equals(dbRecord.getStateId()) && !skipBudgetDeduct(dbRecord)) {
|
||||
OutlayUseDetail oldDetail = dao().fetch(OutlayUseDetail.class, Cnd.where("outlayReimburseId", "=", dbRecord.getId()));
|
||||
BigDecimal oldMoney = oldDetail != null && oldDetail.getAdjustMoney() != null
|
||||
? oldDetail.getAdjustMoney()
|
||||
@@ -652,6 +654,16 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
return Result.error("不支持的经费来源");
|
||||
}
|
||||
|
||||
/**
|
||||
* 慰问和专项活动费用不占用经费预算,审核通过及后续调整实际金额时都不写预算台账。
|
||||
*/
|
||||
private boolean skipBudgetDeduct(UnionReimburse unionReimburse) {
|
||||
return unionReimburse != null && Set.of(
|
||||
"UNION_REIMBURSE_PROJECT_1",
|
||||
"UNION_REIMBURSE_PROJECT_4"
|
||||
).contains(unionReimburse.getReimburseProject());
|
||||
}
|
||||
|
||||
/**
|
||||
* 已通过报销单修改实际金额时,用差额同步预算主表和使用明细,避免重复全量扣减。
|
||||
*/
|
||||
|
||||
+10
-3
@@ -22,23 +22,30 @@ public class UnionReimburseCollectExcelVO implements Serializable {
|
||||
@Excel(name = "所属单位", width = 20)
|
||||
private String unitName;
|
||||
|
||||
@Excel(name = "申请时间", width = 30)
|
||||
private String createTime;
|
||||
|
||||
@Excel(name = "报销项目", width = 20)
|
||||
private String reimburseProject;
|
||||
|
||||
@Excel(name = "备注", width = 20)
|
||||
private String remark;
|
||||
|
||||
@Excel(name = "金额", width = 20)
|
||||
@Excel(name = "申请状态", width = 20)
|
||||
private String stateName;
|
||||
|
||||
private Double money;
|
||||
|
||||
@Excel(name = "申请时间", width = 30)
|
||||
private String createTime;
|
||||
private Integer stateId;
|
||||
|
||||
private String reimburseFundSource;
|
||||
|
||||
//活动名称
|
||||
private String activityName;
|
||||
|
||||
//被慰问人姓名
|
||||
private String condolenceUserName;
|
||||
|
||||
//被慰问人
|
||||
private String condolenceName;
|
||||
|
||||
|
||||
+5
@@ -63,6 +63,11 @@ public class AidFundMemberHistory extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String personType;
|
||||
|
||||
@Column
|
||||
@Comment("人员属性")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String userAttribute;
|
||||
|
||||
@Column
|
||||
@Comment("在职状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
|
||||
+5
@@ -64,4 +64,9 @@ public class AidFundMemberPay extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
private String userState;
|
||||
|
||||
@Column
|
||||
@Comment("人员属性")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String userAttribute;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user