This commit is contained in:
=
2026-07-14 09:19:04 +08:00
parent 9b4f4d57be
commit 24e2d9e826
9 changed files with 428 additions and 218 deletions
@@ -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);
@@ -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
@@ -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;
}
@@ -38,6 +38,13 @@ layout("/layouts/platform.html"){
<el-table-column label="身份" prop="roleName" show-overflow-tooltip></el-table-column>
<el-table-column label="入会时间" prop="joinTime" show-overflow-tooltip></el-table-column>
<el-table-column label="退会时间" prop="exitTime" show-overflow-tooltip></el-table-column>
<el-table-column label="退出方式" width="110">
<template v-slot="{row}">
<el-tag :type="row.exitType === 'AUDIT_EXIT' ? 'success' : 'info'" size="mini">
{{ exitTypeName(row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template v-slot="{row}">
<el-button size="mini" type="primary" @click="onView(row)">查看</el-button>
@@ -76,14 +83,17 @@ layout("/layouts/platform.html"){
async getClubList() {
const resp = await this.$axios.post("/platform/club/infoManage/exitManage/getClubList")
if (resp.code === 0) {
this.clubList = resp.data
this.$set(this, "clubList", resp.data)
}
},
exitTypeName(row) {
return row.exitType === "AUDIT_EXIT" ? "审核退会" : "直接移出"
},
async pageData() {
const resp = await this.$axios.post("/platform/club/infoManage/exitManage/pageData", this.pageForm)
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
this.$set(this, "tableData", resp.data.list)
this.$set(this.pageForm, "totalCount", resp.data.totalCount)
} else {
this.$message.warning(resp.msg)
}
@@ -1,58 +1,60 @@
const clubExitInfo = {
template:
/*language=HTML*/
`
<div>
<div class="process-title">
退会信息
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions border>
<el-descriptions-item label="姓名">{{viewData.userName}}</el-descriptions-item>
<el-descriptions-item label="工号">{{viewData.loginName}}</el-descriptions-item>
<el-descriptions-item label="性别">{{viewData.sex}}</el-descriptions-item>
<el-descriptions-item label="出生年月">{{$moment(viewData.birthday).format('YYYY-MM-DD')}}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{viewData.mobile}}</el-descriptions-item>
<el-descriptions-item label="电子信箱">{{viewData.email}}</el-descriptions-item>
<el-descriptions-item label="分工会">{{viewData.unionName}}</el-descriptions-item>
<el-descriptions-item label="部门" :span="2">{{viewData.unitName}}</el-descriptions-item>
<el-descriptions-item label="职务">{{viewData.position}}</el-descriptions-item>
<el-descriptions-item label="职称" :span="2">{{viewData.technicalTitle}}</el-descriptions-item>
<el-descriptions-item label="学历">{{viewData.education}}</el-descriptions-item>
<el-descriptions-item label="学位" :span="2">{{viewData.academicDegree}}</el-descriptions-item>
<el-descriptions-item label="同时参加其他社团情况" :span="3">{{viewData.sameTimeJoinOtherClubSituation}}</el-descriptions-item>
<el-descriptions-item label="文化、体育方面的活动经历、获奖情况" :span="3">{{viewData.awardsExperience}}</el-descriptions-item>
<el-descriptions-item label="照片" :span="3">
<img :src="viewData.avatar" alt="" style="width: 120px;height: 150px" v-if="viewData.avatar">
</el-descriptions-item>
<el-descriptions-item label="签字" :span="3">
<el-image v-if="viewData.signature" :src="viewData.signature" class="signature-image"></el-image>
<span v-else>暂无</span>
</el-descriptions-item>
</el-descriptions>
<template v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" v-if="!task.ext.isFirstTaskNode">{{ task.taskFormData.opinion }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<snaker-chart ref="snakerChartRef"></snaker-chart>
</div>
`,
[
'<div>',
' <div class="process-title">',
' 退会信息',
' <el-link v-if="row.instanceId" type="primary" @click="openChart">点击查看流程图</el-link>',
' </div>',
' <el-descriptions border>',
' <el-descriptions-item label="姓名">{{viewData.userName}}</el-descriptions-item>',
' <el-descriptions-item label="工号">{{viewData.loginName}}</el-descriptions-item>',
' <el-descriptions-item label="退出方式">{{getExitTypeName()}}</el-descriptions-item>',
' <el-descriptions-item label="性别">{{viewData.sex}}</el-descriptions-item>',
' <el-descriptions-item label="出生年月">{{$moment(viewData.birthday).format(\'YYYY-MM-DD\')}}</el-descriptions-item>',
' <el-descriptions-item label="联系电话">{{viewData.mobile}}</el-descriptions-item>',
' <el-descriptions-item label="电子信箱">{{viewData.email}}</el-descriptions-item>',
' <el-descriptions-item label="分工会">{{viewData.unionName}}</el-descriptions-item>',
' <el-descriptions-item label="部门" :span="2">{{viewData.unitName}}</el-descriptions-item>',
' <el-descriptions-item label="职">{{viewData.position}}</el-descriptions-item>',
' <el-descriptions-item label="职称" :span="2">{{viewData.technicalTitle}}</el-descriptions-item>',
' <el-descriptions-item label="学">{{viewData.education}}</el-descriptions-item>',
' <el-descriptions-item label="学位" :span="2">{{viewData.academicDegree}}</el-descriptions-item>',
' <el-descriptions-item label="同时参加其他社团情况" :span="3">{{viewData.sameTimeJoinOtherClubSituation}}</el-descriptions-item>',
' <el-descriptions-item label="文化、体育方面的活动经历、获奖情况" :span="3">{{viewData.awardsExperience}}</el-descriptions-item>',
' <el-descriptions-item label="照片" :span="3">',
' <img :src="viewData.avatar" alt="" style="width: 120px;height: 150px" v-if="viewData.avatar">',
' </el-descriptions-item>',
' <el-descriptions-item label="签字" :span="3">',
' <el-image v-if="viewData.signature" :src="viewData.signature" class="signature-image"></el-image>',
' <span v-else>暂无</span>',
' </el-descriptions-item>',
' </el-descriptions>',
' <template v-for="task in doneTasks">',
' <div class="mt10">',
' <div class="process-title">{{ task.displayName }}</div>',
' <el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-if="task.ext.isFirstTaskNode">',
' <el-descriptions-item label="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</el-descriptions-item>',
' <el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>',
' <el-descriptions-item label="办理结果">',
' <dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>',
' </el-descriptions-item>',
' </el-descriptions>',
' <el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>',
' <el-descriptions-item label="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</el-descriptions-item>',
' <el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>',
' <el-descriptions-item label="办理结果">',
' <dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>',
' </el-descriptions-item>',
' <el-descriptions-item label="办理意见" v-if="!task.ext.isFirstTaskNode">{{ task.taskFormData.opinion }}</el-descriptions-item>',
' </el-descriptions>',
' </div>',
' </template>',
' <snaker-chart ref="snakerChartRef"></snaker-chart>',
' <slot></slot>',
'</div>'
].join(''),
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
@@ -63,33 +65,43 @@ const clubExitInfo = {
},
methods: {
onOpen(row) {
this.row = row
this.$set(this, "row", row || {})
this.$set(this, "viewData", {})
this.$set(this, "doneTasks", [])
this.$axios.post("/platform/club/infoManage/exitManage/info", { id: row.id }).then((res) => {
if (res.code === 0) {
this.viewData = res.data
this.$set(this, "viewData", res.data || {})
}
})
this.getDoneTasks()
if (row.instanceId) {
this.getDoneTasks()
}
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
this.$set(this, "doneTasks", res.data || [])
}
})
},
openChart(){
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
getExitTypeName() {
const exitType = this.row.exitType || this.viewData.exitType
return exitType === "AUDIT_EXIT" ? "审核退会" : "直接移出"
},
openChart() {
if (this.row.instanceId) {
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
}
}
},
style: /*language=CSS*/ `
.el-descriptions-item__label {
width: 200px;
min-width: 200px;
max-width: 200px;
}
.el-tabs__header {
margin: 0;
}
`
style: /*language=CSS*/ [
'.el-descriptions-item__label {',
' width: 200px;',
' min-width: 200px;',
' max-width: 200px;',
'}',
'.el-tabs__header {',
' margin: 0;',
'}'
].join('')
}
@@ -20,7 +20,12 @@ layout("/layouts/platform.html"){
</el-select>
</search-item>
<search-item label="人员状态:">
<dict-select clearable code="USER_STATE" placeholder="请选择人员状态" v-model="pageForm.userState"></dict-select>
<el-select clearable placeholder="请选择人员状态" v-model="pageForm.userState">
<el-option label="在职" value="在职"></el-option>
<el-option label="在岗" value="在岗"></el-option>
<el-option label="退休" value="退休"></el-option>
<el-option label="不在岗" value="不在岗"></el-option>
</el-select>
</search-item>
<search-item label="性别:">
<el-select clearable filterable placeholder="请选择性别" v-model="pageForm.sex">