Merge branch 'main' of https://dd3skj.picp.vip/JyuHsin/zhgh_cug_v4
Conflicts: src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/controller/TeacherCongressInstitutionController.java src/main/resources/views/platform/zhgh/democratic/teachercongress/institution/basicTable.js src/main/resources/views/platform/zhgh/democratic/teachercongress/institution/index.html
This commit is contained in:
@@ -8,18 +8,25 @@ import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.FieldFilter;
|
||||
import org.nutz.dao.util.Daos;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -29,11 +36,23 @@ import java.util.stream.Collectors;
|
||||
@Api(tags = "用户审批意见")
|
||||
public class SysUserApprovalOpinionController {
|
||||
|
||||
/**
|
||||
* 系统内置的常用审批意见,统一提供给所有使用审核意见组件的页面。
|
||||
*/
|
||||
private static final List<String> DEFAULT_APPROVAL_OPINIONS = List.of("同意", "不同意");
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
/**
|
||||
* 保存当前用户的自定义审批意见。
|
||||
*
|
||||
* @param opinions 前端提交的意见数组,每项包含 id 和 text;系统内置的“同意”“不同意”不会写入用户数据
|
||||
* @return Result,成功时不返回额外数据,失败时 msg 说明校验结果
|
||||
*/
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@ApiOperation("保存审批意见")
|
||||
public Result update(@Param("opinions")JSONObject[] opinions){
|
||||
boolean hasBlank = Arrays.stream(opinions).anyMatch(v -> StrUtil.isBlank(v.getStr("text").trim()));
|
||||
@@ -41,35 +60,68 @@ public class SysUserApprovalOpinionController {
|
||||
return Result.error("内容不能为空,请删除后再提交!");
|
||||
}
|
||||
|
||||
Set<String> opinionTexts = Arrays.stream(opinions).map(v -> v.getStr("text").trim()).collect(Collectors.toSet());
|
||||
if(opinionTexts.size() != opinions.length){
|
||||
// 系统默认意见只参与页面展示,不占用用户自定义意见数量,也不写入用户表。
|
||||
JSONObject[] customOpinions = Arrays.stream(opinions)
|
||||
.filter(v -> !DEFAULT_APPROVAL_OPINIONS.contains(v.getStr("text").trim()))
|
||||
.toArray(JSONObject[]::new);
|
||||
|
||||
Set<String> opinionTexts = Arrays.stream(customOpinions).map(v -> v.getStr("text").trim()).collect(Collectors.toSet());
|
||||
if(opinionTexts.size() != customOpinions.length){
|
||||
return Result.error("内容重复,请删除后再提交!");
|
||||
}
|
||||
|
||||
boolean text = Arrays.stream(opinions).anyMatch(v -> v.getStr("text").trim().length() > 50);
|
||||
boolean text = Arrays.stream(customOpinions).anyMatch(v -> v.getStr("text").trim().length() > 50);
|
||||
if(text){
|
||||
return Result.error("单条内容不能超过50个字!");
|
||||
}
|
||||
|
||||
if(opinions.length > 5){
|
||||
if(customOpinions.length > 5){
|
||||
return Result.error("最多可添加10条审批意见!");
|
||||
}
|
||||
|
||||
for (int i = 0; i < opinions.length; i++) {
|
||||
opinions[i].set("id",i+1);
|
||||
for (int i = 0; i < customOpinions.length; i++) {
|
||||
customOpinions[i].set("id",i+1);
|
||||
}
|
||||
|
||||
dao.update(Sys_user.class, Chain.make("customApprovalOpinions", Arrays.asList(opinions)), Cnd.where(Sys_user::getId,"=", SecurityUtil.getUserId()));
|
||||
dao.update(Sys_user.class, Chain.make("customApprovalOpinions", Arrays.asList(customOpinions)), Cnd.where(Sys_user::getId,"=", SecurityUtil.getUserId()));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户可选的审批意见。
|
||||
*
|
||||
* @return Result,data 为意见数组,每项包含 id 和 text;系统默认意见排在用户自定义意见之前
|
||||
*/
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("获取审批意见")
|
||||
public Result list(){
|
||||
FieldFilter fieldFilter = FieldFilter.create(Sys_user.class, "^customApprovalOpinions");
|
||||
Sys_user sysUser = Daos.ext(dao, fieldFilter).fetch(Sys_user.class, Cnd.where(Sys_user::getId, "=", SecurityUtil.getUserId()));
|
||||
return Result.success(sysUser.getCustomApprovalOpinions());
|
||||
List<JSONObject> opinions = new ArrayList<>();
|
||||
Set<String> opinionTexts = new HashSet<>();
|
||||
|
||||
// 默认意见始终置顶,并通过文本去重,避免与用户历史自定义意见重复。
|
||||
for (String defaultOpinion : DEFAULT_APPROVAL_OPINIONS) {
|
||||
JSONObject opinion = new JSONObject();
|
||||
opinion.set("id", opinions.size() + 1);
|
||||
opinion.set("text", defaultOpinion);
|
||||
opinions.add(opinion);
|
||||
opinionTexts.add(defaultOpinion);
|
||||
}
|
||||
|
||||
List<JSONObject> customOpinions = Optional.ofNullable(sysUser.getCustomApprovalOpinions()).orElseGet(Collections::emptyList);
|
||||
for (JSONObject customOpinion : customOpinions) {
|
||||
String text = customOpinion.getStr("text");
|
||||
if (StrUtil.isBlank(text) || !opinionTexts.add(text.trim())) {
|
||||
continue;
|
||||
}
|
||||
JSONObject opinion = new JSONObject();
|
||||
opinion.set("id", opinions.size() + 1);
|
||||
opinion.set("text", text);
|
||||
opinions.add(opinion);
|
||||
}
|
||||
return Result.success(opinions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -266,6 +266,11 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@DataCenterColumn(name = "单位", key = "SZDWH")
|
||||
private String unitId;
|
||||
|
||||
@Column
|
||||
@Comment("三级单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String threeUnitId;
|
||||
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unitPath;
|
||||
|
||||
+36
-102
@@ -2,7 +2,6 @@ package com.budwk.app.sys.services.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
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;
|
||||
@@ -25,7 +24,6 @@ import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeOrigin;
|
||||
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
@@ -52,7 +50,6 @@ import java.util.stream.Collectors;
|
||||
@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;
|
||||
@@ -114,34 +111,6 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
return fieldMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否符合会员条件
|
||||
*
|
||||
* @param userState 用户状态
|
||||
* @param preparedBy 编制类别
|
||||
* @param postDoctoralJoinDate 博士后进站时间
|
||||
* @return 是否符合会员条件
|
||||
*/
|
||||
private boolean checkMembershipEligibility(String userState, String preparedBy, Date postDoctoralJoinDate) {
|
||||
// 博士后单独判断:只要进站时间在两年内就是会员
|
||||
if ("博士后".equals(preparedBy)) {
|
||||
if (postDoctoralJoinDate != null) {
|
||||
Date twoYearsAgo = DateUtil.offset(DateUtil.date(), DateField.YEAR, -2).toJdkDate();
|
||||
return postDoctoralJoinDate.after(twoYearsAgo);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 其他人员需要判断在岗状态和聘用方式
|
||||
if (!"在岗".equals(userState)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 判断聘用方式
|
||||
Set<String> memberPreparedByTypes = new HashSet<>(Arrays.asList("新人事代理", "校聘合同制", "事业编制"));
|
||||
return memberPreparedByTypes.contains(preparedBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量更新用户数据
|
||||
*
|
||||
@@ -156,7 +125,6 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
|
||||
// 获取角色信息
|
||||
Sys_role publicRole = sysRoleService.getByCode(RoleConstant.PUBLIC);
|
||||
Sys_role memberRole = sysRoleService.getByCode(RoleConstant.MEMBER);
|
||||
|
||||
// 获取单位信息,后续判断单位变更
|
||||
List<Sys_unit> unitList = dao.query(Sys_unit.class, Cnd.where("unitTypeCode", "=", "1"));
|
||||
@@ -181,23 +149,6 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
List<Sys_user> needDoUpdateList = new CopyOnWriteArrayList<>();
|
||||
List<Sys_user> needInitUserList = new CopyOnWriteArrayList<>();
|
||||
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) {
|
||||
@@ -214,22 +165,15 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
u.setSalt(salt);
|
||||
u.setPassword(PwdUtil.getPassword(PwdUtil.generate(12), salt));
|
||||
|
||||
/* if (u.getMember()) {
|
||||
addMemberUserIds.add(u.getId());
|
||||
}*/
|
||||
needInitUserList.add(u);
|
||||
} else {
|
||||
// 修改现有用户
|
||||
u.setId(user.getId());
|
||||
|
||||
// 更新会员状态
|
||||
boolean currentIsMember = user.getMember() != null && user.getMember();
|
||||
|
||||
if (!currentIsMember) {
|
||||
// addMemberUserIds.add(user.getId());
|
||||
} else{
|
||||
// removeMemberUserIds.add(user.getId());
|
||||
}
|
||||
/*
|
||||
* aidFundMember 由基金会员业务维护,数据中心源表默认值为 0。
|
||||
* 全量更新已有用户时置空,避免 updateIgnoreNull 把已迁移的基金会员标识覆盖为非会员。
|
||||
*/
|
||||
u.setAidFundMember(null);
|
||||
|
||||
needDoUpdateList.add(u);
|
||||
}
|
||||
@@ -312,45 +256,6 @@ 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());
|
||||
@@ -444,8 +349,11 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
long endTime = System.currentTimeMillis();
|
||||
log.info("全量更新用户数据完成,耗时: {} 毫秒", (endTime - startTime));
|
||||
|
||||
return "更新完成: 新增用户 " + needInitUserList.size() + " 个, 更新用户 " + needDoUpdateList.size()
|
||||
+ " 个, 数据源缺失转不在岗 " + leaveUserIds.size() + " 个, 待添加会员 " + addMemberUserIds.size() + " 个, 待移除会员 " + removeMemberUserIds.size() + " 个";
|
||||
if (needInitUserList.isEmpty() && histories.isEmpty()) {
|
||||
return "本次没有可更新的数据。";
|
||||
}
|
||||
return "更新完成: 新增用户 " + needInitUserList.size() + " 个, 有效变更 " + histories.size()
|
||||
+ " 条";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -479,6 +387,18 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
Object sourceValue = mapping.field.get(source);
|
||||
Object userValue = mapping.field.get(user);
|
||||
|
||||
/*
|
||||
* 全量更新使用 updateIgnoreNull,数据源空值不会覆盖系统已有值。
|
||||
* 因此当新值为空、旧值有值时不生成历史记录,避免每次更新都重复记录“有值→空”。
|
||||
* 同时将 null、空字符串、纯空格统一视为业务空值,避免页面显示为空但底层值不同导致“空→空”被记录。
|
||||
*/
|
||||
if (isBlankChangeValue(sourceValue) && isBlankChangeValue(userValue)) {
|
||||
continue;
|
||||
}
|
||||
if (isBlankChangeValue(sourceValue) && !isBlankChangeValue(userValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果值不相等,记录变更
|
||||
if (!ObjectUtil.equals(sourceValue, userValue)) {
|
||||
NutMap change = NutMap.NEW();
|
||||
@@ -543,6 +463,20 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断历史变更字段是否为业务空值。
|
||||
* null、空字符串、纯空格在页面上都会展示为空,统一视为无有效新值,避免生成无意义的空值变更记录。
|
||||
*/
|
||||
private boolean isBlankChangeValue(Object value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
if (value instanceof CharSequence) {
|
||||
return StrUtil.isBlank(value.toString());
|
||||
}
|
||||
return ObjectUtil.isEmpty(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单位变更记录展示值。
|
||||
* 入参 unitId 为系统用户旧单位或数据源新单位ID;返回值优先使用单位名称,查不到名称时保留单位ID,避免变更记录为空导致更新中断。
|
||||
|
||||
+22
-36
@@ -2,6 +2,7 @@ package com.budwk.app.sys.services.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.thread.AsyncUtil;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -15,18 +16,18 @@ import com.budwk.app.sys.param.SysDataUserUpdateParam;
|
||||
import com.budwk.app.sys.services.SysDataUserUpdateService;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
|
||||
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeOrigin;
|
||||
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
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 org.nutz.lang.random.R;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Calendar;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,7 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
|
||||
private SysRoleService sysRoleService;
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public String update(SysDataUserUpdateParam updateParam) {
|
||||
// 基本SQL查询
|
||||
Cnd cnd = Cnd.where("pullTime", "=", updateParam.getPullTime())
|
||||
@@ -67,34 +69,6 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
|
||||
String pwd = PwdUtil.generate(12);
|
||||
sysUser.setPassword(PwdUtil.getPassword(pwd, salt));
|
||||
sysUser.setLoginCount(0);
|
||||
|
||||
// 检查是否符合会员条件
|
||||
// 1. 用户状态为"在岗"
|
||||
if ("在岗".equals(sysUser.getUserState())) {
|
||||
// 2. 检查聘用方式条件
|
||||
List<String> membershipQualifyingPreparedBy = Arrays.asList("新人事代理", "校聘合同制", "事业编制");
|
||||
|
||||
// 第一种情况:聘用方式在指定列表中
|
||||
boolean isQualifiedByPreparedBy = membershipQualifyingPreparedBy.contains(sysUser.getPreparedBy());
|
||||
|
||||
// 第二种情况:聘用方式是博士后,且进站时间不超过两年
|
||||
boolean isQualifiedPostdoc = false;
|
||||
if ("博士后".equals(sysUser.getPreparedBy()) && sysUser.getPostDoctoralJoinDate() != null) {
|
||||
Calendar twoYearsAgo = Calendar.getInstance();
|
||||
twoYearsAgo.add(Calendar.YEAR, -2);
|
||||
Date twoYearsAgoDate = twoYearsAgo.getTime();
|
||||
isQualifiedPostdoc = sysUser.getPostDoctoralJoinDate().after(twoYearsAgoDate);
|
||||
}
|
||||
|
||||
// 如果满足任一条件,设置为会员
|
||||
if (isQualifiedByPreparedBy || isQualifiedPostdoc) {
|
||||
sysUser.setMember(true);
|
||||
} else {
|
||||
sysUser.setMember(false);
|
||||
}
|
||||
} else {
|
||||
sysUser.setMember(false);
|
||||
}
|
||||
}
|
||||
|
||||
List<List<Sys_user>> splitSysUsers = ListUtil.split(sysUsers, 500);
|
||||
@@ -102,6 +76,18 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
|
||||
//等待任务全部完成
|
||||
AsyncUtil.waitAll(CompletableFuture.allOf(splitSysUsersFutures.toArray(new CompletableFuture[0])));
|
||||
|
||||
// 增量更新只处理新增人员,需要同步写入用户历史记录;列表的变更类型、变更时间通过 sys_user_history 最新记录展示。
|
||||
List<Sys_user_history> histories = BeanUtil.copyToList(userSources, Sys_user_history.class);
|
||||
for (Sys_user_history history : histories) {
|
||||
history.setId(R.UU32());
|
||||
history.setChangeTypes(List.of(MemberChangeType.NEW.name()));
|
||||
history.setChangeTime(DateUtil.date());
|
||||
history.setChangeOrigin(MemberChangeOrigin.SYSTEM.name());
|
||||
}
|
||||
List<List<Sys_user_history>> splitHistories = ListUtil.split(histories, 500);
|
||||
List<CompletableFuture<List<Sys_user_history>>> splitHistoryFutures = splitHistories.stream().map(v -> CompletableFuture.supplyAsync(() -> dao.fastInsert(v))).toList();
|
||||
AsyncUtil.waitAll(CompletableFuture.allOf(splitHistoryFutures.toArray(new CompletableFuture[0])));
|
||||
|
||||
Sys_role publicRole = sysRoleService.getByCode(RoleConstant.PUBLIC);
|
||||
|
||||
//添加公共角色
|
||||
@@ -143,15 +129,15 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
|
||||
dao.insert(insertUnits);
|
||||
});
|
||||
|
||||
// 计算设置为会员的人数
|
||||
long memberCount = sysUsers.stream().filter(Sys_user::getMember).count();
|
||||
|
||||
// 6.1 更新提案的校领导角色,发送订阅
|
||||
ProposalConfig config = dao.fetch(ProposalConfig.class, Cnd.where("delFlag", "=", false).desc("updatedAt"));
|
||||
for (String unitId : config.getSchoolLeaderUnitIds()) {
|
||||
RoleEventPublisher.broadcast(new RoleEventMsg(unitId, RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name(), RoleEventMsg.RENEW_ROLE));
|
||||
}
|
||||
|
||||
return StrUtil.format("增量更新成功,本次更新新入职老师{}人,新增单位{}个,设置为会员{}人。", sysUsers.size(), insertUnits.size(), memberCount);
|
||||
if (sysUsers.isEmpty() && insertUnits.isEmpty()) {
|
||||
return "本次没有新增人员或单位。";
|
||||
}
|
||||
return StrUtil.format("增量更新成功,本次新增人员{}人,新增单位{}个。", sysUsers.size(), insertUnits.size());
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -425,6 +425,7 @@ public class ClubStatisticsController {
|
||||
}
|
||||
|
||||
private Sql generateSql(String clubId, Boolean auditState, String userState, String sex, Boolean giveMoney) {
|
||||
// 注册流程按社团去重后再关联,避免换届等流程复用社团ID时将会员人数重复统计。
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
club.id,
|
||||
@@ -438,11 +439,22 @@ public class ClubStatisticsController {
|
||||
sum( CASE WHEN ('CLUB_PRESIDENT' MEMBER OF (cu.roleCode) or 'CLUB_VICE_PRESIDENT' MEMBER OF (cu.roleCode) or 'CLUB_SECRETARY' MEMBER OF (cu.roleCode) or 'CLUB_VICE_SECRETARY' MEMBER OF (cu.roleCode)) AND 1 = 1 $myCondition THEN 1 ELSE 0 END ) AS governing_body
|
||||
FROM
|
||||
sys_club club
|
||||
INNER JOIN (
|
||||
SELECT DISTINCT registerInstance.businessNo
|
||||
FROM wf_process_instance registerInstance
|
||||
INNER JOIN wf_process_define registerDefine
|
||||
ON registerDefine.id = registerInstance.processDefineId
|
||||
WHERE registerInstance.state = @finishedState
|
||||
AND registerInstance.delFlag = 0
|
||||
AND registerDefine.name = @registerProcessKey
|
||||
AND registerDefine.delFlag = 0
|
||||
) registeredClub ON registeredClub.businessNo = club.id
|
||||
LEFT JOIN club_user cu ON cu.clubId = club.id
|
||||
LEFT JOIN sys_user su ON cu.userId = su.id
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = club.id
|
||||
$condition
|
||||
""");
|
||||
sql.setParam("finishedState", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
sql.setParam("registerProcessKey", "XHZC");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (auditState != null) {
|
||||
List<SysClubExamineRegister> examineRegisters = sysClubService.dao().query(SysClubExamineRegister.class, Cnd.where("year(registerDate)", "=", DateUtil.thisYear()));
|
||||
@@ -458,7 +470,6 @@ public class ClubStatisticsController {
|
||||
List<String> myClubId = userRoles.stream().map(Sys_user_role::getClubId).toList();
|
||||
cnd.and("club.id", "in", myClubId);
|
||||
}
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.andEX("club.id", "=", clubId);
|
||||
cnd.and("club.dismiss", "=", false);
|
||||
cnd.groupBy("club.id");
|
||||
|
||||
@@ -13,6 +13,13 @@ import java.util.List;
|
||||
|
||||
public interface SysClubExamineService extends BaseService<SysClubExamineRegister> {
|
||||
|
||||
/**
|
||||
* 按会员入会、退休、退会时间统计指定年度的在职和退休人数变化。
|
||||
*
|
||||
* @param clubId 社团ID
|
||||
* @param year 统计年度
|
||||
* @return 在职和退休人员的年初、增加、减少、年末人数
|
||||
*/
|
||||
List<NutMap> getClubUserNum(@Valid String clubId, Integer year);
|
||||
|
||||
List<NutMap> getJgUser(@Valid String clubId);
|
||||
|
||||
+230
-114
@@ -10,7 +10,6 @@ import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.club.model.SysClub;
|
||||
@@ -32,14 +31,15 @@ import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineRegister> implements SysClubExamineService {
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
@Inject
|
||||
private SysClubService sysClubService;
|
||||
|
||||
@@ -49,128 +49,244 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
|
||||
|
||||
@Override
|
||||
public List<NutMap> getClubUserNum(String clubId, Integer year) {
|
||||
// 定义统计的用户状态类型
|
||||
List<String> userStateList = List.of("在职", "退休");
|
||||
List<NutMap> result = new ArrayList<>();
|
||||
Date yearStart = DateUtil.parse(year + "-01-01 00:00:00");
|
||||
Date nextYearStart = DateUtil.parse((year + 1) + "-01-01 00:00:00");
|
||||
List<MembershipPeriod> membershipPeriods = getMembershipPeriods(clubId);
|
||||
NutMap activeResult = createUserChangeResult("在职");
|
||||
NutMap retiredResult = createUserChangeResult("退休");
|
||||
|
||||
// 查询上一年度考核登记数据(用于获取年初人数)
|
||||
SysClubExamineRegister examineRegister = dao().fetch(SysClubExamineRegister.class,
|
||||
Cnd.where("year", "=", year - 1).and("clubId", "=", clubId));
|
||||
for (MembershipPeriod period : membershipPeriods) {
|
||||
// 非正常历史会员没有退会时间且没有已完成退会申请时,无法确定所属年度,不计入统计。
|
||||
if (!period.normal && period.exitTime == null) {
|
||||
continue;
|
||||
}
|
||||
if (isMemberAtYearBoundary(period, yearStart)) {
|
||||
increaseResult(classifyAtBoundary(period, yearStart), activeResult, retiredResult, "yearFirstNum");
|
||||
}
|
||||
if (isInYear(period.joinTime, yearStart, nextYearStart)) {
|
||||
increaseResult(classifyAtEvent(period, period.joinTime), activeResult, retiredResult, "yearAddNum");
|
||||
}
|
||||
if (isInYear(period.exitTime, yearStart, nextYearStart)) {
|
||||
increaseResult(classifyBeforeEvent(period, period.exitTime), activeResult, retiredResult, "yearReduceNum");
|
||||
}
|
||||
// 退休发生在会员有效期内时,作为在职减少一人、退休增加一人处理。
|
||||
if (isInYear(period.retireDate, yearStart, nextYearStart)
|
||||
&& period.joinTime.before(period.retireDate)
|
||||
&& (period.exitTime == null || period.exitTime.after(period.retireDate))) {
|
||||
increaseResult("在职", activeResult, retiredResult, "yearReduceNum");
|
||||
increaseResult("退休", activeResult, retiredResult, "yearAddNum");
|
||||
}
|
||||
if (isMemberAtYearBoundary(period, nextYearStart)) {
|
||||
increaseResult(classifyAtBoundary(period, nextYearStart), activeResult, retiredResult, "thisYearNum");
|
||||
}
|
||||
}
|
||||
return List.of(activeResult, retiredResult);
|
||||
}
|
||||
|
||||
// 构建社团用户申请记录查询SQL(关联用户信息、流程实例)
|
||||
Sql sql = Sqls.create("""
|
||||
/**
|
||||
* 合并旧系统会员生命周期与新系统已完成申请,生成可用于年度统计的入退会区间。
|
||||
* 旧系统记录负责迁移历史,新系统申请只补充旧记录缺失的退会时间和迁移后新增的入会周期。
|
||||
*/
|
||||
private List<MembershipPeriod> getMembershipPeriods(String clubId) {
|
||||
Sql legacySql = Sqls.create("""
|
||||
SELECT
|
||||
cu.*,
|
||||
year(cu.applyDate) as applyYear,
|
||||
cu.userId,
|
||||
CAST(cu.joinTime AS DATETIME) joinTime,
|
||||
STR_TO_DATE(NULLIF(cu.changeTime, ''), '%Y-%m-%d %H:%i:%s') exitTime,
|
||||
cu.isNormal,
|
||||
u.userState,
|
||||
u.retireDate
|
||||
FROM
|
||||
club_user_apply cu
|
||||
LEFT JOIN sys_user u ON cu.userId = u.id
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = cu.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("cu.clubId", "=", clubId);
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.groupBy("cu.clubId, cu.userId, cu.mode");
|
||||
sql.setCondition(cnd);
|
||||
List<NutMap> userList = listMap(sql);
|
||||
FROM sys_club_user cu
|
||||
LEFT JOIN sys_user u ON u.id = cu.userId
|
||||
WHERE cu.clubId = @clubId
|
||||
AND cu.status = 5
|
||||
AND cu.joinTime IS NOT NULL
|
||||
AND cu.joinTime <> ''
|
||||
""").setParam("clubId", clubId);
|
||||
List<MembershipPeriod> periods = listMap(legacySql).stream()
|
||||
.map(row -> new MembershipPeriod(
|
||||
row.getString("userId"),
|
||||
row.getTime("joinTime"),
|
||||
row.getTime("exitTime"),
|
||||
!Boolean.FALSE.equals(row.getBoolean("isNormal")),
|
||||
true,
|
||||
row.getString("userState"),
|
||||
row.getTime("retireDate")
|
||||
))
|
||||
.filter(period -> period.joinTime != null)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
// 遍历在职/退休状态,分别统计人员变动数据
|
||||
for (String userState : userStateList) {
|
||||
NutMap nutMap = new NutMap();
|
||||
nutMap.addv("userState", userState);
|
||||
Sql applySql = Sqls.create("""
|
||||
SELECT
|
||||
cu.userId,
|
||||
cu.mode,
|
||||
cu.applyDate,
|
||||
cu.joinTime,
|
||||
cu.exitTime,
|
||||
u.userState,
|
||||
u.retireDate
|
||||
FROM club_user_apply cu
|
||||
LEFT JOIN sys_user u ON u.id = cu.userId
|
||||
WHERE cu.clubId = @clubId
|
||||
AND COALESCE(cu.delFlag, 0) = 0
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM wf_process_instance ins
|
||||
WHERE ins.businessNo = cu.id
|
||||
AND ins.state = @finishedState
|
||||
)
|
||||
ORDER BY cu.applyDate, cu.mode
|
||||
""")
|
||||
.setParam("clubId", clubId)
|
||||
.setParam("finishedState", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
List<NutMap> applications = listMap(applySql);
|
||||
applications.sort(Comparator.comparing(row -> row.getTime("applyDate"), Comparator.nullsLast(Date::compareTo)));
|
||||
|
||||
// 根据用户状态匹配对应的人员类型集合
|
||||
List<String> personTypeList = switch (userState) {
|
||||
case "在职" -> List.of("在职", "在岗");
|
||||
case "退休" -> List.of("退休", "退休【变号】");
|
||||
default -> new ArrayList<>();
|
||||
};
|
||||
|
||||
// 过滤出符合当前人员类型的用户(非空判断避免NPE)
|
||||
List<NutMap> personTypeUsers = userList.stream()
|
||||
.filter(u -> StrUtil.isNotBlank(u.getString("userState"))
|
||||
&& personTypeList.contains(u.getString("userState")))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 初始化年末人数为0
|
||||
int thisYearNum = 0;
|
||||
if (!Lang.isEmpty(personTypeUsers)) {
|
||||
// 统计年末人数:加入社团且无退出记录的用户数量
|
||||
List<String> exitUserIds = personTypeUsers.stream()
|
||||
.filter(u -> u.getInt("mode") == 0)
|
||||
.map(u -> u.getString("userId"))
|
||||
.toList();
|
||||
|
||||
thisYearNum = (int) personTypeUsers.stream()
|
||||
.filter(u -> u.getInt("mode") == 1)
|
||||
.filter(u -> !exitUserIds.contains(u.getString("userId")))
|
||||
.count();
|
||||
for (NutMap application : applications) {
|
||||
Date applyDate = application.getTime("applyDate");
|
||||
Boolean mode = application.getBoolean("mode");
|
||||
if (applyDate == null || mode == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 统计今年退休人数:已入会且退休日期在本年度的用户
|
||||
int retireCount = (int) personTypeUsers.stream()
|
||||
.filter(u -> u.getTime("retireDate") != null
|
||||
&& u.getInt("mode") == 1
|
||||
&& DateUtil.year(u.getTime("retireDate")) == year)
|
||||
.count();
|
||||
|
||||
// 封装年初人数:从上一年度登记数据中获取,无数据则为0
|
||||
if (Lang.isNotEmpty(examineRegister) && Lang.isNotEmpty(examineRegister.getChangeUserNum())) {
|
||||
JSONObject uState = examineRegister.getChangeUserNum().stream()
|
||||
.filter(c -> c.getStr("userState").equals(userState))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
nutMap.addv("yearFirstNum", Lang.isEmpty(uState) ? 0 : uState.getInt("thisYearNum"));
|
||||
if (mode) {
|
||||
mergeJoinApplication(periods, application, applyDate);
|
||||
} else {
|
||||
nutMap.addv("yearFirstNum", 0);
|
||||
mergeExitApplication(periods, application, applyDate);
|
||||
}
|
||||
|
||||
// 统计年度增加相关数据
|
||||
int joinThisYear = (int) personTypeUsers.stream()
|
||||
.filter(u -> u.getInt("mode") == 1 && u.getInt("applyYear") == year)
|
||||
.count();
|
||||
|
||||
int thisYearJoinAndRetire = (int) userList.stream()
|
||||
.filter(u -> u.getInt("mode") == 1 && u.getInt("applyYear") == year
|
||||
&& u.getTime("retireDate") != null
|
||||
&& DateUtil.year(u.getTime("retireDate")) == year)
|
||||
.count();
|
||||
|
||||
int beforeYearJoinAndRetire = (int) userList.stream()
|
||||
.filter(u -> u.getInt("mode") == 1 && u.getInt("applyYear") == year
|
||||
&& u.getTime("retireDate") != null
|
||||
&& DateUtil.year(u.getTime("retireDate")) < year)
|
||||
.count();
|
||||
|
||||
// 按用户状态封装年度增加人数
|
||||
nutMap.addv("yearAddNum", "在职".equals(userState)
|
||||
? joinThisYear + thisYearJoinAndRetire
|
||||
: beforeYearJoinAndRetire + retireCount);
|
||||
|
||||
// 统计年度减少相关数据
|
||||
int quitThisYear = (int) personTypeUsers.stream()
|
||||
.filter(u -> u.getInt("mode") == 0 && u.getInt("applyYear") == year)
|
||||
.count();
|
||||
|
||||
// 在职人员年度减少:退休人数+主动退出人数;退休人员仅统计主动退出
|
||||
if ("在职".equals(userState)) {
|
||||
int retireThisYear = (int) userList.stream()
|
||||
.filter(u -> u.getTime("retireDate") != null
|
||||
&& DateUtil.year(u.getTime("retireDate")) == year)
|
||||
.count();
|
||||
nutMap.addv("yearReduceNum", retireThisYear + quitThisYear);
|
||||
} else {
|
||||
nutMap.addv("yearReduceNum", quitThisYear);
|
||||
}
|
||||
|
||||
// 封装年末人数
|
||||
nutMap.addv("thisYearNum", thisYearNum);
|
||||
result.add(nutMap);
|
||||
}
|
||||
return result;
|
||||
return periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 已迁移的入会申请与旧会员记录精确对应时不重复新增;迁移后的新申请以审核申请时间作为入会时间。
|
||||
*/
|
||||
private void mergeJoinApplication(List<MembershipPeriod> periods, NutMap application, Date applyDate) {
|
||||
String userId = application.getString("userId");
|
||||
Date applicationJoinTime = application.getTime("joinTime");
|
||||
boolean representedByLegacy = periods.stream()
|
||||
.filter(period -> period.legacy && Objects.equals(period.userId, userId))
|
||||
.filter(period -> Objects.equals(period.joinTime, applicationJoinTime))
|
||||
.anyMatch(period -> period.exitTime == null || !applyDate.after(period.exitTime));
|
||||
boolean representedByNewPeriod = periods.stream()
|
||||
.filter(period -> !period.legacy && Objects.equals(period.userId, userId))
|
||||
.anyMatch(period -> isMemberAt(period, applyDate));
|
||||
if (representedByLegacy || representedByNewPeriod) {
|
||||
return;
|
||||
}
|
||||
periods.add(new MembershipPeriod(
|
||||
userId,
|
||||
applyDate,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
application.getString("userState"),
|
||||
application.getTime("retireDate")
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 退会申请优先关闭入会时间相同的会员周期;新系统复入会后再退会时关闭最近的有效周期。
|
||||
*/
|
||||
private void mergeExitApplication(List<MembershipPeriod> periods, NutMap application, Date applyDate) {
|
||||
String userId = application.getString("userId");
|
||||
Date applicationJoinTime = application.getTime("joinTime");
|
||||
Date exitTime = application.getTime("exitTime") == null ? applyDate : application.getTime("exitTime");
|
||||
MembershipPeriod target = periods.stream()
|
||||
.filter(period -> Objects.equals(period.userId, userId))
|
||||
.filter(period -> Objects.equals(period.joinTime, applicationJoinTime))
|
||||
.filter(period -> period.exitTime == null)
|
||||
.max(Comparator.comparing(period -> period.joinTime))
|
||||
.orElse(null);
|
||||
if (target == null) {
|
||||
target = periods.stream()
|
||||
.filter(period -> Objects.equals(period.userId, userId))
|
||||
.filter(period -> period.exitTime == null && !period.joinTime.after(exitTime))
|
||||
.max(Comparator.comparing(period -> period.joinTime))
|
||||
.orElse(null);
|
||||
}
|
||||
if (target != null) {
|
||||
target.exitTime = exitTime;
|
||||
}
|
||||
}
|
||||
|
||||
private NutMap createUserChangeResult(String userState) {
|
||||
return NutMap.NEW()
|
||||
.addv("userState", userState)
|
||||
.addv("yearFirstNum", 0)
|
||||
.addv("yearAddNum", 0)
|
||||
.addv("yearReduceNum", 0)
|
||||
.addv("thisYearNum", 0);
|
||||
}
|
||||
|
||||
private void increaseResult(String userState, NutMap activeResult, NutMap retiredResult, String field) {
|
||||
NutMap result = "退休".equals(userState) ? retiredResult : "在职".equals(userState) ? activeResult : null;
|
||||
if (result != null) {
|
||||
result.put(field, result.getInt(field) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInYear(Date date, Date yearStart, Date nextYearStart) {
|
||||
return date != null && !date.before(yearStart) && date.before(nextYearStart);
|
||||
}
|
||||
|
||||
private boolean isMemberAt(MembershipPeriod period, Date date) {
|
||||
return !period.joinTime.after(date) && (period.exitTime == null || period.exitTime.after(date));
|
||||
}
|
||||
|
||||
private boolean isMemberAtYearBoundary(MembershipPeriod period, Date boundary) {
|
||||
return period.joinTime.before(boundary) && (period.exitTime == null || !period.exitTime.before(boundary));
|
||||
}
|
||||
|
||||
private String classifyAtBoundary(MembershipPeriod period, Date boundary) {
|
||||
if (period.retireDate != null) {
|
||||
return period.retireDate.before(boundary) ? "退休" : "在职";
|
||||
}
|
||||
return normalizeUserState(period.userState);
|
||||
}
|
||||
|
||||
private String classifyAtEvent(MembershipPeriod period, Date eventTime) {
|
||||
if (period.retireDate != null) {
|
||||
return period.retireDate.after(eventTime) ? "在职" : "退休";
|
||||
}
|
||||
return normalizeUserState(period.userState);
|
||||
}
|
||||
|
||||
private String classifyBeforeEvent(MembershipPeriod period, Date eventTime) {
|
||||
if (period.retireDate != null) {
|
||||
return period.retireDate.before(eventTime) ? "退休" : "在职";
|
||||
}
|
||||
return normalizeUserState(period.userState);
|
||||
}
|
||||
|
||||
private String normalizeUserState(String userState) {
|
||||
if (List.of("在职", "在岗").contains(userState)) {
|
||||
return "在职";
|
||||
}
|
||||
if (List.of("退休", "退休【变号】").contains(userState)) {
|
||||
return "退休";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class MembershipPeriod {
|
||||
private final String userId;
|
||||
private final Date joinTime;
|
||||
private Date exitTime;
|
||||
private final boolean normal;
|
||||
private final boolean legacy;
|
||||
private final String userState;
|
||||
private final Date retireDate;
|
||||
|
||||
private MembershipPeriod(String userId, Date joinTime, Date exitTime, boolean normal, boolean legacy,
|
||||
String userState, Date retireDate) {
|
||||
this.userId = userId;
|
||||
this.joinTime = joinTime;
|
||||
this.exitTime = exitTime;
|
||||
this.normal = normal;
|
||||
this.legacy = legacy;
|
||||
this.userState = userState;
|
||||
this.retireDate = retireDate;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+77
-30
@@ -15,6 +15,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.web.commons.base.Globals;
|
||||
import com.budwk.app.web.controllers.open.commons.service.CommonService;
|
||||
import com.budwk.app.zhgh.club.model.ClubUser;
|
||||
import com.budwk.app.zhgh.club.model.SysClub;
|
||||
import com.budwk.app.zhgh.club.param.ClubUserPageForm;
|
||||
import com.budwk.app.zhgh.club.service.SysClubInfoManageService;
|
||||
import com.budwk.app.zhgh.club.service.SysClubService;
|
||||
@@ -41,6 +42,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -227,19 +229,23 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
|
||||
public Pagination<ClubUserCommonPageVo> exitManagePageData(ClubUserPageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.id,
|
||||
info.clubId,
|
||||
info.userId,
|
||||
info.roleCode AS applyRoleCode,
|
||||
info.clubPosition,
|
||||
info.email,
|
||||
info.mobile,
|
||||
info.birthday,
|
||||
info.avatar,
|
||||
info.sameTimeJoinOtherClubSituation,
|
||||
info.awardsExperience,
|
||||
COALESCE(info.id, scu.id) AS id,
|
||||
scu.clubId,
|
||||
scu.userId,
|
||||
COALESCE(info.roleCode, scu.roleCode) AS applyRoleCode,
|
||||
COALESCE(info.clubPosition, scu.position) AS clubPosition,
|
||||
COALESCE(info.email, scu.email, u.email) AS email,
|
||||
COALESCE(info.mobile, u.mobile) AS mobile,
|
||||
COALESCE(info.birthday, u.birthday) AS birthday,
|
||||
COALESCE(info.avatar, scu.avatar, u.avatar) AS avatar,
|
||||
COALESCE(info.sameTimeJoinOtherClubSituation, scu.sameTimeJoinOtherClubSituation) AS sameTimeJoinOtherClubSituation,
|
||||
COALESCE(info.awardsExperience, scu.awardsExperience) AS awardsExperience,
|
||||
info.signature,
|
||||
info.applyDate,
|
||||
COALESCE(
|
||||
info.applyDate,
|
||||
STR_TO_DATE(scu.changeTime, '%Y-%m-%d %H:%i:%s'),
|
||||
STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(scu.exitAuditList, '$[0].auditTime')), '%Y-%m-%d %H:%i:%s')
|
||||
) AS applyDate,
|
||||
u.username AS userName,
|
||||
u.loginname AS loginName,
|
||||
u.sex,
|
||||
@@ -247,24 +253,32 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
|
||||
u.userState,
|
||||
club.clubName,
|
||||
u.unitname AS unitName,
|
||||
DATE_FORMAT(IFNULL(info.joinTime, club.foundTime), '%Y-%m-%d %H:%i:%s') AS joinTime,
|
||||
DATE_FORMAT(info.exitTime, '%Y-%m-%d %H:%i:%s') AS exitTime,
|
||||
COALESCE(scu.joinTime, DATE_FORMAT(club.foundTime, '%Y-%m-%d %H:%i:%s')) AS joinTime,
|
||||
COALESCE(
|
||||
DATE_FORMAT(info.exitTime, '%Y-%m-%d %H:%i:%s'),
|
||||
JSON_UNQUOTE(JSON_EXTRACT(scu.exitAuditList, '$[0].auditTime')),
|
||||
scu.changeTime
|
||||
) AS exitTime,
|
||||
IF(info.id IS NULL, 'DIRECT_REMOVE', 'AUDIT_EXIT') AS exitType,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state instanceState,
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId
|
||||
FROM
|
||||
club_user_apply info
|
||||
LEFT JOIN sys_club club ON club.id = info.clubId
|
||||
LEFT JOIN vw_user u ON u.id = info.userId
|
||||
sys_club_user scu
|
||||
LEFT JOIN club_user_apply info ON info.clubId = scu.clubId
|
||||
AND info.userId = scu.userId
|
||||
AND info.mode = false
|
||||
LEFT JOIN sys_club club ON club.id = scu.clubId
|
||||
INNER JOIN vw_user u ON u.id = scu.userId
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||
AND ins.state = 20
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("info.mode", "=", false);
|
||||
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.andEX("info.clubId", "=", pageForm.getClubId());
|
||||
cnd.and("scu.isNormal", "=", false);
|
||||
cnd.andEX("scu.clubId", "=", pageForm.getClubId());
|
||||
cnd.andEX("u.personType", "=", pageForm.getPersonType());
|
||||
cnd.andEX("u.userState", "=", pageForm.getUserState());
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
@@ -273,15 +287,17 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
|
||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
|
||||
List<String> clubIdList = commonService.findUserRoleByRoleCode(List.of(RoleConstant.CLUB_PRESIDENT.name(), RoleConstant.CLUB_VICE_PRESIDENT.name(), RoleConstant.CLUB_SECRETARY.name(), RoleConstant.CLUB_VICE_SECRETARY.name(), RoleConstant.CLUB_OPERATOR.name()));
|
||||
List<Sys_user_role> clubList = dao().query(Sys_user_role.class, Cnd.where("roleId", "in", clubIdList).and("userId", "=", SecurityUtil.getUserId()).and("clubId", "is not", null));
|
||||
cnd.and("info.clubId", "in", clubList.stream().map(Sys_user_role::getClubId).toList());
|
||||
cnd.and("scu.clubId", "in", clubList.stream().map(Sys_user_role::getClubId).toList());
|
||||
}
|
||||
cnd.groupBy("info.id");
|
||||
cnd.desc("info.applyDate");
|
||||
cnd.groupBy("scu.id");
|
||||
cnd.desc("exitTime");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<ClubUserCommonPageVo> pagination = listPageVO(pageForm, sql, ClubUserCommonPageVo.class);
|
||||
List<ClubUserCommonPageVo> list = pagination.getList(ClubUserCommonPageVo.class);
|
||||
for (ClubUserCommonPageVo vo : list) {
|
||||
vo.setRoleName(SysClubUserServiceImpl.convertRoleName(List.of(vo.getApplyRoleCode())));
|
||||
if (StrUtil.isNotBlank(vo.getApplyRoleCode())) {
|
||||
vo.setRoleName(SysClubUserServiceImpl.convertRoleName(List.of(vo.getApplyRoleCode())));
|
||||
}
|
||||
}
|
||||
pagination.setList(list);
|
||||
return pagination;
|
||||
@@ -291,7 +307,30 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
|
||||
public ClubUserJoinVo exitManageInfo(String id) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
cua.*,
|
||||
COALESCE(cua.id, scu.id) AS id,
|
||||
scu.clubId,
|
||||
scu.userId,
|
||||
COALESCE(cua.roleCode, scu.roleCode) AS roleCode,
|
||||
COALESCE(cua.clubPosition, scu.position) AS clubPosition,
|
||||
COALESCE(cua.email, scu.email, u.email) AS email,
|
||||
COALESCE(cua.mobile, u.mobile) AS mobile,
|
||||
COALESCE(cua.birthday, u.birthday) AS birthday,
|
||||
COALESCE(cua.avatar, scu.avatar, u.avatar) AS avatar,
|
||||
COALESCE(cua.sameTimeJoinOtherClubSituation, scu.sameTimeJoinOtherClubSituation) AS sameTimeJoinOtherClubSituation,
|
||||
COALESCE(cua.awardsExperience, scu.awardsExperience) AS awardsExperience,
|
||||
cua.signature,
|
||||
COALESCE(
|
||||
cua.applyDate,
|
||||
STR_TO_DATE(scu.changeTime, '%Y-%m-%d %H:%i:%s'),
|
||||
STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(scu.exitAuditList, '$[0].auditTime')), '%Y-%m-%d %H:%i:%s')
|
||||
) AS applyDate,
|
||||
STR_TO_DATE(scu.joinTime, '%Y-%m-%d %H:%i:%s') AS joinTime,
|
||||
COALESCE(
|
||||
cua.exitTime,
|
||||
STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(scu.exitAuditList, '$[0].auditTime')), '%Y-%m-%d %H:%i:%s'),
|
||||
STR_TO_DATE(scu.changeTime, '%Y-%m-%d %H:%i:%s')
|
||||
) AS exitTime,
|
||||
IF(cua.id IS NULL, 'DIRECT_REMOVE', 'AUDIT_EXIT') AS exitType,
|
||||
club.clubName,
|
||||
u.loginname AS loginName,
|
||||
u.username AS userName,
|
||||
@@ -304,10 +343,14 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
|
||||
u.academicDegree,
|
||||
u.position
|
||||
FROM
|
||||
club_user_apply cua
|
||||
LEFT JOIN vw_user u ON u.id = cua.userId
|
||||
LEFT JOIN sys_club club ON club.id = cua.clubId
|
||||
WHERE cua.id = @id
|
||||
sys_club_user scu
|
||||
LEFT JOIN club_user_apply cua ON cua.clubId = scu.clubId
|
||||
AND cua.userId = scu.userId
|
||||
AND cua.mode = false
|
||||
LEFT JOIN vw_user u ON u.id = scu.userId
|
||||
LEFT JOIN sys_club club ON club.id = scu.clubId
|
||||
WHERE scu.isNormal = false
|
||||
AND COALESCE(cua.id, scu.id) = @id
|
||||
""");
|
||||
sql.setParam("id", id);
|
||||
return fetchVO(sql, ClubUserJoinVo.class);
|
||||
@@ -346,7 +389,11 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
|
||||
NutMap nutMap = NutMap.NEW();
|
||||
nutMap.put("id", "0");
|
||||
nutMap.put("clubName", Globals.AppName);
|
||||
nutMap.put("children", sysClubService.getMyManageClub());
|
||||
// 社团管理树只按社团编号展示,避免影响公共社团列表的原有排序。
|
||||
List<SysClub> clubList = sysClubService.getMyManageClub().stream()
|
||||
.sorted(Comparator.comparing(SysClub::getClubCode, Comparator.nullsLast(String::compareTo)))
|
||||
.toList();
|
||||
nutMap.put("children", clubList);
|
||||
result.add(nutMap);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public class ClubUserCommonPageVo extends ClubUser {
|
||||
private String unionId;
|
||||
private String joinTime;
|
||||
private String exitTime;
|
||||
private String exitType;
|
||||
private String applyRoleCode;
|
||||
|
||||
private String roleName;
|
||||
|
||||
@@ -23,4 +23,5 @@ public class ClubUserJoinVo extends ClubUserApply {
|
||||
private String academicDegree;
|
||||
private String position;
|
||||
private String clubName;
|
||||
private String exitType;
|
||||
}
|
||||
|
||||
+2
-2
@@ -262,7 +262,7 @@ public class HonorManageController {
|
||||
excelExportEntity.add(new ExcelExportEntity("分工会", "unionName", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("单位", "unitName", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("奖项", "prize", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("荣誉级别", "honorLevel", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("荣誉类别", "honorLevel", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("授予单位", "grantUnit", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("授予时间", "grantDate", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("奖项介质", "awardType", 20));
|
||||
@@ -276,7 +276,7 @@ public class HonorManageController {
|
||||
excelExportEntity.add(new ExcelExportEntity("会员人数", "memberNumber", 10));
|
||||
excelExportEntity.add(new ExcelExportEntity("工会小组数", "unionGroupNumber", 10));
|
||||
excelExportEntity.add(new ExcelExportEntity("奖项", "prize", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("荣誉级别", "honorLevel", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("荣誉类别", "honorLevel", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("授予单位", "grantUnit", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("参加比赛名称", "gameName", 20));
|
||||
excelExportEntity.add(new ExcelExportEntity("授予时间", "grantDate", 20));
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ public class TourLineController {
|
||||
return Result.error("创建年度不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getTravelAgencyId())) {
|
||||
return Result.error("旅行社名称不能为空");
|
||||
return Result.error("服务单位名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getLineName())) {
|
||||
return Result.error("线路名称不能为空");
|
||||
|
||||
+15
-1
@@ -466,7 +466,13 @@ public class TourMySignupController {
|
||||
ledger.setSignupTime(defaultIfBlank(oldLedger.getSignupTime(), ledger.getSignupTime()));
|
||||
fillStaffInfo(ledger);
|
||||
|
||||
tourLedgerService.updateIgnoreNull(ledger);
|
||||
// 修改报名也重新锁定并校验当前分工会名额,兼容人员所属分工会发生变化的场景。
|
||||
String quotaMessage = tourLedgerService.saveWithUnionSignupQuota(
|
||||
ledger, matter.getSettingId(), matter.getId(), SecurityUtil.getUserId(), true);
|
||||
if (StrUtil.isNotBlank(quotaMessage)) {
|
||||
return Result.error(quotaMessage);
|
||||
}
|
||||
|
||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
||||
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||
if (Lang.isNotEmpty(familyList)) {
|
||||
@@ -661,6 +667,14 @@ public class TourMySignupController {
|
||||
}
|
||||
}
|
||||
if (isInProvinceLine(line.getLineType())) {
|
||||
// 修改报名时排除当前台账,再按实际参加记录校验省内线路间隔。
|
||||
String inProvinceMessage = tourLedgerService.checkInProvinceParticipation(
|
||||
jobNo,
|
||||
setting.getInProvinceYears(),
|
||||
oldLedger == null ? null : oldLedger.getId());
|
||||
if (StrUtil.isNotBlank(inProvinceMessage)) {
|
||||
return Result.error(inProvinceMessage);
|
||||
}
|
||||
Cnd sameYearCnd = Cnd.where(TourLedger::getDelFlag, "=", false)
|
||||
.and(TourLedger::getJobNo, "=", jobNo)
|
||||
.and(TourLedger::getYear, "=", matter.getYear());
|
||||
|
||||
+35
@@ -69,6 +69,8 @@ public class TourSettingController {
|
||||
Cnd lotCnd = Cnd.NEW();
|
||||
lotCnd.desc(TourSettingLot::getLotValue);
|
||||
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
|
||||
// 编辑时同时返回分工会人员数量,确保页面按当前组织和人员总数完整回显。
|
||||
tourSetting.setUnionQuotas(tourSettingService.listUnionQuotaRows(id));
|
||||
return Result.success(tourSetting);
|
||||
}
|
||||
|
||||
@@ -89,15 +91,30 @@ public class TourSettingController {
|
||||
Cnd lotCnd = Cnd.NEW();
|
||||
lotCnd.desc(TourSettingLot::getLotValue);
|
||||
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
|
||||
// 延用上一年配置时带出原分配数量,前端会清除子表ID后作为新配置提交。
|
||||
tourSetting.setUnionQuotas(tourSettingService.listUnionQuotaRows(tourSetting.getId()));
|
||||
return Result.success(tourSetting);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询人员分配页签所需的全部分工会、当前人员总数和已保存人员数量。
|
||||
*
|
||||
* @param settingId 疗休养配置ID,新增时为空
|
||||
* @return 分工会人员分配行
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("tour.setting")
|
||||
public Result unionQuotaRows(String settingId) {
|
||||
return Result.success(tourSettingService.listUnionQuotaRows(settingId));
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.setting")
|
||||
@SLog(type = "tour", tag = "疗休养设置", msg = "保存疗休养配置")
|
||||
public Result doSubmit(TourSetting tourSetting,
|
||||
@Param(value = "lots") String lots,
|
||||
@Param(value = "unionQuotas") String unionQuotas,
|
||||
@Param(value = "lotDeleteList") String[] lotDeleteList) {
|
||||
Result checkResult = check(tourSetting);
|
||||
if (checkResult != null) {
|
||||
@@ -113,6 +130,14 @@ public class TourSettingController {
|
||||
return Result.error("同年度下配置名称已存在");
|
||||
}
|
||||
|
||||
// 后端再次校验分配总量,避免绕过页面直接提交超出出行人数指标的数据。
|
||||
int travelPeopleQuota = tourSetting.getTravelPeopleQuota() == null
|
||||
? 0 : tourSetting.getTravelPeopleQuota();
|
||||
String quotaLimitMessage = tourSettingService.checkUnionQuotaLimit(unionQuotas, travelPeopleQuota);
|
||||
if (StrUtil.isNotBlank(quotaLimitMessage)) {
|
||||
return Result.error(quotaLimitMessage);
|
||||
}
|
||||
|
||||
// 布尔值给默认值,避免前端未传时出现空状态。
|
||||
if (tourSetting.getEnabled() == null) {
|
||||
tourSetting.setEnabled(true);
|
||||
@@ -148,6 +173,7 @@ public class TourSettingController {
|
||||
} else {
|
||||
tourSettingService.insertWith(tourSetting, "lots");
|
||||
}
|
||||
tourSettingService.saveUnionQuotas(tourSetting.getId(), unionQuotas);
|
||||
} else {
|
||||
// 编辑时先处理页面删除的标段,再保存配置和当前标段行。
|
||||
if (Lang.isNotEmpty(lotDeleteList)) {
|
||||
@@ -155,6 +181,7 @@ public class TourSettingController {
|
||||
}
|
||||
tourSettingService.updateIgnoreNull(tourSetting);
|
||||
saveLots(tourSetting);
|
||||
tourSettingService.saveUnionQuotas(tourSetting.getId(), unionQuotas);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
@@ -168,6 +195,8 @@ public class TourSettingController {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getSettingId, "=", id));
|
||||
// 配置删除时同步清理人员分配,避免遗留不可达的子表数据。
|
||||
tourSettingService.clearUnionQuotas(id);
|
||||
tourSettingService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
@@ -205,10 +234,16 @@ public class TourSettingController {
|
||||
if (StrUtil.isBlank(tourSetting.getConfigName())) {
|
||||
return Result.error("配置名称不能为空");
|
||||
}
|
||||
if (tourSetting.getTravelPeopleQuota() == null || tourSetting.getTravelPeopleQuota() < 0) {
|
||||
return Result.error("出行人数指标必须为非负整数");
|
||||
}
|
||||
if (tourSetting.getMinGroupPeople() != null && tourSetting.getMaxGroupPeople() != null
|
||||
&& tourSetting.getMinGroupPeople() > tourSetting.getMaxGroupPeople()) {
|
||||
return Result.error("最少成团人数不能大于最多成团人数");
|
||||
}
|
||||
if (tourSetting.getInProvinceYears() != null && tourSetting.getInProvinceYears() < 0) {
|
||||
return Result.error("省内间隔年限必须为非负整数");
|
||||
}
|
||||
if (tourSetting.getCycleStartYear() != null && tourSetting.getCycleEndYear() != null
|
||||
&& tourSetting.getCycleStartYear() > tourSetting.getCycleEndYear()) {
|
||||
return Result.error("周期开始年度不能大于周期结束年度");
|
||||
|
||||
+30
-3
@@ -531,6 +531,21 @@ public class TourSignupController {
|
||||
.addv("currentLineCost", cycleTotalCost.currentCost())
|
||||
.addv("message", buildCycleTotalCostNotice(cycleTotalCost)));
|
||||
}
|
||||
if (isInProvinceLine(line.getLineType())) {
|
||||
// 进入报名表单前先校验省内参加间隔,最终提交时还会再次执行相同规则。
|
||||
String inProvinceMessage = tourLedgerService.checkInProvinceParticipation(
|
||||
currentJobNo(),
|
||||
setting.getInProvinceYears(),
|
||||
oldLedger == null ? null : oldLedger.getId());
|
||||
if (StrUtil.isNotBlank(inProvinceMessage)) {
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("canApply", false)
|
||||
.addv("noticeRequired", true)
|
||||
.addv("noticeType", "inProvinceYears")
|
||||
.addv("message", inProvinceMessage));
|
||||
}
|
||||
return Result.success(NutMap.NEW().addv("canApply", true).addv("noticeRequired", false));
|
||||
}
|
||||
if (!isOutProvinceLine(line.getLineType())) {
|
||||
return Result.success(NutMap.NEW().addv("canApply", true).addv("noticeRequired", false));
|
||||
}
|
||||
@@ -699,12 +714,16 @@ public class TourSignupController {
|
||||
ledger.setOverCostReimbursed(Boolean.TRUE.equals(ledger.getOverCostReimbursed()));
|
||||
fillStaffInfo(ledger);
|
||||
|
||||
// PC和移动端共用本接口;在写入台账前锁定分工会名额,防止并发报名超过人员分配数量。
|
||||
String quotaMessage = tourLedgerService.saveWithUnionSignupQuota(
|
||||
ledger, matter.getSettingId(), matter.getId(), SecurityUtil.getUserId(), update);
|
||||
if (StrUtil.isNotBlank(quotaMessage)) {
|
||||
return Result.error(quotaMessage);
|
||||
}
|
||||
|
||||
if (update) {
|
||||
tourLedgerService.updateIgnoreNull(ledger);
|
||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
||||
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||
} else {
|
||||
tourLedgerService.insert(ledger);
|
||||
}
|
||||
if (Lang.isNotEmpty(familyList)) {
|
||||
familyList.forEach(item -> {
|
||||
@@ -936,6 +955,14 @@ public class TourSignupController {
|
||||
}
|
||||
}
|
||||
if (isInProvinceLine(line.getLineType())) {
|
||||
// 最终提交以台账中的实际参加记录为准,避免绕过资格提示接口直接报名。
|
||||
String inProvinceMessage = tourLedgerService.checkInProvinceParticipation(
|
||||
jobNo,
|
||||
setting.getInProvinceYears(),
|
||||
oldLedger == null ? null : oldLedger.getId());
|
||||
if (StrUtil.isNotBlank(inProvinceMessage)) {
|
||||
return Result.error(inProvinceMessage);
|
||||
}
|
||||
Cnd sameYearCnd = Cnd.where(TourLedger::getDelFlag, "=", false)
|
||||
.and(TourLedger::getJobNo, "=", jobNo)
|
||||
.and(TourLedger::getYear, "=", matter.getYear());
|
||||
|
||||
+6
-6
@@ -58,13 +58,13 @@ public class TourTravelAgencyController {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourTravelAgency agency = travelAgencyService.fetch(id);
|
||||
return agency == null ? Result.error("旅行社不存在") : Result.success(agency);
|
||||
return agency == null ? Result.error("服务单位不存在") : Result.success(agency);
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.travelAgency")
|
||||
@SLog(type = "tour", tag = "旅行社管理", msg = "保存旅行社信息")
|
||||
@SLog(type = "tour", tag = "服务单位管理", msg = "保存服务单位信息")
|
||||
public Result doSubmit(TourTravelAgency agency) {
|
||||
Result checkResult = check(agency);
|
||||
if (checkResult != null) {
|
||||
@@ -77,7 +77,7 @@ public class TourTravelAgencyController {
|
||||
sameCodeCnd.and(TourTravelAgency::getId, "<>", agency.getId());
|
||||
}
|
||||
if (travelAgencyService.count(sameCodeCnd) > 0) {
|
||||
return Result.error("同年度下旅行社编号已存在");
|
||||
return Result.error("同年度下服务单位编号已存在");
|
||||
}
|
||||
if (agency.getEnabled() == null) {
|
||||
agency.setEnabled(true);
|
||||
@@ -93,7 +93,7 @@ public class TourTravelAgencyController {
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.travelAgency")
|
||||
@SLog(type = "tour", tag = "旅行社管理", msg = "删除旅行社信息")
|
||||
@SLog(type = "tour", tag = "服务单位管理", msg = "删除服务单位信息")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
@@ -110,10 +110,10 @@ public class TourTravelAgencyController {
|
||||
return Result.error("年度不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getAgencyName())) {
|
||||
return Result.error("旅行社名称不能为空");
|
||||
return Result.error("服务单位名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getAgencyCode())) {
|
||||
return Result.error("旅行社编号不能为空");
|
||||
return Result.error("服务单位编号不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getContactName())) {
|
||||
return Result.error("联系人不能为空");
|
||||
|
||||
@@ -102,12 +102,12 @@ public class TourLedger extends BaseModel implements Serializable {
|
||||
private String hotelName;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社ID")
|
||||
@Comment("服务单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String travelAgencyId;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社")
|
||||
@Comment("服务单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String travelAgencyName;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class TourLine extends BaseModel implements Serializable {
|
||||
private String lineName;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社ID")
|
||||
@Comment("服务单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String travelAgencyId;
|
||||
|
||||
|
||||
@@ -43,6 +43,12 @@ public class TourSetting extends BaseModel implements Serializable {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String tourType;
|
||||
|
||||
@Column
|
||||
@Comment("出行人数指标")
|
||||
@ColDefine(type = ColType.INT)
|
||||
@Default("0")
|
||||
private Integer travelPeopleQuota;
|
||||
|
||||
@Column
|
||||
@Comment("可参加人员范围ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@@ -63,6 +69,11 @@ public class TourSetting extends BaseModel implements Serializable {
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer maxGroupPeople;
|
||||
|
||||
@Column
|
||||
@Comment("省内几年去一次")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer inProvinceYears;
|
||||
|
||||
@Column
|
||||
@Comment("省外几年去一次")
|
||||
@ColDefine(type = ColType.INT)
|
||||
@@ -122,11 +133,16 @@ public class TourSetting extends BaseModel implements Serializable {
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 标段管理沿用老疗休养配置的子表设计,后续线路、旅行社等模块可通过标段ID继续关联。
|
||||
* 标段管理沿用老疗休养配置的子表设计,后续线路、服务单位等模块可通过标段ID继续关联。
|
||||
*/
|
||||
@Many(field = "settingId")
|
||||
private List<TourSettingLot> lots;
|
||||
|
||||
/**
|
||||
* 分工会人员分配,仅用于配置弹窗回显与提交,不作为 tour_setting 表字段保存。
|
||||
*/
|
||||
private List<TourSettingUnionQuota> unionQuotas;
|
||||
|
||||
@Column
|
||||
@Comment("服务须知")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Default;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.entity.annotation.TableMeta;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 疗休养配置下的分工会人员数量分配。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_setting_union_quota")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("疗休养配置分工会人员分配")
|
||||
public class TourSettingUnionQuota extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("所属疗休养配置ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String settingId;
|
||||
|
||||
@Column
|
||||
@Comment("分工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String unionId;
|
||||
|
||||
@Column
|
||||
@Comment("分工会名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unionName;
|
||||
|
||||
@Column
|
||||
@Comment("人员数量")
|
||||
@ColDefine(type = ColType.INT)
|
||||
@Default("0")
|
||||
private Integer peopleQuota;
|
||||
|
||||
/**
|
||||
* 当前分工会人员总数,仅用于页面分配参考,不写入数据库。
|
||||
*/
|
||||
private Integer personCount;
|
||||
}
|
||||
@@ -9,14 +9,14 @@ import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 旅行社管理。
|
||||
* 先维护疗休养线路创建会复用的旅行社基础信息,后续线路模块可通过旅行社ID关联。
|
||||
* 服务单位管理。
|
||||
* 先维护疗休养线路创建会复用的服务单位基础信息,后续线路模块可通过服务单位ID关联。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_travel_agency")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养旅行社")
|
||||
@Comment("普惠疗休养服务单位")
|
||||
public class TourTravelAgency extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@@ -32,12 +32,12 @@ public class TourTravelAgency extends BaseModel implements Serializable {
|
||||
private Integer year;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社编号")
|
||||
@Comment("服务单位编号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String agencyCode;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社名称")
|
||||
@Comment("服务单位名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String agencyName;
|
||||
|
||||
|
||||
@@ -3,5 +3,36 @@ package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
|
||||
/**
|
||||
* 疗休养报名台账服务,统一处理报名记录及并发名额校验。
|
||||
*/
|
||||
public interface TourLedgerService extends BaseService<TourLedger> {
|
||||
|
||||
/**
|
||||
* 校验当前人员在省内间隔周期内是否已经实际参加过省内线路。
|
||||
*
|
||||
* @param jobNo 当前登录人工号
|
||||
* @param inProvinceYears 包含当前年份在内的省内限制年数,小于等于0表示不限制
|
||||
* @param excludeLedgerId 修改报名时需要排除的当前台账ID,新增时允许为空
|
||||
* @return 校验通过返回空字符串,否则返回不能报名的业务提示
|
||||
*/
|
||||
String checkInProvinceParticipation(String jobNo,
|
||||
Integer inProvinceYears,
|
||||
String excludeLedgerId);
|
||||
|
||||
/**
|
||||
* 在同一事务中锁定分工会名额、校验并发容量并写入报名台账。
|
||||
*
|
||||
* @param ledger 待新增或修改的报名台账
|
||||
* @param settingId 疗休养配置ID
|
||||
* @param matterId 当前报名事项ID
|
||||
* @param userId 当前登录用户ID
|
||||
* @param update true 表示修改已有报名,false 表示新增报名
|
||||
* @return 保存成功返回空字符串,否则返回业务提示
|
||||
*/
|
||||
String saveWithUnionSignupQuota(TourLedger ledger,
|
||||
String settingId,
|
||||
String matterId,
|
||||
String userId,
|
||||
boolean update);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,44 @@ package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingUnionQuota;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 疗休养配置服务,统一处理配置及分工会人员数量分配业务。
|
||||
*/
|
||||
public interface TourSettingService extends BaseService<TourSetting> {
|
||||
|
||||
/**
|
||||
* 查询全部分工会在指定配置下的人员数量,并实时补充分工会人员总数。
|
||||
*
|
||||
* @param settingId 疗休养配置ID,新增配置时允许为空
|
||||
* @return 按分工会编码排序的人员分配行
|
||||
*/
|
||||
List<TourSettingUnionQuota> listUnionQuotaRows(String settingId);
|
||||
|
||||
/**
|
||||
* 保存指定配置的分工会人员数量,提交内容为 JSON 数组字符串。
|
||||
*
|
||||
* @param settingId 疗休养配置ID
|
||||
* @param unionQuotas 分工会人员分配 JSON
|
||||
*/
|
||||
void saveUnionQuotas(String settingId, String unionQuotas);
|
||||
|
||||
/**
|
||||
* 校验各分工会人员数量是否均为非负整数且合计不超过出行人数指标。
|
||||
*
|
||||
* @param unionQuotas 分工会人员分配 JSON
|
||||
* @param travelPeopleQuota 出行人数指标
|
||||
* @return 校验通过返回空字符串,否则返回业务提示
|
||||
*/
|
||||
String checkUnionQuotaLimit(String unionQuotas, int travelPeopleQuota);
|
||||
|
||||
/**
|
||||
* 清除指定配置下的全部分工会人员分配。
|
||||
*
|
||||
* @param settingId 疗休养配置ID
|
||||
*/
|
||||
void clearUnionQuotas(String settingId);
|
||||
}
|
||||
|
||||
+163
@@ -1,15 +1,178 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
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.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 疗休养报名台账服务实现。
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
|
||||
|
||||
public TourLedgerServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String checkInProvinceParticipation(String jobNo,
|
||||
Integer inProvinceYears,
|
||||
String excludeLedgerId) {
|
||||
if (inProvinceYears == null || inProvinceYears <= 0) {
|
||||
return "";
|
||||
}
|
||||
if (StrUtil.isBlank(jobNo)) {
|
||||
return "未查询到当前登录人工号,不能校验省内线路报名资格";
|
||||
}
|
||||
int currentYear = LocalDate.now().getYear();
|
||||
// N年按包含当前年份在内的N个自然年度计算,例如2026年的3年周期为2024至2026年。
|
||||
long calculatedStartYear = (long) currentYear - inProvinceYears + 1L;
|
||||
int startYear = calculatedStartYear < 0 ? 0 : (int) calculatedStartYear;
|
||||
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND id <> @excludeLedgerId";
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_ledger
|
||||
WHERE delFlag = 0
|
||||
AND joined = 1
|
||||
AND jobNo = @jobNo
|
||||
AND `year` >= @startYear
|
||||
AND `year` <= @currentYear
|
||||
AND lineType IN ('省内线路', '省内')
|
||||
""" + excludeSql);
|
||||
sql.setParam("jobNo", jobNo);
|
||||
sql.setParam("startYear", startYear);
|
||||
sql.setParam("currentYear", currentYear);
|
||||
if (StrUtil.isNotBlank(excludeLedgerId)) {
|
||||
sql.setParam("excludeLedgerId", excludeLedgerId);
|
||||
}
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
dao().execute(sql);
|
||||
if (sql.getInt() <= 0) {
|
||||
return "";
|
||||
}
|
||||
return "您在 " + startYear + " 至 " + currentYear
|
||||
+ " 年内已参加过省内线路,省内线路每 " + inProvinceYears + " 年可参加一次,当前不能报名";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public String saveWithUnionSignupQuota(TourLedger ledger,
|
||||
String settingId,
|
||||
String matterId,
|
||||
String userId,
|
||||
boolean update) {
|
||||
if (ledger == null) {
|
||||
return "报名数据不能为空";
|
||||
}
|
||||
String quotaMessage = lockAndCheckUnionSignupQuota(
|
||||
settingId, matterId, userId, update ? ledger.getId() : null);
|
||||
if (StrUtil.isNotBlank(quotaMessage)) {
|
||||
return quotaMessage;
|
||||
}
|
||||
// 名额校验通过后立即在同一事务内写入台账,事务提交前始终持有分工会名额行锁。
|
||||
if (update) {
|
||||
updateIgnoreNull(ledger);
|
||||
} else {
|
||||
insert(ledger);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定当前配置和登录人所属分工会的名额行,并校验报名后是否超过人员数量。
|
||||
*/
|
||||
private String lockAndCheckUnionSignupQuota(String settingId,
|
||||
String matterId,
|
||||
String userId,
|
||||
String excludeLedgerId) {
|
||||
if (StrUtil.isBlank(settingId) || StrUtil.isBlank(matterId)) {
|
||||
return "疗休养配置或报名事项不存在";
|
||||
}
|
||||
// 分工会信息从用户视图读取,不接受报名表单传入的工会ID,防止跨工会占用名额。
|
||||
View_user user = dao().fetch(View_user.class, Cnd.where(View_user::getId, "=", userId));
|
||||
if (user == null || StrUtil.isBlank(user.getUnionId()) || StrUtil.isBlank(user.getLoginname())) {
|
||||
return "未查询到您所属的分工会,不能报名";
|
||||
}
|
||||
|
||||
// FOR UPDATE 串行化同一配置、同一分工会的抢名额请求,不同分工会之间互不阻塞。
|
||||
Sql quotaSql = Sqls.create("""
|
||||
SELECT peopleQuota
|
||||
FROM tour_setting_union_quota
|
||||
WHERE settingId = @settingId
|
||||
AND unionId = @unionId
|
||||
AND delFlag = 0
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
""");
|
||||
quotaSql.setParam("settingId", settingId);
|
||||
quotaSql.setParam("unionId", user.getUnionId());
|
||||
quotaSql.setCallback(Sqls.callback.map());
|
||||
dao().execute(quotaSql);
|
||||
NutMap quotaRow = quotaSql.getObject(NutMap.class);
|
||||
if (quotaRow == null || quotaRow.isEmpty()) {
|
||||
return "您所在分工会未分配报名名额";
|
||||
}
|
||||
int peopleQuota = quotaRow.getInt("peopleQuota", 0);
|
||||
if (peopleQuota <= 0) {
|
||||
return "您所在分工会的报名名额已满";
|
||||
}
|
||||
|
||||
// 取得名额锁后再次检查同事项重复报名,关闭双击或并发请求绕过前置查询的时间窗口。
|
||||
String excludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND id <> @excludeLedgerId";
|
||||
Sql duplicateSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_ledger
|
||||
WHERE delFlag = 0
|
||||
AND matterId = @matterId
|
||||
AND jobNo = @jobNo
|
||||
""" + excludeSql);
|
||||
duplicateSql.setParam("matterId", matterId);
|
||||
duplicateSql.setParam("jobNo", user.getLoginname());
|
||||
if (StrUtil.isNotBlank(excludeLedgerId)) {
|
||||
duplicateSql.setParam("excludeLedgerId", excludeLedgerId);
|
||||
}
|
||||
duplicateSql.setCallback(Sqls.callback.integer());
|
||||
dao().execute(duplicateSql);
|
||||
if (duplicateSql.getInt() > 0) {
|
||||
return "您已报名当前出行时段,请勿重复提交";
|
||||
}
|
||||
|
||||
// 名额按配置统计,覆盖该配置下的全部事项;待审核报名也占用名额,取消后因台账删除自动释放。
|
||||
String countExcludeSql = StrUtil.isBlank(excludeLedgerId) ? "" : " AND ledger.id <> @excludeLedgerId";
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_ledger ledger
|
||||
INNER JOIN tour_matter matter
|
||||
ON matter.id = ledger.matterId
|
||||
AND matter.delFlag = 0
|
||||
WHERE ledger.delFlag = 0
|
||||
AND matter.settingId = @settingId
|
||||
AND ledger.unionId = @unionId
|
||||
""" + countExcludeSql);
|
||||
countSql.setParam("settingId", settingId);
|
||||
countSql.setParam("unionId", user.getUnionId());
|
||||
if (StrUtil.isNotBlank(excludeLedgerId)) {
|
||||
countSql.setParam("excludeLedgerId", excludeLedgerId);
|
||||
}
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
dao().execute(countSql);
|
||||
int signupCount = countSql.getInt();
|
||||
if (signupCount + 1 > peopleQuota) {
|
||||
return "您所在分工会的报名名额已满(已报名 " + signupCount
|
||||
+ " 人,分配 " + peopleQuota + " 人)";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
+157
@@ -1,15 +1,172 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingUnionQuota;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 疗休养配置服务实现,负责分工会人员分配的查询、校验和持久化。
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourSettingServiceImpl extends BaseServiceImpl<TourSetting> implements TourSettingService {
|
||||
|
||||
public TourSettingServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TourSettingUnionQuota> listUnionQuotaRows(String settingId) {
|
||||
// 一次查询分工会、已保存数量和实时人员总数,避免逐个工会统计产生 N+1 查询。
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
quota.id AS id,
|
||||
@settingId AS settingId,
|
||||
un.id AS unionId,
|
||||
un.name AS unionName,
|
||||
COALESCE(quota.peopleQuota, 0) AS peopleQuota,
|
||||
COALESCE(user_count.personCount, 0) AS personCount
|
||||
FROM sys_union un
|
||||
LEFT JOIN tour_setting_union_quota quota
|
||||
ON quota.unionId = un.id
|
||||
AND quota.settingId = @settingId
|
||||
AND COALESCE(quota.delFlag, 0) = 0
|
||||
LEFT JOIN (
|
||||
SELECT unionId, COUNT(1) AS personCount
|
||||
FROM vw_user
|
||||
GROUP BY unionId
|
||||
) user_count ON user_count.unionId = un.id
|
||||
WHERE COALESCE(un.delFlag, 0) = 0
|
||||
ORDER BY un.unionCode ASC
|
||||
""");
|
||||
sql.setParam("settingId", StrUtil.blankToDefault(settingId, ""));
|
||||
List<NutMap> rows = listMap(sql);
|
||||
return rows.stream().map(row -> {
|
||||
TourSettingUnionQuota quota = new TourSettingUnionQuota();
|
||||
quota.setId(row.getString("id"));
|
||||
quota.setSettingId(row.getString("settingId"));
|
||||
quota.setUnionId(row.getString("unionId"));
|
||||
quota.setUnionName(row.getString("unionName"));
|
||||
quota.setPeopleQuota(toNonNegativeInteger(row.getInt("peopleQuota")));
|
||||
quota.setPersonCount(toNonNegativeInteger(row.getInt("personCount")));
|
||||
return quota;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUnionQuotas(String settingId, String unionQuotas) {
|
||||
if (StrUtil.isBlank(settingId)) {
|
||||
return;
|
||||
}
|
||||
clearUnionQuotas(settingId);
|
||||
if (StrUtil.isBlank(unionQuotas)) {
|
||||
return;
|
||||
}
|
||||
List<TourSettingUnionQuota> quotaList = Json.fromJsonAsList(TourSettingUnionQuota.class, unionQuotas);
|
||||
if (Lang.isEmpty(quotaList)) {
|
||||
return;
|
||||
}
|
||||
// 工会名称以当前组织表为准,防止前端篡改或历史名称继续写入。
|
||||
Map<String, Sys_union> unionMap = dao().query(Sys_union.class,
|
||||
Cnd.where(Sys_union::getDelFlag, "=", false)).stream()
|
||||
.collect(Collectors.toMap(Sys_union::getId, item -> item, (first, second) -> first));
|
||||
List<TourSettingUnionQuota> saveList = quotaList.stream()
|
||||
.filter(item -> item != null
|
||||
&& StrUtil.isNotBlank(item.getUnionId())
|
||||
&& unionMap.containsKey(item.getUnionId()))
|
||||
.map(item -> normalizeUnionQuota(settingId, item, unionMap))
|
||||
.filter(item -> item.getPeopleQuota() > 0)
|
||||
.collect(Collectors.toList());
|
||||
if (Lang.isNotEmpty(saveList)) {
|
||||
dao().insert(saveList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String checkUnionQuotaLimit(String unionQuotas, int travelPeopleQuota) {
|
||||
if (StrUtil.isBlank(unionQuotas)) {
|
||||
return "";
|
||||
}
|
||||
final List<TourSettingUnionQuota> quotaList;
|
||||
try {
|
||||
quotaList = Json.fromJsonAsList(TourSettingUnionQuota.class, unionQuotas);
|
||||
} catch (Exception e) {
|
||||
return "人员分配数据格式不正确";
|
||||
}
|
||||
if (Lang.isEmpty(quotaList)) {
|
||||
return "";
|
||||
}
|
||||
long total = 0L;
|
||||
Set<String> unionIds = new HashSet<>();
|
||||
Set<String> validUnionIds = dao().query(Sys_union.class,
|
||||
Cnd.where(Sys_union::getDelFlag, "=", false)).stream()
|
||||
.map(Sys_union::getId)
|
||||
.collect(Collectors.toSet());
|
||||
for (int index = 0; index < quotaList.size(); index++) {
|
||||
TourSettingUnionQuota quota = quotaList.get(index);
|
||||
if (quota == null || quota.getPeopleQuota() == null) {
|
||||
continue;
|
||||
}
|
||||
// 工会ID必须来自当前组织表且不能重复,防止伪造行绕过总量和组织范围校验。
|
||||
if (StrUtil.isBlank(quota.getUnionId())
|
||||
|| !validUnionIds.contains(quota.getUnionId())
|
||||
|| !unionIds.add(quota.getUnionId())) {
|
||||
return "第" + (index + 1) + "行分工会信息无效或重复";
|
||||
}
|
||||
if (quota.getPeopleQuota() < 0) {
|
||||
return "第" + (index + 1) + "行人员数量必须为非负整数";
|
||||
}
|
||||
total += quota.getPeopleQuota();
|
||||
}
|
||||
if (total > travelPeopleQuota) {
|
||||
return "当前人员数量合计 " + total + ",不能超过出行人数指标 " + travelPeopleQuota;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUnionQuotas(String settingId) {
|
||||
if (StrUtil.isNotBlank(settingId)) {
|
||||
dao().clear(TourSettingUnionQuota.class,
|
||||
Cnd.where(TourSettingUnionQuota::getSettingId, "=", settingId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将前端分配行转换为可信的持久化对象,只接受当前组织表中的工会名称。
|
||||
*/
|
||||
private TourSettingUnionQuota normalizeUnionQuota(String settingId,
|
||||
TourSettingUnionQuota item,
|
||||
Map<String, Sys_union> unionMap) {
|
||||
Sys_union union = unionMap.get(item.getUnionId());
|
||||
TourSettingUnionQuota quota = new TourSettingUnionQuota();
|
||||
quota.setSettingId(settingId);
|
||||
quota.setUnionId(item.getUnionId());
|
||||
quota.setUnionName(union.getName());
|
||||
quota.setPeopleQuota(toNonNegativeInteger(item.getPeopleQuota()));
|
||||
return quota;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将空值或负数统一转换为零,保证页面统计和数据库保存使用相同口径。
|
||||
*/
|
||||
private Integer toNonNegativeInteger(Integer value) {
|
||||
return value == null || value < 0 ? 0 : value;
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -124,6 +124,13 @@ public class UnionReimburseCollectController {
|
||||
return unionReimburseService.updateActuallyAmount(id, realMoney);
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"unionReimburse.collect", "h5.unionReimburse.collect"}, mode = SaMode.OR)
|
||||
public Result updateReimburseProject(String id, String reimburseProject) {
|
||||
return unionReimburseService.updateReimburseProject(id, reimburseProject);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission(value = {"unionReimburse.collect", "h5.unionReimburse.collect"}, mode = SaMode.OR)
|
||||
|
||||
+5
@@ -45,6 +45,11 @@ public class UnionReimburseDesc extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 200)
|
||||
private String fileDesc;
|
||||
|
||||
@Column
|
||||
@Comment("报销项目提示")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String projectTip;
|
||||
|
||||
@Column
|
||||
@Comment("排序字段")
|
||||
@Prev({
|
||||
|
||||
+5
@@ -69,6 +69,11 @@ public interface UnionReimburseService extends BaseService<UnionReimburse> {
|
||||
*/
|
||||
Result updateActuallyAmount(String id, Double realMoney);
|
||||
|
||||
/**
|
||||
* 修改非慰问报销记录的活动项目。
|
||||
*/
|
||||
Result updateReimburseProject(String id, String reimburseProject);
|
||||
|
||||
/**
|
||||
* 批量审核选中的待审核报销申请。
|
||||
*
|
||||
|
||||
+34
@@ -66,6 +66,13 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> implements UnionReimburseService {
|
||||
/** 非慰问报销项目允许在三个活动项目之间切换。 */
|
||||
private static final Set<String> ACTIVITY_REIMBURSE_PROJECTS = Set.of(
|
||||
"UNION_REIMBURSE_PROJECT_2",
|
||||
"UNION_REIMBURSE_PROJECT_3",
|
||||
"UNION_REIMBURSE_PROJECT_4"
|
||||
);
|
||||
|
||||
@Inject
|
||||
private SysFileService sysFileService;
|
||||
@Inject
|
||||
@@ -424,6 +431,11 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
if (dbRecord == null) {
|
||||
return Result.error("未找到对应的报销记录");
|
||||
}
|
||||
// 实际报销金额不得超过申请金额,避免绕过前端校验直接修改数据。
|
||||
BigDecimal declaredMoney = getDeclaredApplyMoney(dbRecord);
|
||||
if (newMoney.compareTo(declaredMoney) > 0) {
|
||||
return Result.error("实际报销金额不能超过申请金额:" + declaredMoney.toPlainString());
|
||||
}
|
||||
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
|
||||
@@ -439,6 +451,28 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result updateReimburseProject(String id, String reimburseProject) {
|
||||
if (!isAdmin()) {
|
||||
return Result.error("无权修改报销项目");
|
||||
}
|
||||
if (StrUtil.hasBlank(id, reimburseProject)) {
|
||||
return Result.error("报销记录ID和报销项目不能为空");
|
||||
}
|
||||
UnionReimburse dbRecord = this.fetch(id);
|
||||
if (dbRecord == null) {
|
||||
return Result.error("未找到对应的报销记录");
|
||||
}
|
||||
// 慰问记录与活动项目字段不同,只允许活动项目之间切换。
|
||||
if (!ACTIVITY_REIMBURSE_PROJECTS.contains(dbRecord.getReimburseProject())
|
||||
|| !ACTIVITY_REIMBURSE_PROJECTS.contains(reimburseProject)) {
|
||||
return Result.error("慰问记录不可修改,报销项目只能在三个活动项目之间切换");
|
||||
}
|
||||
dbRecord.setReimburseProject(reimburseProject);
|
||||
this.updateIgnoreNull(dbRecord);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private void normalizeInvoiceDetails(UnionReimburse unionReimburse) {
|
||||
if (unionReimburse.getInvoiceDetails() == null) {
|
||||
unionReimburse.setInvoiceDetails(new ArrayList<>());
|
||||
|
||||
+5
@@ -8,6 +8,7 @@ import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalType;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -33,6 +34,8 @@ public class ProposalConfigTypeController {
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/config/type/index.html")
|
||||
@@ -47,6 +50,8 @@ public class ProposalConfigTypeController {
|
||||
Sql sql = Sqls.create("select * from proposal_type $condition");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and(Cnd.likeEX("name", name));
|
||||
// 提案类型列表只允许名称和编码参与排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "configType");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageVO(pageForm, sql, ProposalType.class);
|
||||
return Result.success(pagination);
|
||||
|
||||
+7
-1
@@ -19,6 +19,7 @@ import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -54,6 +55,8 @@ public class ProposalConfigUnitController {
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private SysRoleService sysRoleService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/config/unit/index.html")
|
||||
@@ -99,7 +102,10 @@ public class ProposalConfigUnitController {
|
||||
cnd.where().orLike("u2.loginname", pageForm.getSearchKeyword());
|
||||
cnd.where().orLike("t1.name", pageForm.getSearchKeyword());
|
||||
}
|
||||
cnd.asc("t1.code");
|
||||
// 承办单位列表未指定有效排序时继续按单位编码升序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "configUnit")) {
|
||||
cnd.asc("t1.code");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+20
@@ -28,6 +28,7 @@ import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/proposal/dashboard")
|
||||
@@ -35,6 +36,23 @@ import java.util.List;
|
||||
@Ok("json:full")
|
||||
public class ProposalDashboardController {
|
||||
|
||||
/**
|
||||
* 工作台表格允许排序的字段映射,值为查询中的真实字段或安全别名。
|
||||
*/
|
||||
private static final Map<String, String> DASHBOARD_ORDER_COLUMNS = Map.ofEntries(
|
||||
Map.entry("code", "info.code"),
|
||||
Map.entry("name", "info.name"),
|
||||
Map.entry("createUserName", "info.createUserName"),
|
||||
Map.entry("typeName", "typeName"),
|
||||
Map.entry("delegationName", "delegationName"),
|
||||
Map.entry("caseFilingResult", "info.caseFilingResult"),
|
||||
Map.entry("isConsolidation", "info.isConsolidation"),
|
||||
Map.entry("masterUnitName", "masterUnitName"),
|
||||
Map.entry("slaveUnitNames", "slaveUnitNames"),
|
||||
Map.entry("curTaskName", "curTaskName"),
|
||||
Map.entry("instanceState", "instanceState")
|
||||
);
|
||||
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
@Inject
|
||||
@@ -178,6 +196,8 @@ public class ProposalDashboardController {
|
||||
}
|
||||
}
|
||||
|
||||
// 根据工作台字段白名单追加排序,未选择排序时保持原有查询顺序。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, DASHBOARD_ORDER_COLUMNS);
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+36
@@ -7,6 +7,8 @@ import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalExportService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalWriteService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -29,6 +31,10 @@ public class ProposalExportComprehensiveController {
|
||||
|
||||
@Inject
|
||||
private ProposalExportService proposalExportService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
@Inject
|
||||
private ProposalWriteService proposalWriteService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/export/comprehensive/index.html")
|
||||
@@ -73,11 +79,26 @@ public class ProposalExportComprehensiveController {
|
||||
}
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
// 综合导出主列表的聚合字段仅通过固定查询别名排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "exportComprehensive");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalExportService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询综合导出页面当前教代会届次配置的提案类别。
|
||||
*
|
||||
* @param sessionId 教代会届次ID,用于读取该届次配置的提案类别
|
||||
* @return VO 列表,每项包含筛选使用的 id 和页面展示使用的 name
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("proposal.export.comprehensive")
|
||||
@ApiOperation("查询当前届次提案类别")
|
||||
public Result listSessionProposalTypes(@Valid String sessionId) {
|
||||
return Result.success(proposalWriteService.listSessionProposalTypes(sessionId));
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@@ -87,6 +108,21 @@ public class ProposalExportComprehensiveController {
|
||||
proposalExportService.exportSummaryAsExcel(pageForm, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按办理单位导出提案承办单位表压缩包。
|
||||
*
|
||||
* @param pageForm 综合导出页面查询参数,其中 sessionId 为必传的教代会届次ID
|
||||
* @param response HTTP 响应,返回内容为包含多个 XSSF Excel 的 ZIP 文件
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("按办理单位导出提案承办单位表压缩包")
|
||||
@SaCheckPermission("proposal.query.comprehensive")
|
||||
public void exportUndertakeUnitTablesAsZip(@Valid @Param("pageForm") ProposalQueryComprehensiveParam pageForm,
|
||||
HttpServletResponse response) {
|
||||
proposalExportService.exportUndertakeUnitTablesAsZip(pageForm, response);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
|
||||
+7
-1
@@ -17,6 +17,7 @@ import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.flow.vo.ProcessTaskVO;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import com.deepoove.poi.XWPFTemplate;
|
||||
import com.deepoove.poi.config.Configure;
|
||||
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
||||
@@ -63,6 +64,8 @@ public class ProposalExportSingleCustomController {
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@Inject
|
||||
private FlowEngine flowEngine;
|
||||
@@ -100,7 +103,10 @@ public class ProposalExportSingleCustomController {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
cnd.desc("info.createdAt");
|
||||
// 自定义导出列表未指定有效排序时保留提案创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "exportSingleCustom")) {
|
||||
cnd.desc("info.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+59
-22
@@ -6,11 +6,13 @@ 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.util.StrUtil;
|
||||
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.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -31,6 +33,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean
|
||||
@@ -40,10 +43,26 @@ import java.util.stream.Collectors;
|
||||
@Api(tags = "提案统计分析")
|
||||
public class ProposalQueryAnalysisController {
|
||||
|
||||
/**
|
||||
* 统计数据表允许排序的字段白名单。
|
||||
*/
|
||||
private static final Set<String> STATISTICS_ORDER_COLUMNS = Set.of(
|
||||
"dimension", "itemName", "count", "rate"
|
||||
);
|
||||
|
||||
/**
|
||||
* 分析数据表允许排序的字段白名单。
|
||||
*/
|
||||
private static final Set<String> ANALYSIS_ORDER_COLUMNS = Set.of(
|
||||
"dimension", "total", "categoryCount", "topItem", "topCount", "topRate", "analysis"
|
||||
);
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/query/analysis/index.html")
|
||||
@@ -51,36 +70,54 @@ public class ProposalQueryAnalysisController {
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询提案统计数据。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @param dimension 统计维度,为空时返回全部维度
|
||||
* @param statisticsOrderName 统计表排序字段
|
||||
* @param statisticsOrderBy 统计表排序方向
|
||||
* @return 统计行列表,包含维度、分类项、数量和占比
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("统计数据")
|
||||
@SaCheckPermission("proposal.query.analysis")
|
||||
public Result statisticsData(String sessionId, String dimension) {
|
||||
/*
|
||||
* 查询参数说明:
|
||||
* sessionId:教代会届次ID,用于限定统计提案范围;
|
||||
* dimension:统计维度,可传“提案人单位、提案类型、代表团、立案结果、满意度”,为空时返回全部维度。
|
||||
* 返回值:Result.data 为统计行列表,包含 dimension、itemName、count、rate。
|
||||
*/
|
||||
public Result statisticsData(String sessionId, String dimension, String statisticsOrderName, String statisticsOrderBy) {
|
||||
if (StrUtil.isBlank(sessionId)) {
|
||||
return Result.error("请先选择教代会届次");
|
||||
}
|
||||
return Result.success(buildStatisticsRows(sessionId, dimension));
|
||||
List<NutMap> rows = buildStatisticsRows(sessionId, dimension);
|
||||
PageForm orderForm = new PageForm();
|
||||
orderForm.setPageOrderName(statisticsOrderName);
|
||||
orderForm.setPageOrderBy(statisticsOrderBy);
|
||||
// 统计数据生成后按独立白名单执行后端排序。
|
||||
proposalCommonService.sortStatisticsRows(rows, orderForm, STATISTICS_ORDER_COLUMNS);
|
||||
return Result.success(rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询提案分析数据。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @param dimension 统计维度,为空时返回全部维度
|
||||
* @param analysisOrderName 分析表排序字段
|
||||
* @param analysisOrderBy 分析表排序方向
|
||||
* @return 分析行列表,包含总量、最高项、最高占比和分析结论
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("分析数据")
|
||||
@SaCheckPermission("proposal.query.analysis")
|
||||
public Result analysisData(String sessionId, String dimension) {
|
||||
/*
|
||||
* 查询参数说明:
|
||||
* sessionId:教代会届次ID,用于限定分析提案范围;
|
||||
* dimension:统计维度,可传“提案人单位、提案类型、代表团、立案结果、满意度”,为空时返回全部维度。
|
||||
* 返回值:Result.data 为分析行列表,包含 dimension、total、categoryCount、topItem、topCount、topRate、analysis。
|
||||
*/
|
||||
public Result analysisData(String sessionId, String dimension, String analysisOrderName, String analysisOrderBy) {
|
||||
if (StrUtil.isBlank(sessionId)) {
|
||||
return Result.error("请先选择教代会届次");
|
||||
}
|
||||
return Result.success(buildAnalysisRows(sessionId, dimension));
|
||||
List<NutMap> rows = buildAnalysisRows(sessionId, dimension);
|
||||
PageForm orderForm = new PageForm();
|
||||
orderForm.setPageOrderName(analysisOrderName);
|
||||
orderForm.setPageOrderBy(analysisOrderBy);
|
||||
// 分析数据生成后按独立白名单执行后端排序。
|
||||
proposalCommonService.sortStatisticsRows(rows, orderForm, ANALYSIS_ORDER_COLUMNS);
|
||||
return Result.success(rows);
|
||||
}
|
||||
|
||||
@At
|
||||
@@ -94,9 +131,9 @@ public class ProposalQueryAnalysisController {
|
||||
entities.add(new ExcelExportEntity("分类项", "itemName", 30));
|
||||
entities.add(new ExcelExportEntity("数量", "count", 12));
|
||||
entities.add(new ExcelExportEntity("占比", "rate", 12));
|
||||
ExportParams params = new ExportParams();
|
||||
params.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(params, entities, list);
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list);
|
||||
CommonDownloadUtil.download("提案统计数据.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
@@ -114,9 +151,9 @@ public class ProposalQueryAnalysisController {
|
||||
entities.add(new ExcelExportEntity("最高数量", "topCount", 12));
|
||||
entities.add(new ExcelExportEntity("最高占比", "topRate", 12));
|
||||
entities.add(new ExcelExportEntity("分析结论", "analysis", 60));
|
||||
ExportParams params = new ExportParams();
|
||||
params.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(params, entities, list);
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, list);
|
||||
CommonDownloadUtil.download("提案分析数据.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
|
||||
+19
-1
@@ -27,6 +27,7 @@ import org.nutz.mvc.annotation.Param;
|
||||
import javax.validation.Valid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@IocBean
|
||||
@@ -36,6 +37,20 @@ import java.util.List;
|
||||
@Api(tags = "征集进度查询")
|
||||
public class ProposalQueryCollectProgressController {
|
||||
|
||||
/**
|
||||
* 征集进度表格允许排序的字段映射,值为查询中的真实字段或安全别名。
|
||||
*/
|
||||
private static final Map<String, String> COLLECT_PROGRESS_ORDER_COLUMNS = Map.ofEntries(
|
||||
Map.entry("code", "info.code"),
|
||||
Map.entry("name", "info.name"),
|
||||
Map.entry("createUserName", "info.createUserName"),
|
||||
Map.entry("delegationName", "delegationName"),
|
||||
Map.entry("inviteCount", "inviteCount"),
|
||||
Map.entry("finishCount", "finishCount"),
|
||||
Map.entry("curTaskName", "curTaskName"),
|
||||
Map.entry("instanceState", "instanceState")
|
||||
);
|
||||
|
||||
static List<NutMap> states = new ArrayList<>() {{
|
||||
add(NutMap.NEW().addv("code", 10).addv("id", 10).addv("name", "撰写提案"));
|
||||
add(NutMap.NEW().addv("code", 20).addv("id", 20).addv("name", "附议提案"));
|
||||
@@ -83,8 +98,9 @@ public class ProposalQueryCollectProgressController {
|
||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
|
||||
$condition
|
||||
""");
|
||||
sql.setParam("sessionId", pageForm.getSessionId());
|
||||
Cnd cnd = Cnd.NEW();
|
||||
// 征集进度必须按页面选中的教代会过滤,避免共享参数未处理sessionId导致跨届次查询。
|
||||
cnd.andEX("info.sessionId", "=", pageForm.getSessionId());
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
@@ -106,6 +122,8 @@ public class ProposalQueryCollectProgressController {
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.groupBy("info.id");
|
||||
// 征集进度包含关联字段和人数统计别名,只允许白名单字段进入排序条件。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, COLLECT_PROGRESS_ORDER_COLUMNS);
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
List<NutMap> list = (List<NutMap>) pagination.getList();
|
||||
|
||||
+22
-1
@@ -25,6 +25,7 @@ import org.nutz.mvc.annotation.Param;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/proposal/query/comprehensive")
|
||||
@@ -33,6 +34,23 @@ import java.util.Arrays;
|
||||
@Api(tags = "提案综合查询")
|
||||
public class ProposalQueryComprehensiveController {
|
||||
|
||||
/**
|
||||
* 综合查询表格允许排序的字段映射,值为查询中的真实字段或安全别名。
|
||||
*/
|
||||
private static final Map<String, String> COMPREHENSIVE_ORDER_COLUMNS = Map.ofEntries(
|
||||
Map.entry("code", "info.code"),
|
||||
Map.entry("name", "info.name"),
|
||||
Map.entry("createUserName", "info.createUserName"),
|
||||
Map.entry("typeName", "typeName"),
|
||||
Map.entry("delegationName", "delegationName"),
|
||||
Map.entry("caseFilingResult", "info.caseFilingResult"),
|
||||
Map.entry("caseFilingType", "info.caseFilingType"),
|
||||
Map.entry("merge", "merge"),
|
||||
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
|
||||
Map.entry("curTaskName", "curTaskName"),
|
||||
Map.entry("instanceState", "instanceState")
|
||||
);
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
@@ -62,7 +80,8 @@ public class ProposalQueryComprehensiveController {
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.displayName curTaskName,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
|
||||
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||
@@ -120,6 +139,8 @@ public class ProposalQueryComprehensiveController {
|
||||
}
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
// 综合查询包含多表字段及聚合别名,只允许白名单字段进入排序条件。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, COMPREHENSIVE_ORDER_COLUMNS);
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+68
-10
@@ -1,13 +1,16 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.query;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -19,8 +22,10 @@ import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/proposal/query/delegation")
|
||||
@@ -29,9 +34,34 @@ import java.util.stream.Collectors;
|
||||
@Api(tags = "代表团提案统计")
|
||||
public class ProposalQueryDelegationController {
|
||||
|
||||
/**
|
||||
* 代表团汇总表固定列的安全排序映射,动态立案结果列在查询时追加。
|
||||
*/
|
||||
private static final Map<String, String> DELEGATION_SUMMARY_ORDER_COLUMNS = Map.of(
|
||||
"dbtName", "dbt.name",
|
||||
"TOTAL", "TOTAL",
|
||||
"caseRate", "caseRateOrder"
|
||||
);
|
||||
|
||||
/**
|
||||
* 代表团提案明细表允许排序的字段映射。
|
||||
*/
|
||||
private static final Map<String, String> DELEGATION_PROPOSAL_ORDER_COLUMNS = Map.ofEntries(
|
||||
Map.entry("code", "info.code"),
|
||||
Map.entry("name", "info.name"),
|
||||
Map.entry("createUserName", "info.createUserName"),
|
||||
Map.entry("typeName", "typeName"),
|
||||
Map.entry("sessionName", "sessionName"),
|
||||
Map.entry("delegationName", "delegationName"),
|
||||
Map.entry("curTaskName", "curTaskName")
|
||||
);
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@@ -46,28 +76,45 @@ public class ProposalQueryDelegationController {
|
||||
@At
|
||||
@ApiOperation("代表团提案统计")
|
||||
@SaCheckPermission("proposal.query.delegation")
|
||||
public Result pageData(String sessionId) {
|
||||
public Result pageData(PageForm pageForm, String sessionId) {
|
||||
List<Sys_dict> dictList = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT");
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
dbt.id,
|
||||
dbt.`name` AS dbtName,
|
||||
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId ) as TOTAL,
|
||||
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.isSubmit = 1 ) as SUBMIT_COUNT,
|
||||
$resultSql
|
||||
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.isSubmit = 1 ) as SUBMIT_COUNT
|
||||
$resultSql,
|
||||
(select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult = @confirmFiling)
|
||||
/ NULLIF((select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId), 0) AS caseRateOrder
|
||||
FROM
|
||||
teacher_congress_delegation dbt
|
||||
$condition
|
||||
""");
|
||||
String resultStr = dictList.stream().map(v -> {
|
||||
return "( select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult= '%s' ) as `%s`".formatted(v.getCode(), v.getCode());
|
||||
}).collect(Collectors.joining(","));
|
||||
sql.setVar("resultSql", resultStr);
|
||||
Map<String, String> summaryOrderColumns = new HashMap<>(DELEGATION_SUMMARY_ORDER_COLUMNS);
|
||||
List<String> resultSqlParts = new ArrayList<>();
|
||||
int resultIndex = 0;
|
||||
for (Sys_dict dict : dictList) {
|
||||
String resultCode = dict.getCode();
|
||||
// 动态别名仅允许字母、数字和下划线,查询值使用参数绑定,避免字典内容进入SQL结构。
|
||||
if (StrUtil.isBlank(resultCode) || !resultCode.matches("[A-Za-z0-9_]+")) {
|
||||
continue;
|
||||
}
|
||||
String paramName = "caseFilingResult" + resultIndex++;
|
||||
resultSqlParts.add(", (select count(1) from proposal_info info where info.delegationId = dbt.id and info.sessionId = dbt.sessionId and info.caseFilingResult = @" + paramName + ") AS `" + resultCode + "`");
|
||||
sql.setParam(paramName, resultCode);
|
||||
summaryOrderColumns.put(resultCode, "`" + resultCode + "`");
|
||||
}
|
||||
sql.setVar("resultSql", String.join("", resultSqlParts));
|
||||
sql.setParam("confirmFiling", "CONFIRM_FILING");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("dbt.sessionId", "=", sessionId);
|
||||
if (!AuthUtil.hasRoleOr("SYSADMIN", "SCHOOL_UNION_ADMIN")) {
|
||||
}
|
||||
cnd.asc("dbt.`code`");
|
||||
// 未选择排序时保留原有代表团编码升序,选择后仅应用白名单字段。
|
||||
if (!proposalCommonService.applySafePageOrder(cnd, pageForm, summaryOrderColumns)) {
|
||||
cnd.asc("dbt.`code`");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
return Result.success(baseService.listMap(sql));
|
||||
}
|
||||
@@ -101,7 +148,18 @@ public class ProposalQueryDelegationController {
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
// 先应用明细字段白名单,再隔离公共搜索中的原始排序处理,阻止任意字段进入SQL。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, DELEGATION_PROPOSAL_ORDER_COLUMNS);
|
||||
String requestedOrderName = pageForm.getPageOrderName();
|
||||
String requestedOrderBy = pageForm.getPageOrderBy();
|
||||
pageForm.setPageOrderName(null);
|
||||
pageForm.setPageOrderBy(null);
|
||||
try {
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
} finally {
|
||||
pageForm.setPageOrderName(requestedOrderName);
|
||||
pageForm.setPageOrderBy(requestedOrderBy);
|
||||
}
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
|
||||
+25
-1
@@ -5,6 +5,7 @@ import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -18,6 +19,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/proposal/query/history")
|
||||
@@ -26,8 +28,27 @@ import javax.validation.Valid;
|
||||
@Api(tags = "提案管理系统-查询统计-历史提案查询")
|
||||
public class ProposalQueryHistoryController {
|
||||
|
||||
/**
|
||||
* 历史提案表格允许排序的字段映射,值为查询中的真实字段或安全别名。
|
||||
*/
|
||||
private static final Map<String, String> HISTORY_ORDER_COLUMNS = Map.ofEntries(
|
||||
Map.entry("code", "info.code"),
|
||||
Map.entry("name", "info.name"),
|
||||
Map.entry("createUserName", "info.createUserName"),
|
||||
Map.entry("sessionName", "sessionName"),
|
||||
Map.entry("delegationName", "delegationName"),
|
||||
Map.entry("caseFilingResult", "info.caseFilingResult"),
|
||||
Map.entry("caseFilingType", "info.caseFilingType"),
|
||||
Map.entry("merge", "merge"),
|
||||
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
|
||||
Map.entry("curTaskName", "curTaskName"),
|
||||
Map.entry("instanceState", "instanceState")
|
||||
);
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/query/history/index.html")
|
||||
@@ -53,7 +74,8 @@ public class ProposalQueryHistoryController {
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.displayName curTaskName,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
|
||||
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||
@@ -69,6 +91,8 @@ public class ProposalQueryHistoryController {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
// 历史查询包含多表字段及聚合别名,只允许白名单字段进入排序条件。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, HISTORY_ORDER_COLUMNS);
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+14
-1
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.query;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.engine.util.FlowUtil;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
@@ -31,6 +32,15 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Ok("json:full")
|
||||
public class ProposalQueryUnitReplyController {
|
||||
|
||||
/**
|
||||
* 承办单位办理统计允许排序的字段白名单。
|
||||
*/
|
||||
private static final Set<String> UNIT_REPLY_ORDER_COLUMNS = Set.of(
|
||||
"unitName", "sum", "masterSum", "masterReplySum", "masterNoReplySum",
|
||||
"slaveSum", "slaveReplySum", "slaveNoReplySum"
|
||||
);
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
@@ -45,9 +55,10 @@ public class ProposalQueryUnitReplyController {
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.query.unitReply")
|
||||
public Result data(String sessionId, String undertakeUnitId, String caseFilingResult) {
|
||||
public Result data(PageForm pageForm, String sessionId, String undertakeUnitId, String caseFilingResult) {
|
||||
/*
|
||||
* 查询参数说明:
|
||||
* pageForm:表格排序字段和排序方向;
|
||||
* sessionId:教代会届次ID,用于限定统计的提案范围;
|
||||
* undertakeUnitId:承办单位ID,用于只统计某个承办单位,为空时统计全部承办单位;
|
||||
* caseFilingResult:立案结果字典值,对应 proposal_info.caseFilingResult,为空时不限制立案结果。
|
||||
@@ -139,6 +150,8 @@ public class ProposalQueryUnitReplyController {
|
||||
tableData.add(tableRow);
|
||||
});
|
||||
|
||||
// 统计结果生成后在服务层按白名单字段排序,避免前端字段参与任意业务处理。
|
||||
proposalCommonService.sortStatisticsRows(tableData, pageForm, UNIT_REPLY_ORDER_COLUMNS);
|
||||
return Result.success().addData(Map.of("tableData", tableData, "slaveNeedReply", slaveNeedReply));
|
||||
}
|
||||
}
|
||||
|
||||
+23
-1
@@ -21,6 +21,7 @@ import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/proposal/query/yearReport")
|
||||
@@ -29,6 +30,22 @@ import javax.validation.Valid;
|
||||
@Api(tags = "提案年度报告")
|
||||
public class ProposalQueryYearReportController {
|
||||
|
||||
/**
|
||||
* 年度报告表格允许排序的字段映射,值为查询中的真实字段或安全别名。
|
||||
*/
|
||||
private static final Map<String, String> YEAR_REPORT_ORDER_COLUMNS = Map.ofEntries(
|
||||
Map.entry("code", "info.code"),
|
||||
Map.entry("name", "info.name"),
|
||||
Map.entry("createUserName", "info.createUserName"),
|
||||
Map.entry("typeName", "typeName"),
|
||||
Map.entry("delegationName", "delegationName"),
|
||||
Map.entry("caseFilingResult", "info.caseFilingResult"),
|
||||
Map.entry("isConsolidation", "info.isConsolidation"),
|
||||
Map.entry("undertakeUnits", "undertakeUnitsOrder"),
|
||||
Map.entry("curTaskName", "curTaskName"),
|
||||
Map.entry("instanceState", "instanceState")
|
||||
);
|
||||
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@@ -56,7 +73,8 @@ public class ProposalQueryYearReportController {
|
||||
ins.processDefineId instanceProcessDefineId,
|
||||
t.displayName curTaskName,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
|
||||
GROUP_CONCAT(DISTINCT pru.unitName ORDER BY pru.isMaster DESC, pru.unitName) AS undertakeUnitsOrder
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||
@@ -70,8 +88,12 @@ public class ProposalQueryYearReportController {
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
// 年度报告必须按页面选中的教代会过滤,避免共享参数未处理sessionId导致跨届次查询。
|
||||
cnd.andEX("info.sessionId", "=", pageForm.getSessionId());
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
// 年度报告包含多表字段及聚合别名,只允许白名单字段进入排序条件。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, YEAR_REPORT_ORDER_COLUMNS);
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(),sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+29
-1
@@ -1,6 +1,8 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.query;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.engine.util.FlowUtil;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
@@ -10,6 +12,7 @@ import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalReplyUnit;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
@@ -30,10 +33,19 @@ import java.util.stream.Collectors;
|
||||
@Api(tags = "提案承办单位满意度")
|
||||
public class ProposalUnderTakeSatisfactionController {
|
||||
|
||||
/**
|
||||
* 满意度统计固定列的排序白名单,动态满意度列在查询时追加。
|
||||
*/
|
||||
private static final Set<String> SATISFACTION_ORDER_COLUMNS = Set.of(
|
||||
"unitName", "sum", "masterSum", "slaveSum"
|
||||
);
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/query/underTakeSatisfaction/index.html")
|
||||
@@ -42,10 +54,24 @@ public class ProposalUnderTakeSatisfactionController {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询指定教代会和承办单位的满意度统计结果。
|
||||
*
|
||||
* @param pageForm 表格排序参数
|
||||
* @param sessionId 教代会届次ID
|
||||
* @param undertakeUnitId 承办单位ID
|
||||
* @return 满意度统计结果
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("proposal.query.underTake.satisfaction")
|
||||
public Result data(String sessionId, String undertakeUnitId) {
|
||||
public Result data(PageForm pageForm, String sessionId, String undertakeUnitId) {
|
||||
List<Sys_dict> feedbackCodes = sysDictService.getSubListByCode("PROPOSAL_FEEDBACK");
|
||||
Set<String> orderColumns = new HashSet<>(SATISFACTION_ORDER_COLUMNS);
|
||||
// 动态满意度列仅从后端字典加入白名单,不接受前端自行扩展字段。
|
||||
feedbackCodes.stream()
|
||||
.map(Sys_dict::getCode)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.forEach(orderColumns::add);
|
||||
|
||||
// 当前届次所有的提案ID
|
||||
Sql sql = Sqls.create("select id from proposal_info where sessionId = @sessionId").setParam("sessionId", sessionId);
|
||||
@@ -109,6 +135,8 @@ public class ProposalUnderTakeSatisfactionController {
|
||||
tableData.add(tableRow);
|
||||
});
|
||||
|
||||
// 汇总完成后在服务层执行白名单排序,数字列按数值顺序处理。
|
||||
proposalCommonService.sortStatisticsRows(tableData, pageForm, orderColumns);
|
||||
return Result.success(tableData);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -111,7 +111,10 @@ public class ProposalExpeditingController {
|
||||
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 提案催办列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "expediting")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+2
@@ -78,6 +78,8 @@ public class ProposalSeniorBasicController {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
// 内容修改列表仅允许页面展示字段参与排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorBasic");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+2
@@ -78,6 +78,8 @@ public class ProposalSeniorDeleteController {
|
||||
}
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
// 高级删除列表仅允许页面展示字段参与排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorDelete");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+2
@@ -93,6 +93,8 @@ public class ProposalSeniorFeedBackController {
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
// cnd.and("latestFeedBack.id","is not",null);
|
||||
cnd.groupBy("info.id");
|
||||
// 反馈评分修改列表包含反馈计算列,使用页面独立白名单排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorFeedback");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+2
@@ -82,6 +82,8 @@ public class ProposalSeniorTypeController {
|
||||
}
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id");
|
||||
// 类型修改列表仅允许页面展示字段参与排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "seniorType");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+11
-4
@@ -5,23 +5,23 @@ import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
@@ -39,6 +39,8 @@ public class ProposalAllFinishedController {
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/allFinished/index.html")
|
||||
@@ -62,7 +64,9 @@ public class ProposalAllFinishedController {
|
||||
ins.variable instanceVariable,
|
||||
ins.processDefineId instanceProcessDefineId
|
||||
FROM wf_process_instance ins
|
||||
LEFT JOIN proposal_info info ON info.id = ins.businessNo
|
||||
/* Limit all-finished data to proposal workflow instances and discard other module instances. */
|
||||
INNER JOIN wf_process_define def ON def.id = ins.processDefineId AND def.name = 'JDHTA_NC'
|
||||
INNER JOIN proposal_info info ON info.id = ins.businessNo
|
||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
|
||||
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
|
||||
@@ -72,9 +76,12 @@ public class ProposalAllFinishedController {
|
||||
cnd.and("ins.state", "=", "20");
|
||||
|
||||
if(!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())){
|
||||
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
|
||||
// Non-admin users can only see proposal flows where they participated in at least one task.
|
||||
cnd.and(new Static("EXISTS (SELECT 1 FROM wf_process_task wt INNER JOIN wf_process_task_actor wta ON wta.processTaskId = wt.id WHERE wt.processInstanceId = ins.id AND wta.actorId = '" + SecurityUtil.getUserId() + "')"));
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
// 已办结列表仅允许页面展示字段参与排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "allFinished");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+19
-1
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.democratic.proposal.controller.transact;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
@@ -10,6 +11,7 @@ import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalCaseCheckService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -20,7 +22,9 @@ import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@@ -42,6 +46,8 @@ public class ProposalCaseCheckController {
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private ProposalCaseCheckService proposalCaseCheckService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/caseCheck/index.html")
|
||||
@@ -104,12 +110,24 @@ public class ProposalCaseCheckController {
|
||||
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 委员会审查列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "caseCheck")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.caseCheck")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("caseCheck", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.caseCheck")
|
||||
@ApiOperation("查询当前提案承办单位")
|
||||
|
||||
+4
-1
@@ -85,7 +85,10 @@ public class ProposalCommissionerController {
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.and("pco.id", approval ? "is not" : "is", null);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 委员查询包含统计列,未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "commissioner")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+18
-1
@@ -4,12 +4,14 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalCommitteeFilingService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -27,6 +29,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@@ -40,6 +43,8 @@ public class ProposalCommitteeFilingController {
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private ProposalCommitteeFilingService proposalCommitteeFilingService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/committeeFiling/index.html")
|
||||
@@ -102,12 +107,24 @@ public class ProposalCommitteeFilingController {
|
||||
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 立案审核列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "committeeFiling")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommitteeFilingService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.committeeFiling")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("committeeFiling", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.committeeFiling")
|
||||
@ApiOperation("并案审核")
|
||||
|
||||
+15
-1
@@ -6,6 +6,7 @@ import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
@@ -36,6 +37,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@@ -118,12 +120,24 @@ public class ProposalCommitteeFilingUnitController {
|
||||
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 确认承办单位列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "committeeFilingUnit")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommitteeFilingUnitService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.committeeFilingUnit")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("committeeFilingUnit", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.committeeFilingUnit")
|
||||
@ApiOperation("执行任务")
|
||||
|
||||
+7
-1
@@ -10,6 +10,7 @@ import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -33,6 +34,8 @@ public class ProposalControlController {
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/control/index.html")
|
||||
@@ -84,7 +87,10 @@ public class ProposalControlController {
|
||||
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 状态调整列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "control")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+15
-1
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum;
|
||||
@@ -32,6 +33,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -118,10 +120,22 @@ public class ProposalDelegationController {
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 团长审核列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "delegation")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.delegation")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("delegation", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-1
@@ -5,6 +5,7 @@ import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
@@ -26,6 +27,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@@ -107,12 +109,24 @@ public class ProposalFeedbackEvaluationController {
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 反馈评分列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "feedbackEvaluation")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.feedbackEvaluation")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("feedbackEvaluation", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.feedbackEvaluation")
|
||||
@ApiOperation("获取立案信息")
|
||||
|
||||
+2
-4
@@ -6,7 +6,6 @@ import cn.hutool.core.util.StrUtil;
|
||||
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.utils.PageUtil;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
@@ -99,10 +98,9 @@ public class ProposalInviteController {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
|
||||
// 页面排序仅使用邀请列表白名单,未指定有效字段时保留创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "invite")) {
|
||||
cnd.desc("info.createdAt");
|
||||
} else {
|
||||
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
}
|
||||
cnd.groupBy("t.id");
|
||||
sql.setCondition(cnd);
|
||||
|
||||
+26
@@ -62,6 +62,28 @@ import java.util.Objects;
|
||||
@Ok("json:full")
|
||||
@Api(tags = "提案管理-我的提案")
|
||||
public class ProposalMineController {
|
||||
/** 我的提案列表允许排序的页面字段与 SQL 字段映射。 */
|
||||
private static final Map<String, String> MINE_ORDER_COLUMNS = Map.of(
|
||||
"code", "info.code",
|
||||
"name", "info.name",
|
||||
"createTime", "info.createTime",
|
||||
"typeName", "type.name",
|
||||
"sourceName", "sd.name",
|
||||
"sessionName", "tcs.fullName",
|
||||
"taskName", "t.displayName",
|
||||
"instanceState", "ins.state"
|
||||
);
|
||||
|
||||
/** 邀请附议人列表允许排序的页面字段与 SQL 字段映射。 */
|
||||
private static final Map<String, String> SECONDER_ORDER_COLUMNS = Map.of(
|
||||
"loginName", "t1.loginName",
|
||||
"userName", "t1.userName",
|
||||
"sex", "t1.sex",
|
||||
"unitName", "t1.unitName",
|
||||
"unionName", "t1.unionName",
|
||||
"delegationName", "t2.name"
|
||||
);
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
@@ -128,6 +150,8 @@ public class ProposalMineController {
|
||||
cnd.and("info.createdBy", "=", SecurityUtil.getUserId());
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.andEX("info.sessionId", "=", sessionId);
|
||||
// 排序字段必须经过白名单映射,避免前端参数直接参与 SQL 排序。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, MINE_ORDER_COLUMNS);
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
@@ -255,6 +279,8 @@ public class ProposalMineController {
|
||||
seg.orLike("t1.userName", pageForm.getSearchKeyword());
|
||||
cnd.and(seg);
|
||||
}
|
||||
// 邀请列表与主列表使用独立白名单,确保关联表字段排序准确且安全。
|
||||
proposalCommonService.applySafePageOrder(cnd, pageForm, SECONDER_ORDER_COLUMNS);
|
||||
sql.setCondition(cnd);
|
||||
Pagination<ProposalInviteSeconderVO> pagination = proposalCommonService.listPageVO(pageForm, sql, ProposalInviteSeconderVO.class);
|
||||
return Result.success(pagination);
|
||||
|
||||
+19
-1
@@ -3,12 +3,14 @@ package com.budwk.app.zhgh.democratic.proposal.controller.transact;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -19,7 +21,9 @@ import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@@ -39,6 +43,8 @@ public class ProposalPreAuditController {
|
||||
|
||||
@Inject
|
||||
private BaseService baseService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/preAudit/index.html")
|
||||
@@ -102,9 +108,21 @@ public class ProposalPreAuditController {
|
||||
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 预审核列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "preAudit")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.preAudit")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("preAudit", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.vo.LabelValueVO;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
@@ -38,6 +39,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -135,12 +137,24 @@ public class ProposalSchoolLeaderApprovalController {
|
||||
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 分管领导审批列表未指定有效排序时保留任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "schoolLeaderApproval")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.schoolLeaderApproval")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("schoolLeaderApproval", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.schoolLeaderApproval")
|
||||
@ApiOperation("获取主办单位")
|
||||
|
||||
+19
-1
@@ -21,6 +21,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalSecondedService;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -41,6 +42,7 @@ import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 附议提案
|
||||
@@ -51,6 +53,17 @@ import java.util.List;
|
||||
@Ok("json:full")
|
||||
@Api(tags = "提案管理系统-提案附议")
|
||||
public class ProposalSecondedController {
|
||||
/** 提案附议列表允许排序的页面字段与 SQL 字段或查询别名映射。 */
|
||||
private static final Map<String, String> ORDER_COLUMNS = Map.of(
|
||||
"code", "info.code",
|
||||
"name", "info.name",
|
||||
"createUserName", "info.createUserName",
|
||||
"typeName", "type.name",
|
||||
"delegationName", "tcd.name",
|
||||
"taskActorName", "taskActorName",
|
||||
"curTaskName", "curTaskName",
|
||||
"instanceState", "ins.state"
|
||||
);
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@@ -59,6 +72,8 @@ public class ProposalSecondedController {
|
||||
@Inject
|
||||
private ProposalSecondedService proposalSecondedService;
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
@Inject
|
||||
private FlowCommonService flowCommonService;
|
||||
|
||||
@At("")
|
||||
@@ -141,7 +156,10 @@ public class ProposalSecondedController {
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 用户未指定有效排序字段时,继续沿用原有任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafePageOrder(cnd, pageForm, ORDER_COLUMNS)) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+2
@@ -96,6 +96,8 @@ public class ProposalUnSubmitQueryController {
|
||||
cnd.and("info.sessionId", "=", pageForm.getSessionId());
|
||||
}
|
||||
|
||||
// 未提交查询包含子查询统计列,统一通过页面独立白名单排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "unSubmitQuery");
|
||||
sql.setCondition(cnd);
|
||||
Pagination<NutMap> pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+2
@@ -137,6 +137,8 @@ public class ProposalUnderTakeReadController {
|
||||
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("info.id","task.id","JSON_EXTRACT( unit_data.DATA, '$.underTakeId' )");
|
||||
// 初步阅览列表的计算字段使用固定查询别名排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "unitRead");
|
||||
// cnd.groupBy("task.id");
|
||||
// cnd.groupBy("JSON_EXTRACT( unit_data.DATA, '$.underTakeId' ) ");
|
||||
sql.setCondition(cnd);
|
||||
|
||||
+26
-5
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
@@ -37,6 +38,7 @@ import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
@@ -80,8 +82,9 @@ public class ProposalUnderTakeReplyController {
|
||||
public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval, boolean transfer) {
|
||||
/*
|
||||
* 承办单位答复列表需要按“承办单位”维度展示。
|
||||
* 同一个任务可能挂了多个承办单位负责人,若只按 taskId 分组会把多条待审核合并成一条,
|
||||
* 因此这里优先使用任务参与人的承办单位信息拆行,历史数据缺失时再回退到流程变量。
|
||||
* 新流程生成的会签任务可能未写入 underTakeName、underTakeId、underTakeIsMaster,
|
||||
* 因此优先读取任务变量,变量缺失时再按“提案ID + 任务参与人单位ID”关联承办单位记录兜底,
|
||||
* 兼容已经生成的历史待办,同时保持原有任务变量的数据优先级。
|
||||
*/
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -107,8 +110,13 @@ public class ProposalUnderTakeReplyController {
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.taskName),'结束') curTaskKey,
|
||||
CASE WHEN t.taskState = 20 AND ( rt.id IS NULL OR rt.taskState = 10 ) THEN 1 ELSE 0 END AS canRevoke,
|
||||
t.variable->>'$.underTakeName' AS underTakeName,
|
||||
IF(JSON_EXTRACT(t.variable, '$.underTakeIsMaster') = true, 1, 0) AS underTakeIsMaster,
|
||||
COALESCE(NULLIF(t.variable->>'$.underTakeName', ''), replyUnit.unitName) AS underTakeName,
|
||||
COALESCE(NULLIF(t.variable->>'$.underTakeId', ''), replyUnit.unitId) AS underTakeId,
|
||||
CASE
|
||||
WHEN JSON_EXTRACT(t.variable, '$.underTakeIsMaster') IS NOT NULL
|
||||
THEN IF(JSON_EXTRACT(t.variable, '$.underTakeIsMaster') = true, 1, 0)
|
||||
ELSE IFNULL(replyUnit.isMaster, 0)
|
||||
END AS underTakeIsMaster,
|
||||
t.variable->>'$.tf_transferUserId' AS tf_transferUserId,
|
||||
transferUser.username AS transferUserName,
|
||||
GROUP_CONCAT(DISTINCT ta.actorName, '(', ta.actorAccount, ')' ) AS auditUser
|
||||
@@ -119,6 +127,7 @@ public class ProposalUnderTakeReplyController {
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id
|
||||
LEFT JOIN proposal_info info ON info.id = ins.businessNo
|
||||
LEFT JOIN proposal_reply_unit replyUnit ON replyUnit.proposalId = info.id AND replyUnit.unitId = ta.actorUnitId
|
||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||
LEFT JOIN proposal_merge mer ON mer.proposalId = info.id
|
||||
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
|
||||
@@ -141,7 +150,10 @@ public class ProposalUnderTakeReplyController {
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
cnd.desc("t.createdAt");
|
||||
// 承办答复列表未指定有效排序时,继续按任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "unitReply")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
|
||||
@@ -160,6 +172,15 @@ public class ProposalUnderTakeReplyController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("proposal.unitReply")
|
||||
@ApiOperation("导出列表")
|
||||
public void exportList(@Valid @Param("pageForm") ProposalSearchParam pageForm, boolean approval,
|
||||
@Param("tableColumns") ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
proposalCommonService.exportWorkflowList("unitReply", pageForm, approval, tableColumns, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 承办单位答复节点撤回需要基于整组当前节点判断。
|
||||
* 只有“当前流程正在办理的节点”和“这条列表记录所属节点”完全一致时,才允许撤回;
|
||||
|
||||
+4
-1
@@ -100,7 +100,10 @@ public class ProposalUndertakeSuggestionController {
|
||||
cnd.groupBy("t.id");
|
||||
|
||||
cnd.having(Cnd.where("count", approval ? ">" : "=", 0));
|
||||
cnd.desc("t.createdAt");
|
||||
// 承办意见列表未指定有效排序时,继续按任务创建时间倒序。
|
||||
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "suggestion")) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
|
||||
+2
@@ -80,6 +80,8 @@ public class ProposalViceDelegationController {
|
||||
cnd.and("info.delegationId", "in", proposalCommonService.getSelfManageDelegationIds());
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
// 副团长查阅列表仅允许页面已展示字段参与排序。
|
||||
proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "viceDelegation");
|
||||
cnd.groupBy("info.id");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
|
||||
+16
@@ -76,6 +76,19 @@ public class ProposalWriteController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前教代会届次可用于撰写提案的类型。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @return VO 列表,每项包含保存使用的 id 和页面展示使用的 name
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("proposal.write")
|
||||
@ApiOperation("查询届次可用提案类型")
|
||||
public Result listSessionProposalTypes(@Valid String sessionId) {
|
||||
return Result.success(proposalWriteService.listSessionProposalTypes(sessionId));
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("proposal.write")
|
||||
@@ -84,6 +97,7 @@ public class ProposalWriteController {
|
||||
@SLog(tag = "提案管理系统-我的提案", msg = "保存提案")
|
||||
public Result save(@Param("info") ProposalInfo proposalInfo) {
|
||||
proposalWriteService.checkCurrentUserDelegate(proposalInfo.getSessionId());
|
||||
proposalWriteService.validateProposalType(proposalInfo.getSessionId(), proposalInfo.getTypeId());
|
||||
if (StrUtil.isBlank(proposalInfo.getCode())) {
|
||||
proposalInfo.setCode(proposalWriteService.generateProposalCode(proposalInfo.getSessionId(), proposalInfo.getDelegationId()));
|
||||
}
|
||||
@@ -100,6 +114,7 @@ public class ProposalWriteController {
|
||||
@SLog(tag = "提案管理系统-我的提案", msg = "提交提案")
|
||||
public Result submit(@Param("info") ProposalInfo proposalInfo) {
|
||||
proposalWriteService.checkCurrentUserDelegate(proposalInfo.getSessionId());
|
||||
proposalWriteService.validateProposalType(proposalInfo.getSessionId(), proposalInfo.getTypeId());
|
||||
if (StrUtil.isBlank(proposalInfo.getCode())) {
|
||||
proposalInfo.setCode(proposalWriteService.generateProposalCode(proposalInfo.getSessionId(), proposalInfo.getDelegationId()));
|
||||
}
|
||||
@@ -130,6 +145,7 @@ public class ProposalWriteController {
|
||||
@ApiOperation("重新提交提案")
|
||||
@SLog(tag = "提案管理系统-我的提案", msg = "重新提交提案")
|
||||
public Result submitAgain(@Param("info") ProposalInfo proposalInfo, @Param("taskId") Long taskId) {
|
||||
proposalWriteService.validateProposalType(proposalInfo.getSessionId(), proposalInfo.getTypeId());
|
||||
dao.insertOrUpdate(proposalInfo);
|
||||
|
||||
Dict dict = Dict.create();
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.controller.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 撰写提案时当前教代会届次允许选择的提案类型。
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ApiModel("届次可用提案类型")
|
||||
public class ProposalWriteTypeVO {
|
||||
|
||||
@ApiModelProperty("提案类型ID,保存提案时写入 proposal_info.typeId")
|
||||
private Integer id;
|
||||
|
||||
@ApiModelProperty("提案类型名称,来源于当前教代会届次的提案类型配置")
|
||||
private String name;
|
||||
}
|
||||
+21
-6
@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.engine.AssignmentHandler;
|
||||
import com.budwk.app.flow.engine.core.Execution;
|
||||
import com.budwk.app.flow.engine.core.ServiceContext;
|
||||
@@ -36,12 +37,7 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
|
||||
SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class);
|
||||
String proposalId = execution.getProcessInstance().getBusinessNo();
|
||||
|
||||
// 当前节点需要同时处理主办和协办负责人。
|
||||
// 这里不能按单位ID去重,必须保留每个承办单位对应的一条分派记录,
|
||||
// 否则同一用户负责多个单位时,会被错误合并成更少的待办。
|
||||
List<String> unitIds = new ArrayList<>();
|
||||
unitIds.addAll(resolveMasterUnitIds(execution, dao, proposalId));
|
||||
unitIds.addAll(resolveSlaveUnitIds(execution, dao, proposalId));
|
||||
List<String> unitIds = resolveUnitIds(model, execution, dao, proposalId);
|
||||
|
||||
if (unitIds.isEmpty()) {
|
||||
throw new BaseException("提案没有配置承办单位");
|
||||
@@ -76,6 +72,25 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
|
||||
return assignee;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定当前答复节点需要分派的单位范围。
|
||||
* 二次答复页面已明确提交 tf_secondReplyUnitIds 时,该字段就是唯一分派范围;
|
||||
* 未选择的主办或协办单位不能再从历史承办单位记录中补回。
|
||||
* 旧流程没有该字段时,继续按主办、协办配置回退,保证历史流程可以正常运行。
|
||||
*/
|
||||
private List<String> resolveUnitIds(TaskModel model, Execution execution, Dao dao, String proposalId) {
|
||||
String secondReplyUnitIdsKey = FlowConst.TASK_FORM_DATA_PREFIX + "secondReplyUnitIds";
|
||||
if ("two_unit_reply".equals(model.getName()) && execution.getArgs().containsKey(secondReplyUnitIdsKey)) {
|
||||
return getStringList(execution.getArgs(), secondReplyUnitIdsKey);
|
||||
}
|
||||
|
||||
// 初次答复需要同时处理主办和协办负责人,旧二次答复流程也沿用该兼容逻辑。
|
||||
List<String> unitIds = new ArrayList<>();
|
||||
unitIds.addAll(resolveMasterUnitIds(execution, dao, proposalId));
|
||||
unitIds.addAll(resolveSlaveUnitIds(execution, dao, proposalId));
|
||||
return unitIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先从当前流程变量读取主办单位,读不到时回落到承办单位记录表,兼容重新流转场景。
|
||||
*/
|
||||
|
||||
@@ -33,6 +33,11 @@ public class ProposalInfo extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String code;
|
||||
|
||||
@Column
|
||||
@Comment("立案编号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String caseFilingCode;
|
||||
|
||||
@Column
|
||||
@Comment("提案名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
|
||||
@@ -25,7 +25,7 @@ public class ProposalType extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Comment("名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 10)
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String name;
|
||||
|
||||
@Column
|
||||
|
||||
+4
@@ -21,6 +21,8 @@ public class ProposalQueryComprehensiveParam extends PageForm {
|
||||
private String code;
|
||||
@ApiModelProperty(name = "教代会ID")
|
||||
private String sessionId;
|
||||
@ApiModelProperty(name = "search session id")
|
||||
private String searchSessionId;
|
||||
@ApiModelProperty(name = "教代会ID")
|
||||
private String[] sessionIds;
|
||||
@ApiModelProperty(name = "代表团ID")
|
||||
@@ -79,6 +81,8 @@ public class ProposalQueryComprehensiveParam extends PageForm {
|
||||
} else {
|
||||
// cnd.andEX("info.sessionId", "=", searchParam.getSessionId());
|
||||
}
|
||||
// Isolated single-session filter for pages that should not reuse sessionId semantics.
|
||||
cnd.andEX("info.sessionId", "=", searchParam.getSearchSessionId());
|
||||
|
||||
//提案名称
|
||||
if (StrUtil.isNotBlank(searchParam.getName())) {
|
||||
|
||||
+8
@@ -34,6 +34,14 @@ public interface ProposalExportService extends BaseService<ProposalInfo> {
|
||||
*/
|
||||
void exportSummaryAsExcel(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 按办理单位导出提案承办单位表压缩包。
|
||||
*
|
||||
* @param pageForm 综合导出页面查询参数;sessionId 应传当前教代会届次ID,其他条件沿用页面查询条件
|
||||
* @param response HTTP 响应,方法直接输出 ZIP;ZIP 中每个有提案的单位对应一个 XSSF Excel
|
||||
*/
|
||||
void exportUndertakeUnitTablesAsZip(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 导出提案立案汇总表excel
|
||||
*
|
||||
|
||||
+17
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.democratic.proposal.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.zhgh.democratic.proposal.controller.vo.ProposalWriteTypeVO;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
|
||||
import javax.validation.Valid;
|
||||
@@ -10,6 +11,22 @@ import java.util.Map;
|
||||
|
||||
public interface ProposalWriteService extends BaseService<ProposalInfo> {
|
||||
|
||||
/**
|
||||
* 查询指定教代会届次允许选择的提案类型。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @return 提案类型 VO 列表,id 用于保存,name 用于页面展示
|
||||
*/
|
||||
List<ProposalWriteTypeVO> listSessionProposalTypes(String sessionId);
|
||||
|
||||
/**
|
||||
* 校验所选提案类型是否属于当前教代会届次。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @param typeId 提案类型ID
|
||||
*/
|
||||
void validateProposalType(String sessionId, Integer typeId);
|
||||
|
||||
List<Sys_dict> listSource(String sessionId);
|
||||
|
||||
/**
|
||||
|
||||
+46
@@ -1,17 +1,51 @@
|
||||
package com.budwk.app.zhgh.democratic.proposal.service.common;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public interface ProposalCommonService extends BaseService<ProposalInfo> {
|
||||
|
||||
/**
|
||||
* 按页面允许的字段白名单添加排序条件,避免前端字段直接进入SQL。
|
||||
*
|
||||
* @param cnd 查询条件
|
||||
* @param pageForm 分页及排序参数
|
||||
* @param allowedOrderColumns 前端字段与数据库字段或查询别名的映射
|
||||
* @return 是否成功添加安全排序条件
|
||||
*/
|
||||
boolean applySafePageOrder(Cnd cnd, PageForm pageForm, Map<String, String> allowedOrderColumns);
|
||||
|
||||
/**
|
||||
* 按提案列表页面注册的独立字段白名单添加排序条件。
|
||||
*
|
||||
* @param cnd 查询条件
|
||||
* @param pageForm 分页及排序参数
|
||||
* @param pageCode 提案列表页面代码
|
||||
* @return 是否成功添加安全排序条件
|
||||
*/
|
||||
boolean applySafeProposalListOrder(Cnd cnd, PageForm pageForm, String pageCode);
|
||||
|
||||
/**
|
||||
* 按白名单字段对已汇总的统计结果进行后端排序。
|
||||
*
|
||||
* @param rows 统计结果行
|
||||
* @param pageForm 排序字段及方向参数
|
||||
* @param allowedOrderColumns 允许排序的统计字段
|
||||
*/
|
||||
void sortStatisticsRows(List<NutMap> rows, PageForm pageForm, Set<String> allowedOrderColumns);
|
||||
|
||||
/**
|
||||
* 查询提案信息
|
||||
*/
|
||||
@@ -105,6 +139,18 @@ public interface ProposalCommonService extends BaseService<ProposalInfo> {
|
||||
*/
|
||||
void exportYearReport(String sessionId, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 按办理页面当前筛选条件和可见列导出提案列表。
|
||||
*
|
||||
* @param pageCode 页面代码,用于固定查询范围、数据权限和排序白名单
|
||||
* @param pageForm 页面筛选条件
|
||||
* @param approval 审核状态筛选条件
|
||||
* @param tableColumns 当前页面可见列
|
||||
* @param response HTTP 响应
|
||||
*/
|
||||
void exportWorkflowList(String pageCode, ProposalSearchParam pageForm, boolean approval,
|
||||
ExportTableColumns[] tableColumns, HttpServletResponse response);
|
||||
|
||||
|
||||
/**
|
||||
* 合并提案
|
||||
|
||||
+469
-5
@@ -7,16 +7,24 @@ import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
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 com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
|
||||
import com.budwk.app.flow.constant.FlowConst;
|
||||
import com.budwk.app.flow.engine.FlowEngine;
|
||||
import com.budwk.app.flow.engine.util.FlowUtil;
|
||||
import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.flow.vo.ProcessTaskVO;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
@@ -26,11 +34,13 @@ import com.budwk.app.sys.models.Sys_user_role;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.web.commons.base.Globals;
|
||||
import com.budwk.app.zhgh.democratic.proposal.constants.ProposalState;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.*;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalWriteService;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.Teacher_congress_delegate;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegate.service.TeacherCongressDelegateService;
|
||||
@@ -53,12 +63,15 @@ import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.Collator;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -66,6 +79,12 @@ import java.util.stream.Collectors;
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalCommonService {
|
||||
|
||||
/** PC端提案列表页面的排序字段白名单,页面代码由对应Controller固定传入。 */
|
||||
private static final Map<String, Map<String, String>> PROPOSAL_LIST_ORDER_COLUMNS = createProposalListOrderColumns();
|
||||
|
||||
/** 办理页面允许导出的列表字段,避免请求参数携带非页面字段。 */
|
||||
private static final Map<String, Set<String>> WORKFLOW_EXPORT_COLUMNS = createWorkflowExportColumns();
|
||||
|
||||
//找出富文本里面上传的图片
|
||||
static String IMG_SRC_REGEX = "<img\\s+[^>]*src\\s*=\\s*([\"'])(/platform/sys/file/download\\?id=.*?)\\1[^>]*>";
|
||||
|
||||
@@ -88,6 +107,391 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
super(dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用业务页面提供的字段白名单构建排序条件,非法字段或非法排序方向将被忽略。
|
||||
*
|
||||
* @param cnd 查询条件
|
||||
* @param pageForm 分页及排序参数
|
||||
* @param allowedOrderColumns 前端字段与数据库字段或查询别名的映射
|
||||
* @return 是否成功添加安全排序条件
|
||||
*/
|
||||
@Override
|
||||
public boolean applySafePageOrder(Cnd cnd, PageForm pageForm, Map<String, String> allowedOrderColumns) {
|
||||
if (cnd == null || pageForm == null || allowedOrderColumns == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 仅允许白名单中的字段参与排序,防止构造任意SQL排序字段。
|
||||
String orderColumn = allowedOrderColumns.get(pageForm.getPageOrderName());
|
||||
String orderBy = PageUtil.getOrder(pageForm.getPageOrderBy());
|
||||
if (StrUtil.isBlank(orderColumn) || StrUtil.isBlank(orderBy)) {
|
||||
return false;
|
||||
}
|
||||
cnd.orderBy(orderColumn, orderBy);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据页面代码读取独立白名单并应用排序,未注册页面不会接受前端排序字段。
|
||||
*
|
||||
* @param cnd 查询条件
|
||||
* @param pageForm 分页及排序参数
|
||||
* @param pageCode 提案列表页面代码
|
||||
* @return 是否成功添加安全排序条件
|
||||
*/
|
||||
@Override
|
||||
public boolean applySafeProposalListOrder(Cnd cnd, PageForm pageForm, String pageCode) {
|
||||
if (StrUtil.isBlank(pageCode)) {
|
||||
return false;
|
||||
}
|
||||
return applySafePageOrder(cnd, pageForm, PROPOSAL_LIST_ORDER_COLUMNS.get(pageCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建各列表页独立排序白名单,显示字段与真实SQL字段或固定查询别名一一对应。
|
||||
*
|
||||
* @return 不可变的页面排序白名单
|
||||
*/
|
||||
private static Map<String, Map<String, String>> createProposalListOrderColumns() {
|
||||
Map<String, Map<String, String>> pages = new HashMap<>();
|
||||
pages.put("invite", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("viceDelegation", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("unSubmitQuery", orderColumns("proposalCode", "info.code", "proposalName", "info.name", "username", "vu.username", "mobile", "vu.mobile", "unitName", "vu.unitName", "delegationName", "tcd.name", "typeName", "pt.name", "mannerName", "manner.name", "secondedNum", "secondedNum", "secondedAgreeNum", "secondedAgreeNum", "delegationAudit", "delegationAudit"));
|
||||
pages.put("unitReply", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "caseFilingResult", "info.caseFilingResult", "merge", "merge", "underTakeName", "underTakeName", "underTakeIsMaster", "underTakeIsMaster", "curTaskName", "curTaskName", "auditUser", "auditUser", "instanceState", "ins.state", "transfer", "transferUserName"));
|
||||
pages.put("unitRead", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "isConsolidation", "isConsolidation", "isMasterUnderTake", "isMasterUnderTake", "underTakeName", "underTakeName", "processInstanceNodeName", "inst.processInstanceNodeName"));
|
||||
pages.put("suggestion", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "suggestUnits", "info.suggestUnits", "count", "count", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("commissioner", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "CONFIRM_FILING_COUNT", "CONFIRM_FILING_COUNT", "SUGGESTION_COUNT", "SUGGESTION_COUNT", "NOT_COUNT", "NOT_COUNT", "taskName", "taskName", "instanceState", "ins.state"));
|
||||
pages.put("feedbackEvaluation", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "merge", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("delegation", standardOrderColumns());
|
||||
pages.put("control", standardOrderColumns());
|
||||
pages.put("schoolLeaderApproval", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "undertakeUnits", "masterUnitName", "merge", "merge", "curTaskName", "curTaskName", "auditUser", "auditUser", "instanceState", "ins.state"));
|
||||
pages.put("committeeFilingUnit", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "caseFilingResult", "info.caseFilingResult", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("allFinished", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "instanceState", "ins.state"));
|
||||
pages.put("preAudit", standardOrderColumns());
|
||||
pages.put("committeeFiling", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "sessionName", "tcs.fullName", "caseFilingResult", "info.caseFilingResult", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("caseCheck", orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state", "finishTime", "t.finishTime"));
|
||||
pages.put("exportComprehensive", orderColumns("code", "info.code", "caseFilingCode", "info.caseFilingCode", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "caseFilingType", "info.caseFilingType", "merge", "merge", "brief", "info.brief", "measures", "info.measures", "undertakeUnits", "masterUnitName", "curTaskName", "curTaskName", "instanceState", "ins.state"));
|
||||
pages.put("exportSingleCustom", standardOrderColumns());
|
||||
pages.put("configUnit", orderColumns("name", "t1.name", "code", "t1.code", "unitLeader", "u2.username"));
|
||||
pages.put("configType", orderColumns("name", "name", "code", "code"));
|
||||
pages.put("seniorBasic", seniorOrderColumns());
|
||||
pages.put("expediting", orderColumns("code", "info.code", "name", "info.name", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "curTaskName", "curTaskName", "underTakeName", "underTakeName", "taskName", "t.displayName", "actorName", "ta.actorName", "instanceState", "ins.state"));
|
||||
pages.put("seniorDelete", seniorOrderColumns());
|
||||
pages.put("seniorFeedback", mergeOrderColumns(seniorOrderColumns(), orderColumns("tf_feedback", "tf_feedback")));
|
||||
pages.put("seniorType", seniorOrderColumns());
|
||||
return Collections.unmodifiableMap(pages);
|
||||
}
|
||||
|
||||
/** 创建常规流程列表共用的字段映射。 */
|
||||
private static Map<String, String> standardOrderColumns() {
|
||||
return orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "sessionName", "tcs.fullName", "delegationName", "tcd.name", "curTaskName", "curTaskName", "instanceState", "ins.state");
|
||||
}
|
||||
|
||||
/** 创建高级管理列表共用的字段映射。 */
|
||||
private static Map<String, String> seniorOrderColumns() {
|
||||
return orderColumns("code", "info.code", "name", "info.name", "createUserName", "info.createUserName", "typeName", "type.name", "delegationName", "tcd.name", "caseFilingResult", "info.caseFilingResult", "isConsolidation", "merge", "curTaskName", "curTaskName", "instanceState", "ins.state");
|
||||
}
|
||||
|
||||
/**
|
||||
* 按前端字段、SQL字段成对创建不可变映射。
|
||||
*
|
||||
* @param mappings 交替排列的前端字段和SQL字段
|
||||
* @return 不可变字段映射
|
||||
*/
|
||||
private static Map<String, String> orderColumns(String... mappings) {
|
||||
if (mappings == null || mappings.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("排序字段映射必须成对配置");
|
||||
}
|
||||
Map<String, String> columns = new LinkedHashMap<>();
|
||||
for (int i = 0; i < mappings.length; i += 2) {
|
||||
columns.put(mappings[i], mappings[i + 1]);
|
||||
}
|
||||
return Collections.unmodifiableMap(columns);
|
||||
}
|
||||
|
||||
/** 合并基础字段和页面扩展字段,并返回不可变映射。 */
|
||||
private static Map<String, String> mergeOrderColumns(Map<String, String> base, Map<String, String> extension) {
|
||||
Map<String, String> columns = new LinkedHashMap<>(base);
|
||||
columns.putAll(extension);
|
||||
return Collections.unmodifiableMap(columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按办理页面当前筛选条件和可见列导出列表,查询范围与页面分页列表保持一致。
|
||||
*
|
||||
* @param pageCode 页面代码
|
||||
* @param pageForm 页面筛选条件
|
||||
* @param approval 审核状态
|
||||
* @param tableColumns 页面可见列
|
||||
* @param response HTTP 响应
|
||||
*/
|
||||
@Override
|
||||
public void exportWorkflowList(String pageCode, ProposalSearchParam pageForm, boolean approval,
|
||||
ExportTableColumns[] tableColumns, HttpServletResponse response) {
|
||||
List<ExportTableColumns> exportColumns = getWorkflowExportColumns(pageCode, tableColumns);
|
||||
if (exportColumns.isEmpty()) {
|
||||
throw Lang.makeThrow("请选择至少一个列表字段后再导出");
|
||||
}
|
||||
|
||||
Sql sql = buildWorkflowExportSql(pageCode, pageForm, approval);
|
||||
List<NutMap> rows = listMap(sql);
|
||||
convertWorkflowExportValues(rows, pageCode);
|
||||
|
||||
List<ExcelExportEntity> excelColumns = new ArrayList<>();
|
||||
for (ExportTableColumns column : exportColumns) {
|
||||
excelColumns.add(new ExcelExportEntity(column.getLabel(), column.getProp(), 20));
|
||||
}
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
exportParams.setTitle("提案列表");
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelColumns, rows);
|
||||
CommonDownloadUtil.download("提案列表.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/** 根据页面代码构造导出查询,保证数据权限、筛选条件及排序规则与列表页一致。 */
|
||||
private Sql buildWorkflowExportSql(String pageCode, ProposalSearchParam pageForm, boolean approval) {
|
||||
boolean isUnitReply = "unitReply".equals(pageCode);
|
||||
boolean isSchoolLeaderApproval = "schoolLeaderApproval".equals(pageCode);
|
||||
boolean requiresUnionFilter = Set.of("preAudit", "committeeFiling", "committeeFilingUnit",
|
||||
"schoolLeaderApproval", "caseCheck").contains(pageCode);
|
||||
String taskName = getWorkflowTaskName(pageCode);
|
||||
|
||||
String schoolLeaderColumns = isSchoolLeaderApproval ? """
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
|
||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames,
|
||||
""" : "";
|
||||
String auditUserColumn = (isSchoolLeaderApproval || isUnitReply)
|
||||
? "GROUP_CONCAT(DISTINCT ta.actorName, '(', ta.actorAccount, ')') AS auditUser,"
|
||||
: "";
|
||||
String unitReplyColumns = isUnitReply ? """
|
||||
COALESCE(NULLIF(t.variable->>'$.underTakeName', ''), replyUnit.unitName) AS underTakeName,
|
||||
CASE
|
||||
WHEN JSON_EXTRACT(t.variable, '$.underTakeIsMaster') IS NOT NULL
|
||||
THEN IF(JSON_EXTRACT(t.variable, '$.underTakeIsMaster') = true, 1, 0)
|
||||
ELSE IFNULL(replyUnit.isMaster, 0)
|
||||
END AS underTakeIsMaster,
|
||||
""" : "";
|
||||
String unionJoin = requiresUnionFilter ? "LEFT JOIN vw_user vu ON vu.id = info.createUserId" : "";
|
||||
String schoolLeaderJoin = isSchoolLeaderApproval ? "LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id" : "";
|
||||
String unitReplyJoin = isUnitReply
|
||||
? "LEFT JOIN proposal_reply_unit replyUnit ON replyUnit.proposalId = info.id AND replyUnit.unitId = ta.actorUnitId\n LEFT JOIN sys_user transferUser ON transferUser.id = t.variable->>'$.tf_transferUserId'"
|
||||
: "";
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
info.*,
|
||||
type.name AS typeName,
|
||||
IF(mer.proposalId IS NOT NULL, 1, 0) AS merge,
|
||||
tcs.fullName AS sessionName,
|
||||
tcd.`name` AS delegationName,
|
||||
ins.state AS instanceState,
|
||||
t.finishTime,
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName
|
||||
FROM wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN proposal_info info ON info.id = ins.businessNo
|
||||
LEFT JOIN proposal_type type ON type.id = info.typeId
|
||||
LEFT JOIN proposal_merge mer ON mer.proposalId = info.id
|
||||
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
|
||||
LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
$condition
|
||||
""".formatted(schoolLeaderColumns, auditUserColumn, unitReplyColumns, unionJoin, schoolLeaderJoin, unitReplyJoin));
|
||||
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (isUnitReply) {
|
||||
cnd.and("t.taskName", "in", List.of("unit_reply", "two_unit_reply", "opinion_unit_reply"));
|
||||
if (!"superadmin".equals(SecurityUtil.getUserLoginname())) {
|
||||
cnd.and("ta.actorId", "=", SecurityUtil.getUserId());
|
||||
}
|
||||
} else {
|
||||
cnd.and("t.taskName", "=", taskName);
|
||||
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
|
||||
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
|
||||
}
|
||||
}
|
||||
if (approval) {
|
||||
cnd.and("t.taskState", "in", isUnitReply
|
||||
? List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.TRANSFER.getCode())
|
||||
: List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
} else {
|
||||
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
if (requiresUnionFilter) {
|
||||
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
|
||||
}
|
||||
ProposalSearchParam.buildSearch(cnd, pageForm);
|
||||
cnd.groupBy("t.id");
|
||||
if (!applySafeProposalListOrder(cnd, pageForm, pageCode)) {
|
||||
cnd.desc("t.createdAt");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
}
|
||||
|
||||
/** 获取页面对应的流程任务编码,页面代码不匹配时拒绝导出。 */
|
||||
private String getWorkflowTaskName(String pageCode) {
|
||||
Map<String, String> taskNames = Map.of(
|
||||
"delegation", "delegation",
|
||||
"preAudit", "preAudit",
|
||||
"committeeFiling", "committee",
|
||||
"committeeFilingUnit", "committeeFilingUnit",
|
||||
"schoolLeaderApproval", "schoolLeader",
|
||||
"feedbackEvaluation", "feedback",
|
||||
"caseCheck", "caseCheck"
|
||||
);
|
||||
String taskName = taskNames.get(pageCode);
|
||||
if (StrUtil.isBlank(taskName) && !"unitReply".equals(pageCode)) {
|
||||
throw Lang.makeThrow("不支持的提案列表导出页面");
|
||||
}
|
||||
return taskName;
|
||||
}
|
||||
|
||||
/** 将字典、枚举及组合字段转换为与列表页面相同的显示文本。 */
|
||||
private void convertWorkflowExportValues(List<NutMap> rows, String pageCode) {
|
||||
Map<String, String> caseFilingResultMap = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT")
|
||||
.stream().collect(Collectors.toMap(Sys_dict::getCode, Sys_dict::getName));
|
||||
Map<Integer, String> processStateMap = Arrays.stream(ProcessInstanceStateEnum.values())
|
||||
.collect(Collectors.toMap(ProcessInstanceStateEnum::getCode, ProcessInstanceStateEnum::getMessage));
|
||||
for (NutMap row : rows) {
|
||||
if ("schoolLeaderApproval".equals(pageCode)) {
|
||||
row.put("undertakeUnits", formatUndertakeUnits(row));
|
||||
}
|
||||
// 所有含“是否并案”列的办理页面统一导出“是/否”,避免未并案记录出现空白。
|
||||
row.put("merge", row.getInt("merge") == 1 ? "是" : "否");
|
||||
if ("unitReply".equals(pageCode)) {
|
||||
row.put("underTakeIsMaster", row.getInt("underTakeIsMaster") == 1 ? "主办" : "协办");
|
||||
}
|
||||
row.put("caseFilingResult", caseFilingResultMap.getOrDefault(row.getString("caseFilingResult"), row.getString("caseFilingResult")));
|
||||
Integer instanceState = row.getInt("instanceState");
|
||||
row.put("instanceState", processStateMap.getOrDefault(instanceState, row.getString("instanceState")));
|
||||
}
|
||||
}
|
||||
|
||||
/** 校领导审批页按页面展示规则拼接承办单位。 */
|
||||
private String formatUndertakeUnits(NutMap row) {
|
||||
String masterUnitNames = StrUtil.nullToEmpty(row.getString("masterUnitName"));
|
||||
String slaveUnitNames = StrUtil.nullToEmpty(row.getString("slaveUnitNames"));
|
||||
if ("SUGGESTION".equals(row.getString("caseFilingResult"))) {
|
||||
return StrUtil.isNotBlank(masterUnitNames) ? masterUnitNames : slaveUnitNames;
|
||||
}
|
||||
if ("CONFIRM_FILING".equals(row.getString("caseFilingResult"))) {
|
||||
List<String> unitNames = new ArrayList<>();
|
||||
if (StrUtil.isNotBlank(masterUnitNames)) {
|
||||
unitNames.add("主办:" + masterUnitNames);
|
||||
}
|
||||
if (StrUtil.isNotBlank(slaveUnitNames)) {
|
||||
unitNames.add("协办:" + slaveUnitNames);
|
||||
}
|
||||
return StrUtil.join(";", unitNames);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/** 根据页面列定义过滤导出字段,确保导出范围只包含列表可展示字段。 */
|
||||
private List<ExportTableColumns> getWorkflowExportColumns(String pageCode, ExportTableColumns[] tableColumns) {
|
||||
Set<String> allowedColumns = WORKFLOW_EXPORT_COLUMNS.get(pageCode);
|
||||
if (allowedColumns == null || tableColumns == null) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(tableColumns)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(column -> StrUtil.isNotBlank(column.getLabel()) && allowedColumns.contains(column.getProp()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** 初始化各办理页面可导出的列表字段白名单。 */
|
||||
private static Map<String, Set<String>> createWorkflowExportColumns() {
|
||||
Set<String> commonColumns = Set.of("code", "caseFilingCode", "name", "createUserName", "typeName",
|
||||
"sessionName", "delegationName", "curTaskName", "instanceState");
|
||||
Map<String, Set<String>> columns = new HashMap<>();
|
||||
columns.put("delegation", commonColumns);
|
||||
columns.put("preAudit", commonColumns);
|
||||
columns.put("caseCheck", Set.of("code", "caseFilingCode", "name", "createUserName", "typeName",
|
||||
"sessionName", "delegationName", "curTaskName", "instanceState", "finishTime"));
|
||||
columns.put("committeeFiling", Set.of("code", "caseFilingCode", "name", "typeName", "sessionName",
|
||||
"caseFilingResult", "delegationName", "curTaskName", "instanceState"));
|
||||
columns.put("committeeFilingUnit", Set.of("code", "caseFilingCode", "name", "typeName", "sessionName",
|
||||
"caseFilingResult", "delegationName", "curTaskName", "instanceState"));
|
||||
columns.put("schoolLeaderApproval", Set.of("code", "caseFilingCode", "name", "typeName", "undertakeUnits",
|
||||
"merge", "curTaskName", "auditUser", "instanceState"));
|
||||
columns.put("unitReply", Set.of("code", "caseFilingCode", "name", "typeName", "caseFilingResult",
|
||||
"merge", "underTakeName", "underTakeIsMaster", "curTaskName", "auditUser", "instanceState"));
|
||||
columns.put("feedbackEvaluation", Set.of("code", "caseFilingCode", "name", "typeName", "merge",
|
||||
"curTaskName", "instanceState"));
|
||||
return Collections.unmodifiableMap(columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按白名单字段对统计结果执行后端排序,数字按数值比较,文本按中文区域规则比较。
|
||||
*
|
||||
* @param rows 统计结果行
|
||||
* @param pageForm 排序字段及方向参数
|
||||
* @param allowedOrderColumns 允许排序的统计字段
|
||||
*/
|
||||
@Override
|
||||
public void sortStatisticsRows(List<NutMap> rows, PageForm pageForm, Set<String> allowedOrderColumns) {
|
||||
if (rows == null || pageForm == null || allowedOrderColumns == null) {
|
||||
return;
|
||||
}
|
||||
String orderName = pageForm.getPageOrderName();
|
||||
String orderBy = pageForm.getPageOrderBy();
|
||||
boolean ascending = "ascending".equals(orderBy);
|
||||
boolean descending = "descending".equals(orderBy);
|
||||
if (StrUtil.isBlank(orderName) || !allowedOrderColumns.contains(orderName) || (!ascending && !descending)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 空值始终排在末尾,降序只反转有效值的比较结果。
|
||||
rows.sort((leftRow, rightRow) -> {
|
||||
Object leftValue = leftRow.get(orderName);
|
||||
Object rightValue = rightRow.get(orderName);
|
||||
if (leftValue == null && rightValue == null) {
|
||||
return 0;
|
||||
}
|
||||
if (leftValue == null) {
|
||||
return 1;
|
||||
}
|
||||
if (rightValue == null) {
|
||||
return -1;
|
||||
}
|
||||
int result = compareStatisticsValue(leftValue, rightValue);
|
||||
return descending ? -result : result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较统计字段值,兼容不同整数类型以及中文文本。
|
||||
*
|
||||
* @param leftValue 左侧字段值
|
||||
* @param rightValue 右侧字段值
|
||||
* @return 标准比较结果
|
||||
*/
|
||||
private int compareStatisticsValue(Object leftValue, Object rightValue) {
|
||||
if (leftValue instanceof Number && rightValue instanceof Number) {
|
||||
BigDecimal leftNumber = new BigDecimal(leftValue.toString());
|
||||
BigDecimal rightNumber = new BigDecimal(rightValue.toString());
|
||||
return leftNumber.compareTo(rightNumber);
|
||||
}
|
||||
String leftText = String.valueOf(leftValue);
|
||||
String rightText = String.valueOf(rightValue);
|
||||
String leftPercent = StrUtil.removeSuffix(leftText, "%");
|
||||
String rightPercent = StrUtil.removeSuffix(rightText, "%");
|
||||
// 百分比展示值按实际数值比较,避免字符串顺序导致10%排在9%之前。
|
||||
if (leftText.endsWith("%") && rightText.endsWith("%")
|
||||
&& NumberUtil.isNumber(leftPercent) && NumberUtil.isNumber(rightPercent)) {
|
||||
return new BigDecimal(leftPercent).compareTo(new BigDecimal(rightPercent));
|
||||
}
|
||||
return Collator.getInstance(Locale.CHINA).compare(leftText, rightText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NutMap info(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
@@ -228,8 +632,11 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
}
|
||||
|
||||
// 按任务节点分组
|
||||
Map<String, List<ProcessTaskVO>> taskGroups = doneTaskVos.stream().collect(Collectors.groupingBy(ProcessTaskVO::getDisplayName));
|
||||
Map<String, List<ProcessTaskVO>> taskGroups = doneTaskVos.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.groupingBy(ProcessTaskVO::getDisplayName));
|
||||
docData.putAll(taskGroups);
|
||||
putProposalExportTaskAliases(id, taskGroups, docData);
|
||||
|
||||
// 提案附议
|
||||
List<NutMap> secondInfos = new ArrayList<>();
|
||||
@@ -300,22 +707,79 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
Configure config = Configure.builder()
|
||||
.bind("secondedList", policy)
|
||||
.bind("seconders", policy)
|
||||
.bind("提案附议", policy)
|
||||
.bind("提案委委员审议", policy)
|
||||
.bind("brief", htmlRenderPolicy)
|
||||
.bind("measures", htmlRenderPolicy)
|
||||
.bind("taskFormData.tf_opinion", htmlRenderPolicy)
|
||||
.build();
|
||||
|
||||
Map<String, Object> renderDocCellDataMap = MapUtil.of("proposal", docCellDataMap);
|
||||
renderDocCellDataMap.put("secondedList", secondInfos);
|
||||
renderDocCellDataMap.put("delegationAuditList", delegationAuditList);
|
||||
// 数据库模板使用平铺占位符(例如 {{code}}、{{sessionName}}),不能将基础字段嵌套到 proposal 节点。
|
||||
// 流程节点分组已写入 docData,附议人与代表团审核列表也必须写入同一上下文,才能被模板的循环标签识别。
|
||||
docData.put("secondedList", secondInfos);
|
||||
docData.put("delegationAuditList", delegationAuditList);
|
||||
|
||||
try {
|
||||
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate("proposal"), config).render(renderDocCellDataMap).writeAndClose(byteArrayOutputStream);
|
||||
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate("proposal"), config).render(docData).writeAndClose(byteArrayOutputStream);
|
||||
} catch (IOException e) {
|
||||
log.error("提案导出失败{},提案id:{}", e.getMessage(), id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将现行工作流节点映射到历史提案表模板使用的区块名称,并按主协办单位拆分办理答复。
|
||||
* 模板仍使用旧节点名称时,避免已完成的意见和答复因键名不一致而无法渲染。
|
||||
*
|
||||
* @param proposalId 提案ID,用于查询主办单位
|
||||
* @param taskGroups 已完成任务按显示名称分组后的数据
|
||||
* @param docData Word 模板的平铺渲染数据
|
||||
*/
|
||||
private void putProposalExportTaskAliases(String proposalId, Map<String, List<ProcessTaskVO>> taskGroups,
|
||||
NutMap docData) {
|
||||
putTaskAlias(docData, "提案委主任审核", taskGroups.get("校工会预审核"));
|
||||
putTaskAlias(docData, "提案委主任意见", taskGroups.get("提案委员会立案"));
|
||||
putTaskAlias(docData, "提案委委员审议", taskGroups.get("委员会确认承办单位"));
|
||||
|
||||
List<ProcessTaskVO> replyTasks = taskGroups.get("承办单位答复");
|
||||
if (Lang.isEmpty(replyTasks)) {
|
||||
return;
|
||||
}
|
||||
Sql masterUnitSql = Sqls.create("SELECT unitName FROM proposal_reply_unit WHERE proposalId = @proposalId AND isMaster = 1")
|
||||
.setParam("proposalId", proposalId);
|
||||
List<NutMap> masterUnitRows = listMap(masterUnitSql);
|
||||
Set<String> masterUnitNames = masterUnitRows.stream()
|
||||
.map(row -> row.getString("unitName"))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
List<ProcessTaskVO> masterReplyTasks = new ArrayList<>();
|
||||
List<ProcessTaskVO> slaveReplyTasks = new ArrayList<>();
|
||||
for (ProcessTaskVO replyTask : replyTasks) {
|
||||
Dict taskFormData = replyTask.getTaskFormData();
|
||||
String unitName = taskFormData == null ? null : taskFormData.getStr("unitName");
|
||||
if (masterUnitNames.contains(unitName)) {
|
||||
masterReplyTasks.add(replyTask);
|
||||
} else {
|
||||
slaveReplyTasks.add(replyTask);
|
||||
}
|
||||
}
|
||||
putTaskAlias(docData, "主办单位答复", masterReplyTasks);
|
||||
putTaskAlias(docData, "协办单位答复", slaveReplyTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅在存在已完成任务时写入模板区块,空任务不触发 Word 条件标签。
|
||||
*
|
||||
* @param docData Word 模板的平铺渲染数据
|
||||
* @param templateKey Word 模板中的条件或循环标签名称
|
||||
* @param tasks 对应的已完成流程任务
|
||||
*/
|
||||
private void putTaskAlias(NutMap docData, String templateKey, List<ProcessTaskVO> tasks) {
|
||||
if (Lang.isNotEmpty(tasks)) {
|
||||
docData.put(templateKey, tasks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportProposalFeedBackAsDocx(String id, ByteArrayOutputStream byteArrayOutputStream) {
|
||||
if (id == null || id.isEmpty()) {
|
||||
|
||||
+32
-2
@@ -76,6 +76,8 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
|
||||
throw new BaseException("没有进行中的流程任务");
|
||||
}
|
||||
|
||||
validateAndNormalizeCaseFilingCode(args);
|
||||
|
||||
String proposalId = args.getStr("proposalId");
|
||||
List<String> proposalIds = proposalCommonService.mergeProposal(proposalId);
|
||||
if (ObjectUtil.isEmpty(proposalIds)) {
|
||||
@@ -98,16 +100,44 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
|
||||
|
||||
syncCommitteeFilingUnitData(proposalIds, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* old-flow 页面提交后,需要把立案结果以及主协办单位同步落业务表。
|
||||
* 校验并规范化立案编号。确定立案时必须传入不超过 20 个字符的编号;
|
||||
* 其他立案结果不保存编号,避免切换选项后把历史输入带入业务表和流程变量。
|
||||
*
|
||||
* @param args 流程提交参数,其中 tf_caseFilingResult 为立案结果,tf_caseFilingCode 为手工填写的立案编号
|
||||
*/
|
||||
private void validateAndNormalizeCaseFilingCode(Dict args) {
|
||||
String caseFilingResultKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult";
|
||||
String caseFilingCodeKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingCode";
|
||||
if (!"CONFIRM_FILING".equals(args.getStr(caseFilingResultKey))) {
|
||||
args.put(caseFilingCodeKey, "");
|
||||
return;
|
||||
}
|
||||
|
||||
String caseFilingCode = StrUtil.trim(args.getStr(caseFilingCodeKey));
|
||||
if (StrUtil.isBlank(caseFilingCode)) {
|
||||
throw new BaseException("立案编号不能为空");
|
||||
}
|
||||
if (caseFilingCode.length() > 20) {
|
||||
throw new BaseException("立案编号不能超过20个字符");
|
||||
}
|
||||
args.put(caseFilingCodeKey, caseFilingCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* old-flow 页面提交后,需要把立案编号、立案结果以及主协办单位同步落业务表。
|
||||
* 若这次改成“不予立案”等无需承办单位的结果,也要先清掉旧的承办单位记录,避免脏数据残留。
|
||||
*/
|
||||
private void syncCommitteeFilingUnitData(List<String> proposalIds, Dict args) {
|
||||
String caseFilingCode = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingCode");
|
||||
String caseFilingResult = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult");
|
||||
String caseFilingType = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingType");
|
||||
|
||||
dao().update(ProposalInfo.class,
|
||||
Chain.make("caseFilingResult", caseFilingResult).add("caseFilingType", caseFilingType),
|
||||
Chain.make("caseFilingCode", caseFilingCode)
|
||||
.add("caseFilingResult", caseFilingResult)
|
||||
.add("caseFilingType", caseFilingType),
|
||||
Cnd.where(ProposalInfo::getId, "in", proposalIds));
|
||||
|
||||
dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
|
||||
|
||||
+248
@@ -4,8 +4,10 @@ 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.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.param.ExportTableColumns;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.CommonDownloadUtil;
|
||||
@@ -27,7 +29,15 @@ import com.deepoove.poi.XWPFTemplate;
|
||||
import com.deepoove.poi.config.Configure;
|
||||
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xwpf.usermodel.BreakType;
|
||||
import org.apache.poi.xwpf.usermodel.IBodyElement;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||
@@ -51,6 +61,8 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -61,6 +73,9 @@ import java.util.zip.ZipOutputStream;
|
||||
@Slf4j
|
||||
public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalExportService {
|
||||
|
||||
private static final String WORK_SUGGESTION_SECTION_MARKER = "__WORK_SUGGESTION_SECTION__";
|
||||
private static final int UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT = 8;
|
||||
|
||||
@Inject
|
||||
private ProposalCommonService proposalCommonService;
|
||||
@Inject
|
||||
@@ -246,6 +261,239 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
||||
CommonDownloadUtil.download(title + ".xlsx", workbook, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按办理单位生成提案承办单位表。查询时忽略页面单个承办单位条件,其他查询条件继续生效;
|
||||
* 同一提案涉及多个单位时,会进入每个相关单位的 Excel,并保留完整主办、协办单位信息。
|
||||
*
|
||||
* @param pageForm 综合导出页面查询参数,sessionId 必须为当前教代会届次ID
|
||||
* @param response HTTP 响应,直接输出包含多个 XSSF Excel 的 ZIP 文件
|
||||
*/
|
||||
@Override
|
||||
public void exportUndertakeUnitTablesAsZip(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response) {
|
||||
if (pageForm == null || StrUtil.isBlank(pageForm.getSessionId())) {
|
||||
throw new BaseException("请选择教代会");
|
||||
}
|
||||
Teacher_congress_session session = dao().fetch(Teacher_congress_session.class, pageForm.getSessionId());
|
||||
if (session == null) {
|
||||
throw new BaseException("教代会届次不存在");
|
||||
}
|
||||
|
||||
List<NutMap> unitRows = queryUndertakeUnitExportRows(pageForm);
|
||||
if (Lang.isEmpty(unitRows)) {
|
||||
throw new BaseException("暂无可导出的承办单位提案数据");
|
||||
}
|
||||
|
||||
Map<String, NutMap> proposalRows = new LinkedHashMap<>();
|
||||
Map<String, String> unitNames = new LinkedHashMap<>();
|
||||
Map<String, LinkedHashSet<String>> unitProposalIds = new LinkedHashMap<>();
|
||||
for (NutMap row : unitRows) {
|
||||
String proposalId = row.getString("id");
|
||||
String unitId = row.getString("unitId");
|
||||
if (StrUtil.isBlank(proposalId) || StrUtil.isBlank(unitId)) {
|
||||
continue;
|
||||
}
|
||||
String unitName = StrUtil.blankToDefault(row.getString("unitName"), "未命名单位");
|
||||
NutMap proposalRow = proposalRows.computeIfAbsent(proposalId, key -> NutMap.NEW()
|
||||
.addv("id", proposalId)
|
||||
.addv("code", row.getString("code"))
|
||||
.addv("caseFilingCode", row.getString("caseFilingCode"))
|
||||
.addv("name", row.getString("name"))
|
||||
.addv("createUserName", row.getString("createUserName"))
|
||||
.addv("caseFilingResult", row.getString("caseFilingResult"))
|
||||
.addv("masterUnitNames", new LinkedHashSet<String>())
|
||||
.addv("slaveUnitNames", new LinkedHashSet<String>()));
|
||||
getUnitNameSet(proposalRow, Boolean.TRUE.equals(row.getBoolean("isMaster"))
|
||||
? "masterUnitNames" : "slaveUnitNames").add(unitName);
|
||||
unitNames.putIfAbsent(unitId, unitName);
|
||||
unitProposalIds.computeIfAbsent(unitId, key -> new LinkedHashSet<>()).add(proposalId);
|
||||
}
|
||||
if (unitProposalIds.isEmpty()) {
|
||||
throw new BaseException("暂无可导出的承办单位提案数据");
|
||||
}
|
||||
|
||||
String title = StrUtil.format("{}第{}教职工代表大会第{}会议提案承办单位表",
|
||||
Globals.AppName, session.getJ(), session.getC());
|
||||
ByteArrayOutputStream zipBytes = new ByteArrayOutputStream();
|
||||
Map<String, Integer> zipEntryNameCounts = new HashMap<>();
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(zipBytes)) {
|
||||
for (Map.Entry<String, LinkedHashSet<String>> unitEntry : unitProposalIds.entrySet()) {
|
||||
String unitName = unitNames.get(unitEntry.getKey());
|
||||
List<NutMap> excelRows = buildUndertakeUnitExcelRows(unitEntry.getValue(), proposalRows);
|
||||
try (Workbook workbook = buildUndertakeUnitWorkbook(title, unitName, excelRows);
|
||||
ByteArrayOutputStream workbookBytes = new ByteArrayOutputStream()) {
|
||||
workbook.write(workbookBytes);
|
||||
String baseEntryName = "提案承办单位表-" + sanitizeZipFileName(unitName);
|
||||
int sameNameCount = zipEntryNameCounts.merge(baseEntryName, 1, Integer::sum);
|
||||
String entryName = baseEntryName + (sameNameCount > 1 ? "-" + sameNameCount : "") + ".xlsx";
|
||||
zipOutputStream.putNextEntry(new ZipEntry(entryName));
|
||||
workbookBytes.writeTo(zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
zipOutputStream.finish();
|
||||
} catch (IOException e) {
|
||||
log.error("按办理单位导出提案承办单位表失败:{}", e.getMessage(), e);
|
||||
throw new BaseException("导出提案承办单位表失败");
|
||||
}
|
||||
CommonDownloadUtil.download(sanitizeZipFileName(title) + ".zip", zipBytes.toByteArray(), response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前届次已确定立案或作为工作建议的提案及其全部办理单位。
|
||||
* 页面承办单位单选条件会被清空,避免只生成一个单位文件;其他页面条件继续沿用。
|
||||
*/
|
||||
private List<NutMap> queryUndertakeUnitExportRows(ProposalQueryComprehensiveParam pageForm) {
|
||||
ProposalQueryComprehensiveParam exportPageForm = new ProposalQueryComprehensiveParam();
|
||||
BeanUtil.copyProperties(pageForm, exportPageForm);
|
||||
exportPageForm.setUnderTakeUnitId(null);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
info.id,
|
||||
info.code,
|
||||
info.caseFilingCode,
|
||||
info.name,
|
||||
info.createUserName,
|
||||
info.caseFilingResult,
|
||||
pru.unitId,
|
||||
pru.unitName,
|
||||
pru.isMaster
|
||||
FROM
|
||||
proposal_info info
|
||||
LEFT JOIN proposal_type type ON type.id = info.typeId
|
||||
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
|
||||
LEFT JOIN teacher_congress_delegation tcd ON tcd.id = info.delegationId
|
||||
LEFT JOIN teacher_congress_delegate tcde ON tcde.loginName = info.createUserLoginName
|
||||
INNER JOIN proposal_reply_unit pru ON pru.proposalId = info.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.where("info.sessionId", "=", pageForm.getSessionId());
|
||||
cnd.and("info.caseFilingResult", "in", List.of("CONFIRM_FILING", "SUGGESTION"));
|
||||
ProposalQueryComprehensiveParam.buildSearch(cnd, exportPageForm);
|
||||
cnd.asc("info.code");
|
||||
cnd.asc("pru.unitName");
|
||||
sql.setCondition(cnd);
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将某个办理单位涉及的提案整理为 Excel 行。确定立案在前,工作建议通过标记行单独分组。
|
||||
*/
|
||||
private List<NutMap> buildUndertakeUnitExcelRows(LinkedHashSet<String> proposalIds,
|
||||
Map<String, NutMap> proposalRows) {
|
||||
List<NutMap> filingRows = new ArrayList<>();
|
||||
List<NutMap> suggestionRows = new ArrayList<>();
|
||||
for (String proposalId : proposalIds) {
|
||||
NutMap proposalRow = proposalRows.get(proposalId);
|
||||
if (proposalRow == null) {
|
||||
continue;
|
||||
}
|
||||
LinkedHashSet<String> masterUnitNames = getUnitNameSet(proposalRow, "masterUnitNames");
|
||||
LinkedHashSet<String> slaveUnitNames = getUnitNameSet(proposalRow, "slaveUnitNames");
|
||||
NutMap excelRow = NutMap.NEW()
|
||||
.addv("code", proposalRow.getString("code"))
|
||||
.addv("caseFilingCode", proposalRow.getString("caseFilingCode"))
|
||||
.addv("name", proposalRow.getString("name"))
|
||||
.addv("createUserName", proposalRow.getString("createUserName"))
|
||||
.addv("masterUnitNames", "")
|
||||
.addv("slaveUnitNames", "")
|
||||
.addv("workSuggestionUnitNames", "")
|
||||
.addv("remark", "");
|
||||
if ("CONFIRM_FILING".equals(proposalRow.getString("caseFilingResult"))) {
|
||||
excelRow.put("masterUnitNames", String.join(",", masterUnitNames));
|
||||
excelRow.put("slaveUnitNames", String.join(",", slaveUnitNames));
|
||||
filingRows.add(excelRow);
|
||||
} else {
|
||||
LinkedHashSet<String> workSuggestionUnitNames = new LinkedHashSet<>(masterUnitNames);
|
||||
workSuggestionUnitNames.addAll(slaveUnitNames);
|
||||
excelRow.put("workSuggestionUnitNames", String.join(",", workSuggestionUnitNames));
|
||||
suggestionRows.add(excelRow);
|
||||
}
|
||||
}
|
||||
if (!suggestionRows.isEmpty()) {
|
||||
filingRows.add(NutMap.NEW()
|
||||
.addv("code", WORK_SUGGESTION_SECTION_MARKER)
|
||||
.addv("caseFilingCode", "")
|
||||
.addv("name", "")
|
||||
.addv("createUserName", "")
|
||||
.addv("masterUnitNames", "")
|
||||
.addv("slaveUnitNames", "")
|
||||
.addv("workSuggestionUnitNames", "")
|
||||
.addv("remark", ""));
|
||||
filingRows.addAll(suggestionRows);
|
||||
}
|
||||
return filingRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个办理单位的 XSSF Excel,并把工作建议标记行合并为分组标题。
|
||||
*/
|
||||
private Workbook buildUndertakeUnitWorkbook(String title, String unitName, List<NutMap> excelRows) {
|
||||
List<ExcelExportEntity> exportEntities = new ArrayList<>();
|
||||
exportEntities.add(new ExcelExportEntity("提案编号", "code", 18));
|
||||
exportEntities.add(new ExcelExportEntity("立案编号", "caseFilingCode", 18));
|
||||
exportEntities.add(new ExcelExportEntity("提案名称", "name", 45));
|
||||
exportEntities.add(new ExcelExportEntity("提案人", "createUserName", 15));
|
||||
exportEntities.add(new ExcelExportEntity("主办单位", "masterUnitNames", 28));
|
||||
exportEntities.add(new ExcelExportEntity("协办单位", "slaveUnitNames", 28));
|
||||
exportEntities.add(new ExcelExportEntity("工作建议送达单位", "workSuggestionUnitNames", 32));
|
||||
exportEntities.add(new ExcelExportEntity("备注", "remark", 20));
|
||||
for (ExcelExportEntity exportEntity : exportEntities) {
|
||||
exportEntity.setWrap(true);
|
||||
}
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
exportParams.setTitle(title);
|
||||
exportParams.setSecondTitle("(" + unitName + ")");
|
||||
exportParams.setSheetName("提案承办单位表");
|
||||
exportParams.setHeaderHeight(30);
|
||||
exportParams.setHeight((short) 24);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, excelRows);
|
||||
mergeWorkSuggestionSection(workbook);
|
||||
return workbook;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找工作建议标记行,将八个单元格合并并设置为居中的分组标题。
|
||||
*/
|
||||
private void mergeWorkSuggestionSection(Workbook workbook) {
|
||||
Sheet sheet = workbook.getSheetAt(0);
|
||||
for (Row row : sheet) {
|
||||
Cell firstCell = row.getCell(0);
|
||||
if (firstCell == null || !WORK_SUGGESTION_SECTION_MARKER.equals(firstCell.toString())) {
|
||||
continue;
|
||||
}
|
||||
CellStyle sectionStyle = workbook.createCellStyle();
|
||||
sectionStyle.cloneStyleFrom(firstCell.getCellStyle());
|
||||
sectionStyle.setAlignment(HorizontalAlignment.CENTER);
|
||||
sectionStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
Font sectionFont = workbook.createFont();
|
||||
sectionFont.setFontName("宋体");
|
||||
sectionFont.setFontHeightInPoints((short) 11);
|
||||
sectionFont.setBold(true);
|
||||
sectionStyle.setFont(sectionFont);
|
||||
for (int columnIndex = 0; columnIndex < UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT; columnIndex++) {
|
||||
Cell cell = row.getCell(columnIndex);
|
||||
if (cell == null) {
|
||||
cell = row.createCell(columnIndex);
|
||||
}
|
||||
cell.setCellValue(columnIndex == 0 ? "作为工作建议的提案" : "");
|
||||
cell.setCellStyle(sectionStyle);
|
||||
}
|
||||
row.setHeightInPoints(24);
|
||||
sheet.addMergedRegion(new CellRangeAddress(row.getRowNum(), row.getRowNum(), 0,
|
||||
UNDERTAKE_UNIT_EXPORT_COLUMN_COUNT - 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取提案行中用于去重且保持顺序的办理单位名称集合。 */
|
||||
@SuppressWarnings("unchecked")
|
||||
private LinkedHashSet<String> getUnitNameSet(NutMap proposalRow, String key) {
|
||||
return (LinkedHashSet<String>) proposalRow.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportProposalRegisterSummaryAsExcel(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response) {
|
||||
Sql sql = exportComprehensiveSql(pageForm);
|
||||
|
||||
+73
-1
@@ -4,6 +4,7 @@ import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
@@ -11,6 +12,7 @@ import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.democratic.proposal.constants.ProposalState;
|
||||
import com.budwk.app.zhgh.democratic.proposal.controller.vo.ProposalWriteTypeVO;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalType;
|
||||
import com.budwk.app.zhgh.democratic.proposal.service.ProposalWriteService;
|
||||
@@ -24,6 +26,7 @@ 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.json.Json;
|
||||
import org.nutz.lang.Strings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -44,6 +47,70 @@ public class ProposalWriteServiceImpl extends BaseServiceImpl<ProposalInfo> impl
|
||||
super(dao);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProposalWriteTypeVO> listSessionProposalTypes(String sessionId) {
|
||||
List<String> typeNames = getSessionProposalTypeNames(sessionId);
|
||||
if (typeNames.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<ProposalType> proposalTypes = dao().query(ProposalType.class,
|
||||
Cnd.where(ProposalType::getName, "in", typeNames));
|
||||
List<ProposalWriteTypeVO> result = new ArrayList<>();
|
||||
for (String typeName : typeNames) {
|
||||
ProposalType proposalType = proposalTypes.stream()
|
||||
.filter(item -> typeName.equals(item.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (proposalType != null) {
|
||||
result.add(new ProposalWriteTypeVO(proposalType.getId(), proposalType.getName()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateProposalType(String sessionId, Integer typeId) {
|
||||
if (typeId == null) {
|
||||
throw new BaseException("请选择提案类型");
|
||||
}
|
||||
ProposalType proposalType = dao().fetch(ProposalType.class, typeId);
|
||||
if (proposalType == null || !getSessionProposalTypeNames(sessionId).contains(proposalType.getName())) {
|
||||
throw new BaseException("所选提案类型不属于当前教代会届次");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析指定届次配置的提案类型。标准数据为 JSON 数组,同时兼容历史单值字符串。
|
||||
*
|
||||
* @param sessionId 教代会届次ID
|
||||
* @return 按届次配置顺序排列的提案类型名称;未配置时返回空列表
|
||||
*/
|
||||
private List<String> getSessionProposalTypeNames(String sessionId) {
|
||||
if (StrUtil.isBlank(sessionId)) {
|
||||
throw new BaseException("所属教代会不能为空");
|
||||
}
|
||||
Teacher_congress_session session = dao().fetch(Teacher_congress_session.class, sessionId);
|
||||
if (session == null) {
|
||||
throw new BaseException("所属教代会不存在");
|
||||
}
|
||||
if (StrUtil.isBlank(session.getProposalType())) {
|
||||
return List.of();
|
||||
}
|
||||
String proposalTypeValue = StrUtil.trim(session.getProposalType());
|
||||
if (!proposalTypeValue.startsWith("[")) {
|
||||
return List.of(proposalTypeValue);
|
||||
}
|
||||
try {
|
||||
return Json.fromJsonAsList(String.class, proposalTypeValue).stream()
|
||||
.map(StrUtil::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.toList();
|
||||
} catch (Exception e) {
|
||||
throw new BaseException("教代会届次提案类型配置格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Sys_dict> listSource(String sessionId) {
|
||||
Sql sql = Sqls.create("""
|
||||
@@ -118,7 +185,11 @@ public class ProposalWriteServiceImpl extends BaseServiceImpl<ProposalInfo> impl
|
||||
@Override
|
||||
public List<Sys_dict> listSourceByCode(String code) {
|
||||
Sys_dict dict = sysDictService.fetch(Cnd.where("code", "=", code));
|
||||
return dict == null ? new ArrayList<>() : sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(dict.getId())).asc("location"));
|
||||
return dict == null ? new ArrayList<>() : sysDictService.query(
|
||||
Cnd.NEW().where("parentId", "=", Strings.sNull(dict.getId()))
|
||||
.and("disabled","=",0)
|
||||
.asc("location")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,6 +291,7 @@ public class ProposalWriteServiceImpl extends BaseServiceImpl<ProposalInfo> impl
|
||||
throw new RuntimeException("没有开启的教代会");
|
||||
}
|
||||
Teacher_congress_session teacherCongressSession = teacherCongressSessions.get(0);
|
||||
validateProposalType(teacherCongressSession.getId(), proposalTypeObj.getId());
|
||||
|
||||
// 保存到数据库
|
||||
ProposalInfo proposalInfo = new ProposalInfo();
|
||||
|
||||
+26
-73
@@ -7,7 +7,6 @@ import cn.hutool.core.lang.tree.TreeNode;
|
||||
import cn.hutool.core.lang.tree.TreeUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -20,15 +19,14 @@ import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_union;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_unit;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.service.TeacherCongressDelegationService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.entity.Record;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
@@ -43,7 +41,6 @@ import org.nutz.mvc.annotation.Param;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/teacherCongress/delegation")
|
||||
@@ -63,6 +60,9 @@ public class TeacherCongressDelegationController {
|
||||
@Inject
|
||||
private SysRoleService sysRoleService;
|
||||
|
||||
@Inject
|
||||
private TeacherCongressDelegationService teacherCongressDelegationService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/democratic/teachercongress/delegation/index.html")
|
||||
@SaCheckPermission("tc.delegation")
|
||||
@@ -288,97 +288,50 @@ public class TeacherCongressDelegationController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询团长
|
||||
* 查询代表团负责人,副团长返回多条以兼容V3多个副团长。
|
||||
*
|
||||
* @param sessionId
|
||||
* @param delegationId
|
||||
* @return
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param type 负责人角色编码
|
||||
* @return 负责人列表
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("tc.delegation")
|
||||
public Result headUser(@Valid String sessionId, @Valid String delegationId, @Param(value = "type", df = "TEACHER_CONGRESS_DELEGATION_HEAD") String type) {
|
||||
Sys_role role = null;
|
||||
if (type.equals(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD.name())) {
|
||||
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
} else if (type.equals(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD.name())) {
|
||||
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
|
||||
} else if (type.equals(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT.name())) {
|
||||
role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
|
||||
} else {
|
||||
throw new BaseException("参数错误");
|
||||
}
|
||||
|
||||
Record record = dao.fetch("sys_user_role", Cnd.where("tcSessionId", "=", sessionId).and("tcDelegationId", "=", delegationId).and("roleId", "=", role.getId()));
|
||||
String userId = Optional.ofNullable(record).map(r -> r.getString("userId")).orElse(null);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
u.id as userId,
|
||||
u.loginname as loginName,
|
||||
u.username as userName,
|
||||
u.mobile,
|
||||
n.name as unitName
|
||||
FROM
|
||||
sys_user u
|
||||
LEFT JOIN sys_unit n on n.id = u.unitId
|
||||
WHERE
|
||||
u.id = @userId
|
||||
""");
|
||||
sql.setParam("userId", userId);
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
dao.execute(sql);
|
||||
NutMap user = (NutMap) sql.getResult();
|
||||
return Result.success().addData(user);
|
||||
List<NutMap> users = teacherCongressDelegationService.listHeadUsers(sessionId, delegationId, type);
|
||||
return Result.success().addData(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置团长
|
||||
* 设置代表团团长、副团长和联络人。
|
||||
*
|
||||
* @param delegationId 代表团ID
|
||||
* @param sessionId 届次ID
|
||||
* @param userId 团长id
|
||||
* @param viceUserId 副团长id
|
||||
* @param contactUserId 联络人
|
||||
* @return
|
||||
* @param userId 团长ID
|
||||
* @param viceUserIds 副团长ID数组
|
||||
* @param contactUserId 联络人ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("tc.delegation")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result insertHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId, @Valid String viceUserId, String contactUserId) {
|
||||
// 团长
|
||||
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role.getId()));
|
||||
dao.insert("sys_user_role", Chain.make("userId", userId).add("roleId", role.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
|
||||
|
||||
// 副团长
|
||||
Sys_role role2 = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
|
||||
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role2.getId()));
|
||||
dao.insert("sys_user_role", Chain.make("userId", viceUserId).add("roleId", role2.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
|
||||
|
||||
// 联络人
|
||||
Sys_role role3 = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
|
||||
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role3.getId()));
|
||||
dao.insert("sys_user_role", Chain.make("userId", contactUserId).add("roleId", role3.getId()).add("tcDelegationId", delegationId).add("tcSessionId", sessionId));
|
||||
|
||||
sysUserService.clearCache();
|
||||
public Result insertHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId, @Param("viceUserIds") String[] viceUserIds, String contactUserId) {
|
||||
teacherCongressDelegationService.setHeadUsers(sessionId, delegationId, userId, viceUserIds, contactUserId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除团长角色
|
||||
* 删除代表团负责人角色。
|
||||
*
|
||||
* @param delegationId
|
||||
* @param sessionId
|
||||
* @param userId
|
||||
* @return
|
||||
* @param delegationId 代表团ID
|
||||
* @param sessionId 届次ID
|
||||
* @param userId 用户ID
|
||||
* @param type 负责人角色编码
|
||||
* @return 操作结果
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("tc.delegation")
|
||||
public Result deleteHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId) {
|
||||
Sys_role role = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
//先删除角色
|
||||
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId).and(Sys_user_role::getTcSessionId, "=", sessionId).and(Sys_user_role::getRoleId, "=", role.getId()).and(Sys_user_role::getUserId, "=", userId));
|
||||
sysUserService.clearCache();
|
||||
public Result deleteHead(@Valid String delegationId, @Valid String sessionId, @Valid String userId, @Param(value = "type", df = "TEACHER_CONGRESS_DELEGATION_HEAD") String type) {
|
||||
teacherCongressDelegationService.deleteHeadUser(sessionId, delegationId, userId, type);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.budwk.app.zhgh.democratic.teachercongress.delegation.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教代会代表团业务服务。
|
||||
*/
|
||||
public interface TeacherCongressDelegationService extends BaseService<Teacher_congress_delegation> {
|
||||
|
||||
/**
|
||||
* 按角色查询代表团负责人信息。
|
||||
*
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param type 负责人角色编码
|
||||
* @return 负责人用户列表
|
||||
*/
|
||||
List<NutMap> listHeadUsers(String sessionId, String delegationId, String type);
|
||||
|
||||
/**
|
||||
* 设置代表团团长、副团长和联络人。
|
||||
*
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param userId 团长用户ID
|
||||
* @param viceUserIds 副团长用户ID列表
|
||||
* @param contactUserId 联络人用户ID
|
||||
*/
|
||||
void setHeadUsers(String sessionId, String delegationId, String userId, String[] viceUserIds, String contactUserId);
|
||||
|
||||
/**
|
||||
* 删除代表团指定负责人角色。
|
||||
*
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param userId 用户ID
|
||||
* @param type 负责人角色编码
|
||||
*/
|
||||
void deleteHeadUser(String sessionId, String delegationId, String userId, String type);
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.budwk.app.zhgh.democratic.teachercongress.delegation.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.models.Sys_role;
|
||||
import com.budwk.app.sys.models.Sys_user_role;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.service.TeacherCongressDelegationService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教代会代表团业务服务实现。
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TeacherCongressDelegationServiceImpl extends BaseServiceImpl<Teacher_congress_delegation> implements TeacherCongressDelegationService {
|
||||
|
||||
@Inject
|
||||
private SysRoleService sysRoleService;
|
||||
|
||||
@Inject
|
||||
private SysUserService sysUserService;
|
||||
|
||||
public TeacherCongressDelegationServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按角色查询代表团负责人信息,副团长允许返回多条记录以兼容V3多副团长数据。
|
||||
*
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param type 负责人角色编码
|
||||
* @return 负责人用户列表
|
||||
*/
|
||||
@Override
|
||||
public List<NutMap> listHeadUsers(String sessionId, String delegationId, String type) {
|
||||
Sys_role role = getHeadRole(type);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
u.id as userId,
|
||||
u.loginname as loginName,
|
||||
u.username as userName,
|
||||
u.mobile,
|
||||
n.name as unitName
|
||||
FROM
|
||||
sys_user_role sur
|
||||
LEFT JOIN sys_user u ON u.id = sur.userId
|
||||
LEFT JOIN sys_unit n ON n.id = u.unitId
|
||||
WHERE
|
||||
sur.tcSessionId = @sessionId
|
||||
AND sur.tcDelegationId = @delegationId
|
||||
AND sur.roleId = @roleId
|
||||
ORDER BY u.loginname
|
||||
""");
|
||||
sql.setParam("sessionId", sessionId);
|
||||
sql.setParam("delegationId", delegationId);
|
||||
sql.setParam("roleId", role.getId());
|
||||
return listMap(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代表团团长、副团长和联络人,保存前按角色清理旧值,避免同一角色残留脏数据。
|
||||
*
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param userId 团长用户ID
|
||||
* @param viceUserIds 副团长用户ID列表
|
||||
* @param contactUserId 联络人用户ID
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void setHeadUsers(String sessionId, String delegationId, String userId, String[] viceUserIds, String contactUserId) {
|
||||
// 团长仍然保持单人设置。
|
||||
Sys_role headRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
replaceRoleUsers(sessionId, delegationId, headRole.getId(), new String[]{userId});
|
||||
|
||||
// 副团长支持多人设置,完整保留V3迁移后的多个副团长关系。
|
||||
Sys_role viceHeadRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
|
||||
replaceRoleUsers(sessionId, delegationId, viceHeadRole.getId(), viceUserIds);
|
||||
|
||||
// 联络人保持单人设置,未选择时只清理旧联络人。
|
||||
Sys_role contactRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
|
||||
replaceRoleUsers(sessionId, delegationId, contactRole.getId(), new String[]{contactUserId});
|
||||
|
||||
sysUserService.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除代表团指定负责人角色,按角色类型精确删除,避免删除副团长时影响团长。
|
||||
*
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param userId 用户ID
|
||||
* @param type 负责人角色编码
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void deleteHeadUser(String sessionId, String delegationId, String userId, String type) {
|
||||
Sys_role role = getHeadRole(type);
|
||||
dao().clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId)
|
||||
.and(Sys_user_role::getTcSessionId, "=", sessionId)
|
||||
.and(Sys_user_role::getRoleId, "=", role.getId())
|
||||
.and(Sys_user_role::getUserId, "=", userId));
|
||||
sysUserService.clearCache();
|
||||
}
|
||||
/**
|
||||
* 根据负责人类型解析系统角色,集中校验避免 controller 层出现业务分支。
|
||||
*
|
||||
* @param type 负责人角色编码
|
||||
* @return 系统角色
|
||||
*/
|
||||
private Sys_role getHeadRole(String type) {
|
||||
if (RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD.name().equals(type)) {
|
||||
return sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_HEAD);
|
||||
}
|
||||
if (RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD.name().equals(type)) {
|
||||
return sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_VICE_DELEGATION_HEAD);
|
||||
}
|
||||
if (RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT.name().equals(type)) {
|
||||
return sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_DELEGATION_CONTACT);
|
||||
}
|
||||
throw new BaseException("参数错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换指定角色用户关系,先清理再按去重后的用户ID批量插入。
|
||||
*
|
||||
* @param sessionId 届次ID
|
||||
* @param delegationId 代表团ID
|
||||
* @param roleId 角色ID
|
||||
* @param userIds 用户ID数组
|
||||
*/
|
||||
private void replaceRoleUsers(String sessionId, String delegationId, String roleId, String[] userIds) {
|
||||
dao().clear(Sys_user_role.class, Cnd.where(Sys_user_role::getTcDelegationId, "=", delegationId)
|
||||
.and(Sys_user_role::getTcSessionId, "=", sessionId)
|
||||
.and(Sys_user_role::getRoleId, "=", roleId));
|
||||
if (userIds == null || userIds.length == 0) {
|
||||
return;
|
||||
}
|
||||
Arrays.stream(userIds)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.forEach(userId -> dao().insert("sys_user_role", Chain.make("userId", userId)
|
||||
.add("roleId", roleId)
|
||||
.add("tcDelegationId", delegationId)
|
||||
.add("tcSessionId", sessionId)));
|
||||
}
|
||||
}
|
||||
+14
-12
@@ -1,7 +1,6 @@
|
||||
package com.budwk.app.zhgh.democratic.teachercongress.prepare.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -61,6 +60,18 @@ public class TeacherCongressSessionController {
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询历史届次已保存的提案类型,不需要请求参数。
|
||||
*
|
||||
* @return VO 列表,每项的 proposalType 字段表示一个下拉选项
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("tc.prepare.session")
|
||||
@ApiOperation(value = "查询届次提案类型选项")
|
||||
public Result listProposalTypes() {
|
||||
return Result.success(teacherCongressSessionService.listProposalTypes());
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tc.prepare.session")
|
||||
@@ -74,17 +85,8 @@ public class TeacherCongressSessionController {
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tc.prepare.session")
|
||||
@ApiOperation(value = "修改届次信息")
|
||||
public Result update(Teacher_congress_session session) {
|
||||
int count = dao.count(Teacher_congress_session.class,
|
||||
Cnd.where(Teacher_congress_session::getJ, "=", session.getJ())
|
||||
.and(Teacher_congress_session::getC, "=", session.getC())
|
||||
.and(Teacher_congress_session::getId, "!=", session.getId())
|
||||
);
|
||||
if (count > 0) {
|
||||
throw new BaseException("请勿重复创建");
|
||||
}
|
||||
session.setFullName("第" + session.getJ() + "第" + session.getC());
|
||||
dao.updateIgnoreNull(session);
|
||||
public Result update(@Valid Teacher_congress_session session) {
|
||||
teacherCongressSessionService.updateSession(session);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.budwk.app.zhgh.democratic.teachercongress.prepare.controller.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 届次提案类型下拉选项。
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ApiModel("届次提案类型选项")
|
||||
public class TeacherCongressSessionProposalTypeVO {
|
||||
|
||||
@ApiModelProperty("提案类型,自定义类型保存后也会成为后续届次的可选项")
|
||||
private String proposalType;
|
||||
}
|
||||
+7
@@ -43,6 +43,13 @@ public class Teacher_congress_session extends BaseModel {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String fullName;
|
||||
|
||||
@Column
|
||||
@Comment("提案类型(JSON数组)")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 1000)
|
||||
@NotBlank(message = "提案类型不能为空")
|
||||
@Size(max = 1000, message = "提案类型数据不能超过1000个字符")
|
||||
private String proposalType;
|
||||
|
||||
@Column
|
||||
@Comment("教代会开启时间")
|
||||
@ColDefine(type = ColType.DATE)
|
||||
|
||||
+17
@@ -1,14 +1,31 @@
|
||||
package com.budwk.app.zhgh.democratic.teachercongress.prepare.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.prepare.controller.vo.TeacherCongressSessionProposalTypeVO;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_session;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教代会届次
|
||||
*/
|
||||
public interface TeacherCongressSessionService extends BaseService<Teacher_congress_session> {
|
||||
|
||||
/**
|
||||
* 查询届次中已经保存过的提案类型,供可输入下拉框复用。
|
||||
*
|
||||
* @return 提案类型 VO 列表,每项的 proposalType 字段为一个可选类型
|
||||
*/
|
||||
List<TeacherCongressSessionProposalTypeVO> listProposalTypes();
|
||||
|
||||
void insertSession(Teacher_congress_session session, boolean isExtend);
|
||||
|
||||
/**
|
||||
* 修改届次基础信息并统一校验多选提案类型。
|
||||
*
|
||||
* @param session 届次信息,其中 proposalType 为 JSON 字符串数组
|
||||
*/
|
||||
void updateSession(Teacher_congress_session session);
|
||||
|
||||
void deleteSession(String id);
|
||||
}
|
||||
|
||||
+141
@@ -16,6 +16,7 @@ import com.budwk.app.sys.models.Sys_user_role;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.zhgh.democratic.proposal.models.ProposalType;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegate.models.Teacher_congress_delegate;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.delegation.models.Teacher_congress_delegation_union;
|
||||
@@ -24,19 +25,25 @@ import com.budwk.app.zhgh.democratic.teachercongress.institution.models.Teacher_
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.institution.models.Teacher_congress_institution_user;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.meetings.models.Teacher_congress_meeting;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.meetings.models.Teacher_congress_meeting_file;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.prepare.controller.vo.TeacherCongressSessionProposalTypeVO;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_prepare_file;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_session;
|
||||
import com.budwk.app.zhgh.democratic.teachercongress.prepare.service.TeacherCongressSessionService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
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 org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.random.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -52,10 +59,36 @@ public class TeacherCongressSessionServiceImpl extends BaseServiceImpl<Teacher_c
|
||||
super(dao);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取历史届次中非空的提案类型并去重,用户既可复用已有类型,也可在前端输入新类型。
|
||||
*
|
||||
* @return 提案类型 VO 列表,每项只包含 proposalType 字段
|
||||
*/
|
||||
@Override
|
||||
public List<TeacherCongressSessionProposalTypeVO> listProposalTypes() {
|
||||
Sql sql = Sqls.create("SELECT DISTINCT proposalType FROM teacher_congress_session " +
|
||||
"WHERE proposalType IS NOT NULL AND TRIM(proposalType) <> '' ORDER BY proposalType");
|
||||
sql.setCallback(Sqls.callback.strList());
|
||||
dao().execute(sql);
|
||||
LinkedHashSet<String> proposalTypes = new LinkedHashSet<>();
|
||||
for (String proposalTypeValue : sql.getList(String.class)) {
|
||||
for (String proposalType : parseProposalTypes(proposalTypeValue)) {
|
||||
String normalizedProposalType = StrUtil.trim(proposalType);
|
||||
if (StrUtil.isNotBlank(normalizedProposalType)) {
|
||||
proposalTypes.add(normalizedProposalType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return proposalTypes.stream()
|
||||
.sorted()
|
||||
.map(TeacherCongressSessionProposalTypeVO::new)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void insertSession(Teacher_congress_session session, boolean isExtend) {
|
||||
normalizeAndValidateProposalTypes(session);
|
||||
int count = count(Cnd.where(Teacher_congress_session::getJ, "=", session.getJ())
|
||||
.and(Teacher_congress_session::getC, "=", session.getC()));
|
||||
if (count > 0) {
|
||||
@@ -239,6 +272,114 @@ public class TeacherCongressSessionServiceImpl extends BaseServiceImpl<Teacher_c
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改届次基础信息。提案类型在保存前统一完成 JSON 解析、去重和长度校验,
|
||||
* 避免新增与编辑使用不同的数据格式。
|
||||
*
|
||||
* @param session 届次信息,其中 proposalType 应传 JSON 字符串数组
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void updateSession(Teacher_congress_session session) {
|
||||
normalizeAndValidateProposalTypes(session);
|
||||
int count = count(Cnd.where(Teacher_congress_session::getJ, "=", session.getJ())
|
||||
.and(Teacher_congress_session::getC, "=", session.getC())
|
||||
.and(Teacher_congress_session::getId, "!=", session.getId()));
|
||||
if (count > 0) {
|
||||
throw new BaseException("请勿重复创建");
|
||||
}
|
||||
session.setFullName("第" + session.getJ() + "第" + session.getC());
|
||||
dao().updateIgnoreNull(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多选提案类型规范化为 JSON 数组字符串。每个类型去除首尾空格、去重且最多 50 个字符,
|
||||
* 整体序列化结果最多 1000 个字符。
|
||||
*
|
||||
* @param session 待新增或修改的届次实体
|
||||
*/
|
||||
private void normalizeAndValidateProposalTypes(Teacher_congress_session session) {
|
||||
List<String> values = parseProposalTypes(session.getProposalType());
|
||||
LinkedHashSet<String> normalizedValues = new LinkedHashSet<>();
|
||||
for (String value : values) {
|
||||
String normalizedValue = StrUtil.trim(value);
|
||||
if (StrUtil.isBlank(normalizedValue)) {
|
||||
continue;
|
||||
}
|
||||
if (normalizedValue.length() > 50) {
|
||||
throw new BaseException("单个提案类型不能超过50个字符");
|
||||
}
|
||||
normalizedValues.add(normalizedValue);
|
||||
}
|
||||
if (normalizedValues.isEmpty()) {
|
||||
throw new BaseException("请至少选择或输入一个提案类型");
|
||||
}
|
||||
|
||||
String proposalTypeJson = Json.toJson(new ArrayList<>(normalizedValues));
|
||||
if (proposalTypeJson.length() > 1000) {
|
||||
throw new BaseException("提案类型数据不能超过1000个字符");
|
||||
}
|
||||
session.setProposalType(proposalTypeJson);
|
||||
syncProposalTypes(normalizedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将届次中新输入的类型同步到全局提案类型表,以便继续使用 proposal_info.typeId
|
||||
* 以及现有列表、统计和导出的关联查询。已有同名类型直接复用,不删除历史类型。
|
||||
*
|
||||
* @param proposalTypeNames 当前届次规范化、去重后的提案类型名称
|
||||
*/
|
||||
private void syncProposalTypes(LinkedHashSet<String> proposalTypeNames) {
|
||||
ProposalType lastType = dao().fetch(ProposalType.class, Cnd.NEW().desc(ProposalType::getSort));
|
||||
int nextSort = lastType == null || lastType.getSort() == null ? 1 : lastType.getSort() + 1;
|
||||
for (String proposalTypeName : proposalTypeNames) {
|
||||
ProposalType proposalType = dao().fetch(ProposalType.class,
|
||||
Cnd.where(ProposalType::getName, "=", proposalTypeName));
|
||||
if (proposalType != null) {
|
||||
continue;
|
||||
}
|
||||
ProposalType newProposalType = new ProposalType();
|
||||
newProposalType.setCode(generateProposalTypeCode());
|
||||
newProposalType.setName(proposalTypeName);
|
||||
newProposalType.setSort(nextSort++);
|
||||
dao().insert(newProposalType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成不超过 10 个字符的内部提案类型编码。
|
||||
*
|
||||
* @return 以 TC 开头且在 proposal_type 表中未使用的编码
|
||||
*/
|
||||
private String generateProposalTypeCode() {
|
||||
String code;
|
||||
do {
|
||||
code = "TC" + R.UU32().substring(0, 8).toUpperCase();
|
||||
} while (dao().count(ProposalType.class, Cnd.where(ProposalType::getCode, "=", code)) > 0);
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析届次中保存的提案类型。标准数据为 JSON 字符串数组,同时兼容调整前可能存在的单值数据。
|
||||
*
|
||||
* @param proposalTypeValue 数据库字段值或前端提交的 JSON 字符串
|
||||
* @return 提案类型字符串列表;空值返回空列表
|
||||
*/
|
||||
private List<String> parseProposalTypes(String proposalTypeValue) {
|
||||
if (StrUtil.isBlank(proposalTypeValue)) {
|
||||
return List.of();
|
||||
}
|
||||
String value = proposalTypeValue.trim();
|
||||
if (!value.startsWith("[")) {
|
||||
return List.of(value);
|
||||
}
|
||||
try {
|
||||
return Json.fromJsonAsList(String.class, value);
|
||||
} catch (Exception e) {
|
||||
throw new BaseException("提案类型数据格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void deleteSession(String id) {
|
||||
|
||||
+7
-1
@@ -91,7 +91,13 @@ public class AidFundChangeRecordController {
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("us.aidFundMemberUserType", "=", pageForm.getAidFundMemberUserType());
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or("us.loginname", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
seg.or("us.username", "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType)", "=", pageForm.getAidFundMemberUserType());
|
||||
cnd.andEX("us.unitid", "=", pageForm.getUnitId());
|
||||
cnd.andEX("us.unionid", "=", pageForm.getUnionId());
|
||||
cnd.andEX("info.changeType", "=", pageForm.getChangeType());
|
||||
|
||||
+1
@@ -98,6 +98,7 @@ public class MemberApplyBranchUnionApprovalController {
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN member_apply_record info ON info.id = ins.businessNo
|
||||
LEFT JOIN vw_user u ON u.id = info.userId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
|
||||
+1
@@ -113,6 +113,7 @@ public class MemberApplySchoolUnionApprovalController {
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN member_apply_record info ON info.id = ins.businessNo
|
||||
LEFT JOIN vw_user u ON u.id = info.userId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
|
||||
+1
@@ -87,6 +87,7 @@ public class MemberApplyUnionGroupApprovalController {
|
||||
wf_process_task t
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||
LEFT JOIN member_apply_record info ON info.id = ins.businessNo
|
||||
LEFT JOIN vw_user u ON u.id = info.userId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
|
||||
|
||||
+9
-1
@@ -34,6 +34,9 @@ public class MemberApplyPageForm extends PageForm {
|
||||
|
||||
private String userAttribute; //人员属性
|
||||
|
||||
/** 人员属性多选值,供开启多选查询的页面使用。 */
|
||||
private List<String> userAttributes;
|
||||
|
||||
public static void buildSearch(Cnd cnd, MemberApplyPageForm pageForm) {
|
||||
if (cnd == null) {
|
||||
cnd = Cnd.NEW();
|
||||
@@ -50,6 +53,11 @@ public class MemberApplyPageForm extends PageForm {
|
||||
cnd.andEX("info.personType","in",pageForm.getPersonTypes());
|
||||
cnd.andEX("info.preparedBy","in",pageForm.getPreparedBys());
|
||||
cnd.andEX("u.aidFundMemberUserType","=",pageForm.getAidFundMemberUserType());
|
||||
cnd.andEX("u.userAttribute","=",pageForm.getUserAttribute());
|
||||
// 多选值优先使用IN查询;未传多选值时保留原单值查询,兼容其他调用页面。
|
||||
if (pageForm.getUserAttributes() != null && !pageForm.getUserAttributes().isEmpty()) {
|
||||
cnd.and("u.userAttribute", "in", pageForm.getUserAttributes());
|
||||
} else {
|
||||
cnd.andEX("u.userAttribute", "=", pageForm.getUserAttribute());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -47,6 +47,9 @@ public class MemberChangePageForm extends PageForm {
|
||||
@ApiModelProperty("人员属性")
|
||||
private String userAttribute;
|
||||
|
||||
@ApiModelProperty("人员属性组")
|
||||
private List<String> userAttributes;
|
||||
|
||||
|
||||
public void buildSearch(Cnd cnd, String prefix){
|
||||
if (cnd == null) {
|
||||
@@ -66,6 +69,11 @@ public class MemberChangePageForm extends PageForm {
|
||||
cnd.andEX(prefix + "preparedBy", "in", this.getPreparedBys());
|
||||
cnd.andEX(prefix + "personType", "in", this.getPersonTypes());
|
||||
cnd.andEX(prefix + "aidFundMemberUserType", "=", this.getAidFundMemberUserType());
|
||||
cnd.andEX(prefix + "userAttribute", "=", this.getUserAttribute());
|
||||
// 多选条件优先,兼容仍按单值提交人员属性的旧页面。
|
||||
if (this.getUserAttributes() != null && !this.getUserAttributes().isEmpty()) {
|
||||
cnd.and(prefix + "userAttribute", "in", this.getUserAttributes());
|
||||
} else {
|
||||
cnd.andEX(prefix + "userAttribute", "=", this.getUserAttribute());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -162,10 +162,11 @@ public class MemberInfoPageForm extends PageForm {
|
||||
} else if (Lang.isNotEmpty(this.getPersonTypes())) {
|
||||
cnd.and(prefix + "personType", "in", this.getPersonTypes());
|
||||
}
|
||||
if (StrUtil.isNotBlank(this.getUserAttribute())) {
|
||||
cnd.and(prefix + "userAttribute", "=", this.getUserAttribute());
|
||||
} else if (Lang.isNotEmpty(this.getUserAttributes())) {
|
||||
// 多选条件优先,兼容仍按单值提交人员属性的旧页面。
|
||||
if (Lang.isNotEmpty(this.getUserAttributes())) {
|
||||
cnd.and(prefix + "userAttribute", "in", this.getUserAttributes());
|
||||
} else if (StrUtil.isNotBlank(this.getUserAttribute())) {
|
||||
cnd.and(prefix + "userAttribute", "=", this.getUserAttribute());
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(this.getAidFundMemberUserType())) {
|
||||
|
||||
+15
-2
@@ -42,6 +42,9 @@ public class MemberManagePageForm extends PageForm {
|
||||
|
||||
private String userAttribute;
|
||||
|
||||
/** 人员属性多选查询条件。 */
|
||||
private List<String> userAttributes;
|
||||
|
||||
private String changeType;
|
||||
|
||||
//变更状态数组
|
||||
@@ -93,7 +96,12 @@ public class MemberManagePageForm extends PageForm {
|
||||
cnd.andEX("u.sex", "=", pageForm.getSex());
|
||||
cnd.andEX("u.personType", "=", pageForm.getPersonType());
|
||||
cnd.andEX("u.aidFundMemberUserType", "=", pageForm.getAidFundMemberUserType());
|
||||
cnd.andEX("u.userAttribute", "=", pageForm.getUserAttribute());
|
||||
// 多选条件优先,兼容仍按单值提交人员属性的旧页面。
|
||||
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
|
||||
cnd.and("u.userAttribute", "in", pageForm.getUserAttributes());
|
||||
} else {
|
||||
cnd.andEX("u.userAttribute", "=", pageForm.getUserAttribute());
|
||||
}
|
||||
|
||||
if (StrUtil.isNotBlank(pageForm.getChangeDateBefore()) && StrUtil.isNotBlank(pageForm.getChangeDateEnd())) {
|
||||
cnd.and(new Static(String.format("Date(his.changeTime) >= '%s' and Date(his.changeTime) <= '%s'", pageForm.getChangeDateBefore(), pageForm.getChangeDateEnd())));
|
||||
@@ -148,7 +156,12 @@ public class MemberManagePageForm extends PageForm {
|
||||
cnd.andEX("record.preparedBy", "=", pageForm.getPreparedBy());
|
||||
cnd.andEX("record.personType", "=", pageForm.getPersonType());
|
||||
cnd.andEX("record.aidFundMemberUserType", "=", pageForm.getAidFundMemberUserType());
|
||||
cnd.andEX("record.userAttribute", "=", pageForm.getUserAttribute());
|
||||
// 历史记录查询与当前会员查询使用一致的人员属性多选规则。
|
||||
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
|
||||
cnd.and("record.userAttribute", "in", pageForm.getUserAttributes());
|
||||
} else {
|
||||
cnd.andEX("record.userAttribute", "=", pageForm.getUserAttribute());
|
||||
}
|
||||
|
||||
cnd.groupBy("record.id,task.id");
|
||||
}
|
||||
|
||||
+6
@@ -396,6 +396,12 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
|
||||
cnd.andEX("mh.personType", "=", pageForm.getPersonType());
|
||||
cnd.andEX("mh.preparedBy", "=", pageForm.getPreparedBy());
|
||||
cnd.andEX("mh.userState", "=", pageForm.getUserState());
|
||||
// 历史台账未复用通用 buildSearch,需要在此显式处理人员属性多选条件。
|
||||
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
|
||||
cnd.and("mh.userAttribute", "in", pageForm.getUserAttributes());
|
||||
} else {
|
||||
cnd.andEX("mh.userAttribute", "=", pageForm.getUserAttribute());
|
||||
}
|
||||
if (StrUtil.isAllNotBlank(pageForm.getMemberSearchName(), pageForm.getMemberSearchKeyWord())) {
|
||||
cnd.and(new SqlExpressionGroup().andLike("u." + pageForm.getMemberSearchName(), pageForm.getMemberSearchKeyWord()));
|
||||
}
|
||||
|
||||
+6
@@ -175,6 +175,12 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
|
||||
cnd.andEX("his.unitId", "=", pageForm.getUnitId());
|
||||
cnd.andEX("his.personType", "=", pageForm.getPersonType());
|
||||
cnd.andEX("his.userState", "=", pageForm.getUserState());
|
||||
// 变更记录查询自行拼接历史表条件,需要单独补充人员属性多选及旧单值兼容。
|
||||
if (Lang.isNotEmpty(pageForm.getUserAttributes())) {
|
||||
cnd.and("his.userAttribute", "in", pageForm.getUserAttributes());
|
||||
} else {
|
||||
cnd.andEX("his.userAttribute", "=", pageForm.getUserAttribute());
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageForm.getChangeType())){
|
||||
cnd.and(new Static("JSON_CONTAINS(his.changeTypes, JSON_ARRAY('%s'), '$')".formatted(pageForm.getChangeType())));
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ public class WelfareFilterUserPageForm extends PageForm {
|
||||
@ApiModelProperty("人员类型")
|
||||
private String[] personTypes;
|
||||
|
||||
@ApiModelProperty("人员属性")
|
||||
private String[] userAttributes;
|
||||
|
||||
@ApiModelProperty("聘用方式")
|
||||
private String[] preparedBys;
|
||||
|
||||
|
||||
@@ -444,11 +444,13 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
|
||||
u.userState,
|
||||
u.unitId,
|
||||
u.unitName,
|
||||
threeUnit.name AS threeUnitName,
|
||||
u.unionId,
|
||||
u.unionName,
|
||||
u.member
|
||||
FROM
|
||||
vw_user u
|
||||
LEFT JOIN sys_unit threeUnit ON threeUnit.id = u.threeUnitId
|
||||
LEFT JOIN welfare_list w ON u.id = w.userId AND w.projectId = @projectId
|
||||
$condition
|
||||
""");
|
||||
@@ -471,6 +473,7 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
|
||||
cnd.andEX("u.unionId", "=", pageForm.getUnionId());
|
||||
cnd.andEX("u.unitId", "in", pageForm.getUnitIds());
|
||||
cnd.andEX("u.personType", "in", pageForm.getPersonTypes());
|
||||
cnd.andEX("u.userAttribute", "in", pageForm.getUserAttributes());
|
||||
cnd.andEX("u.preparedBy", "in", pageForm.getPreparedBys());
|
||||
cnd.andEX("u.userState", "in", pageForm.getUserStates());
|
||||
cnd.andEX("u.member","=",pageForm.getIsMember());
|
||||
@@ -523,6 +526,7 @@ public class WelfareListServiceImpl extends BaseServiceImpl<WelfareList> impleme
|
||||
cnd.andEX("unionId", "=", pageForm.getUnionId());
|
||||
cnd.andEX("unitId", "in", pageForm.getUnitIds());
|
||||
cnd.andEX("personType", "in", pageForm.getPersonTypes());
|
||||
cnd.andEX("userAttribute", "in", pageForm.getUserAttributes());
|
||||
cnd.andEX("preparedBy", "in", pageForm.getPreparedBys());
|
||||
cnd.andEX("userState", "in", pageForm.getUserStates());
|
||||
cnd.andEX("member","=",pageForm.getIsMember());
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 评优评先申请表新增历史导入来源和类型名称字段。
|
||||
ALTER TABLE `evaluate_apply`
|
||||
ADD COLUMN `source` int(2) DEFAULT 0 COMMENT '来源 0.流程申请 1.历史导入' AFTER `honorTypeId`,
|
||||
ADD COLUMN `typeName` varchar(200) DEFAULT NULL COMMENT '类型名称' AFTER `source`;
|
||||
@@ -0,0 +1,122 @@
|
||||
-- 普惠疗休养平台电脑端菜单。
|
||||
-- 使用 permission 做幂等判断,避免同一环境重复执行时插入重复菜单。
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT
|
||||
'b7c63f0c73a44a9a8c4f3d3bb1a10001',
|
||||
'',
|
||||
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
|
||||
'普惠疗休养',
|
||||
'Tour',
|
||||
'menu',
|
||||
'',
|
||||
'',
|
||||
'ti-map-alt',
|
||||
1,
|
||||
0,
|
||||
'tour',
|
||||
NULL,
|
||||
991,
|
||||
1,
|
||||
'',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
'',
|
||||
UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
0,
|
||||
'PC',
|
||||
NULL,
|
||||
NULL,
|
||||
'p',
|
||||
0,
|
||||
0
|
||||
FROM sys_menu
|
||||
WHERE (parentId = '' OR parentId IS NULL)
|
||||
AND CHAR_LENGTH(path) = 4
|
||||
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10002', p.id, CONCAT(p.path, '0001'), '疗休养设置', 'Tour Setting', 'menu', '/platform/tour/setting', 'data-pjax', '', 1, 0, 'tour.setting', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.setting') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10003', p.id, CONCAT(p.path, '0002'), '服务单位管理', 'Service Unit', 'menu', '/platform/tour/travelAgency', 'data-pjax', '', 1, 0, 'tour.travelAgency', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.travelAgency') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10004', p.id, CONCAT(p.path, '0003'), '线路管理', 'Route Manage', 'menu', '/platform/tour/route', 'data-pjax', '', 1, 0, 'tour.route', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.route') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10005', p.id, CONCAT(p.path, '0004'), '疗休养事项', 'Tour Matter', 'menu', '/platform/tour/matter', 'data-pjax', '', 1, 0, 'tour.matter', NULL, 4, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.matter') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10006', p.id, CONCAT(p.path, '0005'), '疗休养报名', 'Tour Signup', 'menu', '/platform/tour/signup', 'data-pjax', '', 1, 0, 'tour.signup', NULL, 5, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.signup') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10008', p.id, CONCAT(p.path, '0006'), '我的报名', 'My Signup', 'menu', '/platform/tour/mysignup', 'data-pjax', '', 1, 0, 'tour.mysignup', NULL, 6, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'w', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.mysignup') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10009', p.id, CONCAT(p.path, '0007'), '分工会查询', 'Union Ledger', 'menu', '/platform/tour/unionledger', 'data-pjax', '', 1, 0, 'tour.unionledger', NULL, 7, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.unionledger') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10011', p.id, CONCAT(p.path, '0010'), '分工会审核', 'Union Approval', 'menu', '/platform/tour/unionApproval', 'data-pjax', '', 1, 0, 'tour.unionApproval', NULL, 8, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.unionApproval') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10012', p.id, CONCAT(p.path, '0011'), '校工会审核', 'School Union Approval', 'menu', '/platform/tour/schoolUnionApproval', 'data-pjax', '', 1, 0, 'tour.schoolUnionApproval', NULL, 9, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.schoolUnionApproval') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10007', p.id, CONCAT(p.path, '0008'), '疗休养台账', 'Tour Ledger', 'menu', '/platform/tour/ledger', 'data-pjax', '', 1, 0, 'tour.ledger', NULL, 10, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.ledger') t);
|
||||
|
||||
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
|
||||
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10010', p.id, CONCAT(p.path, '0009'), '线路成团', 'Tour Group', 'menu', '/platform/tour/group', 'data-pjax', '', 1, 0, 'tour.group', NULL, 11, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
|
||||
FROM sys_menu p
|
||||
WHERE p.permission = 'tour'
|
||||
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.group') t);
|
||||
|
||||
-- 给系统管理员默认授权,其他角色可在角色管理中按需分配。
|
||||
INSERT INTO sys_role_menu (roleId, menuId)
|
||||
SELECT r.id, m.id
|
||||
FROM sys_role r
|
||||
JOIN sys_menu m ON m.permission IN (
|
||||
'tour',
|
||||
'tour.setting',
|
||||
'tour.travelAgency',
|
||||
'tour.route',
|
||||
'tour.matter',
|
||||
'tour.signup',
|
||||
'tour.group',
|
||||
'tour.mysignup',
|
||||
'tour.unionledger',
|
||||
'tour.unionApproval',
|
||||
'tour.schoolUnionApproval',
|
||||
'tour.ledger'
|
||||
)
|
||||
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
|
||||
WHERE r.code = 'SYSADMIN'
|
||||
AND rm.roleId IS NULL;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user