Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-04-24 15:35:08 +08:00
126 changed files with 5447 additions and 809 deletions
@@ -285,21 +285,28 @@ public class SysUnitController {
@SaCheckLogin
public Object tree(@Param("pid") String pid, HttpServletRequest req) {
try {
String rootId = StrUtil.blankToDefault(pid, "0");
String virtualRootId = "root";
Cnd cnd = Cnd.NEW();
cnd.andEX("id", "=", pid);
cnd.asc("unitcode");
List<Sys_unit> list = sysUnitService.query(cnd);
List<TreeNode<String>> nodeList = CollUtil.newArrayList();
for (int i = 0; i < list.size(); i++) {
nodeList.add(new TreeNode<>(list.get(i).getId(), list.get(i).getParentId(), list.get(i).getName(), i)
Sys_unit unit = list.get(i);
/*
* 单位根节点存在 id 与 parentId 都为 0 的自引用数据。
* 构建树时把当前查询根挂到虚拟根下,避免“中国地质大学”和 parentId=0 的学院被构造成同级。
*/
String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId();
nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i)
.setExtra(
Map.of(
"unitTypeCode", list.get(i).getUnitTypeCode()
"unitTypeCode", unit.getUnitTypeCode()
)
));
}
List<Tree<String>> treeList = TreeUtil.build(nodeList, StrUtil.blankToDefault(pid, "0"));
List<Tree<String>> treeList = TreeUtil.build(nodeList, virtualRootId);
return Result.success(treeList);
} catch (Exception e) {
return Result.error();
@@ -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);
// 开启流程实例
@@ -133,6 +133,9 @@ public class ClubExamineApplyController {
.and("year", "=", year)
.desc("year")
);
if (club == null) {
return Result.success(0f);
}
Number number = ObjectUtil.defaultIfNull(club.getTotalQuota(), 0);
return Result.success(number.floatValue());
}
@@ -154,7 +157,6 @@ public class ClubExamineApplyController {
}
@At
@SaCheckPermission("club.examine")
public Result info(String id) {
if(StrUtil.isBlank(id)) {
return Result.error("id信息为空,请核查");
@@ -1,17 +1,29 @@
package com.budwk.app.zhgh.club.controller.examine;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.club.param.ClubUserPageForm;
import com.budwk.app.zhgh.club.service.SysClubExamineService;
import com.budwk.app.zhgh.club.vo.ClubExamineVo;
import com.budwk.app.zhgh.club.vo.ClubExaminePageVo;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.apache.poi.ss.usermodel.Workbook;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName ClubExamineSchoolLeaderAuditController
@@ -39,4 +51,49 @@ public class ClubExamineSchoolLeaderAuditController {
Pagination<ClubExaminePageVo> pagination = sysClubExamineService.schoolLeaderAuditPageData(pageForm);
return Result.success(pagination);
}
@At
@Ok("void")
@SaCheckPermission("club.examine.schoolLeaderAudit")
public void doExportPlan(String id, HttpServletResponse response) {
ClubExamineVo examineVo = sysClubExamineService.findOne(id);
if (Lang.isEmpty(examineVo)) {
return;
}
// 导出下一年度活动计划,字段顺序与查看页列表保持一致。
List<NutMap> exportData = sysClubExamineService.getPlanExportData(id);
List<ExcelExportEntity> entityList = new ArrayList<>();
entityList.add(new ExcelExportEntity("活动主题/赛事名称", "activityName", 40));
entityList.add(new ExcelExportEntity("活动/赛事主办单位", "activityUnit", 40));
entityList.add(new ExcelExportEntity("活动地点", "activityAddress", 40));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, exportData);
CommonDownloadUtil.download(examineVo.getClubName() + "下一年度活动计划.xlsx", workbook, response);
}
@At
@Ok("void")
@SaCheckPermission("club.examine.schoolLeaderAudit")
public void doExportActivity(String id, HttpServletResponse response) {
ClubExamineVo examineVo = sysClubExamineService.findOne(id);
if (Lang.isEmpty(examineVo)) {
return;
}
// 导出本年度活动开展情况,字段与年审填报表格列保持一致。
List<NutMap> exportData = sysClubExamineService.getYearActivityExportData(id);
List<ExcelExportEntity> entityList = new ArrayList<>();
entityList.add(new ExcelExportEntity("活动时间", "activityTime", 20));
entityList.add(new ExcelExportEntity("活动主题/赛事名称", "activityName", 40));
entityList.add(new ExcelExportEntity("活动/赛事主办单位", "activityUnit", 40));
entityList.add(new ExcelExportEntity("活动地点", "activityAddress", 40));
entityList.add(new ExcelExportEntity("参加人数", "joinNum", 20));
entityList.add(new ExcelExportEntity("获奖情况", "prize", 30));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, exportData);
CommonDownloadUtil.download(examineVo.getClubName() + "本年度活动开展情况.xlsx", workbook, response);
}
}
@@ -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);
}
@@ -30,4 +30,20 @@ public interface SysClubExamineService extends BaseService<SysClubExamineRegiste
Pagination<ClubExaminePageVo> schoolLeaderAuditPageData(@Valid ClubUserPageForm pageForm);
ClubExamineVo findOne(@Valid String id);
/**
* 查询本年度活动开展情况导出数据。
*
* @param id 年审主键
* @return 导出行数据
*/
List<NutMap> getYearActivityExportData(@Valid String id);
/**
* 查询下一年度活动计划导出数据。
*
* @param id 年审主键
* @return 导出行数据
*/
List<NutMap> getPlanExportData(@Valid String id);
}
@@ -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;
}
}
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.club.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.constant.RoleConstant;
@@ -308,7 +309,7 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
""");
// 构建基础分页查询条件
buildPageBaseCondition(cnd, pageForm, "info");
cnd.and("info.userId", "=", SecurityUtil.getUserId());
// cnd.and("info.userId", "=", SecurityUtil.getUserId());
cnd.groupBy("info.id");
// cnd.desc("registerDate");
cnd.asc("sc.foundTime");
@@ -367,6 +368,41 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
return clubExamineVo;
}
@Override
public List<NutMap> getYearActivityExportData(String id) {
ClubExamineVo examineVo = this.findOne(id);
if (Lang.isEmpty(examineVo) || Lang.isEmpty(examineVo.getYearActivityList())) {
return new ArrayList<>();
}
// 导出字段与前端表格字段保持一致,避免控制层再做字段转换。
return examineVo.getYearActivityList().stream()
.map(item -> Dict.create()
.set("activityTime", item.getStr("activityTime"))
.set("activityName", item.getStr("activityName"))
.set("activityUnit", item.getStr("activityUnit"))
.set("activityAddress", item.getStr("activityAddress"))
.set("joinNum", item.getStr("joinNum"))
.set("prize", item.getStr("prize")))
.map(NutMap::new)
.collect(Collectors.toList());
}
@Override
public List<NutMap> getPlanExportData(String id) {
ClubExamineVo examineVo = this.findOne(id);
if (Lang.isEmpty(examineVo) || Lang.isEmpty(examineVo.getPlans())) {
return new ArrayList<>();
}
// 下年度活动计划导出只保留页面已有的三个业务字段。
return examineVo.getPlans().stream()
.map(item -> Dict.create()
.set("activityName", item.getStr("activityName"))
.set("activityUnit", item.getStr("activityUnit"))
.set("activityAddress", item.getStr("activityAddress")))
.map(NutMap::new)
.collect(Collectors.toList());
}
/**
* 生成审核分页通用SQL
* @param pageForm 分页查询参数
@@ -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;
}
@@ -93,7 +93,7 @@ public class ActivityBudget extends BaseModel implements Serializable {
@Column
@Comment("举办单位")
@ColDefine(type = ColType.VARCHAR, width = 32)
@ColDefine(type = ColType.VARCHAR, width = 100)
private String helpUnitName;
@Column
@@ -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
@@ -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;
};
}
}
@@ -84,7 +84,6 @@ public class UnionReimburseDescController {
@At
@ApiOperation("查询附件说明")
@SaCheckPermission(value = {"unionReimburse.desc", "h5.unionReimburse.desc"}, mode = SaMode.OR)
public Result queryFileDesc() {
List<UnionReimburseDesc> list = unionReimburseDesc.query(Cnd.NEW().desc(UnionReimburseDesc::getCode));
return Result.success(list);
@@ -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;
}
@@ -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 为统计行列表包含 dimensionitemNamecountrate
*/
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 为分析行列表包含 dimensiontotalcategoryCounttopItemtopCounttopRateanalysis
*/
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 字符串必须包含 processTaskIdproposalIdtaskActorUserIdtf_opinionsubmitType
* 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. 如果页面点击的是 invitedelegationcommittee 等关联节点则回查同实例下当前附议人的 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);
@@ -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;
}