This commit is contained in:
zhouhefeng
2026-04-24 16:39:14 +08:00
147 changed files with 5706 additions and 881 deletions
@@ -15,7 +15,6 @@ import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
@@ -151,8 +150,7 @@ public class ActivityCultureApplyActivityController {
return Result.success();
}
if (tissue.getIsEnrollSystem()) {
Sys_home_activity sysHomeActivity = tissue.covertToSysHomeActivity();
activityCultureService.insertOrUpdate(sysHomeActivity);
activityCultureService.syncHomeActivity(tissue);
}
return Result.success();
}
@@ -9,7 +9,6 @@ import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
import com.budwk.app.zhgh.activity.culture.service.ActivityCultureService;
@@ -144,8 +143,7 @@ public class ActivityCultureAuditActivityController {
tissue.setIsUnseal(true);
activityCultureService.update(tissue);
if (args.getInt("submitType") == 1) {
Sys_home_activity sysHomeActivity = tissue.covertToSysHomeActivity();
activityCultureService.insertOrUpdate(sysHomeActivity);
activityCultureService.syncHomeActivity(tissue);
}
flowCommonService.executeTask(args);
return Result.success();
@@ -74,9 +74,15 @@ public class ActivityCultureInfoManageController {
@SaCheckPermission(value = {"activity.culture.infoManage.school", "activity.culture.infoManage.union", "activity.culture.infoManage.club"}, mode = SaMode.OR)
public Result activityStatusChange(@Valid String id, @Valid Boolean isUnseal) {
activityCultureService.update(Chain.make("isUnseal", isUnseal), Cnd.where("id", "=", id));
activityCultureService.dao().update(Sys_home_activity.class,
Chain.make("enable", isUnseal),
Cnd.where("id", "=", id));
ActivityTissue tissue = activityCultureService.fetch(id);
tissue.setIsUnseal(isUnseal);
if (Boolean.TRUE.equals(isUnseal)) {
activityCultureService.syncHomeActivity(tissue);
} else {
activityCultureService.dao().update(Sys_home_activity.class,
Chain.make("enable", false),
Cnd.where("id", "=", id));
}
return Result.success();
}
@@ -24,4 +24,14 @@ public interface ActivityCultureService extends BaseService<ActivityTissue> {
Object pageData(PageForm page, String year, String name, String unionId, Integer activity_type);
NutMap findOne(String id);
/**
* 同步文化活动到首页活动表。
* 1. 非报名类活动不推送首页,直接清理首页记录。
* 2. 校工会活动没有审核流,提交后默认可推首页。
* 3. 分工会、协会活动仅在已审核通过或已存在首页记录时更新首页信息。
*
* @param tissue 文化活动
*/
void syncHomeActivity(ActivityTissue tissue);
}
@@ -5,6 +5,7 @@ import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.model.Audit;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
@@ -80,6 +81,7 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
@Override
public void doEditActivity(ActivityTissue tissue) {
update(tissue);
syncHomeActivity(tissue);
}
@Override
@@ -123,6 +125,7 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
cnd.andEX("tissue.projectTypeCode", "!=", "50004");
cnd.andEX("tissue.unionId", "=", unionId);
cnd.andEX("tissue.activity_type", "=", activity_type);
cnd.groupBy("tissue.id");
if (Strings.isNotBlank(name)) {
cnd.where().andLike("tissue.name", name);
@@ -188,4 +191,38 @@ public class ActivityCultureServiceImpl extends BaseServiceImpl<ActivityTissue>
return nutMap;
}
@Override
public void syncHomeActivity(ActivityTissue tissue) {
if (tissue == null || Strings.isBlank(tissue.getId())) {
return;
}
// 非报名活动不需要出现在首页,编辑成活动管理模式时同步移除首页记录。
if (!Boolean.TRUE.equals(tissue.getIsEnrollSystem())) {
dao().delete(Sys_home_activity.class, tissue.getId());
return;
}
Sys_home_activity oldHomeActivity = dao().fetch(Sys_home_activity.class, tissue.getId());
boolean canCreateHomeActivity = (tissue.getActivity_type() != null
&& tissue.getActivity_type() == 40001)
|| Boolean.TRUE.equals(tissue.getIsUnseal())
|| oldHomeActivity != null;
if (!canCreateHomeActivity) {
return;
}
Sys_home_activity sysHomeActivity = tissue.covertToSysHomeActivity();
// 校工会文化活动没有审核环节,前端未回传启用状态时默认直接上首页。
if (tissue.getActivity_type() != null && tissue.getActivity_type() == 40001 && tissue.getIsUnseal() == null) {
sysHomeActivity.setEnable(true);
}
// 保留首页管理中手工设置的置顶、大图和排序,避免活动编辑时被覆盖。
if (oldHomeActivity != null) {
sysHomeActivity.setTop(oldHomeActivity.getTop());
sysHomeActivity.setPush(oldHomeActivity.getPush());
sysHomeActivity.setSortNo(oldHomeActivity.getSortNo());
}
dao().insertOrUpdate(sysHomeActivity);
}
}
@@ -60,6 +60,7 @@ public class H5ActivityWorksUploadCollectionController {
awc.*
FROM
`activity_works_collection` awc
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("YEAR(awc.startDateTime)", "=", year);
@@ -68,9 +69,9 @@ public class H5ActivityWorksUploadCollectionController {
cnd.and(new Static("now() > awc.startDateTime and now() < awc.endDateTime"));
}//查询已结束的
else if (activityType == 3) {
cnd.and(new Static("now() > awc.startDateTime"));
cnd.and(new Static("now() >= awc.endDateTime"));
} else if (activityType == 4) {
cnd.and(new Static("now() < awc.endDateTime"));
cnd.and(new Static("now() < awc.startDateTime"));
}
cnd.and("awc.enable", "=", 1);
cnd.and("awc.type", "=", 1);
@@ -28,6 +28,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
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.ClubUserApply;
import com.budwk.app.zhgh.club.service.ClubUserJoinService;
import com.budwk.app.zhgh.staffbenefit.condolence.model.Condolence;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -64,6 +65,8 @@ public class ClubUserJoinApplyController {
private FlowEngine flowEngine;
@Inject
private FlowCommonService flowCommonService;
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@SaCheckPermission("club.join.apply")
@@ -112,6 +115,8 @@ public class ClubUserJoinApplyController {
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setMode(true);
clubUserApply.setApplyDate(new Date());
clubUserApply.setJoinTime(clubUserJoinService.resolveJoinTime(clubUserApply.getClubId(), clubUserApply.getUserId()));
clubUserApply.setExitTime(null);
dao.insertOrUpdate(clubUserApply);
return Result.success();
}
@@ -124,12 +129,10 @@ public class ClubUserJoinApplyController {
public Result submit(@Param("data") ClubUserApply clubUserApply,
@Param("mode") Boolean mode) {
if(mode == false) {
clubUserApply = dao.fetch(
ClubUserApply.class,
Cnd.where("userId", "=", SecurityUtil.getUserId())
.and("clubId", "=", clubUserApply.getClubId())
.desc("applyDate")
);
clubUserApply = clubUserJoinService.buildExitApply(clubUserApply.getClubId(), SecurityUtil.getUserId());
if (ObjectUtil.isEmpty(clubUserApply)) {
return Result.error("当前不是该社团成员,无法申请退出!");
}
}
ClubUserApply userApply = dao.fetch(ClubUserApply.class, Cnd.where("userId", "=", clubUserApply.getUserId()).and("clubId", "=", clubUserApply.getClubId()).and("mode", "=", mode).desc(ClubUserApply::getApplyDate));
if (ObjectUtil.isNotEmpty(userApply) && StrUtil.isNotBlank(userApply.getId())) {
@@ -142,9 +145,17 @@ public class ClubUserJoinApplyController {
if(!mode) {
clubUserApply.setId(null);
}
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setMode(mode);
clubUserApply.setApplyDate(new Date());
Date applyDate = new Date();
clubUserApply.setApplyDate(applyDate);
if (mode) {
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setJoinTime(clubUserJoinService.resolveJoinTime(clubUserApply.getClubId(), clubUserApply.getUserId()));
clubUserApply.setExitTime(null);
} else {
clubUserApply.setJoinTime(clubUserJoinService.resolveJoinTime(clubUserApply.getClubId(), clubUserApply.getUserId()));
clubUserApply.setExitTime(applyDate);
}
dao.insertOrUpdate(clubUserApply);
// 开启流程实例
@@ -22,6 +22,7 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.model.*;
import com.budwk.app.zhgh.club.param.ClubUserPageForm;
import com.budwk.app.zhgh.club.service.SysClubInfoManageService;
import com.budwk.app.zhgh.club.service.SysClubService;
import com.budwk.app.zhgh.club.service.impl.SysClubUserServiceImpl;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
@@ -64,6 +65,8 @@ public class ClubChangeManagerController {
private FlowCommonService flowCommonService;
@Inject
private SysClubInfoManageService infoManageService;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/infoManage/change/index.html")
@@ -102,7 +105,10 @@ public class ClubChangeManagerController {
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.userId", "=", SecurityUtil.getUserId());
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> clubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
cnd.and("info.clubId", "in", clubIdList);
}
cnd.andEX("year(info.creatTime)", "=", pageForm.getYear());
if (StrUtil.isNotBlank(pageForm.getClubName())) {
cnd.and("club.clubName", "LIKE", "%" + pageForm.getClubName() + "%");
@@ -112,6 +118,7 @@ public class ClubChangeManagerController {
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("info.id");
sql.setCondition(cnd);
Pagination pagination = infoManageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> listMap = pagination.getList(NutMap.class);
@@ -130,6 +137,17 @@ public class ClubChangeManagerController {
@SaCheckPermission("club.infoManage.change")
@SLog(tag = "社团管理系统-信息管理", msg = "提交变更理事机构")
public Object submit(@Param("data") SysClubManager clubManager) {
Cnd cnd = Cnd.NEW();
cnd.and(SysClubManager::getClubId, "=", clubManager.getClubId());
if (StrUtil.isNotBlank(clubManager.getId())) {
cnd.and(SysClubManager::getId, "!=", clubManager.getId());
}
List<SysClubManager> managerList = dao.query(SysClubManager.class, cnd);
List<String> pendingProcessIds = managerList.stream().map(SysClubManager::getId).toList();
int pendingCount = dao.count(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "in", pendingProcessIds).and(ProcessInstance::getState, "not in", List.of(ProcessInstanceStateEnum.FINISHED.getCode(), ProcessInstanceStateEnum.REJECT.getCode())));
if (pendingCount > 0) {
return Result.error("您有该社团的申请记录尚未完成,请核对!");
}
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
String errMsg = "校工会审核" + clubManager.getPeriod() + "的换届报告通过后才能变更理事成员";
@@ -0,0 +1,63 @@
package com.budwk.app.zhgh.club.controller.infoManage;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
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;
import com.budwk.app.zhgh.club.vo.ClubUserCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserJoinVo;
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;
@IocBean
@Ok("json")
@At("/platform/club/infoManage/exitManage")
public class ClubExitManageController {
@Inject
private SysClubInfoManageService clubInfoManageService;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/infoManage/exitManage/index.html")
@SaCheckPermission("club.infoManage.exitManage")
public void index() {
}
@At
@SaCheckPermission("club.infoManage.exitManage")
public Result pageData(@Valid ClubUserPageForm pageForm) {
Pagination<ClubUserCommonPageVo> pagination = clubInfoManageService.exitManagePageData(pageForm);
return Result.success(pagination);
}
@At
@SaCheckPermission("club.infoManage.exitManage")
public Result info(@Valid String id) {
ClubUserJoinVo clubUserJoinVo = clubInfoManageService.exitManageInfo(id);
return Result.success(clubUserJoinVo);
}
@At
@SaCheckPermission("club.infoManage.exitManage")
public Result getClubList() {
List<SysClub> clubList;
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
clubList = sysClubService.getMyManageClub();
} else {
clubList = sysClubService.getMyManageClub();
}
return Result.success(clubList);
}
}
@@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
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.result.Result;
import com.budwk.app.base.utils.PageUtil;
@@ -15,6 +16,7 @@ import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
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;
import com.budwk.app.zhgh.club.model.SysClubRefresh;
@@ -58,6 +60,8 @@ public class ClubRefreshReportController {
private FlowCommonService flowCommonService;
@Inject
private Dao dao;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/infoManage/refreshReport/index.html")
@@ -96,7 +100,10 @@ public class ClubRefreshReportController {
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.userId", "=", SecurityUtil.getUserId());
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> clubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
cnd.and("info.clubId", "in", clubIdList);
}
cnd.andEX("year(info.creatTime)", "=", pageForm.getYear());
if (StrUtil.isNotBlank(pageForm.getClubName())) {
cnd.and("club.clubName", "LIKE", "%" + pageForm.getClubName() + "%");
@@ -119,6 +126,7 @@ public class ClubRefreshReportController {
public Object submit(@Param("data") SysClubRefresh clubRefresh) {
Cnd cnd = Cnd.NEW();
cnd.and(SysClubRefresh::getClubId, "=", clubRefresh.getClubId());
if(StrUtil.isNotBlank(clubRefresh.getId())) {
cnd.and(SysClubRefresh::getId, "!=", clubRefresh.getId());
}
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
@@ -15,6 +16,7 @@ import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
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;
import com.budwk.app.zhgh.club.model.SysClubRefresh;
@@ -59,6 +61,8 @@ public class ClubRuleUpdateController {
private FlowCommonService flowCommonService;
@Inject
private Dao dao;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:/platform/zhgh/club/infoManage/ruleUpdate/index.html")
@@ -97,7 +101,10 @@ public class ClubRuleUpdateController {
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.userId", "=", SecurityUtil.getUserId());
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> clubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
cnd.and("info.clubId", "in", clubIdList);
}
cnd.andEX("year(info.creatTime)", "=", pageForm.getYear());
if (StrUtil.isNotBlank(pageForm.getClubName())) {
cnd.and("club.clubName", "LIKE", "%" + pageForm.getClubName() + "%");
@@ -120,6 +127,7 @@ public class ClubRuleUpdateController {
public Object submit(@Param("data")SysClubRule clubRule) {
Cnd cnd = Cnd.NEW();
cnd.and(SysClubRule::getClubId, "=", clubRule.getClubId());
if(StrUtil.isNotBlank(clubRule.getId())) {
cnd.and(SysClubRule::getId, "!=", clubRule.getId());
}
@@ -85,4 +85,14 @@ public class ClubUserApply extends BaseModel {
@Comment("申请时间")
@ColDefine(type = ColType.DATETIME)
private Date applyDate;
@Column
@Comment("入会时间")
@ColDefine(type = ColType.DATETIME)
private Date joinTime;
@Column
@Comment("退会时间")
@ColDefine(type = ColType.DATETIME)
private Date exitTime;
}
@@ -47,7 +47,7 @@ public class SysClubExamineRegister extends BaseModel {
@Column
@Comment("社团名称")
@ColDefine(type = ColType.VARCHAR, width = 32)
@ColDefine(type = ColType.VARCHAR)
private String clubName;
@Column
@@ -4,4 +4,25 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.club.model.ClubUserApply;
public interface ClubUserJoinService extends BaseService<ClubUserApply> {
/**
* 构造退会申请数据。
* 当用户没有历史入会申请记录时,使用当前社团成员关系和人员基础信息兜底生成一份退会申请,
* 以保证会长等通过社团注册直接入会的人员也能正常发起退会流程。
*
* @param clubId 社团ID
* @param userId 用户ID
* @return 退会申请对象,不存在当前成员关系时返回null
*/
ClubUserApply buildExitApply(String clubId, String userId);
/**
* 获取社团成员的入会时间,优先取历史有效入会申请记录,
* 没有时兜底为社团创办时间。
*
* @param clubId 社团ID
* @param userId 用户ID
* @return 入会时间
*/
java.util.Date resolveJoinTime(String clubId, String userId);
}
@@ -5,6 +5,7 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.club.param.ClubUserPageForm;
import com.budwk.app.zhgh.club.vo.ClubCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserJoinVo;
import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletResponse;
@@ -23,8 +24,12 @@ public interface SysClubInfoManageService extends BaseService<ClubCommonPageVo>
Pagination<ClubUserCommonPageVo> infoManageUserPageData(@Valid ClubUserPageForm pageForm);
Pagination<ClubUserCommonPageVo> exitManagePageData(@Valid ClubUserPageForm pageForm);
Pagination<ClubUserCommonPageVo> clubManagePersonAuditPageData(@Valid ClubUserPageForm pageForm);
ClubUserJoinVo exitManageInfo(String id);
List<NutMap> getClubTreeData();
/**
@@ -1,14 +1,94 @@
package com.budwk.app.zhgh.club.service.impl;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.ClubUserApply;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.service.ClubUserJoinService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Date;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class ClubUserJoinServiceImpl extends BaseServiceImpl<ClubUserApply> implements ClubUserJoinService {
public ClubUserJoinServiceImpl(Dao dao) {
super(dao);
}
@Override
public ClubUserApply buildExitApply(String clubId, String userId) {
// 优先复用最近一次申请记录中的补充信息,避免用户重复填写基础资料。
ClubUserApply latestApply = fetch(
Cnd.where("userId", "=", userId)
.and("clubId", "=", clubId)
.desc(ClubUserApply::getApplyDate)
);
if (latestApply != null) {
latestApply.setId(null);
return latestApply;
}
// 对于会长等注册社团时直接入会的人员,没有申请记录时从当前成员信息兜底生成退会申请。
ClubUser clubUser = dao().fetch(ClubUser.class, Cnd.where("clubId", "=", clubId).and("userId", "=", userId));
if (clubUser == null) {
return null;
}
View_user viewUser = dao().fetch(View_user.class, Cnd.where("id", "=", userId));
ClubUserApply clubUserApply = new ClubUserApply();
clubUserApply.setClubId(clubId);
clubUserApply.setUserId(userId);
clubUserApply.setClubPosition(clubUser.getClubPosition());
clubUserApply.setEmail(clubUser.getEmail());
clubUserApply.setAvatar(clubUser.getAvatar());
clubUserApply.setSameTimeJoinOtherClubSituation(clubUser.getSameTimeJoinOtherClubSituation());
clubUserApply.setAwardsExperience(clubUser.getAwardsExperience());
clubUserApply.setJoinTime(resolveJoinTime(clubId, userId));
List<String> roleCodes = clubUser.getRoleCode();
if (roleCodes != null && !roleCodes.isEmpty()) {
String exitRoleCode = roleCodes.stream()
.filter(roleCode -> !RoleConstant.CLUB_MEMBER.name().equals(roleCode))
.findFirst()
.orElse(RoleConstant.CLUB_MEMBER.name());
clubUserApply.setRoleCode(exitRoleCode);
} else {
clubUserApply.setRoleCode(RoleConstant.CLUB_MEMBER.name());
}
if (viewUser != null) {
clubUserApply.setBirthday(viewUser.getBirthday());
clubUserApply.setMobile(viewUser.getMobile());
if (clubUserApply.getEmail() == null) {
clubUserApply.setEmail(viewUser.getEmail());
}
if (clubUserApply.getAvatar() == null) {
clubUserApply.setAvatar(viewUser.getAvatar());
}
}
return clubUserApply;
}
@Override
public Date resolveJoinTime(String clubId, String userId) {
ClubUserApply latestJoinApply = fetch(
Cnd.where("userId", "=", userId)
.and("clubId", "=", clubId)
.and("mode", "=", true)
.desc(ClubUserApply::getApplyDate)
);
if (latestJoinApply != null && latestJoinApply.getJoinTime() != null) {
return latestJoinApply.getJoinTime();
}
SysClub club = dao().fetch(SysClub.class, clubId);
if (club != null && club.getFoundTime() != null) {
return DateUtil.parse(club.getFoundTime());
}
return null;
}
}
@@ -20,6 +20,7 @@ import com.budwk.app.zhgh.club.service.SysClubInfoManageService;
import com.budwk.app.zhgh.club.service.SysClubService;
import com.budwk.app.zhgh.club.vo.ClubCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubUserJoinVo;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
@@ -139,11 +140,27 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
u.unitid as unitId,
u.mobile,
club.clubName,
u.unitname as unitName
u.unitname as unitName,
DATE_FORMAT(IFNULL(joinInfo.joinTime, club.foundTime), '%Y-%m-%d %H:%i:%s') as joinTime
FROM
club_user scu
LEFT JOIN sys_club club ON scu.clubId = club.id
RIGHT JOIN `vw_user` u ON scu.userId = u.id
LEFT JOIN (
SELECT
joinApply.userId,
joinApply.clubId,
MAX(joinApply.joinTime) AS joinTime
FROM
club_user_apply joinApply
LEFT JOIN wf_process_instance joinIns ON joinIns.businessNo = joinApply.id
WHERE
joinApply.mode = true
AND joinIns.state = 20
GROUP BY
joinApply.userId,
joinApply.clubId
) joinInfo ON joinInfo.userId = scu.userId AND joinInfo.clubId = scu.clubId
$condition
$order
""");
@@ -206,6 +223,96 @@ public class SysClubInfoManageServiceImpl extends BaseServiceImpl<ClubCommonPage
return listPageVO;
}
@Override
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,
info.signature,
info.applyDate,
u.username AS userName,
u.loginname AS loginName,
u.sex,
u.personType,
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,
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
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("info.mode", "=", false);
cnd.and("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
cnd.andEX("info.clubId", "=", pageForm.getClubId());
cnd.andEX("u.personType", "=", pageForm.getPersonType());
cnd.andEX("u.userState", "=", pageForm.getUserState());
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.and(Cnd.exps("u.loginname", "like", "%" + pageForm.getSearchKeyword() + "%").or("u.username", "like", "%" + pageForm.getSearchKeyword() + "%"));
}
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.groupBy("info.id");
cnd.desc("info.applyDate");
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())));
}
pagination.setList(list);
return pagination;
}
@Override
public ClubUserJoinVo exitManageInfo(String id) {
Sql sql = Sqls.create("""
SELECT
cua.*,
club.clubName,
u.loginname AS loginName,
u.username AS userName,
u.sex,
u.mobile,
u.unitName,
u.unionName,
u.technicalTitle,
u.education,
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
""");
sql.setParam("id", id);
return fetchVO(sql, ClubUserJoinVo.class);
}
@Override
public Pagination<ClubUserCommonPageVo> clubManagePersonAuditPageData(ClubUserPageForm pageForm) {
Sql sql = Sqls.create("""
@@ -29,6 +29,9 @@ public class ClubUserCommonPageVo extends ClubUser {
private String unionName;
private String birthday;
private String unionId;
private String joinTime;
private String exitTime;
private String applyRoleCode;
private String roleName;
}
@@ -9,12 +9,15 @@ import com.budwk.app.sys.models.Sys_file;
import com.budwk.app.sys.utils.SysFileMinIoUtil;
import com.budwk.app.zhgh.dayofficework.edu.models.EduChapters;
import com.budwk.app.zhgh.dayofficework.edu.models.EduCourses;
import com.budwk.app.zhgh.dayofficework.edu.models.EduStudyRecords;
import com.budwk.app.zhgh.dayofficework.edu.models.EduVideos;
import com.budwk.app.zhgh.dayofficework.edu.service.EduCoursesService;
import com.budwk.app.zhgh.dayofficework.edu.service.EduStudyRecordsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
@@ -33,6 +36,8 @@ public class EduCoursesController {
private Dao dao;
@Inject
private EduCoursesService eduCoursesService;
@Inject
private EduStudyRecordsService eduStudyRecordsService;
@At("/courses")
@Ok("beetl:/platform/zhgh/dayofficework/edu/courses/index.html")
@@ -75,6 +80,14 @@ public class EduCoursesController {
@ApiOperation("删除")
@SLog(tag = "理论学习课程", msg = "删除课程,id:${args[0]}")
public Result deleteCourses(@Param("id") String id) {
//理论学习学习记录表
dao.execute(Sqls.create("delete from edu_study_records where courseId=\'" + id + "\'"));
//理论学习视频表
dao.execute(Sqls.create("delete from edu_videos " +
"where chapterId in (select id from edu_chapters where courseId=\'" + id + "\')"));
//理论学习视频章节表
dao.execute(Sqls.create("delete from edu_chapters where courseId=\'" + id + "\'"));
//理论学习课程表
dao.delete(EduCourses.class, id);
return Result.success();
}
@@ -161,6 +174,8 @@ public class EduCoursesController {
@ApiOperation("删除")
@SLog(tag = "理论学习课程", msg = "删除课程视频,id:${args[0]}")
public Result deleteVideo(@Param("id") String id) {
// 删除视频时同步清理学习记录,避免已删除视频继续影响学习进度。
eduStudyRecordsService.deleteStudyRecordsByVideoId(id);
dao.delete(EduVideos.class, id);
return Result.success();
}
@@ -114,29 +114,8 @@ public class H5EduController {
return Result.error("用户未登录");
}
// 获取课程总视频数
List<EduChapters> chapters = dao.query(EduChapters.class, Cnd.where(EduChapters::getCourseId, "=", courseId));
List<String> chapterIds = chapters.stream().map(EduChapters::getId).toList();
int totalVideos = dao.count(EduVideos.class, Cnd.where(EduVideos::getChapterId, "in", chapterIds));
// 获取已完成的视频ID列表
List<EduStudyRecords> completedRecords = dao.query(EduStudyRecords.class,
Cnd.where(EduStudyRecords::getUserId, "=", userId)
.and(EduStudyRecords::getCourseId, "=", courseId)
.and(EduStudyRecords::getIsCompleted, "=", 1));
List<String> completedVideoIds = completedRecords.stream()
.map(EduStudyRecords::getVideoId)
.collect(java.util.stream.Collectors.toList());
int completedVideos = completedVideoIds.size();
java.util.Map<String, Object> progressData = new java.util.HashMap<>();
progressData.put("totalVideos", totalVideos);
progressData.put("completedVideos", completedVideos);
progressData.put("completedVideoIds", completedVideoIds);
return Result.success(progressData);
// 进度只按课程当前仍存在的视频统计,删除旧视频后要立即反映到进度条上。
return Result.success(eduStudyRecordsService.getCurrentCourseProgress(userId, courseId));
} catch (Exception e) {
log.error("获取课程进度失败", e);
return Result.error("获取课程进度失败");
@@ -4,6 +4,7 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.edu.models.EduStudyRecords;
import java.util.List;
import java.util.Map;
/**
* @ClassName EduStudyRecordsService
@@ -54,6 +55,20 @@ public interface EduStudyRecordsService extends BaseService<EduStudyRecords> {
*/
StudyProgressStats getCourseProgressStats(String userId, String courseId);
/**
* 按课程当前仍存在的视频重新计算学习进度,避免已删除视频的历史记录继续参与统计。
* @param userId 用户ID
* @param courseId 课程ID
* @return 当前课程学习进度数据
*/
Map<String, Object> getCurrentCourseProgress(String userId, String courseId);
/**
* 删除视频时同步清理该视频的学习记录,避免历史脏数据影响后续进度统计。
* @param videoId 视频ID
*/
void deleteStudyRecordsByVideoId(String videoId);
/**
* 学习进度统计信息
*/
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.dayofficework.edu.service.impl;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.edu.models.EduChapters;
import com.budwk.app.zhgh.dayofficework.edu.models.EduStudyRecords;
import com.budwk.app.zhgh.dayofficework.edu.models.EduVideos;
import com.budwk.app.zhgh.dayofficework.edu.service.EduStudyRecordsService;
@@ -12,8 +13,12 @@ import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @ClassName EduStudyRecordsServiceImpl
@@ -195,4 +200,64 @@ public class EduStudyRecordsServiceImpl extends BaseServiceImpl<EduStudyRecords>
return new StudyProgressStats(0, 0, 0, 0);
}
}
@Override
public Map<String, Object> getCurrentCourseProgress(String userId, String courseId) {
try {
List<EduChapters> chapters = dao().query(EduChapters.class,
Cnd.where(EduChapters::getCourseId, "=", courseId));
List<String> chapterIds = chapters.stream().map(EduChapters::getId).collect(Collectors.toList());
Map<String, Object> progressData = new HashMap<>();
if (chapterIds.isEmpty()) {
progressData.put("totalVideos", 0);
progressData.put("completedVideos", 0);
progressData.put("completedVideoIds", Collections.emptyList());
return progressData;
}
List<EduVideos> currentVideos = dao().query(EduVideos.class,
Cnd.where(EduVideos::getChapterId, "in", chapterIds));
List<String> currentVideoIds = currentVideos.stream().map(EduVideos::getId).collect(Collectors.toList());
if (currentVideoIds.isEmpty()) {
progressData.put("totalVideos", 0);
progressData.put("completedVideos", 0);
progressData.put("completedVideoIds", Collections.emptyList());
return progressData;
}
// 只统计当前课程仍存在的视频,避免已删除视频的学习记录继续影响进度条。
List<EduStudyRecords> completedRecords = dao().query(EduStudyRecords.class,
Cnd.where(EduStudyRecords::getUserId, "=", userId)
.and(EduStudyRecords::getCourseId, "=", courseId)
.and(EduStudyRecords::getIsCompleted, "=", 1)
.and(EduStudyRecords::getVideoId, "in", currentVideoIds));
List<String> completedVideoIds = completedRecords.stream()
.map(EduStudyRecords::getVideoId)
.distinct()
.collect(Collectors.toList());
progressData.put("totalVideos", currentVideoIds.size());
progressData.put("completedVideos", completedVideoIds.size());
progressData.put("completedVideoIds", completedVideoIds);
return progressData;
} catch (Exception e) {
log.error("按当前课程视频计算学习进度失败", e);
Map<String, Object> progressData = new HashMap<>();
progressData.put("totalVideos", 0);
progressData.put("completedVideos", 0);
progressData.put("completedVideoIds", Collections.emptyList());
return progressData;
}
}
@Override
public void deleteStudyRecordsByVideoId(String videoId) {
try {
dao().clear(EduStudyRecords.class, Cnd.where(EduStudyRecords::getVideoId, "=", videoId));
} catch (Exception e) {
log.error("删除视频学习记录失败, videoId={}", videoId, e);
throw new BaseException("删除视频学习记录失败", e);
}
}
}
@@ -80,7 +80,9 @@ public class OutlayManageClubQueryController {
SELECT * FROM `outlay_manage_club` $condition
""");
cnd.andEX("year", "=", year);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
// if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
// 现逻辑:系统管理员、校工会社团管理员可查看全部社团分页数据,会长仅查看自己负责的社团。
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
List<String> roleIds = sysRoleService.getRoleIdsByCode(List.of(RoleConstant.CLUB_PRESIDENT, RoleConstant.CLUB_SECRETARY));
List<Sys_user_role> userRoles = clubQueryService.dao().query(Sys_user_role.class,
Cnd.where("userId", "=", SecurityUtil.getUserId())
@@ -75,7 +75,7 @@ public class OutlayManageClub extends BaseModel implements Serializable {
@Column
@Comment("社团名称")
@ColDefine(type = ColType.VARCHAR, width = 32)
@ColDefine(type = ColType.VARCHAR)
private String clubName;
@Column
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -102,7 +103,7 @@ public class H5QsvQuizController {
List<String> subjectIds = lastRecordOptional.get().getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
answerRecordId = lastRecordOptional.get().getId();
} else {
@@ -114,7 +115,7 @@ public class H5QsvQuizController {
List<String> subjectIds = answerRecord.getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
}
} else {
@@ -143,7 +144,7 @@ public class H5QsvQuizController {
List<String> subjectIds = answerRecord.getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
}
} else {
@@ -169,7 +170,7 @@ public class H5QsvQuizController {
}
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
repeatTips = true;
} else {
@@ -179,7 +180,7 @@ public class H5QsvQuizController {
List<String> subjectIds = maxAnswerRecordOptional.get().getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
answerRecordId = maxAnswerRecordOptional.get().getId();
} else {
@@ -192,7 +193,7 @@ public class H5QsvQuizController {
List<String> subjectIds = maxAnswerRecordOptional.get().getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
answerRecordId = maxAnswerRecordOptional.get().getId();
} else {
@@ -206,7 +207,7 @@ public class H5QsvQuizController {
List<String> subjectIds = notFinishRecordOptional.get().getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
answerRecordId = notFinishRecordOptional.get().getId();
} else {
@@ -244,7 +245,7 @@ public class H5QsvQuizController {
List<String> subjectIds = maxAnswerRecordOptional.get().getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
answerRecordId = maxAnswerRecordOptional.get().getId();
} else {
@@ -280,7 +281,7 @@ public class H5QsvQuizController {
List<String> subjectIds = answerRecord.getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
} else {
//今天最大那次的记录
@@ -291,7 +292,7 @@ public class H5QsvQuizController {
List<String> subjectIds = maxTodayRecord.get().getSubjectIds();
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
} else {
throw new RuntimeException("业务异常");
@@ -305,9 +306,12 @@ public class H5QsvQuizController {
answerRecordId = answerRecord.getId();
List<String> subjectIds = answerRecord.getSubjectIds();
if(CollectionUtil.isEmpty(subjectIds)){
throw new RuntimeException("题目列表为空!请检查答题显示日期");
}
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
resultSubjects = dao.query(QsvSubject.class, cnd);
}
}
@@ -340,6 +344,13 @@ public class H5QsvQuizController {
float totalScore = 0;
for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) {
QsvSubject dbSubject = dao.fetch(QsvSubject.class, subject.getId());
if (ObjectUtil.isNotEmpty(dbSubject) && "checkbox".equals(dbSubject.getType())
&& ObjectUtil.isNotEmpty(dbSubject.getMaxMulti()) && dbSubject.getMaxMulti() > 0
&& CollectionUtil.size(subject.getUserSelectOptionIds()) > dbSubject.getMaxMulti()) {
// 后端兜底校验最大可选数,避免绕过前端直接提交超限答案。
return Result.error("题目【" + dbSubject.getTitle() + "】最多只能选择" + dbSubject.getMaxMulti() + "");
}
JSONObject entries = extJson.get(subject.getId(), JSONObject.class);
if (ObjectUtil.isNotEmpty(entries)) {
entries.set("optionIds", subject.getUserSelectOptionIds());
@@ -384,7 +395,7 @@ public class H5QsvQuizController {
Cnd cnd = Cnd.where("id", "in", subjectIds);
String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(","));
cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")"));
cnd.orderBy("sortNum","asc");
List<QsvSubject> subjects = dao.query(QsvSubject.class, cnd);
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
@@ -27,6 +27,7 @@ import com.budwk.app.zhgh.staffbenefit.condolence.service.CondolenceTypeService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.staffbenefit.condolence.model.CondolenceType;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -85,19 +86,7 @@ public class UnionReimburseApplyController {
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
@SLog(type = "unionReimburse", tag = "工会报销-报销申请", msg = "保存报销申请")
public Result save(@Param("data") UnionReimburse unionReimburse) {
if (StrUtil.isBlank(unionReimburse.getId())) {
unionReimburse.setCreateTime(new Date());
String documentNo = generateDocumentNo();
unionReimburse.setDocumentNo(documentNo);
}
unionReimburse.setStateId(1);//待提交
if (("UNION_REIMBURSE_PROJECT_1").equals(unionReimburse.getReimburseProject())){
unionReimburse.setRealMoney(unionReimburse.getCondolenceMoney());
}else {
unionReimburse.setRealMoney(unionReimburse.getMoney());
}
dao.insertOrUpdate(unionReimburse);
return Result.success();
return unionReimburseService.saveApply(unionReimburse, 1);
}
@At
@@ -105,18 +94,10 @@ public class UnionReimburseApplyController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result submit(@Param("data") UnionReimburse unionReimburse) {
if (StrUtil.isBlank(unionReimburse.getId())) {
unionReimburse.setCreateTime(new Date());
String documentNo = generateDocumentNo();
unionReimburse.setDocumentNo(documentNo);
Result result = unionReimburseService.saveApply(unionReimburse, 2);
if (result.getCode() != 0) {
return result;
}
unionReimburse.setStateId(2);//待审核
if (("UNION_REIMBURSE_PROJECT_1").equals(unionReimburse.getReimburseProject())){
unionReimburse.setRealMoney(unionReimburse.getCondolenceMoney());
}else {
unionReimburse.setRealMoney(unionReimburse.getMoney());
}
dao.insertOrUpdate(unionReimburse);
// // 开启流程实例
// Dict args = Dict.create();
// args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
@@ -153,7 +134,7 @@ public class UnionReimburseApplyController {
@SaCheckLogin
@ApiOperation("获取当前登录人的申请信息")
public Result info(@Param("id") String id) {
UnionReimburse unionReimburse = dao.fetch(UnionReimburse.class, id);
UnionReimburse unionReimburse = unionReimburseService.getApplyForm(id);
return Result.success(unionReimburse);
}
@@ -190,14 +171,9 @@ public class UnionReimburseApplyController {
cnd.and(seg);
}
cnd.and(View_user::getId, "!=", SecurityUtil.getUserId());
/*if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
} else {
cnd.and(View_user::getId, "=", SecurityUtil.getUserId());
}
}*/
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
}
sql.setCondition(cnd);
Pagination pagination = unionReimburseService.listPageMap(1, 50, sql);
return Result.success(pagination.getList());
@@ -215,20 +191,25 @@ public class UnionReimburseApplyController {
@ApiOperation("查询厉害账号信息")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result findUserBankHistory(String payer) {
Sql sql = Sqls.create("""
SELECT DISTINCT
bankCardNumber,
bankOfDeposit
FROM union_reimburse
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("payer", "=", payer);
sql.setCondition(cnd);
List<NutMap> result = unionReimburseService.listMap(sql);
List<UnionReimburseBankHistoryVO> result = unionReimburseService.getUserBankHistory(payer);
return Result.success(result);
}
@At
@ApiOperation("校验发票号码是否重复")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result checkInvoiceDuplicate(String invoiceNo, String reimburseId) {
Result result = unionReimburseService.checkInvoiceDuplicate(invoiceNo, reimburseId);
return result == null ? Result.success() : result;
}
@At
@ApiOperation("自动识别发票信息")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result recognizeInvoice(String fileId, @Param("verifySwitch") Boolean verifySwitch) {
return unionReimburseService.recognizeInvoice(fileId, verifySwitch);
}
@At
@ApiOperation("查询校工会经费余额")
@@ -281,39 +262,4 @@ public class UnionReimburseApplyController {
// 如果没有找到该社团的经费记录,返回0
return Result.success(0.0);
}
@At
@ApiOperation("生成文件编号")
private String generateDocumentNo() {
int currentYear = DateUtil.thisYear();
String yearPrefix = String.valueOf(currentYear);
org.nutz.dao.Cnd cnd = org.nutz.dao.Cnd.where("documentNo", "LIKE", yearPrefix + "%");
cnd.desc("documentNo");
cnd.limit(1);
List<UnionReimburse> list = dao.query(UnionReimburse.class, cnd);
int nextSeq = 1; // 默认从01开始
if (!list.isEmpty()) {
String maxNo = list.get(0).getDocumentNo();
if (maxNo != null && maxNo.startsWith(yearPrefix)) {
// 提取序号部分(去掉年份前缀)
String seqPart = maxNo.substring(yearPrefix.length());
try {
int currentMaxSeq = Integer.parseInt(seqPart);
nextSeq = currentMaxSeq + 1;
} catch (NumberFormatException e) {
// 如果解析失败,使用默认值1
nextSeq = 1;
}
}
}
// 格式化序号为两位数
String seqStr = String.format("%02d", nextSeq);
return yearPrefix + seqStr;
}
}
@@ -3,11 +3,13 @@ package com.budwk.app.zhgh.dayofficework.unionReimburse.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
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.dayofficework.unionReimburse.service.UnionReimburseService;
import io.swagger.annotations.Api;
@@ -105,6 +107,9 @@ public class UnionReimburseCertifierUserSignController {
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
if (!isAdmin()) {
cnd.andEX("info.unionId", "=", SecurityUtil.getUnionId());
}
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
@@ -116,10 +121,11 @@ public class UnionReimburseCertifierUserSignController {
seg.orLike("info.loginName",pageForm.getSearchKeyword());
cnd.and(seg);
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
String orderField = normalizeSortField(pageForm.getPageOrderName());
if (StrUtil.isAllBlank(orderField, pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
cnd.orderBy(orderField, PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
@@ -128,4 +134,21 @@ public class UnionReimburseCertifierUserSignController {
return Result.success(pagination);
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
private String normalizeSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "loginName" -> "info.loginName";
case "unionName" -> "info.unionName";
case "unitName" -> "info.unitName";
case "createTime" -> "info.createTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
}
@@ -12,6 +12,7 @@ import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.sys.models.Sys_dict;
@@ -93,6 +94,9 @@ public class UnionReimburseCollectController {
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
if (!isAdmin()) {
cnd.andEX("info.unionId", "=", SecurityUtil.getUnionId());
}
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
@@ -106,7 +110,7 @@ public class UnionReimburseCollectController {
cnd.and("info.stateId", "in", List.of(3, 2));
cnd.desc("info.createTime");
applyListSort(cnd, pageForm);
sql.setCondition(cnd);
Pagination<NutMap> pagination = unionReimburseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
@@ -149,7 +153,7 @@ public class UnionReimburseCollectController {
cnd.andEX("YEAR(rei.createTime)", "=", year);
cnd.andEX("u.unitid", "=", unitId);
cnd.andEX("u.unionid", "=", unionId);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
if (!isAdmin()) {
cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId());
}
cnd.desc("rei.createTime");
@@ -166,8 +170,10 @@ public class UnionReimburseCollectController {
try{
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(("收款人名册.xls").getBytes("utf-8"), "ISO8859-1"));
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), excelExportEntities, list);
response.setHeader("Content-Disposition", "attachment;filename=" + new String(("收款人名册.xlsx").getBytes("utf-8"), "ISO8859-1"));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelExportEntities, list);
workbook.write(response.getOutputStream());
}catch (Exception e){
e.printStackTrace();
@@ -197,6 +203,9 @@ public class UnionReimburseCollectController {
Cnd cnd = buildCondition(year, unionId, unitId, reimburseProject, userName, "t1.");
// 只查询流程实例状态为20的数据(已完成状态)
cnd.and("ins.state", "=", 20);
if (!isAdmin()) {
cnd.andEX("t1.unionId", "=", SecurityUtil.getUnionId());
}
cnd.desc("t1.createTime");
sql.setCondition(cnd);
@@ -275,4 +284,34 @@ public class UnionReimburseCollectController {
return cnd;
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
/**
* 收集页列表只允许固定字段排序,前端传入原序时回退到默认申请时间倒序。
*/
private void applyListSort(Cnd cnd, PageForm pageForm) {
String orderField = normalizeSortField(pageForm.getPageOrderName());
if (StrUtil.isAllBlank(orderField, pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
return;
}
cnd.orderBy(orderField, PageUtil.getOrder(pageForm.getPageOrderBy()));
}
private String normalizeSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "loginName" -> "info.loginName";
case "unionName" -> "info.unionName";
case "unitName" -> "info.unitName";
case "createTime" -> "info.createTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
}
@@ -9,14 +9,17 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.model.Audit;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.MoneyUtil;
import com.budwk.app.base.utils.OfficePlusUtil;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
@@ -133,6 +136,9 @@ public class UnionReimburseMineController {
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
if (!isAdmin()) {
cnd.andEX("info.unionId", "=", SecurityUtil.getUnionId());
}
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
@@ -143,13 +149,40 @@ public class UnionReimburseMineController {
seg.orLike("info.loginName", "%" + searchKeyword + "%");
cnd.and(seg);
}
cnd.desc("info.createTime");
applyListSort(cnd, pageForm);
cnd.and("info.userId", "=", SecurityUtil.getUserId());
sql.setCondition(cnd);
Pagination<NutMap> pagination = unionReimburseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
private void applyListSort(Cnd cnd, PageForm pageForm) {
String orderField = normalizeSortField(pageForm.getPageOrderName());
if (StrUtil.isAllBlank(orderField, pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
return;
}
cnd.orderBy(orderField, PageUtil.getOrder(pageForm.getPageOrderBy()));
}
private String normalizeSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "loginName" -> "info.loginName";
case "unionName" -> "info.unionName";
case "unitName" -> "info.unitName";
case "createTime" -> "info.createTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
@At
@ApiOperation("删除")
@Aop(TransAop.READ_COMMITTED)
@@ -1,7 +1,5 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
@@ -10,15 +8,12 @@ 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.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
@@ -35,8 +30,6 @@ import org.nutz.mvc.annotation.Param;
import java.util.Date;
import java.util.List;
import static org.openjdk.nashorn.internal.runtime.regexp.joni.Config.log;
@IocBean
@At("/platform/unionReimburse/review")
@Ok("json:full")
@@ -49,12 +42,12 @@ public class UnionReimburseReviewController {
@Inject
private UnionReimburseService unionReimburseService;
@At("/index")
@Ok("beetl:/platform/zhgh/dayofficework/unionReimburse/review/index.html")
@SaCheckPermission("unionReimburse.review")
public void index() {
}
@At("/h5")
@Ok("beetl:/platform/zhghh5/dayofficework/unionReimburse/review/index.html")
@SaCheckPermission("h5.unionReimburse.review")
@@ -71,37 +64,26 @@ public class UnionReimburseReviewController {
String unitId,
String reimburseProject) {
Sql sql = Sqls.create("""
SELECT
SELECT
info.*
FROM
union_reimburse info
$condition
$condition
""");
Cnd cnd = Cnd.NEW();
if (approval) {
// 已审核:查询 stateId 为 3(报销成功)、4(拒绝)、5(退回)的记录
cnd.and("info.stateId", "in", List.of(3, 4, 5));
} else {
// 未审核:只查询 stateId 为 2(待审核确认)的记录
cnd.and("info.stateId", "=", 2);
}
// 年度查询条件
cnd.andEX("year(info.createTime)", "=", year);
// 工会、单位名称查询条件
cnd.andEX("info.unionId", "=", unionId);
cnd.andEX("info.unitId", "=", unitId);
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", reimburseProject);
// 姓名和工号查询条件
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("info.userName",pageForm.getSearchKeyword());
seg.orLike("info.loginName",pageForm.getSearchKeyword());
seg.orLike("info.userName", pageForm.getSearchKeyword());
seg.orLike("info.loginName", pageForm.getSearchKeyword());
cnd.and(seg);
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
@@ -119,21 +101,18 @@ public class UnionReimburseReviewController {
@ApiOperation("审核")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR)
@SLog( tag = "审核工会报销", msg = "审核工会报销")
public Result reviewTask(@Param("data") UnionReimburse unionReimburse,String submitType) {
@SLog(tag = "审核工会报销", msg = "审核工会报销")
public Result reviewTask(@Param("data") UnionReimburse unionReimburse, String submitType) {
if (submitType == null || !List.of("3", "4", "5").contains(submitType)) {
return Result.error("无效的审核操作类型");
}
UnionReimburse dbRecord = dao.fetch(UnionReimburse.class, unionReimburse.getId());
if (dbRecord == null) {
return Result.error("记录不存在");
}
dbRecord.setReviewTime(new Date());
dbRecord.setStateId(Integer.parseInt(submitType));
dbRecord.setReviewOpinion(unionReimburse.getReviewOpinion());
dao.update(dbRecord);
return Result.success();
}
@@ -142,7 +121,7 @@ public class UnionReimburseReviewController {
@ApiOperation("一键审核")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR)
@SLog( tag = "一键审核", msg = "一键审核")
@SLog(tag = "一键审核", msg = "一键审核")
public Result allReview() {
try {
List<UnionReimburse> reimbursements = unionReimburseService.query(Cnd.where("stateId", "=", 2));
@@ -158,6 +137,4 @@ public class UnionReimburseReviewController {
return Result.error("一键审核失败: " + e.getMessage());
}
}
}
@@ -6,11 +6,14 @@ import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
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.utils.CommonDownloadUtil;
import com.budwk.app.flow.engine.FlowEngine;
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.dayofficework.unionReimburse.param.UnionReimbursePageForm;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import io.swagger.annotations.Api;
@@ -93,6 +96,8 @@ public class UnionReimburseStatisticsController {
}
if (Strings.isNotBlank(unionId)) {
sql.setVar("unionIdCnd", String.format("AND us.unionId = '%s' ", unionId));
} else if (!isAdmin()) {
sql.setVar("unionIdCnd", String.format("AND us.unionId = '%s' ", SecurityUtil.getUnionId()));
}
if (Strings.isNotBlank(condolenceTypeId)) {
sql.setVar("typeCnd", String.format("AND con.condolenceTypeId = '%s' ", condolenceTypeId));
@@ -150,4 +155,7 @@ public class UnionReimburseStatisticsController {
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
}
@@ -415,4 +415,9 @@ public class UnionReimburse extends BaseModel {
@ColDefine(type = ColType.DATETIME)
private Date reviewTime;
/**
* 发票明细列表仅用于申请页表单回显和提交,不直接映射主表字段。
*/
private List<UnionReimburseInvoiceDetail> invoiceDetails;
}
@@ -0,0 +1,66 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.model;
import cn.hutool.json.JSONObject;
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.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.List;
/**
* 工会报销发票明细。
* 每条明细对应一张发票文件,保存发票号码、金额和销售方等信息。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("union_reimburse_invoice_detail")
public class UnionReimburseInvoiceDetail extends BaseModel {
@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 reimburseId;
@Column
@Comment("发票文件")
@ColDefine(type = ColType.MYSQL_JSON)
private List<JSONObject> invoiceFiles;
@Column
@Comment("发票号码")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String invoiceNo;
@Column
@Comment("发票金额")
@ColDefine(type = ColType.FLOAT, width = 10, precision = 2)
private Double invoiceAmount;
@Column
@Comment("销售方信息名称")
@ColDefine(type = ColType.VARCHAR, width = 200)
private String sellerName;
@Column
@Comment("项目名称")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String itemName;
@Column
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String remark;
}
@@ -1,11 +1,41 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm;
import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO;
import org.nutz.dao.sql.Sql;
import java.util.List;
public interface UnionReimburseService extends BaseService<UnionReimburse> {
Sql getSql(UnionReimbursePageForm pageForm);
/**
* 保存或提交报销申请,并同步保存发票明细。
*/
Result saveApply(UnionReimburse unionReimburse, int stateId);
/**
* 查询申请表单详情,补齐发票明细回显数据。
*/
UnionReimburse getApplyForm(String id);
/**
* 查询付款人的历史收款信息。
*/
List<UnionReimburseBankHistoryVO> getUserBankHistory(String payer);
/**
* 校验发票号码是否与历史报销记录重复。
*/
Result checkInvoiceDuplicate(String invoiceNo, String reimburseId);
/**
* 识别单张发票文件,并在需要时调用腾讯云验真接口补齐票面信息。
*/
Result recognizeInvoice(String fileId, Boolean verifySwitch);
}
@@ -1,24 +1,69 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_file;
import com.budwk.app.sys.services.SysFileService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburse;
import com.budwk.app.zhgh.dayofficework.unionReimburse.model.UnionReimburseInvoiceDetail;
import com.budwk.app.zhgh.dayofficework.unionReimburse.param.UnionReimbursePageForm;
import com.budwk.app.zhgh.dayofficework.unionReimburse.service.UnionReimburseService;
import com.budwk.app.zhgh.dayofficework.unionReimburse.vo.UnionReimburseBankHistoryVO;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.impl.PropertiesProxy;
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 com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.ocr.v20181119.OcrClient;
import com.tencentcloudapi.ocr.v20181119.models.TextVatInvoice;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoice;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceItem;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceOCRRequest;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceOCRResponse;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceVerifyRequest;
import com.tencentcloudapi.ocr.v20181119.models.VatInvoiceVerifyResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Date;
import java.util.stream.Collectors;
@Slf4j
@IocBean(args = {"refer:dao"})
public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> implements UnionReimburseService {
public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> implements UnionReimburseService {
@Inject
private SysFileService sysFileService;
@Inject
private PropertiesProxy conf;
public UnionReimburseServiceImpl(Dao dao) {
super(dao);
}
@@ -35,30 +80,609 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse>
$condition
""");
Cnd cnd = Cnd.NEW();
// 年度查询条件
cnd.andEX("year(info.createTime)", "=", pageForm.getYear());
// 工会、单位名称查询条件
cnd.andEX("info.condolenceUnionId", "=", pageForm.getUnionId());
cnd.andEX("info.unitId", "=", pageForm.getUnitId());
cnd.andEX("info.condolenceTypeId", "=", pageForm.getCondolenceTypeId());
cnd.andEX("info.way", "=", pageForm.getWay());
// 报销项目查询条件
cnd.andEX("info.reimburseProject", "=", pageForm.getReimburseProject());
// 姓名和工号查询条件
if (!isAdmin()) {
cnd.andEX("info.condolenceUnionId", "=", SecurityUtil.getUnionId());
}
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("info.condolenceUserName",pageForm.getSearchKeyword());
seg.orLike("info.condolenceLoginName",pageForm.getSearchKeyword());
seg.orLike("info.condolenceUserName", pageForm.getSearchKeyword());
seg.orLike("info.condolenceLoginName", pageForm.getSearchKeyword());
cnd.and(seg);
}
cnd.and("info.stateId", "in", List.of(3, 2));
cnd.desc("info.createTime");
applyStatisticsSort(cnd, pageForm);
sql.setCondition(cnd);
return sql;
}
@Override
public Result saveApply(UnionReimburse unionReimburse, int stateId) {
if (unionReimburse == null) {
return Result.error("报销数据不能为空");
}
UnionReimburse oldRecord = null;
if (StrUtil.isNotBlank(unionReimburse.getId())) {
oldRecord = this.fetch(unionReimburse.getId());
if (oldRecord == null) {
return Result.error("未找到对应的报销记录");
}
}
normalizeInvoiceDetails(unionReimburse);
if (needInvoiceDetails(unionReimburse)) {
fillInvoiceSummary(unionReimburse);
}
if (stateId == 2) {
Result validateResult = validateBeforeSubmit(unionReimburse);
if (validateResult != null) {
return validateResult;
}
Result duplicateResult = validateInvoiceDuplicate(unionReimburse);
if (duplicateResult != null) {
return duplicateResult;
}
}
if (oldRecord == null) {
unionReimburse.setCreateTime(DateUtil.date());
unionReimburse.setDocumentNo(generateDocumentNo());
} else {
unionReimburse.setCreateTime(oldRecord.getCreateTime());
unionReimburse.setDocumentNo(oldRecord.getDocumentNo());
}
unionReimburse.setStateId(stateId);
if ("UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject())) {
unionReimburse.setRealMoney(unionReimburse.getCondolenceMoney());
} else {
unionReimburse.setRealMoney(unionReimburse.getMoney());
}
this.dao().insertOrUpdate(unionReimburse);
saveInvoiceDetails(unionReimburse.getId(), unionReimburse.getInvoiceDetails());
return Result.success(stateId == 2 ? "提交成功" : "保存成功");
}
@Override
public UnionReimburse getApplyForm(String id) {
UnionReimburse unionReimburse = this.fetch(id);
if (unionReimburse == null) {
return null;
}
List<UnionReimburseInvoiceDetail> invoiceDetails = this.dao().query(
UnionReimburseInvoiceDetail.class,
Cnd.where("reimburseId", "=", id).asc("createdAt")
);
unionReimburse.setInvoiceDetails(invoiceDetails == null ? new ArrayList<>() : invoiceDetails);
return unionReimburse;
}
@Override
public List<UnionReimburseBankHistoryVO> getUserBankHistory(String payer) {
if (StrUtil.isBlank(payer)) {
return new ArrayList<>();
}
Sql sql = Sqls.create("""
SELECT
bankUserName,
bankCardNumber,
bankOfDeposit
FROM
union_reimburse
$condition
GROUP BY
bankUserName,
bankCardNumber,
bankOfDeposit
ORDER BY
MAX(createTime) DESC
""");
Cnd cnd = Cnd.NEW();
cnd.and("payer", "=", payer);
cnd.and("bankUserName", "is not", null);
cnd.and("bankCardNumber", "is not", null);
cnd.and("bankOfDeposit", "is not", null);
sql.setCondition(cnd);
return this.listVO(sql, UnionReimburseBankHistoryVO.class);
}
@Override
public Result checkInvoiceDuplicate(String invoiceNo, String reimburseId) {
String currentInvoiceNo = StrUtil.blankToDefault(StrUtil.trim(invoiceNo), "");
if (StrUtil.isBlank(currentInvoiceNo)) {
return Result.error("发票号码不能为空");
}
return queryHistoryInvoiceDuplicate(currentInvoiceNo, reimburseId);
}
@Override
public Result recognizeInvoice(String fileId, Boolean verifySwitch) {
if (StrUtil.isBlank(fileId)) {
return Result.error("请先上传发票文件");
}
Sys_file sysFile = sysFileService.fetch(fileId);
if (sysFile == null) {
return Result.error("发票文件不存在");
}
Credential credential = getInvoiceCredential();
if (credential == null) {
return Result.error("未配置腾讯云发票识别密钥,请先在 application-dev.yaml 中配置 invoice.tencent.secret-id 和 invoice.tencent.secret-key");
}
try {
byte[] fileBytes = sysFileService.download(fileId);
VatInvoiceOCRResponse response = executeOcr(credential, sysFile, fileBytes);
NutMap result = buildOcrResult(response);
String msg = "发票识别成功";
if (Boolean.TRUE.equals(verifySwitch)) {
String verifyMsg = enrichByVerify(credential, result);
msg = StrUtil.isBlank(verifyMsg) ? "发票识别并校验成功" : verifyMsg;
}
return Result.success(msg, result);
} catch (TencentCloudSDKException e) {
log.error("腾讯云发票识别失败,fileId:{}", fileId, e);
return Result.error("腾讯云发票识别失败:" + e.getMessage());
} catch (Exception e) {
log.error("发票识别失败,fileId:{}", fileId, e);
return Result.error("发票识别失败:" + e.getMessage());
}
}
/**
* 统一规整发票明细中的文本和金额,避免空格、空数组等脏数据进入库表。
*/
private void normalizeInvoiceDetails(UnionReimburse unionReimburse) {
if (unionReimburse.getInvoiceDetails() == null) {
unionReimburse.setInvoiceDetails(new ArrayList<>());
return;
}
List<UnionReimburseInvoiceDetail> invoiceDetails = unionReimburse.getInvoiceDetails().stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
invoiceDetails.forEach(detail -> {
detail.setInvoiceNo(StrUtil.trim(detail.getInvoiceNo()));
detail.setSellerName(StrUtil.trim(detail.getSellerName()));
detail.setItemName(StrUtil.trim(detail.getItemName()));
detail.setRemark(StrUtil.trim(detail.getRemark()));
if (detail.getInvoiceFiles() == null) {
detail.setInvoiceFiles(new ArrayList<>());
}
if (detail.getInvoiceAmount() != null) {
detail.setInvoiceAmount(BigDecimal.valueOf(detail.getInvoiceAmount())
.setScale(2, RoundingMode.HALF_UP)
.doubleValue());
}
});
unionReimburse.setInvoiceDetails(invoiceDetails);
}
/**
* 活动类报销由发票明细自动汇总金额和发票张数,减少手工输入出错。
*/
private void fillInvoiceSummary(UnionReimburse unionReimburse) {
double totalMoney = 0D;
int invoiceFileCount = 0;
for (UnionReimburseInvoiceDetail detail : unionReimburse.getInvoiceDetails()) {
if (detail.getInvoiceAmount() != null) {
totalMoney = BigDecimal.valueOf(totalMoney)
.add(BigDecimal.valueOf(detail.getInvoiceAmount()))
.doubleValue();
}
if (Lang.isNotEmpty(detail.getInvoiceFiles())) {
invoiceFileCount++;
}
}
unionReimburse.setMoney(BigDecimal.valueOf(totalMoney).setScale(2, RoundingMode.HALF_UP).doubleValue());
unionReimburse.setInvoiceNumber(String.valueOf(invoiceFileCount));
}
/**
* 提交前补一层后端校验,防止前端绕过校验直接提交。
*/
private Result validateBeforeSubmit(UnionReimburse unionReimburse) {
if (StrUtil.isBlank(unionReimburse.getMobile())) {
return Result.error("请填写联系方式");
}
if (!isValidContact(unionReimburse.getMobile())) {
return Result.error("请输入正确的联系方式");
}
if (StrUtil.isBlank(unionReimburse.getBankUserName())) {
return Result.error("请填写户名");
}
if (StrUtil.isBlank(unionReimburse.getBankCardNumber())) {
return Result.error("请填写银行账号");
}
if (StrUtil.isBlank(unionReimburse.getBankOfDeposit())) {
return Result.error("请填写开户行");
}
if (StrUtil.isBlank(unionReimburse.getPaymentNotes())) {
return Result.error("请填写支付内容");
}
if (!needInvoiceDetails(unionReimburse)) {
return null;
}
if (StrUtil.isBlank(unionReimburse.getActivityName())) {
return Result.error("请填写活动名称");
}
if (StrUtil.isBlank(unionReimburse.getActivityPlace())) {
return Result.error("请填写活动地点");
}
if (unionReimburse.getActivityTime() == null) {
return Result.error("请选择活动时间");
}
if (Lang.isEmpty(unionReimburse.getFiles())) {
return Result.error("请上传附件");
}
if (Lang.isEmpty(unionReimburse.getInvoiceDetails())) {
return Result.error("请至少维护一条发票明细");
}
for (int i = 0; i < unionReimburse.getInvoiceDetails().size(); i++) {
UnionReimburseInvoiceDetail detail = unionReimburse.getInvoiceDetails().get(i);
int rowIndex = i + 1;
if (Lang.isEmpty(detail.getInvoiceFiles())) {
return Result.error("" + rowIndex + "条发票明细请上传发票文件");
}
if (StrUtil.isBlank(detail.getInvoiceNo())) {
return Result.error("" + rowIndex + "条发票明细请填写发票号码");
}
if (detail.getInvoiceAmount() == null || detail.getInvoiceAmount() <= 0) {
return Result.error("" + rowIndex + "条发票明细请填写正确的发票金额");
}
if (StrUtil.isBlank(detail.getSellerName())) {
return Result.error("" + rowIndex + "条发票明细请填写销售方信息名称");
}
if (StrUtil.isBlank(detail.getItemName())) {
return Result.error("" + rowIndex + "条发票明细请填写项目名称");
}
}
return null;
}
/**
* 同次提交和历史记录都要拦截重复发票,避免重复报销。
*/
private Result validateInvoiceDuplicate(UnionReimburse unionReimburse) {
Map<String, List<Integer>> currentInvoiceMap = new LinkedHashMap<>();
List<UnionReimburseInvoiceDetail> details = unionReimburse.getInvoiceDetails();
for (int i = 0; i < details.size(); i++) {
String invoiceNo = StrUtil.trim(details.get(i).getInvoiceNo());
if (StrUtil.isBlank(invoiceNo)) {
continue;
}
currentInvoiceMap.computeIfAbsent(invoiceNo, key -> new ArrayList<>()).add(i + 1);
}
for (Map.Entry<String, List<Integer>> entry : currentInvoiceMap.entrySet()) {
if (entry.getValue().size() > 1) {
String rowText = entry.getValue().stream().map(String::valueOf).collect(Collectors.joining(""));
return Result.error("发票号码【" + entry.getKey() + "】与本次提交的第" + rowText + "条发票重复,不能提交");
}
}
for (String invoiceNo : currentInvoiceMap.keySet()) {
Result duplicateResult = queryHistoryInvoiceDuplicate(invoiceNo, unionReimburse.getId());
if (duplicateResult != null) {
return duplicateResult;
}
}
return null;
}
/**
* 历史重复校验会排除当前单据自身,避免编辑回显时误判。
*/
private Result queryHistoryInvoiceDuplicate(String invoiceNo, String reimburseId) {
Sql sql = Sqls.create("""
SELECT
d.invoiceNo,
r.activityName
FROM
union_reimburse_invoice_detail d
LEFT JOIN union_reimburse r ON r.id = d.reimburseId
WHERE
d.invoiceNo = @invoiceNo
AND (@reimburseId = '' OR d.reimburseId <> @reimburseId)
LIMIT 1
""");
sql.params().set("invoiceNo", invoiceNo);
sql.params().set("reimburseId", StrUtil.blankToDefault(StrUtil.trim(reimburseId), ""));
List<NutMap> repeatList = this.listMap(sql);
if (Lang.isEmpty(repeatList)) {
return null;
}
String activityName = repeatList.get(0).getString("activityName");
if (StrUtil.isBlank(activityName)) {
activityName = "历史报销记录";
}
return Result.error("发票号码【" + invoiceNo + "】与以往活动名称【" + activityName + "】中提交的发票重复,不能提交");
}
/**
* 每次按当前表单全量覆盖明细,避免旧明细残留。
*/
/**
* 发票识别优先读取当前项目配置的 SecretId/SecretKey,没有时再回退读取用户提供的 csv 文件。
*/
private Credential getInvoiceCredential() {
String secretId = StrUtil.trim(conf.get("invoice.tencent.secret-id"));
String secretKey = StrUtil.trim(conf.get("invoice.tencent.secret-key"));
if (StrUtil.isNotBlank(secretId) && StrUtil.isNotBlank(secretKey)) {
return new Credential(secretId, secretKey);
}
return loadCredentialFromSecretFile();
}
/**
* 兼容腾讯云控制台下载的密钥 csv,便于本地开发时不把明文密钥直接写进配置文件。
*/
private Credential loadCredentialFromSecretFile() {
String secretFile = StrUtil.trim(conf.get("invoice.tencent.secret-file"));
if (StrUtil.isBlank(secretFile)) {
return null;
}
try {
Path path = Paths.get(secretFile);
if (!Files.exists(path)) {
return null;
}
List<String> lines = Files.readAllLines(path);
if (lines.size() < 2) {
return null;
}
String[] values = lines.get(1).split(",", -1);
if (values.length < 2) {
return null;
}
String secretId = StrUtil.trim(values[0]);
String secretKey = StrUtil.trim(values[1]);
if (StrUtil.isBlank(secretId) || StrUtil.isBlank(secretKey)) {
return null;
}
return new Credential(secretId, secretKey);
} catch (Exception e) {
log.warn("读取腾讯云发票识别密钥文件失败,file:{}", secretFile, e);
return null;
}
}
/**
* 统一创建腾讯云 OCR 客户端,避免控制器直接依赖第三方 SDK。
*/
private OcrClient createOcrClient(Credential credential) {
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ocr.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
return new OcrClient(credential, "", clientProfile);
}
private VatInvoiceOCRResponse executeOcr(Credential credential, Sys_file sysFile, byte[] fileBytes) throws TencentCloudSDKException {
OcrClient client = createOcrClient(credential);
VatInvoiceOCRRequest request = new VatInvoiceOCRRequest();
request.setImageBase64(Base64.getEncoder().encodeToString(fileBytes));
String fileName = StrUtil.blankToDefault(sysFile.getName(), "").toLowerCase();
if (fileName.endsWith(".pdf")) {
request.setIsPdf(true);
request.setPdfPageNumber(1L);
}
return client.VatInvoiceOCR(request);
}
/**
* 将腾讯云 OCR 返回的票面字段规整成页面可直接回填的键值结构。
*/
private NutMap buildOcrResult(VatInvoiceOCRResponse response) {
NutMap result = NutMap.NEW();
TextVatInvoice[] infos = response.getVatInvoiceInfos();
VatInvoiceItem[] items = response.getItems();
result.setv("invoiceNo", pickValue(infos, "发票号码", "号码", "No"));
result.setv("invoiceCode", pickValue(infos, "发票代码", "代码", "Code"));
result.setv("invoiceDate", normalizeDate(pickValue(infos, "开票日期", "日期", "Date")));
result.setv("invoiceCheckCode", pickValue(infos, "校验码", "校验码后6位", "CheckCode"));
result.setv("invoiceAmount", parseMoney(pickValue(infos, "小写金额", "价税合计(小写)", "价税合计", "合计金额", "AmountWithTax")));
result.setv("sellerName", pickValue(infos, "销售方名称", "销售方信息名称", "销售方", "SellerName"));
result.setv("itemName", joinItemNames(items));
return result;
}
/**
* 自动校验需要票面关键信息,缺字段时只提示人工核对,不阻塞用户继续录入。
*/
private String enrichByVerify(Credential credential, NutMap result) {
String invoiceCode = result.getString("invoiceCode");
String invoiceNo = result.getString("invoiceNo");
String invoiceDate = result.getString("invoiceDate");
String checkCode = lastDigits(result.getString("invoiceCheckCode"), 6);
if (StrUtil.isBlank(checkCode)) {
checkCode = moneyToVerifyValue(result.get("invoiceAmount"));
}
if (StrUtil.hasBlank(invoiceCode, invoiceNo, invoiceDate, checkCode)) {
return "发票识别成功,但自动校验信息不完整,请人工核对后提交";
}
try {
OcrClient client = createOcrClient(credential);
VatInvoiceVerifyRequest request = new VatInvoiceVerifyRequest();
request.setInvoiceCode(invoiceCode);
request.setInvoiceNo(invoiceNo);
request.setInvoiceDate(invoiceDate.replace("-", ""));
request.setAdditional(checkCode);
VatInvoiceVerifyResponse response = client.VatInvoiceVerify(request);
VatInvoice invoice = response.getInvoice();
if (invoice == null) {
return "发票识别成功,但自动校验未返回有效结果,请人工核对后提交";
}
result.setv("invoiceCode", invoice.getCode());
result.setv("invoiceNo", invoice.getNumber());
result.setv("invoiceDate", normalizeDate(invoice.getDate()));
result.setv("invoiceCheckCode", invoice.getCheckCode());
result.setv("sellerName", invoice.getSellerName());
result.setv("invoiceAmount", parseMoney(invoice.getAmountWithTax()));
result.setv("itemName", joinItemNames(invoice.getItems()));
return null;
} catch (Exception e) {
log.warn("腾讯云发票验真失败,invoiceNo:{}", invoiceNo, e);
return "发票识别成功,但自动校验失败,请人工核对后提交";
}
}
private String pickValue(TextVatInvoice[] infos, String... aliases) {
if (infos == null || aliases == null) {
return "";
}
Set<String> aliasSet = new LinkedHashSet<>();
for (String alias : aliases) {
aliasSet.add(normalizeFieldName(alias));
}
for (TextVatInvoice info : infos) {
if (info == null) {
continue;
}
if (aliasSet.contains(normalizeFieldName(info.getName()))) {
return StrUtil.blankToDefault(StrUtil.trim(info.getValue()), "");
}
}
return "";
}
private String normalizeFieldName(String name) {
return StrUtil.blankToDefault(name, "")
.replace("", "")
.replace(":", "")
.replace(" ", "")
.replace("\u00A0", "")
.trim();
}
private String normalizeDate(String dateText) {
String date = StrUtil.blankToDefault(StrUtil.trim(dateText), "");
if (StrUtil.isBlank(date)) {
return "";
}
String digits = date.replaceAll("[^0-9]", "");
if (digits.length() == 8) {
return digits.substring(0, 4) + "-" + digits.substring(4, 6) + "-" + digits.substring(6);
}
return date.replace("", "-").replace("", "-").replace("", "");
}
private Double parseMoney(Object moneyObj) {
String moneyText = StrUtil.blankToDefault(StrUtil.trim(ObjectUtil.defaultIfNull(moneyObj, "").toString()), "");
if (StrUtil.isBlank(moneyText)) {
return null;
}
moneyText = moneyText.replace(",", "").replace("", "").replace("¥", "");
try {
return BigDecimal.valueOf(Double.parseDouble(moneyText)).setScale(2, RoundingMode.HALF_UP).doubleValue();
} catch (Exception e) {
return null;
}
}
private String joinItemNames(VatInvoiceItem[] items) {
if (items == null || items.length == 0) {
return "";
}
return Arrays.stream(items)
.map(VatInvoiceItem::getName)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.joining(""));
}
private String lastDigits(String text, int length) {
String digits = StrUtil.blankToDefault(text, "").replaceAll("[^0-9]", "");
if (digits.length() <= length) {
return digits;
}
return digits.substring(digits.length() - length);
}
private String moneyToVerifyValue(Object moneyObj) {
Double money = parseMoney(moneyObj);
if (money == null) {
return "";
}
return BigDecimal.valueOf(money).setScale(2, RoundingMode.HALF_UP).toPlainString();
}
private void saveInvoiceDetails(String reimburseId, List<UnionReimburseInvoiceDetail> invoiceDetails) {
this.dao().clear(UnionReimburseInvoiceDetail.class, Cnd.where("reimburseId", "=", reimburseId));
if (Lang.isEmpty(invoiceDetails)) {
return;
}
invoiceDetails.forEach(detail -> {
detail.setId(null);
detail.setReimburseId(reimburseId);
this.dao().insert(detail);
});
}
/**
* 当前仅活动类报销维护发票明细,慰问类仍按原有逻辑处理金额。
*/
private boolean needInvoiceDetails(UnionReimburse unionReimburse) {
return unionReimburse != null && !"UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject());
}
/**
* 生成年度流水号,保持与现有单据编号规则一致。
*/
private String generateDocumentNo() {
int currentYear = DateUtil.thisYear();
String yearPrefix = String.valueOf(currentYear);
Cnd cnd = Cnd.where("documentNo", "like", yearPrefix + "%");
cnd.desc("documentNo");
cnd.limit(1);
List<UnionReimburse> list = this.query(cnd);
int nextSeq = 1;
if (Lang.isNotEmpty(list) && StrUtil.isNotBlank(list.get(0).getDocumentNo())) {
String maxNo = list.get(0).getDocumentNo();
if (maxNo.startsWith(yearPrefix)) {
String seqPart = maxNo.substring(yearPrefix.length());
if (StrUtil.isNumeric(seqPart)) {
nextSeq = Integer.parseInt(seqPart) + 1;
}
}
}
return yearPrefix + String.format("%02d", nextSeq);
}
/**
* 统计页排序字段仅允许白名单列,避免传入非法字段。
*/
private void applyStatisticsSort(Cnd cnd, UnionReimbursePageForm pageForm) {
String orderField = normalizeStatisticsSortField(pageForm.getPageOrderName());
if (StrUtil.isBlank(orderField) || StrUtil.isBlank(pageForm.getPageOrderBy())) {
cnd.desc("info.createTime");
return;
}
cnd.orderBy(orderField, "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
}
private String normalizeStatisticsSortField(String pageOrderName) {
if (StrUtil.isBlank(pageOrderName)) {
return null;
}
return switch (pageOrderName) {
case "condolenceLoginName" -> "info.condolenceLoginName";
case "condolenceUnionName" -> "info.condolenceUnionName";
case "unitName" -> "info.unitName";
case "newCreateTime", "createTime" -> "info.createTime";
case "newCondolenceTime" -> "info.condolenceTime";
case "reimburseProject" -> "info.reimburseProject";
default -> null;
};
}
private boolean isAdmin() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
/**
* 联系方式支持手机号和座机,和前端规则保持一致。
*/
private boolean isValidContact(String mobile) {
return mobile.matches("^(1[3-9]\\d{9}|0\\d{2,3}-?\\d{7,8})$");
}
}
@@ -0,0 +1,14 @@
package com.budwk.app.zhgh.dayofficework.unionReimburse.vo;
import lombok.Data;
/**
* 付款人历史银行卡信息。
* 用于申请页下次填写时快速回填户名、银行卡号和开户行。
*/
@Data
public class UnionReimburseBankHistoryVO {
private String bankUserName;
private String bankCardNumber;
private String bankOfDeposit;
}
@@ -79,7 +79,6 @@ public class GrassrootsCongressMaterialMineController {
grassroots_congress_materials_info info
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
$condition
""");
Cnd cnd = Cnd.NEW();
@@ -78,7 +78,7 @@ public class GrassrootsCongressMeetingMineController {
grassroots_congress_meeting_info info
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
-- 任务参与人表一条任务会有多条参与人记录,这里不需要联查,否则同一业务会被展开成多行
$condition
""");
Cnd cnd = Cnd.NEW();
@@ -54,29 +54,41 @@ public class ProposalQueryAnalysisController {
@At
@ApiOperation("统计数据")
@SaCheckPermission("proposal.query.analysis")
public Result statisticsData(String sessionId) {
public Result statisticsData(String sessionId, String dimension) {
/*
* 查询参数说明:
* sessionId:教代会届次ID,用于限定统计提案范围;
* dimension:统计维度,可传“提案人单位、提案类型、代表团、立案结果、满意度”,为空时返回全部维度。
* 返回值:Result.data 为统计行列表,包含 dimension、itemName、count、rate。
*/
if (StrUtil.isBlank(sessionId)) {
return Result.error("请先选择教代会届次");
}
return Result.success(buildStatisticsRows(sessionId));
return Result.success(buildStatisticsRows(sessionId, dimension));
}
@At
@ApiOperation("分析数据")
@SaCheckPermission("proposal.query.analysis")
public Result analysisData(String sessionId) {
public Result analysisData(String sessionId, String dimension) {
/*
* 查询参数说明:
* sessionId:教代会届次ID,用于限定分析提案范围;
* dimension:统计维度,可传“提案人单位、提案类型、代表团、立案结果、满意度”,为空时返回全部维度。
* 返回值:Result.data 为分析行列表,包含 dimension、total、categoryCount、topItem、topCount、topRate、analysis。
*/
if (StrUtil.isBlank(sessionId)) {
return Result.error("请先选择教代会届次");
}
return Result.success(buildAnalysisRows(sessionId));
return Result.success(buildAnalysisRows(sessionId, dimension));
}
@At
@Ok("void")
@ApiOperation("导出统计数据")
@SaCheckPermission("proposal.query.analysis")
public void exportStatisticsData(String sessionId, HttpServletResponse response) {
List<NutMap> list = buildStatisticsRows(sessionId);
public void exportStatisticsData(String sessionId, String dimension, HttpServletResponse response) {
List<NutMap> list = buildStatisticsRows(sessionId, dimension);
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("统计维度", "dimension", 20));
entities.add(new ExcelExportEntity("分类项", "itemName", 30));
@@ -92,8 +104,8 @@ public class ProposalQueryAnalysisController {
@Ok("void")
@ApiOperation("导出分析数据")
@SaCheckPermission("proposal.query.analysis")
public void exportAnalysisData(String sessionId, HttpServletResponse response) {
List<NutMap> list = buildAnalysisRows(sessionId);
public void exportAnalysisData(String sessionId, String dimension, HttpServletResponse response) {
List<NutMap> list = buildAnalysisRows(sessionId, dimension);
List<ExcelExportEntity> entities = new ArrayList<>();
entities.add(new ExcelExportEntity("统计维度", "dimension", 20));
entities.add(new ExcelExportEntity("总量", "total", 12));
@@ -108,15 +120,15 @@ public class ProposalQueryAnalysisController {
CommonDownloadUtil.download("提案分析数据.xlsx", workbook, response);
}
private List<NutMap> buildStatisticsRows(String sessionId) {
Map<String, List<NutMap>> dataMap = loadDimensionData(sessionId);
private List<NutMap> buildStatisticsRows(String sessionId, String dimension) {
Map<String, List<NutMap>> dataMap = loadDimensionData(sessionId, dimension);
List<NutMap> result = new ArrayList<>();
dataMap.forEach((dimension, items) -> {
dataMap.forEach((dimensionName, items) -> {
long total = items.stream().mapToLong(v -> v.getLong("count", 0L)).sum();
for (NutMap item : items) {
long count = item.getLong("count", 0L);
result.add(NutMap.NEW()
.addv("dimension", dimension)
.addv("dimension", dimensionName)
.addv("itemName", item.getString("itemName", "未维护"))
.addv("count", count)
.addv("rate", formatPercent(count, total)));
@@ -125,13 +137,13 @@ public class ProposalQueryAnalysisController {
return result;
}
private List<NutMap> buildAnalysisRows(String sessionId) {
Map<String, List<NutMap>> dataMap = loadDimensionData(sessionId);
private List<NutMap> buildAnalysisRows(String sessionId, String dimension) {
Map<String, List<NutMap>> dataMap = loadDimensionData(sessionId, dimension);
List<NutMap> result = new ArrayList<>();
dataMap.forEach((dimension, items) -> {
dataMap.forEach((dimensionName, items) -> {
if (items.isEmpty()) {
result.add(NutMap.NEW()
.addv("dimension", dimension)
.addv("dimension", dimensionName)
.addv("total", 0)
.addv("categoryCount", 0)
.addv("topItem", "-")
@@ -150,10 +162,10 @@ public class ProposalQueryAnalysisController {
long topCount = top.getLong("count", 0L);
String topItem = top.getString("itemName", "未维护");
String topRate = formatPercent(topCount, total);
String analysis = String.format("%s主要集中在【%s】,数量为%d,占比%s。", dimension, topItem, topCount, topRate);
String analysis = String.format("%s主要集中在【%s】,数量为%d,占比%s。", dimensionName, topItem, topCount, topRate);
result.add(NutMap.NEW()
.addv("dimension", dimension)
.addv("dimension", dimensionName)
.addv("total", total)
.addv("categoryCount", sorted.size())
.addv("topItem", topItem)
@@ -164,13 +176,23 @@ public class ProposalQueryAnalysisController {
return result;
}
private Map<String, List<NutMap>> loadDimensionData(String sessionId) {
private Map<String, List<NutMap>> loadDimensionData(String sessionId, String dimension) {
Map<String, List<NutMap>> map = new LinkedHashMap<>();
map.put("提案人单位", queryUnitStat(sessionId));
map.put("提案类型", queryTypeStat(sessionId));
map.put("代表团", queryDelegationStat(sessionId));
map.put("立案结果", queryCaseFilingResultStat(sessionId));
map.put("满意度", querySatisfactionStat(sessionId));
if (StrUtil.isBlank(dimension) || "提案人单位".equals(dimension)) {
map.put("提案人单位", queryUnitStat(sessionId));
}
if (StrUtil.isBlank(dimension) || "提案类型".equals(dimension)) {
map.put("提案类型", queryTypeStat(sessionId));
}
if (StrUtil.isBlank(dimension) || "代表团".equals(dimension)) {
map.put("代表团", queryDelegationStat(sessionId));
}
if (StrUtil.isBlank(dimension) || "立案结果".equals(dimension)) {
map.put("立案结果", queryCaseFilingResultStat(sessionId));
}
if (StrUtil.isBlank(dimension) || "满意度".equals(dimension)) {
map.put("满意度", querySatisfactionStat(sessionId));
}
return map;
}
@@ -73,9 +73,10 @@ public class ProposalQueryComprehensiveController {
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
LEFT JOIN wf_process_task replyTask on replyTask.processInstanceId = ins.id AND replyTask.taskState = 20 AND replyTask.taskName in ('master_reply','slave_reply','opinion_master_reply')
LEFT JOIN wf_process_task replyTask on replyTask.processInstanceId = ins.id
$condition
""");
// AND replyTask.taskState = 20 AND replyTask.taskName in ('master_reply','slave_reply','opinion_master_reply')
Cnd cnd = Cnd.NEW();
if (ArrayUtil.isNotEmpty(pageForm.getOrigins()) && StrUtil.isNotBlank(pageForm.getCommonKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
@@ -6,6 +6,7 @@ import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalReplyUnit;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import lombok.extern.slf4j.Slf4j;
@@ -44,16 +45,25 @@ public class ProposalQueryUnitReplyController {
@At
@SaCheckPermission("proposal.query.unitReply")
public Result data(String sessionId, String undertakeUnitId) {
public Result data(String sessionId, String undertakeUnitId, String caseFilingResult) {
/*
* 查询参数说明:
* sessionId:教代会届次ID,用于限定统计的提案范围;
* undertakeUnitId:承办单位ID,用于只统计某个承办单位,为空时统计全部承办单位;
* caseFilingResult:立案结果字典值,对应 proposal_info.caseFilingResult,为空时不限制立案结果。
* 返回值:Result.data 中包含 tableData 统计行列表,以及 slaveNeedReply 协办单位是否需要答复配置。
*/
// 提案配置 协办是否需要答复
ProposalConfig proposalConfig = dao.fetch(ProposalConfig.class, Cnd.NEW());
boolean slaveNeedReply = proposalConfig.getSlaveUnitNeedReply();
// 当前届次所有的提案ID
Sql sql = Sqls.create("select id from proposal_info where sessionId = @sessionId").setParam("sessionId", sessionId);
sql.setCallback(Sqls.callback.strList());
dao.execute(sql);
List<String> proposalIds = sql.getList(String.class);
Cnd proposalCnd = Cnd.where(ProposalInfo::getSessionId, "=", sessionId);
proposalCnd.andEX(ProposalInfo::getCaseFilingResult, "=", caseFilingResult);
List<String> proposalIds = dao.query(ProposalInfo.class, proposalCnd)
.stream()
.map(ProposalInfo::getId)
.collect(Collectors.toList());
if(proposalIds.isEmpty()){
return Result.success().addData(Map.of("tableData", Collections.EMPTY_LIST, "slaveNeedReply", slaveNeedReply));
@@ -73,7 +83,7 @@ public class ProposalQueryUnitReplyController {
List<ProposalReplyUnit> replyUnits = dao.query(ProposalReplyUnit.class, cnd);
// 承办单位答复记录
List<ProcessTask> replyTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instIds).and(ProcessTask::getTaskName, "in", List.of("master_reply", "slave_reply", "opinion_master_reply")).and(ProcessTask::getTaskState, "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.DOING.getCode())));
List<ProcessTask> replyTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "in", instIds).and(ProcessTask::getTaskName, "in", List.of("two_unit_reply","unit_reply","master_reply", "slave_reply", "opinion_master_reply")).and(ProcessTask::getTaskState, "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.DOING.getCode())));
// 承办单位
List<NutMap> tableData = new ArrayList<>();
@@ -45,7 +45,7 @@ public class ProposalCaseCheckController {
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/caseCheck/index.html")
@SaCheckPermission("proposal.mine")
@SaCheckPermission("proposal.caseCheck")
public void index() {
}
@@ -83,6 +83,7 @@ public class ProposalCaseCheckController {
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
LEFT JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN vw_user vu ON vu.id = info.createUserId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
$condition
@@ -99,6 +100,8 @@ public class ProposalCaseCheckController {
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
// 工会筛选按提案人所属工会口径处理,来源为 proposal_info.createUserId 关联 vw_user.unionId。
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
@@ -83,6 +83,7 @@ public class ProposalCommitteeFilingController {
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 vw_user vu ON vu.id = info.createUserId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
$condition
@@ -97,6 +98,8 @@ public class ProposalCommitteeFilingController {
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
// 工会筛选按提案人所属工会口径处理,来源为 proposal_info.createUserId 关联 vw_user.unionId。
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
@@ -99,6 +99,7 @@ public class ProposalCommitteeFilingUnitController {
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 vw_user vu ON vu.id = info.createUserId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
$condition
@@ -113,6 +114,8 @@ public class ProposalCommitteeFilingUnitController {
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
// 工会筛选按提案人所属工会口径处理,来源为 proposal_info.createUserId 关联 vw_user.unionId。
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
@@ -42,7 +42,7 @@ public class ProposalPreAuditController {
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/preAudit/index.html")
@SaCheckPermission("proposal.mine")
@SaCheckPermission("proposal.preAudit")
public void index() {
}
@@ -81,6 +81,7 @@ public class ProposalPreAuditController {
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
LEFT JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN vw_user vu ON vu.id = info.createUserId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
$condition
@@ -97,6 +98,8 @@ public class ProposalPreAuditController {
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
// 工会筛选按提案人所属工会口径处理,来源为 proposal_info.createUserId 关联 vw_user.unionId。
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
@@ -114,6 +114,7 @@ public class ProposalSchoolLeaderApprovalController {
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN proposal_merge mer ON mer.proposalId = info.id
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
LEFT JOIN vw_user vu ON vu.id = info.createUserId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
$condition
@@ -130,6 +131,8 @@ public class ProposalSchoolLeaderApprovalController {
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
// 工会筛选按提案人所属工会口径处理,来源为 proposal_info.createUserId 关联 vw_user.unionId。
cnd.andEX("vu.unionId", "=", pageForm.getUnionId());
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
@@ -2,24 +2,25 @@ package com.budwk.app.zhgh.democratic.proposal.controller.transact;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import com.aspose.slides.internal.og.and;
import cn.hutool.json.JSONUtil;
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.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.bpm.service.BpmService;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.entity.ProcessTaskActor;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.models.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;
@@ -28,11 +29,12 @@ import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.json.Json;
import org.nutz.lang.Strings;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@@ -55,11 +57,9 @@ public class ProposalSecondedController {
@Inject
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@Inject
private BpmService bpmService;
@Inject
private ProposalSecondedService proposalSecondedService;
@Inject
private FlowCommonService flowCommonService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/seconded/index.html")
@@ -96,6 +96,7 @@ public class ProposalSecondedController {
t.finishTime,
t.taskParentId,
t.variable taskVariable,
nt.taskName curTaskCode,
if(t.taskName = 'second', ta.actorId, ps.seconderId) taskActorUserId,
if(t.taskName = 'second', ta.actorName, ps.userName) taskActorName,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
@@ -108,36 +109,40 @@ public class ProposalSecondedController {
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
LEFT JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_second ps ON ps.proposalId = info.id
LEFT JOIN proposal_second ps ON ps.proposalId = info.id AND ps.seconderId = @seconderId
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
$condition
""");
sql.setParam("seconderId", SecurityUtil.getUserId());
sql.setParam("seconderId", SecurityUtil.getUserId());
Cnd cnd = Cnd.NEW();
cnd.and("t.taskName", "in", List.of("invite", "second", "delegation", "committee"));
cnd.and("ps.isAgree", "is", null);
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
}
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
if (approval) {
// 已附议页签只展示当前登录附议人已经处理过的记录。
cnd.and("ps.isAgree", "is not", null);
cnd.and("t.taskName", "in", List.of("invite", "second", "delegation", "preAudit", "committee"));
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
// 未附议页签只展示当前登录附议人尚未处理的记录。
cnd.and("ps.isAgree", "is", null);
// 待附议列表只展示两类任务:
// 1. invite / second 节点任务
// 2. delegation / committee 节点,且任务状态必须是已废弃(99)
SqlExpressionGroup taskGroup = Cnd.exps("t.taskName", "in", List.of("invite", "second"));
SqlExpressionGroup delegationOrCommitteeGroup = Cnd.exps("t.taskName", "in", List.of("delegation", "preAudit", "committee"));
delegationOrCommitteeGroup.and("t.taskState", "=", ProcessTaskStateEnum.ABANDON.getCode());
taskGroup.or(delegationOrCommitteeGroup);
cnd.and(taskGroup);
}
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
cnd.desc("t.createdAt");
sql.setCondition(cnd);
Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> list = pagination.getList(NutMap.class);
for (NutMap row : list) {
// String instanceId = row.getString("instanceId");
}
return Result.success(pagination);
}
@@ -146,37 +151,146 @@ public class ProposalSecondedController {
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("修改提案附议信息")
@SLog(tag = "提案管理系统-附议提案", msg = "修改提案附议信息")
/**
* 仅更新附议结果和附议任务状态,不调用流程引擎推进流程。
* 参数说明:
* 1. proposalId:提案主键,用于定位 proposal_second 附议记录。
* 2. taskId:当前页面点击的任务主键,用于定位本次需要完成的 wf_process_task。
* 3. taskActorUserId:附议人用户ID,用于校验附议记录和任务参与人是否匹配。
* 4. opinion:附议意见,写入 proposal_second.opinion。
* 5. submitType:附议结果,1 表示同意,20 表示不同意。
* 返回值:
* Result.success() 表示 proposal_second 和 wf_process_task 更新成功;
* Result.error(...) 表示参数错误或未找到对应业务数据。
*/
public Result updateInfo(@Param("proposalId") String proposalId,
@Param("taskId") Long taskId,
@Param("taskActorUserId") String taskActorUserId,
@Param("opinion") String opinion,
@Param("submitType") Integer submitType) {
// 查询附议信息表
if (Strings.isBlank(proposalId) || Strings.isBlank(taskActorUserId) || taskId == null || submitType == null) {
return Result.error("参数错误");
}
// 先定位 wf_process_task,确保本次请求能准确命中需要完成的附议任务,再更新两张业务表。
ProcessTask processTask = this.getSecondTask(taskId, taskActorUserId);
if (processTask == null) {
return Result.error("未找到附议任务");
}
// 更新 proposal_second,记录当前附议人的最终附议结果、附议意见和附议时间。
ProposalSecond proposalSecond = dao.fetch(
ProposalSecond.class,
Cnd.where(ProposalSecond::getProposalId, "=", proposalId)
.and(ProposalSecond::getSeconderId, "=", taskActorUserId)
);
if (proposalSecond == null) {
return Result.error("未找到附议记录");
}
proposalSecond.setIsAgree(ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode()));
proposalSecond.setOpinion(opinion);
proposalSecond.setIsAgree(submitType == 1);
proposalSecond.setSecondedTime(DateUtil.date());
dao.update(proposalSecond);
// 流程实例
ProcessInstance processInstance = dao.fetch(ProcessInstance.class,Cnd.where(ProcessInstance::getBusinessNo, "=", proposalId));
if(processInstance == null) return Result.success();
ProcessTask processTask = dao.fetch(ProcessTask.class,Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstance.getId())
.and(ProcessTask::getTaskName, "=", "second")
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.ABANDON.getCode())
.and(new Static("id in (select processTaskId from wf_process_task_actor where actorId='" + taskActorUserId + "')"))
.limit(1,1));
if(processTask == null) return Result.success();
// 仅更新本次附议对应的 wf_process_task,不推动实例、也不创建后续任务。
View_user user = dao.fetch(View_user.class, Cnd.where(View_user::getId, "=", taskActorUserId));
Dict taskArgs = Json.fromJson(Dict.class, processTask.getVariable());
taskArgs.set("u_userId", user.getId());
taskArgs.set("initiator", user.getId());
taskArgs.set("initiatorName", user.getUsername());
taskArgs.set("initiatorAccount", user.getLoginname());
taskArgs.set("initiatorUnitId", user.getUnitId());
taskArgs.set("initiatorUnitName", user.getUnitName());
taskArgs.set("initiatorUnionId", user.getUnionId());
taskArgs.set("initiatorUnionName", user.getUnionName());
taskArgs.set("tf_loginName", user.getLoginname());
taskArgs.set("tf_unionId", user.getUnionId());
taskArgs.set("tf_unitId", user.getUnitId());
taskArgs.set("tf_unitName", user.getUnitName());
taskArgs.set("tf_userId", user.getId());
taskArgs.set("tf_userName", user.getUsername());
processTask.setVariable(JSONUtil.toJsonStr(taskArgs));
processTask.setTaskState(ProcessTaskStateEnum.FINISHED.getCode());
processTask.setFinishTime(DateUtil.date());
processTask.setOperator(taskActorUserId);
dao.update(processTask);
return Result.success();
}
/**
* second 节点审核专用接口。
* 参数说明:
* 1. data:前端提交的 JSON 字符串,必须包含 processTaskId、proposalId、taskActorUserId、tf_opinion、submitType。
* 2. processTaskIdwf_process_task 主键,公共流程执行服务依赖该参数定位当前审核任务。
* 3. proposalId:提案主键,附议业务表 proposal_second 通过该字段定位当前提案记录。
* 4. taskActorUserId:附议人用户ID,用于定位当前登录人的附议记录。
* 5. tf_opinion:审核意见,会同步写入流程表单变量和附议业务表 opinion 字段。
* 6. submitType:审核结果,1 表示同意,20 表示不同意。
* 返回值:
* Result.success() 表示公共流程任务执行成功且 proposal_second 已同步更新;
* Result.error(...) 表示请求参数不完整。
*/
@At
@SaCheckPermission("proposal.seconded")
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("执行附议审核任务")
@SLog(tag = "提案管理系统-附议提案", msg = "执行附议审核任务")
public Result executeTask(@Param("data") String data) {
if (Strings.isBlank(data)) {
return Result.error("参数错误");
}
Dict args = Json.fromJson(Dict.class, data);
String proposalId = args.getStr("proposalId");
String taskActorUserId = args.getStr("taskActorUserId");
String opinion = args.getStr("tf_opinion");
Integer submitType = args.getInt("submitType");
if (args.getLong("processTaskId") == null || Strings.isBlank(proposalId) || Strings.isBlank(taskActorUserId) || submitType == null) {
return Result.error("参数错误");
}
flowCommonService.executeTask(args);
proposalSecondedService.updateSecondRecord(proposalId, taskActorUserId, opinion, submitType);
return Result.success();
}
/**
* 根据当前点击的任务和附议人,定位实际需要完成的 second 节点任务。
* 处理规则:
* 1. 如果页面点击的就是 second 节点任务,则直接更新该任务。
* 2. 如果页面点击的是 invite、delegation、committee 等关联节点,则回查同实例下当前附议人的 second 任务。
*/
private ProcessTask getSecondTask(Long taskId, String taskActorUserId) {
ProcessTask currentTask = dao.fetch(ProcessTask.class, taskId);
if (currentTask == null) {
return null;
}
if ("second".equals(currentTask.getTaskName())
&& List.of(ProcessTaskStateEnum.DOING.getCode(), ProcessTaskStateEnum.ABANDON.getCode()).contains(currentTask.getTaskState())
&& this.isTaskActorMatched(currentTask.getId(), taskActorUserId)) {
return currentTask;
}
List<ProcessTask> secondTaskList = dao.query(ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "=", currentTask.getProcessInstanceId())
.and(ProcessTask::getTaskName, "=", "second")
.and(ProcessTask::getTaskState, "in", List.of(ProcessTaskStateEnum.DOING.getCode(), ProcessTaskStateEnum.ABANDON.getCode()))
.desc(ProcessTask::getCreatedAt));
for (ProcessTask secondTask : secondTaskList) {
if (this.isTaskActorMatched(secondTask.getId(), taskActorUserId)) {
return secondTask;
}
}
return null;
}
/**
* 校验任务参与人是否为当前附议人,避免误更新其他代表的 second 任务。
*/
private boolean isTaskActorMatched(Long processTaskId, String taskActorUserId) {
return dao.count(ProcessTaskActor.class,
Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTaskId)
.and(ProcessTaskActor::getActorId, "=", taskActorUserId)) > 0;
}
}
@@ -83,6 +83,7 @@ public class ProposalWriteController {
@ApiOperation("保存提案")
@SLog(tag = "提案管理系统-我的提案", msg = "保存提案")
public Result save(@Param("info") ProposalInfo proposalInfo) {
proposalWriteService.checkCurrentUserDelegate(proposalInfo.getSessionId());
if (StrUtil.isBlank(proposalInfo.getCode())) {
proposalInfo.setCode(proposalWriteService.generateProposalCode(proposalInfo.getSessionId(), proposalInfo.getDelegationId()));
}
@@ -98,6 +99,7 @@ public class ProposalWriteController {
@ApiOperation("提交提案")
@SLog(tag = "提案管理系统-我的提案", msg = "提交提案")
public Result submit(@Param("info") ProposalInfo proposalInfo) {
proposalWriteService.checkCurrentUserDelegate(proposalInfo.getSessionId());
if (StrUtil.isBlank(proposalInfo.getCode())) {
proposalInfo.setCode(proposalWriteService.generateProposalCode(proposalInfo.getSessionId(), proposalInfo.getDelegationId()));
}
@@ -1,19 +1,9 @@
package com.budwk.app.zhgh.democratic.proposal.listenter;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessEventListener;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.service.ProcessInstanceService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalMerge;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
/**
* @version 1.0
@@ -25,27 +15,11 @@ import org.nutz.lang.Lang;
@IocBean
public class ProposalCommitteeFilingRevokeEventListener implements ProcessEventListener {
@Inject
private Dao dao;
@Inject
private ProcessInstanceService processInstanceService;
@Override
public void onEvent(ProcessEvent event) {
if (event.getEventType() == ProcessEventTypeEnum.PROCESS_TASK_REVOKE) {
Long taskId = event.getSourceId();
ProcessTask task = dao.fetch(ProcessTask.class, taskId);
ProcessInstance processInstance = processInstanceService.getById(task.getProcessInstanceId());
// 处理并案信息撤回
if ("committee".equals(task.getTaskName())) {
// 如果有并案,删掉并案数据(傻逼提的需求,nmsl)
ProposalMerge proposalMerge = dao.fetch(ProposalMerge.class, Cnd.where("proposalId", "=", processInstance.getBusinessNo()));
if (Lang.isNotEmpty(proposalMerge)) {
dao.clear(ProposalMerge.class, Cnd.where(ProposalMerge::getProposalId, "=", proposalMerge.getProposalId()));
}
}
// proposal_merge 的删除逻辑已统一收口到 ProposalCommitteeFilingServiceImpl.revokeTask
// 这里不再重复处理业务表,避免事件监听和 service 两处口径不一致。
}
}
}
@@ -77,7 +77,7 @@ public class ProposalQueryComprehensiveParam extends PageForm {
if (Lang.isNotEmpty(searchParam.getSessionIds())) {
cnd.and("info.sessionId", "in", searchParam.getSessionIds());
} else {
cnd.andEX("info.sessionId", "=", searchParam.getSessionId());
// cnd.andEX("info.sessionId", "=", searchParam.getSessionId());
}
//提案名称
@@ -19,6 +19,7 @@ public class ProposalSearchParam extends PageForm {
private String code;
private String sessionId;
private String delegationId;
private String unionId;
private String createUserName;
private String createUserLoginName;
private String createUserKeyword;
@@ -5,5 +5,13 @@ import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
public interface ProposalSecondedService extends BaseService<ProposalSecond> {
/**
* 仅更新附议表 proposal_second 中当前附议人的附议结果。
* 参数说明:
* 1. proposalId:提案ID,用于定位当前提案的附议记录。
* 2. taskActorUserId:附议人用户ID,用于定位当前登录人的附议记录。
* 3. opinion:附议意见,写入附议记录的 opinion 字段。
* 4. submitType:附议结果,1 表示同意,20 表示不同意。
*/
void updateSecondRecord(String proposalId, String taskActorUserId, String opinion, Integer submitType);
}
@@ -22,5 +22,15 @@ public interface ProposalWriteService extends BaseService<ProposalInfo> {
List<Sys_dict> listSourceByCode(@Valid String code);
/**
* 校验当前登录用户在指定教代会届次下是否具备代表身份。
* 参数说明:
* 1. sessionId:教代会届次ID,保存和提交提案时通过该字段定位当前届次代表信息。
* 返回值说明:
* 1. 校验通过时无返回值。
* 2. 校验不通过时抛出业务异常,由上层统一返回提示信息。
*/
void checkCurrentUserDelegate(String sessionId);
ProposalInfo importProposal(Map<String, String> stringStringMap);
}
@@ -234,10 +234,10 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
// 提案附议
List<NutMap> secondInfos = new ArrayList<>();
List<ProcessTaskVO> secondTaskVos = taskGroups.get("提案附议");
if(secondTaskVos != null && secondTaskVos.size() > 0) {
if (secondTaskVos != null && secondTaskVos.size() > 0) {
for (ProcessTaskVO secondTaskVO : secondTaskVos) {
Dict taskFormData = secondTaskVO.getTaskFormData();
if(taskFormData == null) {
if (taskFormData == null) {
continue;
}
NutMap secondInfo = new NutMap();
@@ -248,7 +248,7 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
taskUserSql.setCallback(Sqls.callback.map());
execute(taskUserSql);
NutMap taskUserMap = (NutMap) taskUserSql.getResult();
secondInfo.put("s_mobile", taskUserMap.getString("mobile"));
secondInfo.put("s_mobile", Objects.isNull(taskUserMap) ? "" : taskUserMap.getString("mobile"));
secondInfos.add(secondInfo);
}
}
@@ -256,10 +256,10 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
// 代表团意见
List<NutMap> delegationAuditList = new ArrayList<>();
List<ProcessTaskVO> delegationTaskVos = taskGroups.get("团长审核");
if(delegationTaskVos != null && delegationTaskVos.size() > 0) {
if (delegationTaskVos != null && delegationTaskVos.size() > 0) {
for (ProcessTaskVO delegationTaskVO : delegationTaskVos) {
Dict taskFormData = delegationTaskVO.getTaskFormData();
if(taskFormData == null) {
if (taskFormData == null) {
continue;
}
@@ -601,10 +601,10 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
List<NutMap> caseUnitList = listMap(caseUnitSql);
for (NutMap v : caseUnitList) {
String hostUnitName = v.getString("masterUnitName");
if (StrUtil.isNotBlank(hostUnitName)){
if (StrUtil.isNotBlank(hostUnitName)) {
underTakeNames.add(hostUnitName);
}
if (StrUtil.isNotBlank(v.getString("slaveUnitNames"))){
if (StrUtil.isNotBlank(v.getString("slaveUnitNames"))) {
List<String> helpUnitNames = Arrays.asList(v.getString("slaveUnitNames").split(""));
underTakeNames.addAll(helpUnitNames);
}
@@ -725,73 +725,73 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
String proposalMeasures = MapUtil.getStr(dataMap, "建议措施", "");
// 工号
if(StrUtil.isBlank(loginName)) {
if (StrUtil.isBlank(loginName)) {
throw new RuntimeException("工号不能为空");
}
Sys_user user = sysUserService.getByLoginName(loginName);
if(user == null){
if (user == null) {
throw new RuntimeException("工号不存在");
}
if(StrUtil.isNotBlank(username) && !username.equals(user.getUsername())) {
if (StrUtil.isNotBlank(username) && !username.equals(user.getUsername())) {
throw new RuntimeException("工号和提案人不一致");
}
// 提案时间
if(StrUtil.isBlank(proposalDate)) {
if (StrUtil.isBlank(proposalDate)) {
throw new RuntimeException("提案时间不能为空");
}
// 提案类型
if(StrUtil.isBlank(proposalType)) {
if (StrUtil.isBlank(proposalType)) {
throw new RuntimeException("提案类型不能为空");
}
ProposalType proposalTypeObj = dao().fetch(ProposalType.class, Cnd.where(ProposalType::getName, "=", proposalType));
if(proposalTypeObj == null) {
if (proposalTypeObj == null) {
throw new RuntimeException("提案类型不存在");
}
// 提案方式
if(StrUtil.isBlank(proposalSource)) {
if (StrUtil.isBlank(proposalSource)) {
throw new RuntimeException("提案方式不能为空");
}
Sys_dict dict = sysDictService.fetch(Cnd.where("code", "=", "PROPOSAL_SOURCE"));
if(dict == null) {
if (dict == null) {
throw new RuntimeException("提案方式字典配置不存在");
}
List<Sys_dict> sysDicts = sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(dict.getId())).asc("location"));
if(sysDicts == null || sysDicts.size() == 0) {
if (sysDicts == null || sysDicts.size() == 0) {
throw new RuntimeException("提案方式字典配置不存在");
}
Sys_dict proposalSourceDict = sysDicts.stream().filter(v -> v.getName().equals(proposalSource)).findFirst().orElse(null);
if(proposalSourceDict == null) {
if (proposalSourceDict == null) {
throw new RuntimeException("提案方式不存在");
}
// 提案名称
if(StrUtil.isBlank(proposalName)) {
if (StrUtil.isBlank(proposalName)) {
throw new RuntimeException("提案名称不能为空");
}
// 案由
if(StrUtil.isBlank(proposalContent)) {
if (StrUtil.isBlank(proposalContent)) {
throw new RuntimeException("案由不能为空");
}
// 建议措施
if(StrUtil.isBlank(proposalMeasures)) {
if (StrUtil.isBlank(proposalMeasures)) {
throw new RuntimeException("建议措施不能为空");
}
// 判断当前导入用户是否是代表
Teacher_congress_delegate delegate = teacherCongressDelegateService.fetch(Cnd.where(Teacher_congress_delegate::getUserId, "=", SecurityUtil.getUserId())
.and(Teacher_congress_delegate::getDelFlag, "!=", 1).limit(1));
if(delegate == null) {
if (delegate == null) {
throw new RuntimeException("当前用户不是代表");
}
// 教代会
List<Teacher_congress_session> teacherCongressSessions = this.dao().query(Teacher_congress_session.class, Cnd.where("enable", "=", 1).desc("startDate"));
if(teacherCongressSessions == null || teacherCongressSessions.size() <= 0) {
if (teacherCongressSessions == null || teacherCongressSessions.size() <= 0) {
throw new RuntimeException("没有开启的教代会");
}
Teacher_congress_session teacherCongressSession = teacherCongressSessions.get(0);
@@ -83,6 +83,9 @@ public class ProposalCommitteeFilingServiceImpl extends BaseServiceImpl<Proposal
for (ProcessTask mergeTask : mergeTasks) {
flowCommonService.revokeTask(mergeTask.getId());
}
// 并案整组撤回后,需要同步删除 proposal_merge 中整组并案关系,避免列表仍按并案数据展示。
dao().clear(ProposalMerge.class, Cnd.where(ProposalMerge::getProposalId, "in", mergeProposalIds));
}
}
@@ -1,21 +1,37 @@
package com.budwk.app.zhgh.democratic.proposal.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.web.controllers.open.commons.service.CommonService;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalSecondedService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
@IocBean(args = {"refer:dao"})
public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond> implements ProposalSecondedService {
@Inject
private CommonService commonService;
public ProposalSecondedServiceImpl(Dao dao) {
super(dao);
}
@Override
public void updateSecondRecord(String proposalId, String taskActorUserId, String opinion, Integer submitType) {
if (Strings.isBlank(proposalId) || Strings.isBlank(taskActorUserId) || submitType == null) {
return;
}
// 仅更新当前提案、当前附议人的附议结果,不处理流程任务表。
ProposalSecond proposalSecond = this.fetch(Cnd.where(ProposalSecond::getProposalId, "=", proposalId)
.and(ProposalSecond::getSeconderId, "=", taskActorUserId));
if (proposalSecond == null) {
return;
}
proposalSecond.setIsAgree(ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode()));
proposalSecond.setOpinion(opinion);
proposalSecond.setSecondedTime(DateUtil.date());
this.update(proposalSecond);
}
}
@@ -121,6 +121,23 @@ public class ProposalWriteServiceImpl extends BaseServiceImpl<ProposalInfo> impl
return dict == null ? new ArrayList<>() : sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(dict.getId())).asc("location"));
}
@Override
public void checkCurrentUserDelegate(String sessionId) {
if (StrUtil.isBlank(sessionId)) {
throw new RuntimeException("所属教代会不能为空");
}
// 撰写提案必须绑定当前届次代表身份,避免非代表用户保存或提交提案。
Teacher_congress_delegate delegate = teacherCongressDelegateService.fetch(
Cnd.where(Teacher_congress_delegate::getSessionId, "=", sessionId)
.and(Teacher_congress_delegate::getUserId, "=", SecurityUtil.getUserId())
.and(Teacher_congress_delegate::getDelFlag, "!=", 1)
.limit(1)
);
if (delegate == null) {
throw new RuntimeException("当前用户不是代表,无法撰写提案");
}
}
@Override
public ProposalInfo importProposal(Map<String, String> dataMap) {
String loginName = MapUtil.getStr(dataMap, "工号", "");
@@ -150,7 +150,7 @@ public class TeacherCongressDelegatePushController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tc.delegate.push")
// @SLog(tag = "民主管理-教代会代表推选", msg = "获取本分工会下的非代表用户")
public Result getUnionUser(String sessionId, String unionId) {
public Result getUnionUser(String sessionId, String unionId, String keyword) {
// 查询预选中的代表
Cnd cndx = Cnd.where("sessionId", "=", sessionId);
cndx.andEX("unionId", "=", SecurityUtil.getUnionId());
@@ -173,7 +173,7 @@ public class TeacherCongressDelegatePushController {
`vw_user` u
LEFT JOIN teacher_congress_delegate tcd ON tcd.userId = u.id
AND tcd.sessionId = @sessionId
LEFT JOIN teacher_congress_delegate_push tcdp ON tcdp.userId = tcdp.id
LEFT JOIN teacher_congress_delegate_push tcdp ON tcdp.userId = u.id
AND tcdp.sessionId = @sessionId
$condition
""");
@@ -185,6 +185,14 @@ public class TeacherCongressDelegatePushController {
cnd.and("tcd.userId", "is", null);
cnd.and("tcdp.id", "is", null);
cnd.and("u.unionId", "=", SecurityUtil.getUnionId());
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("u.username", keyword);
group.orLike("u.loginname", keyword);
cnd.and(group);
}
// 搜索候选人时只返回少量命中数据,避免前端大列表筛选导致严重卡顿
cnd.limit(1, 100);
sql.setCondition(cnd);
List<NutMap> userList = baseService.listMap(sql);
@@ -14,6 +14,7 @@ import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@@ -102,7 +103,16 @@ public class DifficultHelpBranchUnionLeaderApprovalController {
$condition
""");
Cnd cnd = Cnd.NEW();
pageParam.buildSearch(cnd, "info.");
// pageParam.buildSearchUnit(cnd, "info.");
if (StrUtil.isNotBlank(pageParam.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("info.userName", pageParam.getSearchKeyword());
seg.orLike("info.loginName", pageParam.getSearchKeyword());
cnd.and(seg);
}
cnd.andEX("info.unionId", "=", pageParam.getUnionId());
cnd.andEX("info.unitId", "=", pageParam.getUnitId());
cnd.and("t.taskName", "=", "36ebafda-44c2-4bd3-bd7f-10839a575a0d");
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
@@ -78,6 +78,7 @@ public class DifficultHelpMineController {
LEFT(info.applyTime, 10) AS applyTime,
info.applyCount,
info.mobile,
info.money,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
@@ -129,7 +129,8 @@ public class AidFundApplyController {
}
//如果有两个工号并且有一个工号已经是会员就不让他申请了
if (userList.size() >= 2) {
int size = userList.stream().filter(Sys_user::getAidFundMember).toList().size();
// 历史用户数据中 aidFundMember 可能为空这里只统计明确为基金会员的工号避免空值自动拆箱报错
int size = userList.stream().filter(sysUser -> Boolean.TRUE.equals(sysUser.getAidFundMember())).toList().size();
if (size > 0) {
return Result.success(Map.of("disabled", true, "label", "请用新工号登陆办理申请业务!"));
}
@@ -149,7 +150,7 @@ public class AidFundApplyController {
aidFundMemberChangeRecord.setUserId(SecurityUtil.getUserId());
Sys_user user = dao.fetch(Sys_user.class, SecurityUtil.getUserId());
if (StrUtil.isBlank(aidFundMemberChangeRecord.getId())) aidFundMemberChangeRecord.setApplyTime(new Date());
if (user.getAidFundMember() == AidFundMemberMode.NORMAL.getCode()) {
if (Boolean.TRUE.equals(user.getAidFundMember())) {
aidFundMemberChangeRecord.setChangeType("AIDFUND_MEMBER_CHANGE_TYPE_FOUR");
} else {
aidFundMemberChangeRecord.setChangeType("AIDFUND_MEMBER_CHANGE_TYPE_ONE");
@@ -159,7 +160,8 @@ public class AidFundApplyController {
Dict args = Dict.create();
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
args.set(FlowConst.FORM_DATA, aidFundMemberChangeRecord);
args.set("aidFundMemberUserType", user.getAidFundMemberUserType());
// 流程变量中的基金会员类型以本次申请提交值为准避免继续使用用户表中的旧值
args.set("aidFundMemberUserType", aidFundMemberChangeRecord.getAidFundMemberUserType());
ProcessInstance instance = flowEngine.startProcessInstanceByKey("JJHY", aidFundMemberChangeRecord.getId(), SecurityUtil.getUserId(), args);
// 自动完成第一个申请任务
@@ -194,7 +196,7 @@ public class AidFundApplyController {
us.username,
us.loginname,
us.mobile,
us.aidFundMemberUserType,
IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType) aidFundMemberUserType,
us.arrivalAtSchoolDate,
us.sex,
us.unitName,
@@ -76,14 +76,17 @@ public class AidFundApplyMineController {
@Param(value = "year") Integer year) {
Sql sql = Sqls.create("""
SELECT
info.*,
info.id,
info.userId,
info.changeType,
info.applyTime,
us.arrivalAtSchoolDate,
us.mobile,
us.loginname loginName,
us.username userName,
us.unitName,
us.unionName,
us.aidFundMemberUserType,
IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType) aidFundMemberUserType,
us.sex,
ins.id AS instanceId,
ins.businessNo,
@@ -113,6 +116,8 @@ public class AidFundApplyMineController {
cnd.andEX("year(info.applyTime)", "=", year);
cnd.and("info.userId", "=", SecurityUtil.getUserId());
// 管理端代操作产生的记录不属于我的申请历史空值按普通申请处理
cnd.andEX("IFNULL(info.adminOperate, 0)", "=", 0);
if (StrUtil.isNotBlank(pageForm.getPageOrderName()) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
@@ -58,7 +58,6 @@ public class AidFundChangeRecordController {
us.mobile,
us.unitName,
us.unionName,
us.aidFundMemberUserType,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
@@ -1,24 +1,24 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.controller;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
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.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.param.AidFundPageForm;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.service.AidFundMemberChangeRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -27,6 +27,8 @@ import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
/**
@@ -44,8 +46,6 @@ public class AidFundFoundationAuditController {
@Inject
private AidFundMemberChangeRecordService changeRecordService;
@At("")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/aidFund/foundationAudit/index.html")
@SaCheckPermission("medicalMutualAid.aidFund.foundationAudit")
@@ -61,79 +61,8 @@ public class AidFundFoundationAuditController {
@At
@ApiOperation("分页查询")
@SaCheckPermission(value = {"medicalMutualAid.aidFund.foundationAudit", "h5.medicalMutualAid.aidFund.foundationAudit"}, mode = SaMode.OR)
public Result pageData(PageForm pageForm,
@Param(value = "year") Integer year,
@Param(value = "aidFundMemberUserType") String aidFundMemberUserType,
@Param(value = "unionId") String unionId,
@Param(value = "unitId") String unitId,
@Param(value = "changeType") String changeType,
@Param(value = "approval") Boolean approval) {
Sql sql = Sqls.create("""
SELECT
info.*,
us.arrivalAtSchoolDate,
us.mobile,
us.loginname loginName,
us.username userName,
us.unitName,
us.unionName,
us.aidFundMemberUserType,
us.sex,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId,
t.id taskId,
t.taskName AS taskKey,
t.displayName taskName,
t.taskType,
t.performType taskPerformType,
t.taskState,
t.finishTime,
t.taskParentId,
t.variable taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke
FROM
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN aid_fund_member_change_record info ON info.id = ins.businessNo
LEFT JOIN vw_user us ON us.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
$condition
""");
Cnd cnd = Cnd.NEW();
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("YEAR(info.applyTime)", "=", year);
cnd.andEX("us.aidFundMemberUserType", "=", aidFundMemberUserType);
cnd.andEX("us.unionId", "=", unionId);
cnd.andEX("us.unitId", "=", unitId);
cnd.andEX("info.changeType", "=", changeType);
cnd.and("t.taskName", "=", "85c7d148-44f5-4333-b854-ab225357fd3e");
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
if (approval) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.applyTime");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
public Result pageData(AidFundPageForm pageForm) {
Sql sql = changeRecordService.getFoundationAuditSql(pageForm);
Pagination<NutMap> pageVO = changeRecordService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
List<NutMap> list = pageVO.getList();
@@ -144,6 +73,39 @@ public class AidFundFoundationAuditController {
return Result.success(pageVO);
}
/**
* 导出互管会审核页面申请记录
*
* @param pageForm 页面筛选参数和列表页查询条件保持一致
* @param response HTTP 响应对象用于输出 Excel 文件流
*/
@At
@Ok("void")
@ApiOperation("导出申请记录")
@SaCheckPermission(value = {"medicalMutualAid.aidFund.foundationAudit", "h5.medicalMutualAid.aidFund.foundationAudit"}, mode = SaMode.OR)
public void exportApplyRecord(AidFundPageForm pageForm, HttpServletResponse response) {
List<NutMap> listMap = changeRecordService.getFoundationAuditExportList(pageForm);
List<ExcelExportEntity> entityList = new ArrayList<>();
entityList.add(new ExcelExportEntity("工号", "loginName", 20));
entityList.add(new ExcelExportEntity("姓名", "userName", 20));
entityList.add(new ExcelExportEntity("性别", "sex", 15));
entityList.add(new ExcelExportEntity("电话", "mobile", 20));
entityList.add(new ExcelExportEntity("工会", "unionName", 20));
entityList.add(new ExcelExportEntity("单位", "unitName", 20));
entityList.add(new ExcelExportEntity("入校时间", "arrivalAtSchoolDate", 20));
entityList.add(new ExcelExportEntity("基金会员类型", "aidFundMemberUserType", 20));
entityList.add(new ExcelExportEntity("缴纳金额", "money", 20));
entityList.add(new ExcelExportEntity("变更类型", "changeType", 20));
entityList.add(new ExcelExportEntity("申请时间", "applyTime", 20));
entityList.add(new ExcelExportEntity("审核状态", "approvalStatus", 20));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, listMap);
String fileName = (pageForm.getYear() == null ? "" : pageForm.getYear()) + "基金会员审核记录.xlsx";
CommonDownloadUtil.download(fileName, workbook, response);
}
@At
@ApiOperation("查询需要缴费多少")
@SaCheckPermission(value = {"medicalMutualAid.aidFund.foundationAudit", "h5.medicalMutualAid.aidFund.foundationAudit"}, mode = SaMode.OR)
@@ -163,4 +125,23 @@ public class AidFundFoundationAuditController {
return Result.success();
}
/**
* 批量审核互管会待办记录
*
* @param param 统一的审批参数包含审批意见和审核动作
* @param ids 勾选的申请记录 ID 数组
* @return 批量审核结果统计
*/
@At
@SLog(tag = "基金会员", msg = "互管会批量审核基金会员记录")
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("批量审核")
@SaCheckPermission(value = {"medicalMutualAid.aidFund.foundationAudit", "h5.medicalMutualAid.aidFund.foundationAudit"}, mode = SaMode.OR)
public Result batchExecuteTask(@Param("data") String param, @Param("ids") String[] ids) {
if (ids == null || ids.length == 0 || StrUtil.isBlank(param)) {
return Result.error("参数错误");
}
return Result.success(changeRecordService.batchExecuteTask(param, ids));
}
}
@@ -10,18 +10,12 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
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.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysUserService;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.mode.AidFundMemberMode;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberChangeRecord;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.param.AidFundPageForm;
@@ -35,7 +29,6 @@ import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -79,55 +72,32 @@ public class AidFundManageController {
@ApiOperation("分页查询")
@SaCheckPermission("medicalMutualAid.aidFund.manage")
public Result pageData(AidFundPageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
id,
loginname,
username,
sex,
arrivalAtSchoolDate,
unitName,
unionName,
aidFundMemberUserType,
aidFundMemberJoinTime
FROM
vw_user
$condition
""");
Cnd cnd = Cnd.NEW();
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name())) {
if (AuthUtil.hasRole(RoleConstant.RETIREMENT_WORKPLACE.name())) {
cnd.and(Sys_user::getAidFundMemberUserType, "=", "离退休人员");
} else {
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
}
}
if (pageForm.getNotPayedCurrentYear()) {
cnd.and("id", "in", Sqls.create("SELECT userid FROM `aid_fund_member_pay` where `year` = YEAR(CURDATE()) and isPayed = 0"));
}
cnd.andEX(Sys_user::getAidFundMemberUserType, "=", pageForm.getAidFundMemberUserType());
cnd.andEX(View_user::getUnionId, "=", pageForm.getUnionId());
cnd.andEX(View_user::getUnitId, "=", pageForm.getUnitId());
sql.setCondition(cnd);
cnd.and(Sys_user::getAidFundMember, "=", AidFundMemberMode.NORMAL.getCode());
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("loginname", pageForm.getSearchKeyword());
seg.orLike("username", pageForm.getSearchKeyword());
cnd.and(seg);
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("unitCode");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
sql.setCondition(cnd);
Pagination pagination = userService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
Sql sql = aidFundMemberService.getManageSql(pageForm);
Pagination pagination = aidFundMemberService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
}
@At
@SaCheckPermission("medicalMutualAid.aidFund.manage")
@Ok("void")
@ApiOperation("导出会员名单")
public void exportMemberList(AidFundPageForm pageForm, HttpServletResponse response) {
List<NutMap> listMap = aidFundMemberService.listMap(aidFundMemberService.getManageSql(pageForm));
List<ExcelExportEntity> entityList = new ArrayList<>();
entityList.add(new ExcelExportEntity("姓名", "username", 20));
entityList.add(new ExcelExportEntity("工号", "loginname", 20));
entityList.add(new ExcelExportEntity("性别", "sex", 20));
entityList.add(new ExcelExportEntity("会员类型", "aidFundMemberUserType", 20));
entityList.add(new ExcelExportEntity("单位", "unitName", 20));
entityList.add(new ExcelExportEntity("工会", "unionName", 20));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, listMap);
CommonDownloadUtil.download("会员名单.xlsx", workbook, response);
}
@At
@SaCheckPermission("medicalMutualAid.aidFund.manage")
@@ -198,9 +168,12 @@ public class AidFundManageController {
return Result.error("请选择要退会的人员");
}
List<AidFundMemberChangeRecord> recordArrayList = new ArrayList<>();
List<Sys_user> sysUserList = userService.query(Cnd.where("id", "in", ids));
for (String id : ids) {
Sys_user user = sysUserList.stream().filter(sysUser -> sysUser.getId().equals(id)).findFirst().orElse(null);
AidFundMemberChangeRecord record = new AidFundMemberChangeRecord()
.setUserId(id)
.setAidFundMemberUserType(user.getAidFundMemberUserType())
.setApplyTime(new Date())
.setChangeType("AIDFUND_MEMBER_CHANGE_TYPE_SEVEN");
recordArrayList.add(record);
@@ -220,25 +193,7 @@ public class AidFundManageController {
if (ObjectUtil.isEmpty(ids) || StrUtil.isBlank(changeType)) {
return Result.error("参数错误");
}
List<AidFundMemberChangeRecord> recordArrayList = new ArrayList<>();
for (String id : ids) {
AidFundMemberChangeRecord record = new AidFundMemberChangeRecord()
.setUserId(id)
.setApplyTime(new Date())
.setChangeType(changeType);
recordArrayList.add(record);
}
Chain add;
if (changeType.equals("AIDFUND_MEMBER_CHANGE_TYPE_EIGTH")) {
//会员退休
add = Chain.make("aidFundMemberUserType", "离退休人员");
} else {
add = Chain.make("aidFundMember", AidFundMemberMode.NONE.getCode())
.add("aidFundMemberQuitTime", new Date());
}
userService.update(add, Cnd.where("id", "in", ids));
userService.insert(recordArrayList);
aidFundMemberService.doBatchChangeTypeByAdmin(ids, changeType);
return Result.success();
}
@@ -258,21 +213,10 @@ public class AidFundManageController {
@ApiOperation("变更基金会员")
@Aop(TransAop.READ_COMMITTED)
public Result doEditChangeType(String userId, String changeType) {
AidFundMemberChangeRecord record = new AidFundMemberChangeRecord()
.setUserId(userId)
.setApplyTime(new Date())
.setChangeType(changeType);
aidFundMemberService.insert(record);
Chain add;
if (changeType.equals("AIDFUND_MEMBER_CHANGE_TYPE_EIGTH")) {
//会员退休
add = Chain.make("aidFundMemberUserType", "离退休人员");
} else {
add = Chain.make("aidFundMember", AidFundMemberMode.NONE.getCode())
.add("aidFundMemberQuitTime", new Date());
if (StrUtil.isBlank(userId) || StrUtil.isBlank(changeType)) {
return Result.error("参数错误");
}
userService.update(add, Cnd.where("id", "=", userId));
aidFundMemberService.doEditChangeTypeByAdmin(userId, changeType);
return Result.success();
}
@@ -63,14 +63,17 @@ public class AidFundRetirementAuditController {
@Param(value = "approval") Boolean approval) {
Sql sql = Sqls.create("""
SELECT
info.*,
info.id,
info.userId,
info.changeType,
info.applyTime,
us.arrivalAtSchoolDate,
us.mobile,
us.loginname loginName,
us.username userName,
us.unitName,
us.unionName,
us.aidFundMemberUserType,
IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType) aidFundMemberUserType,
us.sex,
ins.id AS instanceId,
ins.businessNo,
@@ -106,7 +109,7 @@ public class AidFundRetirementAuditController {
cnd.and(seg);
}
cnd.andEX("YEAR(info.applyTime)", "=", year);
cnd.andEX("us.aidFundMemberUserType", "=", aidFundMemberUserType);
cnd.andEX("IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType)", "=", aidFundMemberUserType);
cnd.andEX("info.changeType", "=", changeType);
cnd.and("t.taskName", "=", "f756c818-6bb7-4f50-b8fa-24390fc7b0b8");
@@ -65,14 +65,17 @@ public class AidFundUnionAuditController {
@Param(value = "approval") Boolean approval) {
Sql sql = Sqls.create("""
SELECT
info.*,
info.id,
info.userId,
info.changeType,
info.applyTime,
us.arrivalAtSchoolDate,
us.mobile,
us.loginname loginName,
us.username userName,
us.unitName,
us.unionName,
us.aidFundMemberUserType,
IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType) aidFundMemberUserType,
us.sex,
ins.id AS instanceId,
ins.businessNo,
@@ -108,7 +111,7 @@ public class AidFundUnionAuditController {
cnd.and(seg);
}
cnd.andEX("YEAR(info.applyTime)", "=", year);
cnd.andEX("us.aidFundMemberUserType", "=", aidFundMemberUserType);
cnd.andEX("IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType)", "=", aidFundMemberUserType);
cnd.andEX("info.changeType", "=", changeType);
cnd.and("t.taskName", "=", "06ed1d6d-5f96-485f-9150-ed1ef4a082d6");
@@ -38,10 +38,20 @@ public class AidFundMemberChangeRecord extends BaseModel {
@ColDefine(type = ColType.VARCHAR, width = 50)
private String changeType;
@Column
@Comment("申请时选择的基金会员类型(dict")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String aidFundMemberUserType;
@Column
@Comment("申请时间")
@ColDefine(type = ColType.DATETIME)
private Date applyTime;
@Column
@Comment("是否系统管理员操作")
@ColDefine(type = ColType.BOOLEAN)
private Boolean adminOperate;
}
@@ -22,6 +22,9 @@ public class AidFundPageForm extends PageForm {
private String changeType;
private Integer year;
//是否已审核
private Boolean approval;
//是否扣款null全部1已缴费0未缴费
private Integer isPayed;
}
@@ -1,6 +1,8 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.param.AidFundPageForm;
import org.nutz.dao.sql.Sql;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberChangeRecord;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberPay;
import org.nutz.lang.util.NutMap;
@@ -9,10 +11,53 @@ import java.util.List;
public interface AidFundMemberChangeRecordService extends BaseService<AidFundMemberChangeRecord> {
/**
* 构建互管会审核页面查询 SQL
*
* @param pageForm 页面筛选参数包含年度姓名/工号工会单位会员类型变更类型和审核状态
* @return 可直接用于分页或导出的查询 SQL
*/
Sql getFoundationAuditSql(AidFundPageForm pageForm);
/**
* 查询互管会审核页面导出数据
*
* @param pageForm 页面筛选参数和列表页保持一致
* @return 导出所需的列表数据每条记录对应一条基金会员变更申请
*/
List<NutMap> getFoundationAuditExportList(AidFundPageForm pageForm);
/**
* 执行流程审核动作
*
* @param param 流程提交参数 JSON包含提交流程所需的任务意见和业务字段
* @param id 基金会员变更记录 ID
*/
void executeTask( String param, String id);
/**
* 批量执行互管会审核任务
*
* @param param 流程提交参数 JSON包含统一的审批意见和提交类型
* @param ids 选中的基金会员变更记录 ID 数组
* @return 批量审核结果包含成功数失败数和失败原因
*/
NutMap batchExecuteTask(String param, String[] ids);
/**
* 计算基金会员应补缴金额
*
* @param arrivalAtSchoolDate 入校时间格式为 yyyy-MM-dd
* @param userId 用户 ID空值时默认取当前登录人
* @return 当前规则下计算出的应缴金额
*/
int getPayMoney(String arrivalAtSchoolDate,String userId);
/**
* 查询用户历年缴费记录
*
* @param userId 用户 ID空值时默认取当前登录人
* @return 用户及其旧工号对应的缴费记录列表
*/
List<AidFundMemberPay> getUserPayRecordList(String userId);
}
@@ -2,6 +2,8 @@ package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.param.AidFundPageForm;
import org.nutz.dao.sql.Sql;
import org.nutz.lang.util.NutMap;
import java.util.List;
@@ -31,5 +33,30 @@ public interface AidFundMemberService extends BaseService {
Double getPayMoney(Sys_user user, Integer year);
/**
* 获取基金会员台账列表查询SQL
*
* @param pageForm 页面查询条件包含会员类型工会单位关键字排序及未缴费筛选参数
* @return 与基金会员台账页面一致的查询SQL
*/
Sql getManageSql(AidFundPageForm pageForm);
List<NutMap> findChangeRecordList(String userId);
/**
* 管理端批量变更基金会员状态并记录系统管理员操作来源
*
* @param ids 需要变更的用户ID数组来自台账页面勾选行
* @param changeType 变更类型字典值对应 AIDFUND_MEMBER_CHANGE_TYPE
*/
void doBatchChangeTypeByAdmin(String[] ids, String changeType);
/**
* 管理端对单个用户执行变更操作并记录系统管理员操作来源
*
* @param userId 被变更的用户ID
* @param changeType 变更类型字典值对应 AIDFUND_MEMBER_CHANGE_TYPE
*/
void doEditChangeTypeByAdmin(String userId, String changeType);
}
@@ -3,20 +3,34 @@ package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.sys.models.Sys_user;
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.staffbenefit.medicalMutualAid.aidFund.mode.AidFundMemberMode;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberChangeRecord;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberPay;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.param.AidFundPageForm;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.service.AidFundMemberChangeRecordService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -33,6 +47,9 @@ import java.util.*;
*/
@IocBean(args = {"refer:dao"})
public class AidFundMemberChangeRecordServiceImpl extends BaseServiceImpl<AidFundMemberChangeRecord> implements AidFundMemberChangeRecordService {
private static final String FOUNDATION_AUDIT_TASK_NAME = "85c7d148-44f5-4333-b854-ab225357fd3e";
private static final String CHANGE_TYPE_JOIN = "AIDFUND_MEMBER_CHANGE_TYPE_ONE";
public AidFundMemberChangeRecordServiceImpl(Dao dao) {
super(dao);
}
@@ -41,21 +58,228 @@ public class AidFundMemberChangeRecordServiceImpl extends BaseServiceImpl<AidFun
@Inject
private FlowCommonService flowCommonService;
@Inject
private ProcessTaskService processTaskService;
@Inject
private SysDictService sysDictService;
@Override
public Sql getFoundationAuditSql(AidFundPageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
info.id,
info.userId,
info.changeType,
info.applyTime,
us.arrivalAtSchoolDate,
us.mobile,
us.loginname loginName,
us.username userName,
us.unitName,
us.unionName,
IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType) aidFundMemberUserType,
us.sex,
ins.id AS instanceId,
ins.businessNo,
ins.state instanceState,
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId,
t.id taskId,
t.taskName AS taskKey,
t.displayName taskName,
t.taskType,
t.performType taskPerformType,
t.taskState,
t.finishTime,
t.taskParentId,
t.variable taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke
FROM
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN aid_fund_member_change_record info ON info.id = ins.businessNo
LEFT JOIN vw_user us ON us.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
$condition
""");
Cnd cnd = Cnd.NEW();
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("YEAR(info.applyTime)", "=", pageForm.getYear());
// 新申请优先按变更表中的基金会员类型筛选历史数据为空时兜底用户表
cnd.andEX("IFNULL(info.aidFundMemberUserType, us.aidFundMemberUserType)", "=", pageForm.getAidFundMemberUserType());
cnd.andEX("us.unionId", "=", pageForm.getUnionId());
cnd.andEX("us.unitId", "=", pageForm.getUnitId());
cnd.andEX("info.changeType", "=", pageForm.getChangeType());
cnd.and("t.taskName", "=", FOUNDATION_AUDIT_TASK_NAME);
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name())){
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
}
if (Boolean.TRUE.equals(pageForm.getApproval())) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.applyTime");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("t.id");
sql.setCondition(cnd);
return sql;
}
@Override
public List<NutMap> getFoundationAuditExportList(AidFundPageForm pageForm) {
List<NutMap> list = listMap(getFoundationAuditSql(pageForm));
Map<String, String> memberTypeMap = getDictNameMap("AIDFUND_MEMBER_USER_TYPE");
Map<String, String> changeTypeMap = getDictNameMap("AIDFUND_MEMBER_CHANGE_TYPE");
list.forEach(map -> {
String arrivalAtSchoolDate = map.getString("arrivalAtSchoolDate");
String changeType = map.getString("changeType");
map.put("approvalStatus", ObjectUtil.equals(map.getInt("taskState"), ProcessTaskStateEnum.DOING.getCode()) ? "未审核" : "已审核");
map.put("aidFundMemberUserType", memberTypeMap.getOrDefault(map.getString("aidFundMemberUserType"), map.getString("aidFundMemberUserType")));
map.put("changeType", changeTypeMap.getOrDefault(changeType, changeType));
map.put("arrivalAtSchoolDate", StrUtil.isBlank(arrivalAtSchoolDate) ? "" : DateUtil.format(DateUtil.parse(arrivalAtSchoolDate), "yyyy-MM-dd"));
map.put("applyTime", map.getTime("applyTime") == null ? "" : DateUtil.formatDateTime(map.getTime("applyTime")));
// 仅加入基金时需要计算补缴金额其他变更类型保持空白避免误导为固定收费
map.put("money", ObjectUtil.equals(changeType, CHANGE_TYPE_JOIN) && StrUtil.isNotBlank(arrivalAtSchoolDate)
? getPayMoney(arrivalAtSchoolDate, map.getString("userId")) : "");
});
return list;
}
private Map<String, String> getDictNameMap(String dictCode) {
return sysDictService.getSubListByCode(dictCode).stream().collect(HashMap::new, (map, dict) -> map.put(dict.getCode(), dict.getName()), HashMap::putAll);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void executeTask(String param, String id) {
Dict args = Json.fromJson(Dict.class, param);
executeTask(args, id);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public NutMap batchExecuteTask(String param, String[] ids) {
NutMap result = NutMap.NEW();
if (ArrayUtil.isEmpty(ids)) {
return result.setv("successCount", 0).setv("failCount", 0).setv("failMessages", List.of("请选择需要审核的记录"));
}
Dict baseArgs = Json.fromJson(Dict.class, param);
List<ProcessTask> doingTasks = processTaskService.getDoingTaskByBizIdTaskName(Arrays.asList(ids), FOUNDATION_AUDIT_TASK_NAME);
Map<String, ProcessTask> taskMap = new HashMap<>();
List<Long> instanceIds = doingTasks.stream().map(ProcessTask::getProcessInstanceId).filter(Objects::nonNull).distinct().toList();
Map<Long, String> businessNoMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(instanceIds)) {
List<ProcessInstance> instances = dao().query(ProcessInstance.class, Cnd.where(ProcessInstance::getId, "in", instanceIds));
instances.forEach(instance -> businessNoMap.put(instance.getId(), instance.getBusinessNo()));
}
doingTasks.forEach(task -> {
String businessNo = businessNoMap.get(task.getProcessInstanceId());
if (StrUtil.isNotBlank(businessNo)) {
taskMap.put(businessNo, task);
}
});
List<String> failMessages = new ArrayList<>();
int successCount = 0;
for (String id : ids) {
AidFundMemberChangeRecord changeRecord = fetch(id);
if (changeRecord == null) {
failMessages.add(id + ":申请记录不存在");
continue;
}
ProcessTask processTask = taskMap.get(id);
if (processTask == null) {
failMessages.add(resolveRecordName(changeRecord) + ":审核任务不存在或已处理");
continue;
}
if (!processTaskService.isAllowed(processTask, SecurityUtil.getUserId())) {
failMessages.add(resolveRecordName(changeRecord) + ":当前账号无权审核该记录");
continue;
}
try {
Dict args = buildBatchTaskArgs(baseArgs, changeRecord, processTask);
executeTask(args, id);
successCount++;
} catch (Exception e) {
failMessages.add(resolveRecordName(changeRecord) + "" + e.getMessage());
}
}
result.setv("successCount", successCount);
result.setv("failCount", failMessages.size());
result.setv("failMessages", failMessages);
return result;
}
/**
* 批量审核沿用单条审核逻辑但需要为每条记录补齐流程任务 ID 和加入基金时的入校时间
*/
private Dict buildBatchTaskArgs(Dict baseArgs, AidFundMemberChangeRecord changeRecord, ProcessTask processTask) {
Dict args = baseArgs.clone();
args.set(FlowConst.PROCESS_TASK_ID_KEY, processTask.getId());
args.set("changeType", changeRecord.getChangeType());
if (ObjectUtil.equals(args.getInt("submitType"), 1) && ObjectUtil.equals(changeRecord.getChangeType(), CHANGE_TYPE_JOIN)) {
Sys_user user = dao().fetch(Sys_user.class, changeRecord.getUserId());
if (user == null || StrUtil.isBlank(user.getArrivalAtSchoolDate())) {
throw new IllegalArgumentException("缺少入校时间,无法批量通过");
}
args.set("arrivalAtSchoolDate", user.getArrivalAtSchoolDate());
}
return args;
}
private String resolveRecordName(AidFundMemberChangeRecord changeRecord) {
Sys_user user = dao().fetch(Sys_user.class, changeRecord.getUserId());
if (user == null || StrUtil.isBlank(user.getUsername())) {
return changeRecord.getId();
}
return user.getUsername();
}
/**
* 统一处理互管会审核通过后的用户状态同步并继续推进流程任务
*/
private void executeTask(Dict args, String id) {
AidFundMemberChangeRecord changeRecord = fetch(id);
if (changeRecord == null) {
throw new IllegalArgumentException("申请记录不存在");
}
if (args.getInt("submitType") == 1) {
//如果是通过
if (ObjectUtil.equals(args.getStr("changeType"), "AIDFUND_MEMBER_CHANGE_TYPE_ONE")) {
//如果是加入基金
dao().update(Sys_user.class, Chain.make("arrivalAtSchoolDate", args.getStr("arrivalAtSchoolDate")).add("aidFundMemberJoinTime", new Date())
.add("aidFundMember", AidFundMemberMode.NORMAL.getCode()),
// 审核通过后需要把本次申请的基金会员类型同步回用户表历史空值则保留用户原值不覆盖
String aidFundMemberUserType = changeRecord.getAidFundMemberUserType();
// 如果是通过加入基金需要回写入校时间和入会状态其他类型按退会逻辑处理
if (ObjectUtil.equals(args.getStr("changeType"), CHANGE_TYPE_JOIN)) {
if (StrUtil.isBlank(args.getStr("arrivalAtSchoolDate"))) {
throw new IllegalArgumentException("缺少入校时间,无法通过审核");
}
Chain chain = Chain.make("arrivalAtSchoolDate", args.getStr("arrivalAtSchoolDate"))
.add("aidFundMemberJoinTime", new Date())
.add("aidFundMember", AidFundMemberMode.NORMAL.getCode());
if (StrUtil.isNotBlank(aidFundMemberUserType)) {
chain.add("aidFundMemberUserType", aidFundMemberUserType);
}
dao().update(Sys_user.class, chain,
Cnd.where(Sys_user::getId, "=", changeRecord.getUserId()));
} else {
dao().update(Sys_user.class, Chain.make("aidFundMember", AidFundMemberMode.NONE.getCode())
.add("aidFundMemberQuitTime", new Date()),
Chain chain = Chain.make("aidFundMember", AidFundMemberMode.NONE.getCode())
.add("aidFundMemberQuitTime", new Date());
if (StrUtil.isNotBlank(aidFundMemberUserType)) {
chain.add("aidFundMemberUserType", aidFundMemberUserType);
}
dao().update(Sys_user.class, chain,
Cnd.where(Sys_user::getId, "=", changeRecord.getUserId()));
}
}
@@ -2,18 +2,28 @@ package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.mode.AidFundMemberMode;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberChangeRecord;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberHistory;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.model.AidFundMemberPay;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.param.AidFundPageForm;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.aidFund.service.AidFundMemberService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
@@ -121,6 +131,62 @@ public class AidFundMemberServiceImpl extends BaseServiceImpl implements AidFund
return (year + 1 - beforeYear) * money + (year - beforeYear) * money * 0.1;
}
/**
* 统一封装基金会员台账列表查询口径分页查询和导出都复用这里避免所见所得不一致
*
* @param pageForm 页面查询条件包含会员类型工会单位关键字排序及未缴费筛选参数
* @return 基金会员台账查询SQL
*/
@Override
public Sql getManageSql(AidFundPageForm pageForm) {
Sql sql = Sqls.create("""
SELECT
id,
loginname,
username,
sex,
arrivalAtSchoolDate,
unitName,
unionName,
aidFundMemberUserType,
aidFundMemberJoinTime
FROM
vw_user
$condition
""");
Cnd cnd = Cnd.NEW();
// 非系统管理员按现有页面权限口径控制数据范围确保导出结果与列表展示一致
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name())) {
if (AuthUtil.hasRole(RoleConstant.RETIREMENT_WORKPLACE.name())) {
cnd.and(Sys_user::getAidFundMemberUserType, "=", "离退休人员");
} else {
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
}
}
if (pageForm.getNotPayedCurrentYear()) {
cnd.and("id", "in", Sqls.create("SELECT userid FROM `aid_fund_member_pay` where `year` = YEAR(CURDATE()) and isPayed = 0"));
}
cnd.andEX(Sys_user::getAidFundMemberUserType, "=", pageForm.getAidFundMemberUserType());
cnd.andEX(View_user::getUnionId, "=", pageForm.getUnionId());
cnd.andEX(View_user::getUnitId, "=", pageForm.getUnitId());
cnd.and(Sys_user::getAidFundMember, "=", AidFundMemberMode.NORMAL.getCode());
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.orLike("loginname", pageForm.getSearchKeyword());
seg.orLike("username", pageForm.getSearchKeyword());
cnd.and(seg);
}
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("unitCode");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
sql.setCondition(cnd);
return sql;
}
@Override
public List<NutMap> findChangeRecordList(String userId) {
Sql sql = Sqls.create("""
@@ -168,5 +234,93 @@ public class AidFundMemberServiceImpl extends BaseServiceImpl implements AidFund
return listMap(sql);
}
/**
* 管理端批量变更基金会员状态时统一同步用户表并记录系统管理员操作的变更记录
*
* @param ids 需要变更的用户ID数组来自台账页面勾选结果
* @param changeType 变更类型字典值对应 AIDFUND_MEMBER_CHANGE_TYPE
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void doBatchChangeTypeByAdmin(String[] ids, String changeType) {
List<Sys_user> sysUserList = dao().query(Sys_user.class, Cnd.where("id", "in", ids));
List<AidFundMemberChangeRecord> recordArrayList = new ArrayList<>();
for (String id : ids) {
Sys_user user = sysUserList.stream().filter(sysUser -> sysUser.getId().equals(id)).findFirst().orElse(null);
if (user == null) {
continue;
}
recordArrayList.add(buildAdminChangeRecord(user, changeType));
}
updateUserChangeType(ids, changeType);
if (!recordArrayList.isEmpty()) {
dao().insert(recordArrayList);
}
}
/**
* 管理端单个变更操作时统一同步用户表并记录系统管理员操作的变更记录
*
* @param userId 被变更的用户ID
* @param changeType 变更类型字典值对应 AIDFUND_MEMBER_CHANGE_TYPE
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void doEditChangeTypeByAdmin(String userId, String changeType) {
Sys_user user = dao().fetch(Sys_user.class, userId);
if (user == null) {
return;
}
dao().insert(buildAdminChangeRecord(user, changeType));
updateUserChangeType(new String[]{userId}, changeType);
}
/**
* 管理端变更落记录时需要明确标记记录来源避免被我的申请误识别为本人发起
*
* @param user 当前被操作的用户
* @param changeType 变更类型字典值
* @return 已补齐管理员操作标记和会员类型快照的变更记录
*/
private AidFundMemberChangeRecord buildAdminChangeRecord(Sys_user user, String changeType) {
return new AidFundMemberChangeRecord()
.setUserId(user.getId())
.setAidFundMemberUserType(resolveChangeRecordMemberType(user, changeType))
.setApplyTime(new Date())
.setChangeType(changeType)
.setAdminOperate(true);
}
/**
* 退休变更需要把记录中的会员类型同步成退休类型其余情况沿用当前用户档案中的会员类型
*
* @param user 当前被操作的用户
* @param changeType 变更类型字典值
* @return 变更记录中应保存的基金会员类型
*/
private String resolveChangeRecordMemberType(Sys_user user, String changeType) {
if (ObjectUtil.equals(changeType, "AIDFUND_MEMBER_CHANGE_TYPE_EIGTH")) {
return "离退休人员";
}
return user.getAidFundMemberUserType();
}
/**
* 管理端变更批量变更共用同一套用户表更新规则确保页面操作口径一致
*
* @param ids 需要更新的用户ID数组
* @param changeType 变更类型字典值
*/
private void updateUserChangeType(String[] ids, String changeType) {
Chain add;
if (ObjectUtil.equals(changeType, "AIDFUND_MEMBER_CHANGE_TYPE_EIGTH")) {
add = Chain.make("aidFundMemberUserType", "离退休人员");
} else {
add = Chain.make("aidFundMember", AidFundMemberMode.NONE.getCode())
.add("aidFundMemberQuitTime", new Date());
}
dao().update(Sys_user.class, add, Cnd.where("id", "in", ids));
}
}
@@ -73,6 +73,29 @@ public class MedicalCalculateMoneyController {
return Result.success(applyData);
}
@At
@ApiOperation("更新医疗互助补助金额明细")
@SaCheckPermission("medicalMutualAid.medical.calculateMoney")
public Object setGrantsOrUnionInto(String keys, double value) {
//获取申请最新年份
Sql yearSql = Sqls.create("SELECT MAX(YEAR(applyTime)) AS currentYear FROM medical_apply");
yearSql.setCallback(Sqls.callback.longs());
medicalCalculateMoneyService.execute(yearSql);
long year = yearSql.getLong();
MedicalMoneyDetail moneyDetail = medicalCalculateMoneyService.dao().fetch(MedicalMoneyDetail.class, Cnd.where("year", "=", year));
if (Lang.isEmpty(moneyDetail)) {
moneyDetail = new MedicalMoneyDetail();
moneyDetail.setYear((int) year);
}
if (keys.equals("grants")) {
moneyDetail.setSchoolGrants(new BigDecimal(value));
} else if (keys.equals("into")) {
moneyDetail.setUnionInto(new BigDecimal(value));
}
medicalCalculateMoneyService.insertOrUpdate(moneyDetail);
return null;
}
@At
@ApiOperation("按比例修改补助金额")
@@ -0,0 +1,126 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service.MedicalDashBoardService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardCategoryStatVO;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardOverviewVO;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardUnionResultVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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 java.util.List;
/**
* @author Codex
* @description 医疗互助数据看板
*/
@IocBean
@At("/platform/medicalMutualAid/medical/dashboard")
@Api("医疗互助数据看板")
@Ok("json:full")
public class MedicalDashBoardController {
@Inject
private MedicalDashBoardService medicalDashBoardService;
@At("")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/medical/dashboard/index.html")
@SaCheckPermission("medicalMutualAid.medical.dashboard")
public void index() {
}
/**
* 查询医疗互助总览统计
* 返回值字段说明
* 1. fundMemberCount当前基金会员总数
* 2. currentYearApplyCount本年度已办结申请数
* 3. majorDiseaseApplyCount重疾已办结申请数
* 4. commonDiseaseApplyCount普通疾病已办结申请数
* 5. totalSubsidyMoney补助金额合计
* 6. totalLoveSubsidyMoney爱心基金金额合计
*
* @return 医疗互助总览统计 VO
*/
@At
@ApiOperation("查询医疗互助总览统计")
@SaCheckPermission("medicalMutualAid.medical.dashboard")
public Result overview() {
MedicalDashboardOverviewVO vo = medicalDashBoardService.getOverview();
return Result.success(vo);
}
/**
* 按工会统计医疗互助已办结申请数据
* 返回值字段说明
* 1. tableData表格使用的工会统计列表
* 2. 列表元素中 applyCount 为已办结申请数subsidyMoney 为补助金额合计
*
* @return 工会统计结果 VO
*/
@At
@ApiOperation("按工会统计医疗互助数据")
@SaCheckPermission("medicalMutualAid.medical.dashboard")
public Result unionStat() {
MedicalDashboardUnionResultVO vo = medicalDashBoardService.getUnionStat();
return Result.success(vo);
}
/**
* 按在职状态统计医疗互助已办结申请数据
* 返回列表字段说明
* 1. code在职状态编码
* 2. name在职状态名称
* 3. applyCount该状态下已办结申请数
* 4. subsidyMoney该状态下补助金额合计
*
* @return 在职状态统计 VO 列表
*/
@At
@ApiOperation("按在职状态统计医疗互助数据")
@SaCheckPermission("medicalMutualAid.medical.dashboard")
public Result userStateStat() {
List<MedicalDashboardCategoryStatVO> list = medicalDashBoardService.getUserStateStat();
return Result.success(list);
}
/**
* 按人员类型统计医疗互助已办结申请数据
* 返回列表字段说明
* 1. code人员类型编码
* 2. name人员类型名称
* 3. applyCount该类型下已办结申请数
* 4. subsidyMoney该类型下补助金额合计
*
* @return 人员类型统计 VO 列表
*/
@At
@ApiOperation("按人员类型统计医疗互助数据")
@SaCheckPermission("medicalMutualAid.medical.dashboard")
public Result personTypeStat() {
List<MedicalDashboardCategoryStatVO> list = medicalDashBoardService.getPersonTypeStat();
return Result.success(list);
}
/**
* 按疾病类型统计医疗互助已办结申请数据
* 返回列表字段说明
* 1. code疾病类型 ID
* 2. name疾病名称
* 3. applyCount该疾病下已办结申请数
* 4. subsidyMoney该疾病下补助金额合计
*
* @return 疾病类型统计 VO 列表
*/
@At
@ApiOperation("按疾病类型统计医疗互助数据")
@SaCheckPermission("medicalMutualAid.medical.dashboard")
public Result diseaseTypeStat() {
List<MedicalDashboardCategoryStatVO> list = medicalDashBoardService.getDiseaseTypeStat();
return Result.success(list);
}
}
@@ -55,6 +55,7 @@ public class MedicalUnionAuditController {
@SaCheckPermission("medicalMutualAid.medical.unionAudit")
public Result pageData(MedicalPageForm pageForm) {
Cnd cnd = Cnd.NEW();
cnd.and("t.taskName", "=", "a9bbf69c-5edf-4161-a364-47996d212472");
Sql sql = medicalAuditService.getAuditSql(pageForm, cnd);
sql.setCondition(cnd);
@@ -0,0 +1,61 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model.MedicalApply;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardCategoryStatVO;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardOverviewVO;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardUnionResultVO;
import java.util.List;
/**
* @author Codex
* @description 医疗互助数据看板 service
*/
public interface MedicalDashBoardService extends BaseService<MedicalApply> {
/**
* 查询医疗互助数据看板总览数据
* 返回值包含
* 1. 已办结申请总数
* 2. 本年度已办结申请数
* 3. 重疾/普通疾病申请数
* 4. 补助金额与爱心基金金额汇总
*
* @return 医疗互助总览统计 VO
*/
MedicalDashboardOverviewVO getOverview();
/**
* 按工会统计医疗互助已办结申请数据
* 返回值中的 chartData tableData 当前来源相同
* 便于前端图表和表格按既有字段直接取值
*
* @return 工会统计结果 VO
*/
MedicalDashboardUnionResultVO getUnionStat();
/**
* 按人员在职状态统计医疗互助已办结申请数据
* code 为在职状态编码name 为字典名称
*
* @return 在职状态统计列表
*/
List<MedicalDashboardCategoryStatVO> getUserStateStat();
/**
* 按人员类型统计医疗互助已办结申请数据
* code 为人员类型编码name 为字典名称
*
* @return 人员类型统计列表
*/
List<MedicalDashboardCategoryStatVO> getPersonTypeStat();
/**
* 按疾病类型统计医疗互助已办结申请数据
* code 为疾病类型 IDname 为疾病名称
*
* @return 疾病类型统计列表
*/
List<MedicalDashboardCategoryStatVO> getDiseaseTypeStat();
}
@@ -186,6 +186,7 @@ public class MedicalApplyServiceImpl extends BaseServiceImpl<MedicalApply> imple
// 查询申请成功的记录
List<NutMap> applyRecords = getApplyRecord(allUserIds);
applyRecords.removeIf(item -> applyId.equals(item.getString("id")));
//是否首次申请 爱心互助基金 重大疾病
boolean isFirstApply = applyRecords.stream().filter(v -> v.getBoolean("isMajorDiseases")).toList().isEmpty();
@@ -230,7 +231,7 @@ public class MedicalApplyServiceImpl extends BaseServiceImpl<MedicalApply> imple
Boolean isBeyondAllMoney = allMoney.compareTo(medicalSetting.getPreviousYearMaxMoney()) > 0;
Sys_user user = dao().fetch(Sys_user.class, SecurityUtil.getUserId());
Sys_user user = dao().fetch(Sys_user.class, applyInfo.getString("userId"));
boolean fullTenYears = DateUtil.thisYear() - Integer.parseInt(user.getAidFundDeductTime()) >= 10;
boolean fullFifYears = DateUtil.thisYear() - Integer.parseInt(user.getAidFundDeductTime()) >= 15;
@@ -72,7 +72,9 @@ public class MedicalAuditServiceImpl extends BaseServiceImpl<MedicalApply> imple
seg.orLike("us.username", pageForm.getSearchKeyword());
cnd.and(seg);
}
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
if(!SecurityUtil.getUserLoginname().equals("superadmin")){
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
}
cnd.andEX("year(info.applyTime)", "=", pageForm.getYear());
cnd.andEX("info.diseaseId", "=", pageForm.getDiseaseId());
cnd.andEX("us.unionid", "=", pageForm.getUnionId());
@@ -0,0 +1,248 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model.MedicalApply;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service.MedicalDashBoardService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardCategoryStatVO;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardOverviewVO;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardUnionResultVO;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo.MedicalDashboardUnionStatVO;
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.lang.util.NutMap;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* @author Codex
* @description 医疗互助数据看板 service 实现
*/
@IocBean(args = {"refer:dao"})
public class MedicalDashBoardServiceImpl extends BaseServiceImpl<MedicalApply> implements MedicalDashBoardService {
public MedicalDashBoardServiceImpl(Dao dao) {
super(dao);
}
@Override
public MedicalDashboardOverviewVO getOverview() {
Sql sql = Sqls.create("""
SELECT
(SELECT COUNT(1) FROM sys_user WHERE aidFundMember = 1) AS fundMemberCount,
COUNT(DISTINCT CASE WHEN YEAR(info.applyTime) = YEAR(NOW()) THEN info.id END) AS currentYearApplyCount,
COUNT(DISTINCT CASE WHEN type.isMajorDiseases = 1 THEN info.id END) AS majorDiseaseApplyCount,
COUNT(DISTINCT CASE WHEN type.isMajorDiseases = 0 THEN info.id END) AS commonDiseaseApplyCount,
COALESCE(SUM(info.subsidyMoney), 0) AS totalSubsidyMoney,
COALESCE(SUM(info.loveSubsidyMoney), 0) AS totalLoveSubsidyMoney
FROM
medical_apply info
INNER JOIN wf_process_instance ins ON ins.businessNo = info.id
LEFT JOIN medical_disease_type type ON type.id = info.diseaseId
WHERE
ins.state = @state
""");
sql.setParam("state", ProcessInstanceStateEnum.FINISHED.getCode());
NutMap data = fetchOne(sql);
MedicalDashboardOverviewVO vo = new MedicalDashboardOverviewVO();
vo.setFundMemberCount(data.getLong("fundMemberCount", 0L));
vo.setCurrentYearApplyCount(data.getLong("currentYearApplyCount", 0L));
vo.setMajorDiseaseApplyCount(data.getLong("majorDiseaseApplyCount", 0L));
vo.setCommonDiseaseApplyCount(data.getLong("commonDiseaseApplyCount", 0L));
vo.setTotalSubsidyMoney(getBigDecimalValue(data, "totalSubsidyMoney"));
vo.setTotalLoveSubsidyMoney(getBigDecimalValue(data, "totalLoveSubsidyMoney"));
return vo;
}
@Override
public MedicalDashboardUnionResultVO getUnionStat() {
Sql sql = Sqls.create("""
SELECT
un.id AS unionId,
un.`name` AS unionName,
un.unionCode AS unionCode,
COUNT(applyInfo.id) AS applyCount,
COALESCE(SUM(applyInfo.subsidyMoney), 0) AS subsidyMoney
FROM
sys_union un
LEFT JOIN vw_user us ON us.unionId = un.id
LEFT JOIN (
SELECT
info.id,
info.userId,
info.subsidyMoney
FROM
medical_apply info
INNER JOIN wf_process_instance ins ON ins.businessNo = info.id
WHERE
ins.state = @state
) applyInfo ON applyInfo.userId = us.id
GROUP BY
un.id, un.`name`, un.unionCode
ORDER BY
un.unionCode
""");
sql.setParam("state", ProcessInstanceStateEnum.FINISHED.getCode());
List<MedicalDashboardUnionStatVO> list = toUnionStatList(listMap(sql));
MedicalDashboardUnionResultVO resultVO = new MedicalDashboardUnionResultVO();
resultVO.setTableData(new ArrayList<>(list));
return resultVO;
}
@Override
public List<MedicalDashboardCategoryStatVO> getUserStateStat() {
Sql sql = Sqls.create("""
SELECT
dict.`code` AS code,
dict.`name` AS name,
COUNT(info.id) AS applyCount,
COALESCE(SUM(info.subsidyMoney), 0) AS subsidyMoney
FROM
sys_dict dict
LEFT JOIN sys_dict parent ON parent.id = dict.parentId
LEFT JOIN vw_user us ON us.userState = dict.`code`
LEFT JOIN medical_apply info ON info.userId = us.id
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id AND ins.state = @state
WHERE
parent.`code` = 'USER_STATE'
AND ins.id IS NOT NULL
GROUP BY
dict.`code`, dict.`name`, dict.location
ORDER BY
dict.location
""");
sql.setParam("state", ProcessInstanceStateEnum.FINISHED.getCode());
return toCategoryStatList(listMap(sql));
}
@Override
public List<MedicalDashboardCategoryStatVO> getPersonTypeStat() {
Sql sql = Sqls.create("""
SELECT
dict.`code` AS code,
dict.`name` AS name,
COUNT(info.id) AS applyCount,
COALESCE(SUM(info.subsidyMoney), 0) AS subsidyMoney
FROM
sys_dict dict
LEFT JOIN sys_dict parent ON parent.id = dict.parentId
LEFT JOIN vw_user us ON us.personType = dict.`code`
LEFT JOIN medical_apply info ON info.userId = us.id
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id AND ins.state = @state
WHERE
parent.`code` = 'USER_PERSON_TYPE'
AND ins.id IS NOT NULL
GROUP BY
dict.`code`, dict.`name`, dict.location
ORDER BY
dict.location
""");
sql.setParam("state", ProcessInstanceStateEnum.FINISHED.getCode());
return toCategoryStatList(listMap(sql));
}
@Override
public List<MedicalDashboardCategoryStatVO> getDiseaseTypeStat() {
Sql sql = Sqls.create("""
SELECT
type.id AS code,
type.diseaseName AS name,
COUNT(info.id) AS applyCount,
COALESCE(SUM(info.subsidyMoney), 0) AS subsidyMoney
FROM
medical_disease_type type
LEFT JOIN medical_apply info ON info.diseaseId = type.id
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id AND ins.state = @state
WHERE
ins.id IS NOT NULL
GROUP BY
type.id, type.diseaseName, type.diseaseCode
ORDER BY
type.diseaseCode
""");
sql.setParam("state", ProcessInstanceStateEnum.FINISHED.getCode());
return toCategoryStatList(listMap(sql));
}
/**
* 执行单行统计 SQL
* 该方法用于处理总览类统计返回结果仅一行
*
* @param sql 已设置好查询参数的 SQL 对象
* @return 单行统计数据字段不存在时返回空 NutMap避免上层空指针
*/
private NutMap fetchOne(Sql sql) {
sql.setCallback(Sqls.callback.map());
dao().execute(sql);
Object result = sql.getResult();
if (result instanceof NutMap) {
return (NutMap) result;
}
return NutMap.NEW();
}
/**
* 将工会统计查询结果转换为 VO
*
* @param dataList SQL 查询返回的 map 列表字段需包含 unionIdunionNameunionCodeapplyCountsubsidyMoney
* @return 工会统计 VO 列表
*/
private List<MedicalDashboardUnionStatVO> toUnionStatList(List<NutMap> dataList) {
List<MedicalDashboardUnionStatVO> list = new ArrayList<>();
for (NutMap data : dataList) {
MedicalDashboardUnionStatVO vo = new MedicalDashboardUnionStatVO();
vo.setUnionId(data.getString("unionId"));
vo.setUnionName(data.getString("unionName"));
vo.setUnionCode(data.getString("unionCode"));
vo.setApplyCount(data.getLong("applyCount", 0L));
vo.setSubsidyMoney(getBigDecimalValue(data, "subsidyMoney"));
list.add(vo);
}
return list;
}
/**
* 将通用分类统计查询结果转换为 VO
*
* @param dataList SQL 查询返回的 map 列表字段需包含 codenameapplyCountsubsidyMoney
* @return 分类统计 VO 列表
*/
private List<MedicalDashboardCategoryStatVO> toCategoryStatList(List<NutMap> dataList) {
List<MedicalDashboardCategoryStatVO> list = new ArrayList<>();
for (NutMap data : dataList) {
MedicalDashboardCategoryStatVO vo = new MedicalDashboardCategoryStatVO();
vo.setCode(data.getString("code"));
vo.setName(data.getString("name"));
vo.setApplyCount(data.getLong("applyCount", 0L));
vo.setSubsidyMoney(getBigDecimalValue(data, "subsidyMoney"));
list.add(vo);
}
return list;
}
/**
* NutMap 中安全读取金额字段
* SQL 聚合查询返回的金额字段有时会是 BigDecimalDoubleLong 或字符串
* 因此这里统一做一次转换避免 controller/service 层重复判空和类型判断
*
* @param data NutMap 查询结果对象
* @param key 金额字段名
* @return 对应金额字段为空时返回 0
*/
private BigDecimal getBigDecimalValue(NutMap data, String key) {
Object value = data.get(key);
if (value == null) {
return BigDecimal.ZERO;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
return new BigDecimal(String.valueOf(value));
}
}
@@ -0,0 +1,34 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author Codex
* @description 医疗互助分类统计返回值
*/
@Data
public class MedicalDashboardCategoryStatVO {
/**
* 分类编码
* 不同接口场景下可能表示在职状态编码人员类型编码疾病ID
*/
private String code;
/**
* 分类名称用于前端直接展示图表/表格标题
*/
private String name;
/**
* 当前分类下的已办结申请数
*/
private Long applyCount;
/**
* 当前分类下的已办结补助金额合计
*/
private BigDecimal subsidyMoney;
}
@@ -0,0 +1,43 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author Codex
* @description 医疗互助数据看板总览统计返回值
*/
@Data
public class MedicalDashboardOverviewVO {
/**
* 当前基金会员总数
*/
private Long fundMemberCount;
/**
* 本年度已办结申请数
*/
private Long currentYearApplyCount;
/**
* 已办结重疾申请数
*/
private Long majorDiseaseApplyCount;
/**
* 已办结普通疾病申请数
*/
private Long commonDiseaseApplyCount;
/**
* 已办结补助金额合计
*/
private BigDecimal totalSubsidyMoney;
/**
* 已办结爱心基金金额合计
*/
private BigDecimal totalLoveSubsidyMoney;
}
@@ -0,0 +1,19 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo;
import lombok.Data;
import java.util.List;
/**
* @author Codex
* @description 医疗互助按工会统计返回值
*/
@Data
public class MedicalDashboardUnionResultVO {
/**
* 表格数据
* 当前页面仅保留工会统计表格因此只返回表格所需列表
*/
private List<MedicalDashboardUnionStatVO> tableData;
}
@@ -0,0 +1,38 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author Codex
* @description 医疗互助按工会统计返回值
*/
@Data
public class MedicalDashboardUnionStatVO {
/**
* 工会ID
*/
private String unionId;
/**
* 工会名称
*/
private String unionName;
/**
* 工会编码
*/
private String unionCode;
/**
* 已办结申请数
*/
private Long applyCount;
/**
* 已办结补助金额合计
*/
private BigDecimal subsidyMoney;
}
@@ -1,20 +1,15 @@
package com.budwk.app.zhgh.staffmanage.member.controller.info;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
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.result.Result;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberInfoPageForm;
import com.budwk.app.zhgh.staffmanage.member.service.MemberInfoService;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
@@ -22,27 +17,13 @@ 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.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import org.nutz.mvc.annotation.POST;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.BufferedOutputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @version 1.0
* @Author hqw
* @nameMemberInfoInputController
* @Date 2026/4/8 19:31
* @注释 人员补录
* 人员补录
*/
@At("/platform/member/info/input")
@Ok("json:full")
@@ -58,7 +39,6 @@ public class MemberInfoInputController {
public void index() {
}
@At
@SaCheckPermission("member.info.input")
public Result pageData(MemberInfoPageForm pageForm) {
@@ -74,30 +54,53 @@ public class MemberInfoInputController {
@SLog(tag = "会员信息录入", type = "add", msg = "新增会员")
public Result add(Sys_user user, String unionId, String unitId) {
try {
memberInfoService.addMember(user,unionId,unitId);
memberInfoService.addMember(user, unionId, unitId);
return Result.success("新增成功");
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@SaCheckPermission("member.info.input")
public Result findOne(@Param("recordId") String recordId, @Param("userId") String userId) {
try {
return Result.success(memberInfoService.getMemberInputDetail(recordId, userId));
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("member.info.input")
@SLog(tag = "人员补录", msg = "删除会")
@SLog(tag = "人员补录", type = "update", msg = "编辑补录人")
public Result update(Sys_user user, String unionId, String unitId, String recordId) {
try {
memberInfoService.updateMemberInput(user, unionId, unitId, recordId);
return Result.success("编辑成功");
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
@At
@POST
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("member.info.input")
@SLog(tag = "人员补录", msg = "删除会员")
public Result doDelete(String id, String userId) {
try {
memberInfoService.dao().delete(MemberChangeRecord.class, id);
Sys_role memberRole = memberInfoService.dao().fetch(Sys_role.class, Cnd.where("code", "=", RoleConstant.MEMBER.name()));
Sys_role publicRole = memberInfoService.dao().fetch(Sys_role.class, Cnd.where("code", "=", RoleConstant.PUBLIC.name()));
memberInfoService.dao().execute(Sqls.create("delete from sys_user_role where userId = \'"+userId+"\' and roleId = \'"+publicRole.getId()+"\'"));
memberInfoService.dao().execute(Sqls.create("delete from sys_user_role where userId = \'"+userId+"\' and roleId = \'"+memberRole.getId()+"\'"));
memberInfoService.dao().execute(Sqls.create("delete from sys_user_role where userId = '" + userId + "' and roleId = '" + publicRole.getId() + "'"));
memberInfoService.dao().execute(Sqls.create("delete from sys_user_role where userId = '" + userId + "' and roleId = '" + memberRole.getId() + "'"));
memberInfoService.dao().delete(Sys_user.class, userId);
return Result.success("删除成功");
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
}
@@ -237,5 +237,10 @@ public class MemberChangeRecord extends BaseModel {
@ColDefine(type = ColType.VARCHAR,width = 10)
private String aidFundMemberUserType;
@Column
@Comment("入校时间")
@ColDefine(type = ColType.DATE)
private String arrivalAtSchoolDate;
private Boolean edit;
}
@@ -40,6 +40,23 @@ public interface MemberInfoService extends BaseService<Sys_user> {
*/
void addMember(Sys_user user, String unionId, String unitId);
/**
* 获取人员补录编辑详情
* @param recordId 补录记录ID
* @param userId 用户ID
* @return 编辑详情
*/
NutMap getMemberInputDetail(String recordId, String userId);
/**
* 更新人员补录信息
* @param user 用户信息
* @param unionId 工会ID
* @param unitId 单位ID
* @param recordId 补录记录ID
*/
void updateMemberInput(Sys_user user, String unionId, String unitId, String recordId);
/**
* 获取会员历史
* @param pageForm
@@ -194,6 +194,7 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
user.setCreateAt(System.currentTimeMillis());
user.setLoginCount(0);
user.setDisabled(false);
user.setMember(true);
if (user.getMember() == null) {
user.setMember(true);
@@ -268,6 +269,99 @@ public class MemberInfoServiceImpl extends BaseServiceImpl<Sys_user> implements
}
@Override
public NutMap getMemberInputDetail(String recordId, String userId) {
MemberChangeRecord record = dao().fetch(MemberChangeRecord.class, recordId);
if (ObjectUtil.isEmpty(record)) {
throw new BaseException("补录记录不存在");
}
Sys_user user = dao().fetch(Sys_user.class, userId);
if (ObjectUtil.isEmpty(user)) {
throw new BaseException("人员信息不存在");
}
NutMap detail = NutMap.NEW();
detail.putAll(BeanUtil.beanToMap(user));
// 编辑页复用新增表单需要把补录记录中的单位/工会及记录ID一起回填
detail.put("recordId", record.getId());
detail.put("unitId", record.getUnitId());
detail.put("unitName", record.getUnitName());
detail.put("unionId", record.getUnionId());
detail.put("unionName", record.getUnionName());
return detail;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void updateMemberInput(Sys_user user, String unionId, String unitId, String recordId) {
if (StrUtil.isBlank(user.getId())) {
throw new BaseException("用户ID不能为空");
}
if (StrUtil.isBlank(recordId)) {
throw new BaseException("补录记录ID不能为空");
}
if (StrUtil.isBlank(user.getLoginname())) {
throw new BaseException("工号不能为空");
}
if (StrUtil.isBlank(user.getUsername())) {
throw new BaseException("姓名不能为空");
}
Sys_user existUser = dao().fetch(Sys_user.class, Cnd.where("loginname", "=", user.getLoginname()).and("id", "!=", user.getId()));
if (ObjectUtil.isNotEmpty(existUser)) {
throw new BaseException("该工号已存在");
}
Sys_user oldUser = dao().fetch(Sys_user.class, user.getId());
if (ObjectUtil.isEmpty(oldUser)) {
throw new BaseException("人员信息不存在");
}
user.setCreateAt(oldUser.getCreateAt());
user.setLoginCount(oldUser.getLoginCount());
user.setDisabled(false);
user.setMember(true);
if (user.getMember() == null) {
user.setMember(oldUser.getMember());
}
if (user.getWelfareMember() == null) {
user.setWelfareMember(oldUser.getWelfareMember());
}
dao().updateIgnoreNull(user);
MemberChangeRecord record = dao().fetch(MemberChangeRecord.class, recordId);
if (ObjectUtil.isEmpty(record)) {
throw new BaseException("补录记录不存在");
}
Sys_unit unit = dao().fetch(Sys_unit.class, Cnd.where("id", "=", unitId));
Sys_union union = dao().fetch(Sys_union.class, Cnd.where("id", "=", unionId));
// 补录列表展示来源于变更记录编辑时同步更新记录内容保证列表和详情一致
record.setLoginname(user.getLoginname());
record.setUsername(user.getUsername());
record.setUnitId(unitId);
record.setUnitName(ObjectUtil.isNotEmpty(unit) ? unit.getName() : "");
record.setUnionId(unionId);
record.setUnionName(ObjectUtil.isNotEmpty(union) ? union.getName() : "");
record.setSex(user.getSex());
record.setBirthday(user.getBirthday());
record.setIdCard(user.getIdCard());
record.setNation(user.getNation());
record.setPolitical(user.getPolitical());
record.setEducation(user.getEducation());
record.setAcademicDegree(user.getAcademicDegree());
record.setPosition(user.getProfessionalTitle());
record.setUserState(user.getUserState());
record.setPreparedBy(user.getPreparedBy());
record.setPersonType(user.getPersonType());
record.setMobile(user.getMobile());
record.setEmail(user.getEmail());
record.setMember(user.getMember());
record.setWelfareMember(user.getWelfareMember());
record.setRetireDate(user.getRetireDate());
record.setFamilies(user.getFamilies());
record.setPersonalData(user.getPersonalData());
record.setUserAttribute(user.getUserAttribute());
record.setCampus(user.getCampus());
record.setProfessionalTitle(user.getProfessionalTitle());
record.setAidFundMemberUserType(user.getAidFundMemberUserType());
dao().updateIgnoreNull(record);
}
@Override
public Sql getMemberHistorySql(MemberInfoPageForm pageForm) {
@@ -95,6 +95,7 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
u.isModelWorker,
u.userAttribute,
u.aidFundMemberUserType,
u.arrivalAtSchoolDate,
his.id hisId,
his.changeTypes,
his.changeTime
@@ -220,10 +221,12 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
allowChangeFieldNames.add("member");
allowChangeFieldNames.add("aidFundMemberUserType");
allowChangeFieldNames.add("userAttribute");
allowChangeFieldNames.add("arrivalAtSchoolDate");
Map<String, String> dictMap = dictList.stream().collect(Collectors.toMap(Sys_dict::getCode, Sys_dict::getName));
dictMap.put("member", "会员状态");
dictMap.put("aidFundMemberUserType", "人员分类");
dictMap.put("userAttribute", "人员属性");
dictMap.put("arrivalAtSchoolDate", "入校时间");
return NutMap.NEW().addv("allowChangeFieldNames", allowChangeFieldNames).addv("dictMap", dictMap);
}