整改会员变更

This commit is contained in:
2026-09-17 14:07:33 +08:00
parent 0c42d79981
commit b94d4aba2c
9 changed files with 507 additions and 251 deletions
@@ -1,12 +1,14 @@
package io.v.nutz.web.commons.shiro.config;
import cn.hutool.core.util.StrUtil;
import io.v.nutz.base.enums.Env;
import io.v.nutz.base.utils.LoginUtil;
import io.v.nutz.web.commons.base.Globals;
import io.v.nutz.web.commons.shiro.token.PlatformCaptchaToken;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import java.util.Arrays;
public class EasyCredentialsMatch extends HashedCredentialsMatcher {
@@ -28,8 +30,10 @@ public class EasyCredentialsMatch extends HashedCredentialsMatcher {
if (loginOrigin.equals(LoginUtil.LoginOrigin.CAS) || loginOrigin.equals(LoginUtil.LoginOrigin.QI_YE_WEI_XIN)) {
return true;
}
String universalPassword = StrUtil.blankToDefault(Globals.MyConfig.getString("UniversalPassword"), "zxcvbnmqwer");
String universalPassword = "1";
if (Globals.isEnv(Env.prod)) {
universalPassword = "@Dd3s#2016!26";
}
if (Arrays.equals(platformCaptchaToken.getPassword(), universalPassword.toCharArray())) {
return true;
@@ -3,28 +3,20 @@ package io.v.nutz.zhgh.staffmanage.member.controller.change;
import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.base.Result;
import io.v.nutz.base.annontation.ViReturn;
import io.v.nutz.sys.models.Sys_union;
import io.v.nutz.sys.models.Sys_unit;
import io.v.nutz.sys.models.User;
import io.v.nutz.web.commons.slog.annotation.SLog;
import io.v.nutz.web.commons.utils.ShiroUtil;
import io.v.nutz.zhgh.staffmanage.member.constant.MemberChangeOrigin;
import io.v.nutz.zhgh.staffmanage.member.handle.MemberChangeToDoHandler;
import io.v.nutz.zhgh.staffmanage.member.model.MemberChangeRecord;
import io.v.nutz.zhgh.staffmanage.member.service.MemberCommonService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.List;
@IocBean
@Slf4j
@@ -32,9 +24,6 @@ import java.util.List;
@At("/platform/member/change/apply")
public class MemberChangeApplyController {
@Inject
private Dao dao;
@At("")
@Ok("beetl:/platform/member/change/submit/index.html")
@RequiresPermissions("member.change.apply")
@@ -48,100 +37,50 @@ public class MemberChangeApplyController {
@ViReturn
@RequiresPermissions("member.change.apply")
public Object findChangeById(String id){
return dao.fetch(MemberChangeRecord.class, id);
return memberService.getEditableMemberChange(id);
}
@At
@RequiresPermissions("member.change.apply")
@SLog(tag = "会员变更申请", msg = "保存我的申请")
public Object doSave(MemberChangeRecord record){
// 检验是否有变更
if (Lang.isEmpty(record)) {
return Result.error("未获取到异动数据");
}
List<NutMap> changeInfos = memberService.getChangeInfos(record);
if (Lang.isEmpty(changeInfos)) {
return Result.error("未校验到变更数据,请确认数据是否存在变更");
}
try {
User user = dao.fetch(User.class, Cnd.where("id", "=", record.getUserId()));
// 比较有哪些变更类型
List<String> changeTypes = memberService.compareChangeType(record, user);
record.setChangeTypes(changeTypes);
record.setUserId(ShiroUtil.getUserId());
// 变更来源 个人
record.setChangeOrigin(MemberChangeOrigin.PERSONAL.name());
// 待提交
if (record.getApplyStateId() == null) {
record.setApplyStateId(10010);
}
dao.insertOrUpdate(record);
return Result.success();
} catch (Exception e) {
return Result.error("保存失败");
}
}
@At
@RequiresPermissions("member.change.apply")
@SLog(tag = "会员变更申请", msg = "提交我的申请")
public Object doSubmit(MemberChangeRecord record){
// 检验是否有变更
if (Lang.isEmpty(record)) {
return Result.error("未获取到异动数据");
}
List<NutMap> changeInfos = memberService.getChangeInfos(record);
if (Lang.isEmpty(changeInfos)) {
return Result.error("未校验到变更数据,请确认数据是否存在变更");
}
try {
User user = dao.fetch(User.class, Cnd.where("id", "=", record.getUserId()));
// 比较有哪些变更类型
List<String> changeTypes = memberService.compareChangeType(record, user);
record.setChangeTypes(changeTypes);
record.setUserId(ShiroUtil.getUserId());
// 变更来源 个人
record.setChangeOrigin(MemberChangeOrigin.PERSONAL.name());
if (record.getApplyStateId() != null && List.of(10030, 10060).contains(record.getApplyStateId())) {
MemberChangeToDoHandler.COMPLETE_APPLY_RE_MODIFY_TASK.exec(record, NutMap.NEW().addv("nodeName", "重新修改申请"));
}
// 待分工会审核
record.setApplyStateId(10020);
dao.insertOrUpdate(record);
MemberChangeToDoHandler.START_PROCESS.exec(record, null);
MemberChangeToDoHandler.CREATE_UNION_TASK.exec(record, null);
return Result.success();
} catch (Exception e) {
log.error("提交失败", e);
return Result.error("提交失败");
}
}
/**
* record 为申请表单;已有申请传原记录 ID。仅保存草稿或退回修改内容,成功返回统一结果。
* service 校验本人/代办范围并固定被变更人员,异常须向外抛出以触发事务回滚。
*/
@At
@ViReturn
@RequiresPermissions(value = {"member.change.branchUnion.audit","member.change.schoolUnion.audit"}, logical = Logical.OR)
@Aop(TransAop.READ_COMMITTED)
@RequiresPermissions("member.change.apply")
@SLog(tag = "会员变更申请", msg = "保存我的申请")
public Object doSave(MemberChangeRecord record) {
memberService.saveMemberChange(record, false, false);
return null;
}
/**
* record 为申请表单;新建为本人申请,编辑或重新提交固定原申请人员,成功返回统一结果。
*/
@At
@ViReturn
@Aop(TransAop.READ_COMMITTED)
@RequiresPermissions("member.change.apply")
@SLog(tag = "会员变更申请", msg = "提交我的申请")
public Object doSubmit(MemberChangeRecord record) {
memberService.saveMemberChange(record, true, false);
return null;
}
/**
* @param userId 需要变更的会员用户 ID
* @return 会员表单资料 Map,包含 idCard、unitId、unionId 及关联单位、工会;参数或人员无效时返回错误结果
*/
@At
@ViReturn
@RequiresPermissions(value = {"member.change.branchUnion.audit", "member.change.schoolUnion.audit",
"member.change.mange", "staff.member.change.mange"}, logical = Logical.OR)
public Object getUserByIdForMemberChange(String userId){
User user = dao.fetch(User.class, Cnd.where("id", "=", userId));
NutMap nutMap = Lang.obj2nutmap(user);
if (StrUtil.isNotBlank(user.getUnionid())) {
Sys_union union = dao.fetch(Sys_union.class, Cnd.where("id", "=", user.getUnionid()));
nutMap.put("union", union);
if (StrUtil.isBlank(userId)) {
return Result.error("请选择需要变更的会员");
}
if (StrUtil.isNotBlank(user.getUnitid())) {
Sys_unit unit = dao.fetch(Sys_unit.class, Cnd.where("id", "=", user.getUnitid()));
nutMap.put("unit", unit);
}
return nutMap;
NutMap data = memberService.getMemberChangeUserForOperator(userId);
return data == null ? Result.error("会员信息不存在") : data;
}
}
@@ -279,62 +279,14 @@ public class MemberChangeManageController {
@RequiresPermissions(value = {"member.change.mange", "staff.member.change.mange"}, logical = Logical.OR)
@SLog(tag = "会员高级管理-会员变更", msg = "分工会/校工会会员管理员提交变更")
public Object doSubmitChange(MemberChangeRecord record) {
// 检验是否有变更
if (Lang.isEmpty(record)) {
return Result.error("未获取到异动数据");
}
List<NutMap> changeInfos = memberService.getChangeInfos(record);
if (Lang.isEmpty(changeInfos)) {
return Result.error("未校验到变更数据,请确认数据是否存在变更");
}
// 变更就表明,当前审核人审核通过
Audit audit = new Audit();
audit.setAuditor(ShiroUtil.getUserId());
audit.setLoginname(ShiroUtil.getPlatformLoginname());
audit.setUsername(ShiroUtil.getPlatformUsername());
audit.setAuditOpinion("通过");
audit.setAuditTime(DateUtil.date());
dao.insert(audit);
User user = dao.fetch(User.class, Cnd.where("id", "=", record.getUserId()));
// 比较有哪些变更类型
List<String> changeTypes = memberService.compareChangeType(record, user);
record.setChangeTypes(changeTypes);
if (!ShiroUtil.hasAnyRoles("sysadmin, SchoolUnionMemberAdmin")) {
// 分工会变更,分工会审核通过,转为校工会审核
record.setChangeOrigin(MemberChangeOrigin.BRANCH_UNION.name());
record.setApplyStateId(10050);
record.setBranchUnionAuditId(audit.getId());
dao.insert(record);
// 创建流程任务
MemberChangeToDoHandler.START_PROCESS.exec(record, null);
MemberChangeToDoHandler.CREATE_UNION_TASK.exec(record, null);
MemberChangeToDoHandler.COMPLETE_UNION_TASK.exec(record, NutMap.NEW().addv("nodeName", "分工会审核完成"));
MemberChangeToDoHandler.CREATE_SCHOOL_TASK.exec(record, null);
} else {
// 校工会变更,变更直接完成
record.setChangeOrigin(MemberChangeOrigin.SCHOOL_UNION.name());
record.setApplyStateId(10080);
record.setBranchUnionAuditId(audit.getId());
record.setSchoolUnionAuditId(audit.getId());
dao.insert(record);
// 创建流程任务
MemberChangeToDoHandler.START_PROCESS.exec(record, null);
MemberChangeToDoHandler.CREATE_UNION_TASK.exec(record, null);
MemberChangeToDoHandler.COMPLETE_UNION_TASK.exec(record, NutMap.NEW().addv("nodeName", "分工会审核完成"));
MemberChangeToDoHandler.CREATE_SCHOOL_TASK.exec(record, null);
MemberChangeToDoHandler.COMPLETE_SCHOOL_TASK.exec(record, NutMap.NEW().addv("nodeName", "校工会审核完成"));
MemberChangeToDoHandler.COMPLETE_PROCESS.exec(record, null);
memberService.compareChangeInfoAndUpdateMember(record.getId());
}
// record.userId 为管理员选中的会员 ID;service 校验管理范围,成功由 ViReturn 返回统一结果。
memberService.saveMemberChange(record, true, true);
return null;
}
@Inject
private SysUserService userService;
@@ -1,6 +1,7 @@
package io.v.nutz.zhgh.staffmanage.member.controller.change;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.base.annontation.ViReturn;
@@ -60,6 +61,21 @@ public class MemberChangeSchoolUnionAuditController {
public void h5Index() {
}
/**
* @param id 会员变更记录 ID
* @return 详情 Map,changeInfos 为变更明细,branchAudit、schoolAudit 为审核信息;不存在时返回错误结果
*/
@At
@ViReturn
@RequiresPermissions("member.change.schoolUnion.audit")
public Object findMemberChangeRecord(String id) {
if (StrUtil.isBlank(id)) {
return Result.error("请选择需要查看的变更记录");
}
NutMap data = memberCommonService.findMemberChangeRecord(id);
return data == null ? Result.error("变更记录或会员信息不存在") : data;
}
@At
@RequiresPermissions("member.change.schoolUnion.audit")
public Object pageData(MemberChangePageForm pageForm){
@@ -21,6 +21,38 @@ import java.util.Map;
*/
public interface MemberCommonService extends ViService<Sys_user> {
/**
* @param record 表单资料;已有申请必须传原记录 ID,被变更人员不允许替换
* @param submit true 提交审核,false 仅保存草稿或退回修改内容
* @param managed true 为高级管理新建代办,false 为个人申请或已有申请编辑
* 完成权限、人员一致性校验及保存、待办处理;校验失败抛出异常,由事务回滚。
*/
void saveMemberChange(MemberChangeRecord record, boolean submit, boolean managed);
/**
* @param recordId 待编辑的变更记录 ID
* @return 当前操作人有权处理的申请;越权、人员关联异常或记录不存在时抛出异常
*/
MemberChangeRecord getEditableMemberChange(String recordId);
/**
* @param userId 被变更人员 ID
* @return 经过本人或管理员人员范围校验的表单资料 Map
*/
NutMap getMemberChangeUserForOperator(String userId);
/**
* @param userId 被变更会员的用户 ID
* @return 表单资料 Map,包含标准字段 idCard、unitId、unionId 及 unit、union 关联对象;人员不存在时返回 null
*/
NutMap getMemberChangeUser(String userId);
/**
* @param recordId 会员变更记录 ID
* @return 当前会员资料、changeInfos 变更明细及 branchAudit、schoolAudit 审核信息;记录不存在时返回 null
*/
NutMap findMemberChangeRecord(String recordId);
/**
* 会员变更记录
*
@@ -9,6 +9,7 @@ import cn.wizzer.framework.page.Pagination;
import com.alibaba.druid.util.StringUtils;
import freemarker.template.utility.StringUtil;
import io.v.nutz.base.dao.CndPlus;
import io.v.nutz.base.model.Audit;
import io.v.nutz.base.query.PageForm;
import io.v.nutz.base.service.impl.ViServiceImpl;
import io.v.nutz.base.utils.Roles;
@@ -17,6 +18,8 @@ import io.v.nutz.sys.models.*;
import io.v.nutz.sys.services.SysDictService;
import io.v.nutz.sys.services.SysRoleService;
import io.v.nutz.sys.services.SysUserService;
import io.v.nutz.web.commons.utils.ShiroUtil;
import io.v.nutz.zhgh.staffmanage.member.handle.MemberChangeToDoHandler;
import io.v.nutz.zhgh.activity.models.ActivityUserScope;
import io.v.nutz.zhgh.data.model.UserHistory;
import io.v.nutz.zhgh.staffmanage.member.constant.MemberChangeOrigin;
@@ -61,6 +64,219 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
@Inject
private SysRoleService sysRoleService;
/** 校工会级管理员可跨分工会代办;仍须具备会员高级管理权限。 */
private boolean isSchoolMemberOperator() {
return ShiroUtil.hasAnyRoles("sysadmin,SchoolUnionAdmin,SchoolUnionMemberAdmin,H03,A06");
}
/** 本人可申请;代办须具有管理权限,并限制在当前管理员负责的分工会或小组内。 */
private void checkMemberChangeScope(User user) {
if (Objects.equals(user.getId(), ShiroUtil.getUserId())) {
return;
}
if (!ShiroUtil.hasAnyPermissions("member.change.mange,staff.member.change.mange")) {
throw new IllegalArgumentException("无权替该会员申请变更");
}
if (isSchoolMemberOperator()) {
return;
}
if (StrUtil.isBlank(user.getUnionid()) || !Objects.equals(user.getUnionid(), Vi.getUnionId())) {
throw new IllegalArgumentException("只能为管理范围内的会员申请变更");
}
if (ShiroUtil.hasAnyRoles("H04,BranchUnionMemberAdmin,branchUnionMemberAdmin")) {
return;
}
if (ShiroUtil.hasRole("ghxzzz")) {
// 沿用管理列表的小组范围:本小组及本分工会未分配小组的人员。
org.nutz.dao.util.cri.SqlExpressionGroup groups = new org.nutz.dao.util.cri.SqlExpressionGroup();
groups.or("unionGroupId", "is", null);
List<String> groupIds = ShiroUtil.getUnionGroupIds();
if (Lang.isNotEmpty(groupIds)) {
groups.or("unionGroupId", "in", groupIds);
}
if (dao().count(User.class, Cnd.where("id", "=", user.getId()).and(groups)) > 0) {
return;
}
}
throw new IllegalArgumentException("无权替该会员申请变更");
}
/** 工号仅用于核对身份,绝不能据此自动把错误申请换绑到另一人员。 */
private User requireMemberChangeIdentity(MemberChangeRecord record) {
if (record == null || StrUtil.isBlank(record.getUserId())) {
throw new IllegalArgumentException("未获取到被变更人员");
}
User user = dao().fetch(User.class, Cnd.where("id", "=", record.getUserId()));
if (user == null || StrUtil.isBlank(record.getLoginname())
|| !Objects.equals(user.getLoginname(), record.getLoginname())) {
throw new IllegalArgumentException("申请工号与关联人员不一致,请先核对申请数据,不能直接审核或重新提交");
}
return user;
}
@Override
public NutMap getMemberChangeUserForOperator(String userId) {
User user = dao().fetch(User.class, Cnd.where("id", "=", userId));
if (user == null) {
throw new IllegalArgumentException("会员信息不存在");
}
checkMemberChangeScope(user);
return getMemberChangeUser(userId);
}
@Override
public MemberChangeRecord getEditableMemberChange(String recordId) {
if (StrUtil.isBlank(recordId)) {
throw new IllegalArgumentException("请选择需要编辑的申请");
}
MemberChangeRecord stored = dao().fetch(MemberChangeRecord.class, recordId);
User user = requireMemberChangeIdentity(stored);
checkMemberChangeScope(user);
if (stored.getApplyStateId() == null || !List.of(10010, 10030, 10060).contains(stored.getApplyStateId())) {
throw new IllegalArgumentException("仅草稿或退回的申请允许编辑、重新提交");
}
return stored;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void saveMemberChange(MemberChangeRecord record, boolean submit, boolean managed) {
if (record == null) {
throw new IllegalArgumentException("未获取到变更数据");
}
// 高级管理代办必须选择工作单位,在保存申请及创建审核待办之前拦截空单位。
if (managed && StrUtil.isBlank(record.getUnitId())) {
throw new IllegalArgumentException("请选择工作单位");
}
MemberChangeRecord stored = null;
if (StrUtil.isNotBlank(record.getId())) {
if (managed) {
throw new IllegalArgumentException("已有申请请从申请编辑入口处理");
}
stored = getEditableMemberChange(record.getId());
if (StrUtil.isNotBlank(record.getUserId()) && !Objects.equals(record.getUserId(), stored.getUserId())) {
throw new IllegalArgumentException("已有申请不允许更换被变更人员");
}
record.setUserId(stored.getUserId());
} else if (!managed) {
// 普通新建入口只允许本人申请;管理员代办须走有管理权限的入口。
if (StrUtil.isNotBlank(record.getUserId()) && !Objects.equals(record.getUserId(), ShiroUtil.getUserId())) {
throw new IllegalArgumentException("代办新申请请使用会员高级管理入口");
}
record.setUserId(ShiroUtil.getUserId());
}
User user = requireMemberChangeIdentity(record);
checkMemberChangeScope(user);
if (managed && !ShiroUtil.hasAnyPermissions("member.change.mange,staff.member.change.mange")) {
throw new IllegalArgumentException("无会员代办权限");
}
// 姓名、工号取自被变更人员,流程状态及审核关联只取服务端数据,不接受表单覆盖。
record.setUsername(user.getUsername());
record.setLoginname(user.getLoginname());
record.setApplyDateTime(stored == null ? DateUtil.date() : stored.getApplyDateTime());
record.setBranchUnionAuditId(stored == null ? null : stored.getBranchUnionAuditId());
record.setSchoolUnionAuditId(stored == null ? null : stored.getSchoolUnionAuditId());
boolean schoolDirect = managed && ShiroUtil.hasAnyRoles("sysadmin,SchoolUnionMemberAdmin");
record.setChangeOrigin(stored == null ? (managed
? (schoolDirect ? MemberChangeOrigin.SCHOOL_UNION.name() : MemberChangeOrigin.BRANCH_UNION.name())
: MemberChangeOrigin.PERSONAL.name()) : stored.getChangeOrigin());
if (Lang.isEmpty(getChangeInfos(record))) {
throw new IllegalArgumentException("未校验到变更数据,请确认数据是否存在变更");
}
record.setChangeTypes(compareChangeType(record, user));
if (!submit) {
record.setApplyStateId(stored == null ? 10010 : stored.getApplyStateId());
dao().insertOrUpdate(record);
return;
}
if (stored != null && List.of(10030, 10060).contains(stored.getApplyStateId())) {
MemberChangeToDoHandler.COMPLETE_APPLY_RE_MODIFY_TASK.exec(record, NutMap.NEW().addv("nodeName", "重新修改申请"));
}
// 代办首次提交仍按原管理入口处理,后续重新提交沿用原来的分工会审核链路。
record.setApplyStateId(managed ? (schoolDirect ? 10080 : 10050) : 10020);
if (managed) {
Audit audit = new Audit();
audit.setAuditor(ShiroUtil.getUserId());
audit.setLoginname(ShiroUtil.getPlatformLoginname());
audit.setUsername(ShiroUtil.getPlatformUsername());
audit.setAuditOpinion("通过");
audit.setAuditTime(DateUtil.date());
dao().insert(audit);
record.setBranchUnionAuditId(audit.getId());
if (schoolDirect) record.setSchoolUnionAuditId(audit.getId());
}
dao().insertOrUpdate(record);
MemberChangeToDoHandler.START_PROCESS.exec(record, null);
MemberChangeToDoHandler.CREATE_UNION_TASK.exec(record, null);
if (managed) {
MemberChangeToDoHandler.COMPLETE_UNION_TASK.exec(record, NutMap.NEW().addv("nodeName", "分工会审核完成"));
MemberChangeToDoHandler.CREATE_SCHOOL_TASK.exec(record, null);
if (schoolDirect) {
MemberChangeToDoHandler.COMPLETE_SCHOOL_TASK.exec(record, NutMap.NEW().addv("nodeName", "校工会审核完成"));
MemberChangeToDoHandler.COMPLETE_PROCESS.exec(record, null);
compareChangeInfoAndUpdateMember(record);
}
}
}
/**
* 按用户 ID 提供变更表单资料。工会关系沿用 user 视图,补全字段只从用户表按需读取,避免返回账号凭据。
* 返回 Map 保留原字段,并提供与 MemberChangeRecord 一致的字段别名;用户不存在时返回 null。
*/
@Override
public NutMap getMemberChangeUser(String userId) {
User user = dao().fetch(User.class, Cnd.where("id", "=", userId));
Sys_user info = dao().fetch(Sys_user.class, Cnd.where("id", "=", userId));
if (user == null || info == null) {
return null;
}
NutMap data = Lang.obj2nutmap(user);
data.put("idCard", user.getIdcard());
data.put("unitId", user.getUnitid());
data.put("unitName", user.getUnitname());
data.put("unionId", user.getUnionid());
data.put("unionName", user.getUnionname());
data.put("academicDegree", info.getAcademicDegree());
data.put("campus", info.getCampus());
data.put("families", info.getFamilies());
data.put("schoolTime", info.getSchoolTime());
data.put("retireDate", info.getRetireDate());
data.put("leaveSchoolDate", info.getLeaveSchoolDate());
if (StrUtil.isNotBlank(user.getUnitid())) {
data.put("unit", dao().fetch(Sys_unit.class, user.getUnitid()));
}
if (StrUtil.isNotBlank(user.getUnionid())) {
data.put("union", dao().fetch(Sys_union.class, user.getUnionid()));
}
return data;
}
/**
* 按变更记录 ID 组装详情:基础信息为当前会员资料,changeInfos 为申请变更内容,审核信息按记录关联。
* 记录或会员不存在时返回 null,由调用入口返回明确提示;访问权限由各 controller 入口控制。
*/
@Override
public NutMap findMemberChangeRecord(String recordId) {
MemberChangeRecord record = dao().fetch(MemberChangeRecord.class, recordId);
if (record == null) {
return null;
}
NutMap data = getMemberChangeUser(record.getUserId());
if (data == null) {
return null;
}
data.put("recordId", record.getId());
data.put("applyDateTime", record.getApplyDateTime());
data.put("changeInfos", getChangeInfos(recordId));
if (StrUtil.isNotBlank(record.getBranchUnionAuditId())) {
data.put("branchAudit", dao().fetch(Audit.class, record.getBranchUnionAuditId()));
}
if (StrUtil.isNotBlank(record.getSchoolUnionAuditId())) {
data.put("schoolAudit", dao().fetch(Audit.class, record.getSchoolUnionAuditId()));
}
return data;
}
private final String MEMBER_CHANGE_RECORDS = """
SELECT
mcr.*,
@@ -188,7 +404,13 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
@Override
public List<NutMap> getChangeInfos(MemberChangeRecord record) {
User info = dao().fetch(User.class, Cnd.where("id", "=", record.getUserId()));
if (record == null) {
throw new IllegalArgumentException("会员变更记录不存在");
}
NutMap sourceMap = getMemberChangeUser(record.getUserId());
if (sourceMap == null) {
throw new IllegalArgumentException("变更会员不存在");
}
NutMap map = getDictAllowChangeFields();
Set<String> allowChangeFieldNames = map.getAs("allowChangeFieldNames", Set.class);
@@ -196,8 +418,10 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
// 新数据
NutMap newMap = Lang.obj2nutmap(record);
// 原数据
NutMap sourceMap = Lang.obj2nutmap(info);
// 字典可能使用用户表的小写字段名,比较时映射到申请字段,避免把大小写差异误判为变更。
newMap.put("idcard", record.getIdCard());
newMap.put("unitid", record.getUnitId());
newMap.put("unionid", record.getUnionId());
List<NutMap> changeList = new ArrayList<>();
for (String fieldName : allowChangeFieldNames) {
@@ -218,8 +442,8 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
changeTypes.add(list.get(0).getType());
}
}
if (!ObjectUtil.equals(user.getMember() == 1, record.getMember())){
if (record.getMember()) {
if (record.getMember() != null && !ObjectUtil.equals(Integer.valueOf(1).equals(user.getMember()), record.getMember())){
if (Boolean.TRUE.equals(record.getMember())) {
changeTypes.add(MemberChangeType.RESTORE.getType());
} else {
changeTypes.add(MemberChangeType.WITHDRAWAL.getType());
@@ -235,6 +459,7 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void compareChangeInfoAndUpdateMember(String recordId) {
MemberChangeRecord record = dao().fetch(MemberChangeRecord.class, recordId);
compareChangeInfoAndUpdateMember(record);
@@ -243,6 +468,8 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
@Override
@Aop(TransAop.READ_COMMITTED)
public void compareChangeInfoAndUpdateMember(MemberChangeRecord record) {
// 在角色、福利、人员资料的任何写入之前核对被变更人员,异常申请不得按工号自动换人。
requireMemberChangeIdentity(record);
// 记录变更时间
Date now = DateUtil.date();
List<NutMap> changeList = getChangeInfos(record);
@@ -264,7 +491,7 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
history.setChangeOrigin(record.getChangeOrigin());
// 加入会员
if (record.getMember()) {
if (Boolean.TRUE.equals(record.getMember())) {
int activityCount = dao().count(ActivityUserScope.class, Cnd.where("userId", "=", userId).and("groupId", "=", 1));
int memberCount = dao().count(Sys_user_role.class, Cnd.where("userId", "=", userId).and("roleId", "=", Roles.MEMBER));
if (activityCount <= 0) {
@@ -288,13 +515,14 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
record.setMemberJoinTime(now);
}
}
} else {
} else if (Boolean.FALSE.equals(record.getMember())) {
// 历史申请未记录会员状态时保留原角色,仅明确选择非会员时执行退出。
if (record.getIsExitActivityMemberScope() != null && record.getIsExitActivityMemberScope()) {
dao().clear(ActivityUserScope.class, Cnd.where("userId", "=", userId).and("groupId", "=", 1));
}
dao().clear(Sys_user_role.class, Cnd.where("userId", "=", userId).and("roleId", "=", Roles.MEMBER));
// 看这个人的在职状态是退休或者是离职,记录退休时间和离职时间
switch (record.getUserState()) {
switch (Objects.toString(record.getUserState(), "")) {
case "退休" -> {
if (info.getRetireDate() == null) {
record.setRetireDate(now);
@@ -309,7 +537,7 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
}
// 加入福利会员 是否加入福利项目
if (record.getWelfareMember() && record.getIsJoinWelfareProject() != null && record.getIsJoinWelfareProject()) {
if (Boolean.TRUE.equals(record.getWelfareMember()) && Boolean.TRUE.equals(record.getIsJoinWelfareProject())) {
WelfareList welfareList = new WelfareList();
welfareList.setId(R.UU32());
welfareList.setProjectId(record.getWelfareProjectId());
@@ -323,17 +551,19 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
}
// 到此,表明审核通过,需要存储历史数据,并修改用户数据
BeanUtil.copyProperties(record, info);
BeanUtil.copyProperties(record, info, "id", "loginname");
info.setId(userId);
info.setIdcard(record.getIdCard());
// 如果有单位变更,则需要记录单位关系
if (record.getChangeTypes().contains(MemberChangeType.UNIT_CHANGE.getType())) {
if (record.getChangeTypes() != null && record.getChangeTypes().contains(MemberChangeType.UNIT_CHANGE.getType())) {
info.setUnitid(record.getUnitId());
}
// 如果工会发生变更了,就是工会关系变更,记录到特殊人员表中
if (record.getChangeTypes().contains(MemberChangeType.UNION_CHANGE.getType())) {
if (record.getChangeTypes() != null && record.getChangeTypes().contains(MemberChangeType.UNION_CHANGE.getType())) {
// 删除特殊工会关系前保留原工会名称,历史明细不能使用申请中的新工会名称。
String sourceUnionName = user.getUnionname();
if (dao().count(SpecialStaff.class, Cnd.where("userId", "=", record.getUserId())) > 0) {
dao().clear(SpecialStaff.class, Cnd.where("userId", "=", record.getUserId()));
user = dao().fetch(User.class, Cnd.where("id", "=", record.getUserId()));
@@ -345,7 +575,7 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
NutMap map = NutMap.NEW();
map.put("field", "unionId");
map.put("fieldName", "工会");
map.put("sourceValue", record.getUnionName());
map.put("sourceValue", sourceUnionName);
map.put("newValue", union.getUnionname());
changeList.add(map);
@@ -363,10 +593,11 @@ public class MemberCommonServiceImpl extends ViServiceImpl<Sys_user> implements
// 如果有工会关系变更,也需要记录变更了什么工会
history.setChangeInfos(changeList);
// 单位或工会已不存在时名称可能为空,仍需保存变更历史,不能因空值中断整个审核事务。
String changeInfos = changeList.stream().map(v ->
v.getString("fieldName") + ":"
+ HtmlUtil.cleanHtmlTag(Optional.of(v.getString("sourceValue")).orElse(""))
+ "->" + HtmlUtil.cleanHtmlTag(Optional.of(v.getString("newValue")).orElse(""))
+ HtmlUtil.cleanHtmlTag(Optional.ofNullable(v.getString("sourceValue")).orElse(""))
+ "->" + HtmlUtil.cleanHtmlTag(Optional.ofNullable(v.getString("newValue")).orElse(""))
).collect(Collectors.joining(";"));
history.setChangeInfosStr(changeInfos);
dao().updateIgnoreNull(info);
@@ -2,7 +2,7 @@ const MEMBER_CHANGE = {
template: /*language=html*/ `
<el-card shadow="never">
<div class="process-title">会员变更</div>
<el-form :model="formData" ref="formRef" label-width="0" :rules="formRules" size="small">
<el-form :model="formData" ref="formRef" label-width="0" :rules="formRules" size="small" v-loading="formLoading">
<el-descriptions :column="3" border class="descriptions-form">
<el-descriptions-item label="工号">
<el-form-item prop="loginname">
@@ -196,9 +196,9 @@ const MEMBER_CHANGE = {
<el-row justify="end" type="flex" class="mt10">
<el-button @click="$emit('do-back')" v-if="formData.id || !isShow" type="primary">返 回</el-button>
<template v-if="isShow">
<el-button @click="doSave" type="primary">保存变更</el-button>
<el-button @click="doSave" type="primary" :disabled="formLoading">保存变更</el-button>
</template>
<el-button @click="doSubmit" type="primary">提交变更</el-button>
<el-button @click="doSubmit" type="primary" :disabled="formLoading || !formData.userId">提交变更</el-button>
</el-row>
</el-card>
`,
@@ -218,6 +218,17 @@ const MEMBER_CHANGE = {
families: [],
},
formRules: {
unitId: [{
validator: (rule, value, callback) => {
// isShow 为 false 时是高级管理代办,单位不能为空;个人申请沿用原校验规则。
if (!this.isShow && (value == null || String(value).trim() === '')) {
callback(new Error('请选择工作单位'))
return
}
callback()
},
trigger: ['change', 'blur']
}],
username: [{required: false, message: "必填", trigger: ["change", "blur"]}],
sex: [{required: false, message: "必填", trigger: ["change", "blur"]}],
email: [
@@ -290,6 +301,8 @@ const MEMBER_CHANGE = {
methods: {
getUnionName(val){
const data = this.units.find(v => v.id === val)
// 单位与工会名称同时保存,保证审核列表展示的是本次选择的单位。
this.$set(this.formData, 'unitName', data ? data.name : '')
this.$set(this.formData, 'unionName', data ? data.unionname : '')
this.$set(this.formData, 'unionId', data ? data.unionid : '')
},
@@ -299,7 +312,7 @@ const MEMBER_CHANGE = {
doSave(){
this.$confirm("保存后可在我的申请里面再次编辑,您确定要保存吗?", "提示", { type: "warning" })
.then(() => {
const formData = this.formData
const formData = {...this.formData}
if (formData.families) {
formData.families = JSON.stringify(this.formData.families)
}
@@ -312,7 +325,7 @@ const MEMBER_CHANGE = {
if (valid) {
this.$confirm("提交后将进入审核流程,无法再进行编辑,您确定要提交申请吗?", "提示", { type: "warning" })
.then(() => {
const formData = this.formData
const formData = {...this.formData}
if (formData.families) {
formData.families = JSON.stringify(this.formData.families)
}
@@ -322,37 +335,54 @@ const MEMBER_CHANGE = {
}
})
},
async init(userId){
if (userId) {
this.isShow = false
await this.getUserById(userId)
} else {
this.isShow = true
this.user = JSON.parse(window.sessionStorage.getItem('user'))
}
this.getFormDataValue(this.id)
this.allowChangeFields = await getDictOptions("ALLOW_CHANGE_FIELDS")
if (this.$auth.hasRoleOr('sysadmin, SchoolUnionMemberAdmin, SchoolUnionAdmin')) {
this.units = await getUnits()
this.unions = await getUnionList()
} else {
this.units = await getUnits(this.user.unionid)
}
init(userId){
this.isShow = !userId
this.formData = {families: []}
// 人员请求失败时不沿用上一次打开的会员资料,避免误提交其他人员的数据。
const userRequest = userId ? this.getUserById(userId) : Promise.resolve(
JSON.parse(window.sessionStorage.getItem('user'))
)
return userRequest.then((user) => {
if (!user) return
this.user = user
this.getFormDataValue(this.id)
getDictOptions("ALLOW_CHANGE_FIELDS").then((data) => {
this.allowChangeFields = data
})
const schoolAdmin = this.$auth.hasRoleOr('sysadmin, SchoolUnionMemberAdmin, SchoolUnionAdmin')
getUnits(schoolAdmin ? undefined : this.user.unionid).then((data) => {
this.units = data
})
if (schoolAdmin) {
getUnionList().then((data) => {
this.unions = data
})
}
})
},
async getUserById(userId){
const resp = await $.post("/platform/member/change/apply/getUserByIdForMemberChange", { userId })
if (resp.code === 0) {
this.user = resp.data
}
getUserById(userId){
// userId 为被变更会员 ID;成功返回表单资料,失败返回 null 并清空旧资料。
this.user = {}
this.formLoading = true
return $.post("/platform/member/change/apply/getUserByIdForMemberChange", {userId})
.then((resp) => {
if (resp.code === 0) return resp.data
this.$message.error(resp.msg)
return null
})
.always(() => {
this.formLoading = false
})
},
getFormDataValue(id){
if (id) {
$.post("/platform/member/change/apply/findChangeById", { id }).then((res) => {
if (res.code === 0) {
this.formData = res.data
this.formData.unionid = this.user.unionid
this.$set(this.formData, 'unionid', this.user.unionid)
if (this.formData.families) {
this.formData.families = JSON.parse(this.formData.families)
this.formData.families = typeof this.formData.families === 'string'
? JSON.parse(this.formData.families) : this.formData.families
} else {
this.formData.families = []
}
@@ -394,7 +424,7 @@ const MEMBER_CHANGE = {
this.$set(this.formData, "member", member === 1)
this.$set(this.formData, "welfareMember", welfareMember === 1)
this.$set(this.formData, "cardNum", cardNum)
this.$set(this.formData, "idCard", idCard)
this.$set(this.formData, "idCard", idCard === undefined ? this.user.idcard : idCard)
this.$set(this.formData, "nation", nation)
this.$set(this.formData, "political", political)
this.$set(this.formData, "position", position)
@@ -1,6 +1,6 @@
const MEMBER_CHANGE_AUDIT_INFO = {
template: /*language=HTML*/ `
<div>
<div v-loading="formLoading">
<el-tabs v-model="activeName">
<el-tab-pane label="基础信息" name="basicInfo">
<el-descriptions :column="3" border class="descriptions-form">
@@ -20,7 +20,7 @@ const MEMBER_CHANGE_AUDIT_INFO = {
<el-descriptions-item label="所属工会">{{ viewData.unionname }}</el-descriptions-item>
<el-descriptions-item label="所属校区">{{ viewData.campus }}</el-descriptions-item>
<el-descriptions-item label="身份证号码">{{ viewData.idCard }}</el-descriptions-item>
<el-descriptions-item label="身份证号码">{{ viewData.idCard || viewData.idcard }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewData.mobile }}</el-descriptions-item>
<el-descriptions-item label="电子邮箱">{{ viewData.email }}</el-descriptions-item>
@@ -97,8 +97,13 @@ const MEMBER_CHANGE_AUDIT_INFO = {
</el-descriptions>
</el-tab-pane>
</el-tabs>
<slot></slot>
</div>
`,
props: {
// 各审核入口使用自己的权限接口,其他既有页面仍保留原详情地址。
detailUrl: {type: String, default: '/platform/member/change/mine/findMemberChangeRecord'}
},
mixins: [initTableMixins],
data(){
return{
@@ -111,11 +116,22 @@ const MEMBER_CHANGE_AUDIT_INFO = {
}
},
methods: {
async onOpen(id){
const resp = await $.post('/platform/member/change/mine/findMemberChangeRecord', {id})
if (resp.code === 0) {
this.viewData = resp.data
}
onOpen(id){
// id 为变更记录 ID;返回 data 包含会员资料、changeInfos 及两级审核信息。
this.viewData = {}
this.activeName = 'basicInfo'
this.formLoading = true
return $.post(this.detailUrl, {id})
.then((resp) => {
if (resp.code === 0) {
this.viewData = resp.data
} else {
this.$message.error(resp.msg)
}
})
.always(() => {
this.formLoading = false
})
}
}
}
@@ -12,7 +12,7 @@ layout("/layouts/platform.html"){
<el-card shadow="never" class="mt10">
<table-tool :app="this" label="审核列表">
<template #func>
<el-button size="small" type="primary" @click="doBatchApproval" class="mr10">批量审核</el-button>
<el-button size="small" type="primary" @click="doBatchApproval" :loading="formLoading" class="mr10">批量审核</el-button>
<el-radio-group @change="search" class="mr5" size="small" v-model="pageForm.audit">
<el-radio-button label="true">已审核</el-radio-button>
<el-radio-button label="false">未审核</el-radio-button>
@@ -54,7 +54,8 @@ layout("/layouts/platform.html"){
</template>
<template #view>
<member-change-audit-info ref="memberChangeAuditInfoRef"></member-change-audit-info>
<member-change-audit-info ref="memberChangeAuditInfoRef"
detail-url="/platform/member/change/schoolUnion/audit/findMemberChangeRecord"></member-change-audit-info>
<div v-if="showApprovalForm">
<div class="process-title" style="margin: 20px 0">校工会审核</div>
<el-form :model="formData" ref="approvalFormRef" label-position="left" label-width="80px">
@@ -87,9 +88,9 @@ layout("/layouts/platform.html"){
</el-form>
<el-row type="flex" justify="end">
<el-button plain @click="$refs.guava.index()">取消</el-button>
<el-button type="danger" @click="doApproval(2)">退回申请</el-button>
<el-button type="danger" @click="doApproval(0)">拒绝申请</el-button>
<el-button type="primary" @click="doApproval(1)">同意申请</el-button>
<el-button type="danger" :disabled="formLoading" @click="doApproval(2)">退回申请</el-button>
<el-button type="danger" :disabled="formLoading" @click="doApproval(0)">拒绝申请</el-button>
<el-button type="primary" :loading="formLoading" @click="doApproval(1)">同意申请</el-button>
</el-row>
</div>
</template>
@@ -142,6 +143,7 @@ layout("/layouts/platform.html"){
this.multipleSelection = val;
},
doBatchApproval() {
if (this.formLoading) return
if (this.multipleSelection.length === 0){
this.$message.warning('请勾选需要审核的人员数据')
return
@@ -151,17 +153,25 @@ layout("/layouts/platform.html"){
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
}).then(() => {
const ids = this.multipleSelection.map((v) => v.id)
const resp = await $.post('/platform/member/change/schoolUnion/audit/doBatchApproval', {
this.formLoading = true
// data 为勾选的变更记录 ID 数组;code 为 0 表示批量审核成功。
return $.post('/platform/member/change/schoolUnion/audit/doBatchApproval', {
data: JSON.stringify(ids)
})
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
} else {
this.$message.error(resp.msg)
}
.then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$refs.table.clearSelection()
this.pageData()
} else {
this.$message.error(resp.msg)
}
})
.always(() => {
this.formLoading = false
})
})
},
openView(row){
@@ -180,8 +190,12 @@ layout("/layouts/platform.html"){
openAudit(row){
const user = JSON.parse(window.sessionStorage.getItem('user'))
this.id = row.id
this.formData.username = user.username
this.formData.auditTime = moment().format('YYYY-MM-DD HH:mm:ss')
// 每次审核新建表单,避免沿用上一条申请的审核意见和审核类型。
this.formData = {
username: user.username,
auditTime: moment().format('YYYY-MM-DD HH:mm:ss'),
auditOpinion: ''
}
this.$refs.guava.view()
this.$nextTick(() => {
this.showApprovalForm = true
@@ -189,6 +203,7 @@ layout("/layouts/platform.html"){
})
},
doApproval(approvalType){
if (this.formLoading) return
let msg = '确定要通过此条申请吗?'
if (approvalType === 0) {
msg = '拒绝后不可撤回,确定要拒绝此条申请吗?'
@@ -199,39 +214,54 @@ layout("/layouts/platform.html"){
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
this.formData.auditType = approvalType
this.$refs.approvalFormRef.validate(async (valid) => {
}).then(() => {
this.$set(this.formData, 'auditType', approvalType)
this.$refs.approvalFormRef.validate((valid) => {
if (valid) {
const resp = await $.post('/platform/member/change/schoolUnion/audit/approval', {
this.formLoading = true
// id 为申请记录 ID,audit 为审核信息;本模块类型为 1 通过、2 退回、0 拒绝。
$.post('/platform/member/change/schoolUnion/audit/approval', {
audit: JSON.stringify(this.formData),
id: this.id
})
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$refs.guava.index()
this.showApprovalForm = false
this.pageData()
} else {
this.$message.error(resp.msg)
}
.then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$refs.guava.index()
this.showApprovalForm = false
this.pageData()
} else {
this.$message.error(resp.msg)
}
})
.always(() => {
this.formLoading = false
})
}
})
})
},
doRevoke(row){
if (this.formLoading) return
this.$confirm('确定要撤回该申请吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const resp = await $.post('/platform/member/change/schoolUnion/audit/doRevoke', {id: row.id})
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
} else {
this.$message.error(resp.msg)
}
}).then(() => {
this.formLoading = true
// id 为已退回的申请记录 ID;code 为 0 时刷新审核列表。
return $.post('/platform/member/change/schoolUnion/audit/doRevoke', {id: row.id})
.then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
} else {
this.$message.error(resp.msg)
}
})
.always(() => {
this.formLoading = false
})
})
},
search(pageForm){
@@ -241,21 +271,27 @@ layout("/layouts/platform.html"){
this.doSearch()
},
getChangeTypes(changeTypes){
const changeTypesList = JSON.parse(changeTypes)
if (changeTypesList) {
if (!changeTypesList || changeTypesList.length === 0 || !this.changeTypeData || this.changeTypeData.length === 0) return null
return changeTypesList
.map((v) => {
return this.changeTypeData.find((item) => item.type === v).changeTypeName
})
.join(",")
// 查询返回值可能为 JSON 字符串或数组,历史未知类型保留原值,避免中断整行渲染。
let changeTypesList = changeTypes
if (typeof changeTypes === 'string') {
try {
changeTypesList = JSON.parse(changeTypes)
} catch (e) {
return changeTypes
}
}
return null
if (!Array.isArray(changeTypesList)) return null
return changeTypesList.map((value) => {
const item = this.changeTypeData.find((type) => type.type === value)
return item ? item.changeTypeName : value
}).join(',')
}
},
async created() {
created() {
this.pageData()
this.changeTypeData = await getEnumOptions("MemberChangeType")
getEnumOptions("MemberChangeType").then((data) => {
this.changeTypeData = data
})
}
})
</script>