Merge remote-tracking branch 'origin/main'

This commit is contained in:
zhouhefeng
2026-05-20 09:04:22 +08:00
96 changed files with 4040 additions and 678 deletions
@@ -24,6 +24,8 @@ import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.basic.param.ActivityUserScopePageParam;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.activity.basic.template.UserTemp;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.service.SysClubService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
@@ -84,6 +86,9 @@ public class ActivityBasicScopeController {
@Inject
private RedisService redisService;
@Inject
private SysClubService sysClubService;
@At("")
@Ok("beetl:platform/zhgh/activity/basic/userScope/index.html")
@SaCheckPermission("activity.basic.scope")
@@ -97,7 +102,6 @@ public class ActivityBasicScopeController {
* @return {@link Result}
*/
@At
@SaCheckPermission("activity.basic")
public Result pageData(@Param("data") ActivityUserScopePageParam pageForm) {
Sql sql = Sqls.create("""
SELECT
@@ -216,6 +220,8 @@ public class ActivityBasicScopeController {
return Result.success(new NutMap() {{
addv("is_A06", StpUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name()));
addv("is_H04", StpUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name()));
addv("is_H02", StpUtil.hasRole(RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name()));
addv("is_CLUB_PRESIDENT", StpUtil.hasRole(RoleConstant.CLUB_PRESIDENT.name()));
addv("is_sysadmin", StpUtil.hasRole(RoleConstant.SYSADMIN.name()));
addv("unionid", SecurityUtil.getUnionId());
}});
@@ -344,9 +350,30 @@ public class ActivityBasicScopeController {
}
try {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())){
cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId());
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
boolean isBranchUnionChairman = AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name());
boolean isClubPresident = AuthUtil.hasRole(RoleConstant.CLUB_PRESIDENT.name());
if (isBranchUnionChairman && isClubPresident) {
// 同时具备分工会主席和社团会长身份时,未额外筛选前默认查看两类权限范围的并集。
List<String> manageClubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
SqlExpressionGroup scopeGroup = new SqlExpressionGroup();
scopeGroup.or("u.unionid", "=", SecurityUtil.getUnionId());
if (!manageClubIdList.isEmpty()) {
scopeGroup.or("clubuser.clubid", "in", manageClubIdList);
}
cnd.and(scopeGroup);
} else if (isClubPresident) {
// 社团会长设置活动人员范围时,只允许查看自己可管理社团下的成员。
List<String> manageClubIdList = sysClubService.getMyManageClub().stream().map(SysClub::getId).toList();
if (manageClubIdList.isEmpty()) {
cnd.and("clubuser.clubid", "in", List.of("__EMPTY_CLUB_ID__"));
} else {
cnd.and("clubuser.clubid", "in", manageClubIdList);
}
} else {
// 分工会主席等分工会侧角色,继续只允许查看本分工会人员。
cnd.andEX("u.unionid", "=", SecurityUtil.getUnionId());
}
}
String[] unionIds = getUnionIds(activityUserScopePageParam);
if (Lang.isNotEmpty(unionIds)) {
@@ -21,6 +21,7 @@ import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
@@ -125,8 +126,17 @@ public class ActivityCultureApplyUserController {
public Result queryTeammate(@Valid String keyword, Integer signUpMethod, String tissueId) {
Sql sql = Sqls.create("select id userId,username userName,loginname loginName,sex,mobile,unitName from vw_user $condition limit 0,10");
Cnd cnd = Cnd.NEW();
cnd.or(Cnd.likeEX("username", keyword));
cnd.or(Cnd.likeEX("loginname", keyword));
SqlExpressionGroup keywordGroup = new SqlExpressionGroup();
keywordGroup.andLike("username", keyword);
keywordGroup.orLike("loginname", keyword);
cnd.and(keywordGroup);
if (StrUtil.isNotBlank(tissueId)) {
ActivityTissue tissue = dao.fetch(ActivityTissue.class, tissueId);
// 组队/分工会报名选人时,候选人必须落在活动新建时配置的参加人员范围内。
if (ObjectUtil.isNotNull(tissue) && ObjectUtil.isNotNull(tissue.getGroupId())) {
cnd.and("id", "in", activityBasicScopeService.buildGroupUserIdSubSql(tissue.getGroupId()));
}
}
if (signUpMethod == 3) {
cnd.and("unionid", "=", SecurityUtil.getUnionId());
}
@@ -112,7 +112,7 @@ public class ActivityCultureAuditActivityController {
cnd.andEX("tissue.unionId", "=", unionId);
cnd.andEX("tissue.activity_type", "=", activity_type);
if (approval) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
@@ -161,6 +161,11 @@ public class ActivityCultureUserStatisticsController {
cnd.and("tissue.signUpMethod", "in", List.of(1, 2, 3));
if (activity_type == 40002) {
cnd.and("tissue.unionId", "=", SecurityUtil.getUnionId());
} else if (activity_type == 40003) {
// 协会会长进入社团报名统计时,只允许查看自己管理社团下的活动名称。
List<SysClub> myManageClub = sysClubService.getMyManageClub();
List<String> clubIdList = myManageClub.stream().map(SysClub::getId).collect(Collectors.toList());
cnd.and("tissue.clubId", "in", clubIdList);
}
}
// 只查询流程实例状态为20的数据(已完成状态)
@@ -169,34 +169,35 @@ public class ActivityPrizeListController {
Map fourMap = NutMap.NEW();
Map fiveMap = NutMap.NEW();
// 部分历史数据可能未配置竞赛类别,分组汇总时需要做空值保护,避免筛选时空指针
//甲组
List<NutMap> oneList = allList.stream().filter(a -> a.getString("bszbName").equals("甲组")).collect(toList());
List<NutMap> oneList = allList.stream().filter(a -> Objects.equals("甲组", a.getString("bszbName"))).collect(toList());
if (Lang.isNotEmpty(oneList)) {
oneMap = oneList.stream().collect(Collectors.groupingBy(v -> v.getString("allName")));
}
//乙组
List<NutMap> twoList = allList.stream().filter(a -> a.getString("bszbName").equals("乙组")).collect(toList());
List<NutMap> twoList = allList.stream().filter(a -> Objects.equals("乙组", a.getString("bszbName"))).collect(toList());
if (Lang.isNotEmpty(twoList)) {
twoMap = twoList.stream().collect(Collectors.groupingBy(v -> v.getString("allName")));
}
//丙组
List<NutMap> threeList = allList.stream().filter(a -> a.getString("bszbName").equals("丙组")).collect(toList());
List<NutMap> threeList = allList.stream().filter(a -> Objects.equals("丙组", a.getString("bszbName"))).collect(toList());
if (Lang.isNotEmpty(threeList)) {
threeMap = threeList.stream().collect(Collectors.groupingBy(v -> v.getString("allName")));
}
//丁组
List<NutMap> fourList = allList.stream().filter(a -> a.getString("bszbName").equals("丁组")).collect(toList());
List<NutMap> fourList = allList.stream().filter(a -> Objects.equals("丁组", a.getString("bszbName"))).collect(toList());
if (Lang.isNotEmpty(fourList)) {
fourMap = fourList.stream().collect(Collectors.groupingBy(v -> v.getString("allName")));
}
//团体
List<NutMap> fiveList = allList.stream().filter(a -> a.getString("bszbName").equals("团体")).collect(toList());
List<NutMap> fiveList = allList.stream().filter(a -> Objects.equals("团体", a.getString("bszbName"))).collect(toList());
if (Lang.isNotEmpty(fiveList)) {
fiveMap = fiveList.stream().collect(Collectors.groupingBy(v -> v.getString("allName")));
}
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.activity.sports.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
@@ -9,6 +10,8 @@ import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchool;
import com.budwk.app.zhgh.activity.sports.service.ActivitySportsApplyUserService;
import com.budwk.app.zhgh.activity.sports.service.ActivitySportsService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
@@ -19,7 +22,12 @@ 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.nutz.mvc.annotation.Param;
import org.nutz.mvc.annotation.AdaptBy;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -45,6 +53,10 @@ public class ActivitySportsReadingController {
public Dao dao;
@Inject
public BaseService baseService;
@Inject
private ActivitySportsService activitySportsService;
@Inject
private ActivitySportsApplyUserService activitySportsApplyUserService;
@At
@SaCheckPermission("activity.reading")
@@ -150,5 +162,38 @@ public class ActivitySportsReadingController {
return Result.success(apply);
}
@At
@Ok("void")
@SaCheckPermission("activity.reading")
public void downloadImportTemplate(HttpServletResponse response) {
checkManageRole();
activitySportsApplyUserService.downloadImportTemplate(response);
}
@At
@SaCheckPermission("activity.reading")
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
public Result importUserActivitys(@Param("file") TempFile file, @Param("businessId") String activityId) {
checkManageRole();
return Result.success(activitySportsApplyUserService.importUserActivitys(file, activityId));
}
@At
@Ok("void")
@SaCheckPermission("activity.reading")
public void doExportByEnroll(String id, String unionId, HttpServletResponse response) {
checkManageRole();
activitySportsService.exportXlsx(id, unionId, response);
}
/**
* 参考原项目,导入和整表导出只开放给校工会管理员和系统管理员,避免普通阅览权限误操作。
*/
private void checkManageRole() {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
throw new BaseException("您没有操作权限");
}
}
}
@@ -6,7 +6,9 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolApply;
import com.budwk.app.zhgh.activity.sports.param.ActivitySportsApplyUserPageParam;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.upload.TempFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public interface ActivitySportsApplyUserService extends BaseService<ActivitySchoolApply> {
@@ -31,4 +33,20 @@ public interface ActivitySportsApplyUserService extends BaseService<ActivityScho
List<NutMap> getUserUnion(String query);
/**
* 下载报名人员导入模板。
*
* @param response 响应流
*/
void downloadImportTemplate(HttpServletResponse response);
/**
* 导入报名人员并返回错误明细,全部成功时返回 null。
*
* @param file 导入文件
* @param activityId 活动ID
* @return 导入错误统计
*/
NutMap importUserActivitys(TempFile file, String activityId);
}
@@ -1,22 +1,33 @@
package com.budwk.app.zhgh.activity.sports.service.impl;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.activity.basic.models.ActivityEvent;
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;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchool;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolApply;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolEvent;
import com.budwk.app.zhgh.activity.sports.models.ActivitySchoolTeam;
import com.budwk.app.zhgh.activity.sports.param.ActivitySportsApplyUserPageParam;
import com.budwk.app.zhgh.activity.sports.service.ActivitySportsApplyUserService;
import com.budwk.app.zhgh.activity.sports.template.ActivitySportsApplyImportTemp;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
@@ -31,7 +42,9 @@ import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.upload.TempFile;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -487,4 +500,224 @@ public class ActivitySportsApplyUserServiceImpl extends BaseServiceImpl<Activity
sql.setCondition(cnd);
return listMap(sql);
}
@Override
public void downloadImportTemplate(HttpServletResponse response) {
try {
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, ActivitySportsApplyImportTemp.class, new ArrayList<>());
CommonDownloadUtil.download("报名人员导入模板.xlsx", workbook, response);
} catch (Exception e) {
throw new BaseException("下载导入模板失败");
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public NutMap importUserActivitys(TempFile file, String activityId) {
if (file == null) {
throw new BaseException("请选择导入文件");
}
if (StrUtil.isBlank(activityId)) {
throw new BaseException("请先选择活动名称");
}
ActivitySchool activitySchool = dao().fetch(ActivitySchool.class, activityId);
if (activitySchool == null) {
throw new BaseException("活动不存在");
}
try {
List<ActivitySportsApplyImportTemp> importList = ExcelImportUtil.importExcel(file.getFile(), ActivitySportsApplyImportTemp.class, new ImportParams());
if (Lang.isEmpty(importList)) {
return null;
}
List<ActivitySportsApplyImportTemp> errorList = new ArrayList<>();
int totalCount = 0;
int successCount = 0;
String currentUnionId = getUnionId();
for (ActivitySportsApplyImportTemp importTemp : importList) {
totalCount++;
String loginName = normalizeLoginName(importTemp.getLoginName());
if (StrUtil.isBlank(loginName)) {
importTemp.setNotes("工号不能为空");
errorList.add(importTemp);
continue;
}
Sys_user sysUser = dao().fetch(Sys_user.class, Cnd.where("loginname", "=", loginName));
if (sysUser == null) {
importTemp.setNotes("此用户不存在,请检查工号");
errorList.add(importTemp);
continue;
}
ActivityBasicUnit basicUnit = dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", sysUser.getUnitId()));
if (basicUnit == null) {
importTemp.setNotes("请检查该人员活动单位是否配置正确");
errorList.add(importTemp);
continue;
}
ActivityBasicUnion basicUnion = dao().fetch(ActivityBasicUnion.class, Cnd.where("id", "=", basicUnit.getUnionId()));
if (basicUnion == null) {
importTemp.setNotes("请检查该人员所属活动工会是否配置正确");
errorList.add(importTemp);
continue;
}
if (!hasImportAuth() && !basicUnion.getId().equals(currentUnionId)) {
importTemp.setNotes("此用户不是当前工会人员");
errorList.add(importTemp);
continue;
}
String identityName = normalizeIdentityName(importTemp.getIdentity());
String projectCode = normalizeText(importTemp.getProjectCode());
if (StrUtil.isBlank(projectCode) && StrUtil.isBlank(identityName)) {
importTemp.setNotes("项目编号和身份不能同时为空");
errorList.add(importTemp);
continue;
}
ActivitySchoolApply activitySchoolApply = buildBaseApply(activityId, sysUser, basicUnit, basicUnion);
if (StrUtil.isNotBlank(identityName)) {
fillIdentityInfo(activitySchoolApply, identityName);
}
if (StrUtil.isBlank(projectCode) && StrUtil.isNotBlank(identityName)) {
replaceUnionRoleUser(activitySchoolApply, identityName);
insert(activitySchoolApply);
successCount++;
continue;
}
ActivityEvent activityEvent = dao().fetch(ActivityEvent.class, Cnd.where("projectCode", "=", projectCode));
if (activityEvent == null) {
importTemp.setNotes("项目编号不存在,请检查后重试");
errorList.add(importTemp);
continue;
}
ActivitySchoolEvent schoolEvent = dao().fetch(ActivitySchoolEvent.class, Cnd.where("activityId", "=", activityId).and("eventId", "=", activityEvent.getId()));
if (schoolEvent == null) {
importTemp.setNotes("该项目不属于当前活动,请检查项目编号");
errorList.add(importTemp);
continue;
}
activitySchoolApply.setEventId(schoolEvent.getEventId());
activitySchoolApply.setAwardsMode(activityEvent.getProjectType());
if ("2".equals(activityEvent.getProjectType())) {
ActivitySchoolTeam schoolTeam = dao().fetch(ActivitySchoolTeam.class, Cnd.where("activityId", "=", activityId).and("eventId", "=", schoolEvent.getEventId()).asc("location"));
if (schoolTeam == null) {
importTemp.setNotes("团体项目未配置队伍信息,请先维护小队");
errorList.add(importTemp);
continue;
}
activitySchoolApply.setTeamId(schoolTeam.getId());
activitySchoolApply.setTeam(schoolTeam.getName());
}
// 兼容参考项目导入逻辑:带项目编号的记录按运动员处理。
activitySchoolApply.setIdentity(List.of("1"));
clear(Cnd.where("activityId", "=", activityId).and("userId", "=", sysUser.getId()).and("eventId", "=", schoolEvent.getEventId()));
insert(activitySchoolApply);
successCount++;
}
if (Lang.isNotEmpty(errorList)) {
NutMap result = NutMap.NEW();
result.setv("totalCount", totalCount);
result.setv("successCount", successCount);
result.setv("errorCount", errorList.size());
result.setv("errorList", errorList.stream().map(v -> NutMap.NEW()
.addv("工会", normalizeText(v.getUnionName()))
.addv("工号", normalizeText(v.getLoginName()))
.addv("姓名", normalizeText(v.getUserName()))
.addv("性别", normalizeText(v.getSex()))
.addv("项目编号", normalizeText(v.getProjectCode()))
.addv("身份", normalizeText(v.getIdentity()))
.addv("错误信息", normalizeText(v.getNotes()))).collect(Collectors.toList()));
return result;
}
return null;
} catch (Exception e) {
throw new BaseException("导入报名人员失败:" + e.getMessage());
}
}
/**
* 导入记录统一补齐报名基础字段,避免多入口写入时字段不一致。
*/
private ActivitySchoolApply buildBaseApply(String activityId, Sys_user sysUser, ActivityBasicUnit basicUnit, ActivityBasicUnion basicUnion) {
ActivitySchoolApply activitySchoolApply = new ActivitySchoolApply();
activitySchoolApply.setActivityId(activityId);
activitySchoolApply.setUnitId(basicUnit.getId());
activitySchoolApply.setUnionId(basicUnion.getId());
activitySchoolApply.setApplyUser(SecurityUtil.getUserId());
activitySchoolApply.setUserId(sysUser.getId());
activitySchoolApply.setLoginname(sysUser.getLoginname());
activitySchoolApply.setUsername(sysUser.getUsername());
activitySchoolApply.setSex(sysUser.getSex());
activitySchoolApply.setMobile(sysUser.getMobile());
activitySchoolApply.setProfessionalLevel(sysUser.getProfessionalLevel());
activitySchoolApply.setBirthday(sysUser.getBirthday() == null ? null : DateUtil.formatDate(sysUser.getBirthday()));
activitySchoolApply.setApplyDate(DateUtil.now());
activitySchoolApply.setStatus(2);
activitySchoolApply.setUnitname(basicUnit.getName());
activitySchoolApply.setActivityUnionId(basicUnion.getId());
activitySchoolApply.setActivityUnionName(basicUnion.getName());
return activitySchoolApply;
}
/**
* 角色类导入只保留当前活动当前工会当前人员的一条对应身份记录,防止重复导入产生脏数据。
*/
private void replaceUnionRoleUser(ActivitySchoolApply activitySchoolApply, String identityName) {
Cnd cnd = Cnd.where("activityId", "=", activitySchoolApply.getActivityId())
.and("activityUnionId", "=", activitySchoolApply.getActivityUnionId())
.and("userId", "=", activitySchoolApply.getUserId());
if ("教练".equals(identityName)) {
cnd.and("unionCoach", "=", true);
} else if ("领队".equals(identityName)) {
cnd.and("unionLeader", "=", true);
} else if ("团长".equals(identityName)) {
cnd.and("unionHead", "=", true);
} else if ("工作人员".equals(identityName)) {
cnd.and("unionStaff", "=", true);
}
clear(cnd);
}
/**
* 统一处理导入身份映射,保证页面展示、导出与后续查询读取到一致的身份字段。
*/
private void fillIdentityInfo(ActivitySchoolApply activitySchoolApply, String identityName) {
if ("教练".equals(identityName)) {
activitySchoolApply.setUnionCoach(true);
activitySchoolApply.setIdentity(List.of("2"));
} else if ("领队".equals(identityName)) {
activitySchoolApply.setUnionLeader(true);
activitySchoolApply.setIdentity(List.of("3"));
} else if ("团长".equals(identityName)) {
activitySchoolApply.setUnionHead(true);
activitySchoolApply.setIdentity(List.of("6"));
} else if ("工作人员".equals(identityName)) {
activitySchoolApply.setUnionStaff(true);
activitySchoolApply.setIdentity(List.of("7"));
}
}
private boolean hasImportAuth() {
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
}
private String normalizeLoginName(String loginName) {
return StrUtil.isBlank(loginName) ? "" : loginName.replaceAll("\\s+", "");
}
private String normalizeText(String text) {
return StrUtil.trimToEmpty(text);
}
private String normalizeIdentityName(String identity) {
String identityName = normalizeText(identity);
if ("运动员".equals(identityName)) {
return "";
}
return identityName;
}
}
@@ -0,0 +1,32 @@
package com.budwk.app.zhgh.activity.sports.template;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
/**
* 体育活动报名导入模板
*/
@Data
public class ActivitySportsApplyImportTemp {
@Excel(name = "工会", width = 20)
private String unionName;
@Excel(name = "工号", width = 20)
private String loginName;
@Excel(name = "姓名", width = 20)
private String userName;
@Excel(name = "性别", width = 12)
private String sex;
@Excel(name = "项目编号", width = 20)
private String projectCode;
@Excel(name = "身份", width = 20)
private String identity;
@Excel(name = "备注", width = 30)
private String notes;
}
@@ -9,6 +9,7 @@ import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
@@ -81,17 +82,20 @@ public class ActivityWorksCollectionManageController {
@At
@SaCheckPermission("activity.workscollection.manage")
@Aop(TransAop.READ_COMMITTED)
public Result insert(@Param("data") @Valid Activity_works_collection worksCollection) {
worksCollection.setIsSubmit(true);
dao.insertWith(worksCollection, "subjectTypes");
worksCollection.getSubjectTypes().forEach(item -> {
dao.insertLinks(item, "worksTypes");
});
syncHomeActivity(worksCollection);
return Result.success();
}
@At
@SaCheckPermission("activity.workscollection.manage")
@Aop(TransAop.READ_COMMITTED)
public Result save(@Param("data") @Valid Activity_works_collection worksCollection) {
worksCollection.setIsSubmit(false);
dao.insertWith(worksCollection, "subjectTypes");
@@ -122,6 +126,7 @@ public class ActivityWorksCollectionManageController {
Cnd.where(Activity_works_worksType::getId, "not in ", workIds)
.and(Activity_works_worksType::getSubjectId,"=",item.getId()));
});
syncHomeActivity(worksCollection);
return Result.success();
}
@@ -132,6 +137,7 @@ public class ActivityWorksCollectionManageController {
dao.delete(Activity_works_collection.class, id);
dao.clear(Activity_works_collection_upload.class, Cnd.where(Activity_works_collection_upload::getActivityId, "=", id));
dao.clear(Activity_works_subjectType.class, Cnd.where(Activity_works_subjectType::getActivityId, "=", id));
dao.delete(Sys_home_activity.class, id);
return Result.success();
}
@@ -148,11 +154,26 @@ public class ActivityWorksCollectionManageController {
@At
@SaCheckPermission("activity.workscollection.manage")
@Aop(TransAop.READ_COMMITTED)
public Result enableChange(@Valid String id, @Valid Boolean enable) {
dao.update(Activity_works_collection.class, Chain.make("enable", enable), Cnd.where("id", "=", id));
Activity_works_collection worksCollection = dao.fetch(Activity_works_collection.class, id);
worksCollection.setEnable(enable);
syncHomeActivity(worksCollection);
return Result.success();
}
/**
* 同步作品征集到首页活动表:开启的活动写入首页,关闭的活动移除首页入口。
*/
private void syncHomeActivity(Activity_works_collection worksCollection) {
if (Boolean.TRUE.equals(worksCollection.getEnable())) {
dao.insertOrUpdate(worksCollection.covertToSysHomeActivity());
} else {
dao.delete(Sys_home_activity.class, worksCollection.getId());
}
}
@At
@SaCheckPermission("activity.workscollection.manage")
public Result sendNotice(@Valid String id) {
@@ -11,6 +11,7 @@ import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection_upload;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter;
@@ -18,6 +19,7 @@ import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
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.mvc.annotation.At;
@@ -94,11 +96,17 @@ public class ActivityWorksCollectionUploadController {
*/
@At
@SaCheckPermission("activity.workscollection.upload")
@Aop(TransAop.READ_COMMITTED)
public Result insert(@Param("data") @Valid Activity_works_collection_upload upload) {
String activityId = upload.getActivityId();
Activity_works_collection activity = dao.fetch(Activity_works_collection.class, activityId);
if (activity.getEndDateTime().getTime() < System.currentTimeMillis()){
long now = System.currentTimeMillis();
// 作品上传只允许在活动开始时间和结束时间之间提交,防止活动未开始或已结束后继续上传。
if (activity.getStartDateTime().getTime() > now) {
return Result.error("活动未开始,不能上传!");
}
if (activity.getEndDateTime().getTime() < now){
return Result.error("活动已结束!");
}
if (activity.getActivityGroupId() != null) {
@@ -124,9 +132,15 @@ public class ActivityWorksCollectionUploadController {
@At
@SaCheckPermission("activity.workscollection.upload")
@Aop(TransAop.READ_COMMITTED)
public Result update(@Param("data") @Valid Activity_works_collection_upload upload) {
Activity_works_collection activity = dao.fetch(Activity_works_collection.class, upload.getActivityId());
if (activity.getEndDateTime().getTime() < System.currentTimeMillis()){
long now = System.currentTimeMillis();
// 编辑作品同样按活动时间窗口控制,避免过期或未开始活动被继续提交作品信息。
if (activity.getStartDateTime().getTime() > now) {
return Result.error("活动未开始,不能上传!");
}
if (activity.getEndDateTime().getTime() < now){
return Result.error("活动已结束!");
}
dao.updateIgnoreNull(upload);
@@ -155,12 +169,19 @@ public class ActivityWorksCollectionUploadController {
@At
@SaCheckPermission("activity.workscollection.upload")
/**
* 查询当前登录人有权限上传作品的活动主题。
*
* @param year 活动年度,前端传年度选择框的 yyyy 值,按活动开始时间所在年份过滤。
* @return Resultdata 为活动列表,仅返回 id、name,且保留已开启和面向对象权限过滤。
*/
@ApiOperation("获取有权限的活动列表")
@Ok("json:{ignoreNull:true}")
public Result listPerMissionActivity() {
public Result listPerMissionActivity(Long year) {
Dao extDao = Daos.ext(dao, FieldFilter.create(Activity_works_collection.class, "id|name|"));
Cnd cnd = Cnd.NEW();
cnd.and(Activity_works_collection::getEnable,"=",1);
cnd.andEX("YEAR(startDateTime)", "=", year);
cnd.desc(Activity_works_collection::getStartDateTime);
List<Activity_works_collection> list = extDao.query(Activity_works_collection.class, cnd);
list = list.stream().filter(item -> {
@@ -1,6 +1,8 @@
package com.budwk.app.zhgh.activity.workscollection.models;
import com.budwk.app.base.model.BaseModel;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.services.SysHomeConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
@@ -17,7 +19,7 @@ import java.util.List;
@Table("activity_works_collection")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("作品征集活动")
public class Activity_works_collection extends BaseModel {
public class Activity_works_collection extends BaseModel implements SysHomeConvert {
@Name
@Comment("ID")
@@ -123,4 +125,21 @@ public class Activity_works_collection extends BaseModel {
@Many(field = "activityId")
private List<Activity_works_collection_upload> uploads;
@Override
public Sys_home_activity covertToSysHomeActivity() {
Sys_home_activity sysHomeActivity = new Sys_home_activity();
sysHomeActivity.setId(this.getId());
sysHomeActivity.setName(this.getName());
sysHomeActivity.setCover(this.getCover());
sysHomeActivity.setContent(this.getContent());
sysHomeActivity.setUrl("/platform/activity/worksCollection/upload");
sysHomeActivity.setH5Url("/platform/activity/worksCollection/upload/h5");
sysHomeActivity.setStartDate(this.getStartDateTime());
sysHomeActivity.setEndDate(this.getEndDateTime());
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(Boolean.TRUE.equals(this.getEnable()));
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeActivity;
}
}
@@ -68,7 +68,7 @@ public class ClubUserJoinApplyController {
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@At("")
@SaCheckPermission("club.join.apply")
@Ok("beetl:/platform/zhgh/club/join/apply/index.html")
public void index() {
@@ -128,6 +128,12 @@ public class ClubUserJoinApplyController {
@SLog(tag = "社团管理系统-申请入/退会", msg = "申请社团入/退会")
public Result submit(@Param("data") ClubUserApply clubUserApply,
@Param("mode") Boolean mode) {
if (Boolean.TRUE.equals(mode)) {
String qualificationMsg = clubUserJoinService.checkJoinQualification(SecurityUtil.getUserId());
if (StrUtil.isNotBlank(qualificationMsg)) {
return Result.error(99, qualificationMsg);
}
}
if(mode == false) {
clubUserApply = clubUserJoinService.buildExitApply(clubUserApply.getClubId(), SecurityUtil.getUserId());
if (ObjectUtil.isEmpty(clubUserApply)) {
@@ -192,6 +198,10 @@ public class ClubUserJoinApplyController {
@ApiOperation("校验是否申请过社团")
@SaCheckPermission(value = {"club.join.apply", "h5.club.join.apply"}, mode = SaMode.OR)
public Result checkApplyClub(@Valid String clubId) {
String qualificationMsg = clubUserJoinService.checkJoinQualification(SecurityUtil.getUserId());
if (StrUtil.isNotBlank(qualificationMsg)) {
return Result.error(99, qualificationMsg);
}
View_user user = dao.fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
if (user.getMember() != 1) {
return Result.error(99, "抱歉,您不满足入会的条件!");
@@ -48,7 +48,7 @@ public class ClubUserJoinApprovalController {
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@At("")
@SaCheckPermission("club.join.clubApproval")
@Ok("beetl:/platform/zhgh/club/join/clubApproval/index.html")
public void index() {}
@@ -45,7 +45,7 @@ public class ClubUserJoinMineController {
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@At("")
@SaCheckPermission("club.join.mine")
@Ok("beetl:/platform/zhgh/club/join/mine/index.html")
public void index() {}
@@ -57,7 +57,7 @@ public class ClubCommonController {
@SaCheckLogin
public Result listClubByRole() {
Cnd cnd = Cnd.NEW();
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name(), RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())) {
// 报销申请只允许选择当前用户已加入的协会,避免看到未加入的协会数据。
List<ClubUser> clubUsers = sysClubService.dao().query(ClubUser.class, Cnd.where("userId", "=", SecurityUtil.getUserId()));
List<String> myClubId = clubUsers.stream().map(ClubUser::getClubId).distinct().toList();
@@ -78,4 +78,12 @@ public class ClubCommonController {
return Result.success(list);
}
@At
@SaCheckLogin
public Result listManageClubByRole() {
// 活动人员范围设置只允许按“可管理的社团”选择,避免把仅作为成员加入的社团也展示出来。
List<SysClub> clubList = sysClubService.getMyManageClub();
return Result.success(clubList);
}
}
@@ -25,4 +25,12 @@ public interface ClubUserJoinService extends BaseService<ClubUserApply> {
* @return 入会时间
*/
java.util.Date resolveJoinTime(String clubId, String userId);
/**
* 校验当前用户是否满足申请加入社团的资格。
*
* @param userId 用户ID
* @return 校验不通过时返回提示语,通过时返回null
*/
String checkJoinQualification(String userId);
}
@@ -1,6 +1,9 @@
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 cn.hutool.core.util.StrUtil;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.ClubUserApply;
@@ -15,10 +18,37 @@ import java.util.List;
@IocBean(args = {"refer:dao"})
public class ClubUserJoinServiceImpl extends BaseServiceImpl<ClubUserApply> implements ClubUserJoinService {
private static final List<String> ALLOW_JOIN_PERSON_TYPES = List.of(
"教职工",
"未起薪人员",
"教职工新入会",
"教职工其他",
"劳动服务公司",
"后勤聘用人员",
"退休人员[一]",
"退休人员[二]",
"校聘劳务派遣全额"
);
public ClubUserJoinServiceImpl(Dao dao) {
super(dao);
}
@Override
public String checkJoinQualification(String userId) {
View_user user = dao().fetch(View_user.class, Cnd.where("id", "=", userId));
if (user == null) {
return "抱歉,您不满足入会的条件!";
}
if (user.getMember() == null || user.getMember() != 1) {
return "抱歉,您不满足入会的条件!";
}
if (StrUtil.isBlank(user.getPersonType()) || !ALLOW_JOIN_PERSON_TYPES.contains(user.getPersonType())) {
return "抱歉,您不满足入会的条件!";
}
return null;
}
@Override
public ClubUserApply buildExitApply(String clubId, String userId) {
// 优先复用最近一次申请记录中的补充信息,避免用户重复填写基础资料。
@@ -275,7 +275,14 @@ public class SysClubServiceImpl extends BaseServiceImpl<SysClub> implements SysC
}
cnd.andEX("year(info.createTime)", "=", pageForm.getYear());
cnd.and("info.userId", "=", SecurityUtil.getUserId());
cnd.desc("createTime");
// 我的注册列表默认按社团编码升序展示,点击表头时按用户选择的方向排序。
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.asc("info.clubCode");
} else if ("clubCode".equals(pageForm.getPageOrderName())) {
cnd.orderBy("info.clubCode", PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("info.id");
sql.setCondition(cnd);
return listPageVO(pageForm, sql, ClubRegisterPageVo.class);
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.dayofficework.message.strategy.impl;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.sms.SmsService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.dayofficework.message.enums.GlobalMessageChannel;
import com.budwk.app.zhgh.dayofficework.message.strategy.GlobalMessageSendStrategy;
@@ -21,6 +22,8 @@ public class SmsMessageSendStrategy implements GlobalMessageSendStrategy {
@Inject
private Dao dao;
@Inject
private SmsService smsService;
@Override
public GlobalMessageChannel getChannel() {
@@ -70,29 +73,24 @@ public class SmsMessageSendStrategy implements GlobalMessageSendStrategy {
}
/**
* 发送短信消息
* 发送短信消息
* 接收人由全局消息服务传入的用户ID或登录名查询得到,发送时使用用户工号对接通讯平台;
* 返回结果由 SmsService 内部解析并记录,当前策略只负责逐个用户触发发送并记录异常。
*/
private void sendSmsMessage(String title, String content, Integer type, List<Sys_user> users, JSONObject config) {
for (Sys_user user : users) {
try {
String mobile = user.getMobile();
if (mobile == null || mobile.trim().isEmpty()) {
log.warn("用户{}没有手机号,跳过短信发送", user.getUsername());
String loginName = user.getLoginname();
if (loginName == null || loginName.trim().isEmpty()) {
log.warn("用户{}没有号,跳过短信发送", user.getUsername());
continue;
}
// 构建短信内容
String smsContent = buildSmsContent(title, content, type);
// 执行发送
boolean success = true;
if (success) {
log.info("短信发送成功:用户{},手机号:{}", user.getUsername(), mobile);
} else {
log.warn("短信发送失败:用户{},手机号:{}", user.getUsername(), mobile);
}
smsService.sendSmsByAccount(loginName, smsContent);
log.info("短信发送完成:用户{},工号:{}", user.getUsername(), loginName);
} catch (Exception e) {
log.error("发送短信给用户{}时出错:{}", user.getUsername(), e.getMessage(), e);
}
@@ -0,0 +1,113 @@
package com.budwk.app.zhgh.dayofficework.message.strategy.impl;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.sms.SmsService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.dayofficework.message.enums.GlobalMessageChannel;
import com.budwk.app.zhgh.dayofficework.message.strategy.GlobalMessageSendStrategy;
import lombok.extern.slf4j.Slf4j;
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 java.util.List;
/**
* 微信发送策略
*/
@IocBean
@Slf4j
public class WechatMessageSendStrategy implements GlobalMessageSendStrategy {
@Inject
private Dao dao;
@Inject
private SmsService smsService;
@Override
public GlobalMessageChannel getChannel() {
return GlobalMessageChannel.WECHAT;
}
@Override
public boolean supportAsync() {
return true;
}
@Override
public void send(String title, String content, Integer type, List<String> receiverIds, JSONObject config) {
try {
log.info("开始发送微信消息,标题:{},接收人数量:{}", title, receiverIds.size());
// receiverIds 为 sys_user.id,按用户ID查询后使用工号对接通讯平台微信文本消息接口。
List<Sys_user> users = dao.query(Sys_user.class, Cnd.where(Sys_user::getId, "in", receiverIds));
sendWechatMessage(title, content, type, users, config);
log.info("微信消息发送完成,标题:{},接收人数量:{}", title, users.size());
} catch (Exception e) {
log.error("微信消息发送失败,标题:{},错误:{}", title, e.getMessage(), e);
throw new RuntimeException("微信消息发送失败:" + e.getMessage(), e);
}
}
@Override
public void sendByLoginName(String title, String content, Integer type, List<String> receiverLoginNames, JSONObject config) {
try {
log.info("开始发送微信消息(按登录名),标题:{},接收人数量:{}", title, receiverLoginNames.size());
// receiverLoginNames 为 sys_user.loginname,查询用户后仍使用工号作为通讯平台接收人标识。
List<Sys_user> users = dao.query(Sys_user.class, Cnd.where(Sys_user::getLoginname, "in", receiverLoginNames));
sendWechatMessage(title, content, type, users, config);
log.info("微信消息发送完成(按登录名),标题:{},接收人数量:{}", title, users.size());
} catch (Exception e) {
log.error("微信消息发送失败(按登录名),标题:{},错误:{}", title, e.getMessage(), e);
throw new RuntimeException("微信消息发送失败:" + e.getMessage(), e);
}
}
/**
* 发送微信文本消息。
* 接收人来自全局消息服务传入的用户ID或登录名,方法内部转换为 Sys_user 后取工号发送;
* SmsService 返回 MsgPlatformResponse,当前策略按单人发送记录日志,单个用户失败不影响其他接收人。
*/
private void sendWechatMessage(String title, String content, Integer type, List<Sys_user> users, JSONObject config) {
for (Sys_user user : users) {
try {
String loginName = user.getLoginname();
if (loginName == null || loginName.trim().isEmpty()) {
log.warn("用户{}没有工号,跳过微信发送", user.getUsername());
continue;
}
String wechatContent = buildWechatContent(title, content, type);
smsService.sendWechatTextByAccountOrMobile(loginName, wechatContent);
log.info("微信发送完成:用户{},工号:{}", user.getUsername(), loginName);
} catch (Exception e) {
log.error("发送微信给用户{}时出错:{}", user.getUsername(), e.getMessage(), e);
}
}
}
/**
* 构建微信文本内容。
* type=2 表示待办消息,其余类型按系统通知处理,内容格式保持与短信策略一致。
*/
private String buildWechatContent(String title, String content, Integer type) {
StringBuilder sb = new StringBuilder();
if (type != null && type == 2) {
sb.append("【待办通知】");
} else {
sb.append("【系统通知】");
}
sb.append(title);
if (content != null && !content.trim().isEmpty()) {
sb.append("").append(content);
}
return sb.toString();
}
}
@@ -457,9 +457,13 @@ public class SiteCugApplyController {
return result;
}
/**
* 当预约日期就是今天时,已经过了开始时间的时间块直接标记为不可预约,
* 这样全天候和分段预约在前端展示时都会统一变成禁用状态。
*/
private NutMap buildAvailabilityBlock(String day, int startMinute, int endMinute, List<int[]> reservedRanges, List<int[]> disabledRanges, boolean siteClosed, int groupIndex, String groupLabel) {
String status = "available";
if (siteClosed || intersectsAny(startMinute, endMinute, disabledRanges)) {
if (siteClosed || isExpiredAvailabilityBlock(day, startMinute) || intersectsAny(startMinute, endMinute, disabledRanges)) {
status = "closed";
} else if (intersectsAny(startMinute, endMinute, reservedRanges)) {
status = "reserved";
@@ -478,6 +482,19 @@ public class SiteCugApplyController {
.addv("endDateTime", day + " " + formatMinutes(endMinute) + ":00");
}
/**
* 仅在当天按“时间块开始时间是否已经过去”判断是否失效,
* 避免用户继续选中已经开始或已经结束的预约时段。
*/
private boolean isExpiredAvailabilityBlock(String day, int startMinute) {
String today = DateUtil.today();
if (!StrUtil.equals(today, day)) {
return false;
}
DateTime blockStart = DateUtil.parseDateTime(day + " " + formatMinutes(startMinute) + ":00");
return !blockStart.isAfter(DateUtil.date());
}
private boolean isSiteClosed(SiteCugInfo siteInfo, String day, Set<String> holidaySet) {
if (isWeekend(day)) {
return true;
@@ -113,22 +113,23 @@ public class UnionReimburseApplyController {
return Result.success();
}
// @At
// @ApiOperation("重新提交申请")
// @Aop(TransAop.READ_COMMITTED)
// @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
// public Result submitAgain(@Param("data") UnionReimburse unionReimburse, @Param("taskId") Long taskId) {
// if (StrUtil.isBlank(unionReimburse.getId())) unionReimburse.setCreateTime(new Date());
//
// dao.insertOrUpdate(unionReimburse);
//
// Dict dict = Dict.create();
// dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
// dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
// dict.set("userId", unionReimburse.getCondolenceUserId());
// flowCommonService.executeTask(dict);
// return Result.success();
// }
@At
@ApiOperation("重新提交申请")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result submitAgain(@Param("data") UnionReimburse unionReimburse, @Param("taskId") Long taskId) {
// 退回重提也必须复用当前提交保存逻辑,避免绕过发票明细落库和提交校验。
Result result = unionReimburseService.saveApply(unionReimburse, 2);
if (result.getCode() != 0) {
return result;
}
Dict dict = Dict.create();
dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
flowCommonService.executeTask(dict);
return Result.success();
}
@At
@SaCheckLogin
@@ -179,6 +180,14 @@ public class UnionReimburseApplyController {
return Result.success(pagination.getList());
}
@At
@ApiOperation("查询付款人")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
@SLog(type = "unionReimburse", tag = "查询付款人", msg = "按经费来源查询付款人")
public Result listPayerUser(String keyword, String reimburseFundSource, String clubId) {
return Result.success(unionReimburseService.listPayerUser(keyword, reimburseFundSource, clubId));
}
@At
@ApiOperation("查询职工慰问类型")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
@@ -267,6 +276,6 @@ public class UnionReimburseApplyController {
@ApiOperation("统一查询经费余额")
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result getBudgetBalance(String reimburseFundSource, String clubId) {
return unionReimburseService.getBudgetBalance(reimburseFundSource, clubId);
return unionReimburseService.getBudgetBalance(reimburseFundSource, clubId, null);
}
}
@@ -7,6 +7,7 @@ import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
@@ -26,12 +27,14 @@ 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.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;
import org.nutz.lang.util.NutMap;
@@ -117,12 +120,10 @@ public class UnionReimburseCollectController {
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.collect", "h5.unionReimburse.collect"}, mode = SaMode.OR)
public Result updateActuallyAmount(String id,Double realMoney) {
UnionReimburse unionReimburse = unionReimburseService.fetch(id);
unionReimburse.setRealMoney(realMoney);
unionReimburseService.updateIgnoreNull(unionReimburse);
return Result.success();
return unionReimburseService.updateActuallyAmount(id, realMoney);
}
@At
@@ -237,6 +238,9 @@ public class UnionReimburseCollectController {
item.setReimburseFundSource(typeDict.getName());
}
}
if (StrUtil.isNotBlank(item.getCreateTime())) {
item.setCreateTime(DateUtil.format(DateUtil.parse(item.getCreateTime()), "yyyy-MM-dd HH:mm:ss"));
}
// 拼接备注字段(仿照系统代码逻辑,使用activityName和condolenceName字段)
if (cn.hutool.core.util.StrUtil.isNotBlank(item.getActivityName())) {
@@ -189,7 +189,7 @@ public class UnionReimburseMineController {
@SaCheckPermission(value = {"unionReimburse.mine", "h5.unionReimburse.mine"}, mode = SaMode.OR)
@SLog( tag = "删除工会报销", msg = "删除工会报销")
public Result delete(@Param("id") String id) {
unionReimburseService.delete(id);
unionReimburseService.deleteApply(id);
return Result.success();
}
@@ -227,7 +227,7 @@ public class UnionReimburseMineController {
tempFile.deleteOnExit();
docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd"));
docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
docData.put("unitName", unionReimburse.getUnitName());
docData.put("userName", unionReimburse.getUserName());
docData.put("loginName", unionReimburse.getLoginName());
@@ -309,7 +309,7 @@ public class UnionReimburseMineController {
.create();
docData.put("qrcode", pictureRenderData);
docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd"));
docData.put("createTime", DateUtil.format(unionReimburse.getCreateTime(), "yyyy-MM-dd HH:mm:ss"));
docData.put("unitName", unionReimburse.getUnitName());
docData.put("userName", unionReimburse.getUserName());
docData.put("loginName", unionReimburse.getLoginName());
@@ -12,7 +12,6 @@ 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.Cnd;
import org.nutz.dao.Sqls;
@@ -32,7 +31,6 @@ import java.util.List;
@At("/platform/unionReimburse/review")
@Ok("json:full")
@Api("工会报销审核")
@Slf4j
public class UnionReimburseReviewController {
@Inject
@@ -110,19 +108,7 @@ public class UnionReimburseReviewController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.review", "h5.unionReimburse.review"}, mode = SaMode.OR)
@SLog(tag = "工会报销审核", msg = "一键审核工会报销")
public Result allReview() {
try {
List<UnionReimburse> reimbursements = unionReimburseService.query(Cnd.where("stateId", "=", 2));
for (UnionReimburse reimbursement : reimbursements) {
Result result = unionReimburseService.reviewApply(reimbursement.getId(), "通过", 3);
if (result.getCode() != 0) {
return result;
}
}
return Result.success("一键审核完成,共处理" + reimbursements.size() + "条记录");
} catch (Exception e) {
log.error("一键审核失败", e);
return Result.error("一键审核失败: " + e.getMessage());
}
public Result allReview(@Param("ids[]") String[] ids) {
return unionReimburseService.batchReviewApply(ids);
}
}
@@ -6,6 +6,7 @@ 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 org.nutz.lang.util.NutMap;
import java.util.List;
@@ -23,11 +24,26 @@ public interface UnionReimburseService extends BaseService<UnionReimburse> {
*/
UnionReimburse getApplyForm(String id);
/**
* 删除报销申请,并同步删除关联发票明细。
*/
void deleteApply(String id);
/**
* 查询付款人的历史收款信息。
*/
List<UnionReimburseBankHistoryVO> getUserBankHistory(String payer);
/**
* 查询付款人候选人;协会经费按所选协会过滤,只返回该协会成员。
*
* @param keyword 姓名或工号关键字
* @param reimburseFundSource 当前选择的经费来源
* @param clubId 协会经费对应的协会ID
* @return 可选付款人列表
*/
List<NutMap> listPayerUser(String keyword, String reimburseFundSource, String clubId);
/**
* 校验发票号码是否与历史报销记录重复。
*/
@@ -41,10 +57,23 @@ public interface UnionReimburseService extends BaseService<UnionReimburse> {
/**
* 查询报销可用经费余额,同时校验是否已分配以及协会归属权限。
*/
Result getBudgetBalance(String reimburseFundSource, String clubId);
Result getBudgetBalance(String reimburseFundSource, String clubId, String unionId);
/**
* 审核报销,审核通过时扣减对应预算并新增预算使用详情。
*/
Result reviewApply(String reimburseId, String reviewOpinion, int submitType);
/**
* 管理员修改实际报销金额,已报销成功的数据需要同步调整经费预算管理台账。
*/
Result updateActuallyAmount(String id, Double realMoney);
/**
* 批量审核选中的待审核报销申请。
*
* @param reimburseIds 前端表格勾选的报销申请ID数组,只处理状态为待审核确认的数据
* @return Result,成功时返回本次实际审核通过的记录数量,失败时返回具体错误原因
*/
Result batchReviewApply(String[] reimburseIds);
}
@@ -4,6 +4,7 @@ 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.page.Pagination;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_file;
@@ -122,7 +123,10 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
normalizeInvoiceDetails(unionReimburse);
if (needInvoiceDetails(unionReimburse)) {
fillInvoiceSummary(unionReimburse);
} else {
fillCondolenceInvoiceSummary(unionReimburse);
}
fillDefaultRealMoney(unionReimburse);
if (stateId == 2) {
Result validateResult = validateBeforeSubmit(unionReimburse);
if (validateResult != null) {
@@ -139,23 +143,29 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
}
if (oldRecord == null) {
unionReimburse.setCreateTime(DateUtil.date());
unionReimburse.setCreateTime(toSecondPrecision(DateUtil.date()));
unionReimburse.setDocumentNo(generateDocumentNo());
} else {
unionReimburse.setCreateTime(oldRecord.getCreateTime());
unionReimburse.setCreateTime(toSecondPrecision(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());
}
fillDefaultRealMoney(unionReimburse);
this.dao().insertOrUpdate(unionReimburse);
saveInvoiceDetails(unionReimburse.getId(), unionReimburse.getInvoiceDetails());
return Result.success(stateId == 2 ? "提交成功" : "保存成功");
}
/**
* 申请时间统一保存到秒,避免数据库和页面出现毫秒级时间。
*/
private Date toSecondPrecision(Date date) {
if (date == null) {
return null;
}
return new Date((date.getTime() / 1000) * 1000);
}
@Override
public UnionReimburse getApplyForm(String id) {
UnionReimburse unionReimburse = this.fetch(id);
@@ -170,6 +180,16 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
return unionReimburse;
}
@Override
public void deleteApply(String id) {
if (StrUtil.isBlank(id)) {
return;
}
// 删除申请主记录时同步清理发票明细,避免遗留无归属的发票关联数据。
this.dao().clear(UnionReimburseInvoiceDetail.class, Cnd.where("reimburseId", "=", id));
this.delete(id);
}
@Override
public List<UnionReimburseBankHistoryVO> getUserBankHistory(String payer) {
if (StrUtil.isBlank(payer)) {
@@ -199,6 +219,55 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
return this.listVO(sql, UnionReimburseBankHistoryVO.class);
}
/**
* 付款人查询要跟随经费来源;选择协会经费时,只允许从当前协会会员中选择付款人。
*/
@Override
public List<NutMap> listPayerUser(String keyword, String reimburseFundSource, String clubId) {
boolean clubFundSource = "UNION_REIMBURSE_FUND_SOURCE_3".equals(reimburseFundSource);
if (clubFundSource && StrUtil.isBlank(clubId)) {
return new ArrayList<>();
}
String clubJoinSql = clubFundSource ? "INNER JOIN club_user cu ON cu.userId = u.id" : "";
Sql sql = Sqls.create("""
SELECT
u.id,
u.username AS userName,
u.loginname AS loginName,
u.sex,
u.mobile,
u.technicalTitle,
IFNULL(u.unitname, '暂无') AS unitName,
u.unitid AS unitId,
u.unionid AS unionId,
u.unionname AS unionName,
DATE(u.birthday) AS birthday,
u.idCard,
u.unionCode
FROM
vw_user u
%s
$condition
""".formatted(clubJoinSql));
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup seg = new SqlExpressionGroup();
seg.or("u.loginname", "like", "%" + keyword + "%");
seg.or("u.username", "like", "%" + keyword + "%");
cnd.and(seg);
}
cnd.and("u.id", "!=", SecurityUtil.getUserId());
if (clubFundSource) {
cnd.and("cu.clubId", "=", clubId);
} else if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
cnd.and("u.unionid", "=", SecurityUtil.getUnionId());
}
cnd.groupBy("u.id");
sql.setCondition(cnd);
Pagination pagination = this.listPageMap(1, 50, sql);
return pagination.getList();
}
@Override
public Result checkInvoiceDuplicate(String invoiceNo, String reimburseId) {
String currentInvoiceNo = StrUtil.blankToDefault(StrUtil.trim(invoiceNo), "");
@@ -244,7 +313,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
* 按经费来源统一查询当前可用余额,未分配或权限不匹配时直接拦截。
*/
@Override
public Result getBudgetBalance(String reimburseFundSource, String clubId) {
public Result getBudgetBalance(String reimburseFundSource, String clubId, String unionId) {
if (StrUtil.isBlank(reimburseFundSource)) {
return Result.error("请选择经费来源");
}
@@ -257,8 +326,9 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
return Result.success(formatMoney(outlayManageSchool.getTotalQuota().subtract(outlayManageSchool.getUsedQuota())));
}
if ("UNION_REIMBURSE_FUND_SOURCE_2".equals(reimburseFundSource)) {
String queryUnionId = StrUtil.blankToDefault(unionId, SecurityUtil.getUnionId());
OutlayManageUnion outlayManageUnion = dao().fetch(OutlayManageUnion.class,
Cnd.where("unionId", "=", SecurityUtil.getUnionId()).and("year", "=", DateUtil.thisYear()));
Cnd.where("unionId", "=", queryUnionId).and("year", "=", DateUtil.thisYear()));
if (outlayManageUnion == null) {
return Result.error("分工会经费未分配");
}
@@ -315,6 +385,58 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
return Result.success();
}
/**
* 批量审核前只查询前端勾选且仍处于待审核确认状态的数据,避免误处理未勾选或状态已变化的申请。
*/
@Override
public Result batchReviewApply(String[] reimburseIds) {
if (reimburseIds == null || reimburseIds.length == 0) {
return Result.error("请选择需要审核的报销申请");
}
List<UnionReimburse> reimbursements = this.query(Cnd.where("id", "in", reimburseIds).and("stateId", "=", 2));
if (reimbursements.isEmpty()) {
return Result.error("所选记录中没有待审核的数据");
}
for (UnionReimburse reimbursement : reimbursements) {
Result result = this.reviewApply(reimbursement.getId(), "通过", 3);
if (result.getCode() != 0) {
return result;
}
}
return Result.success("一键审核完成,共处理" + reimbursements.size() + "条记录");
}
/**
* 管理员修改实际金额时,报销成功的数据已经写入预算台账,需要按差额同步已使用额度和使用明细。
*/
@Override
public Result updateActuallyAmount(String id, Double realMoney) {
if (StrUtil.isBlank(id)) {
return Result.error("报销记录ID不能为空");
}
BigDecimal newMoney = normalizeActualMoney(realMoney);
if (newMoney == null || newMoney.compareTo(BigDecimal.ZERO) <= 0) {
return Result.error("实际报销金额必须大于0");
}
UnionReimburse dbRecord = this.fetch(id);
if (dbRecord == null) {
return Result.error("未找到对应的报销记录");
}
if (Integer.valueOf(3).equals(dbRecord.getStateId())) {
OutlayUseDetail oldDetail = dao().fetch(OutlayUseDetail.class, Cnd.where("outlayReimburseId", "=", dbRecord.getId()));
BigDecimal oldMoney = oldDetail != null && oldDetail.getAdjustMoney() != null
? oldDetail.getAdjustMoney()
: getApplyMoney(dbRecord);
Result syncResult = syncApprovedActualAmount(dbRecord, oldDetail, oldMoney, newMoney);
if (syncResult != null && syncResult.getCode() != 0) {
return syncResult;
}
}
dbRecord.setRealMoney(newMoney.doubleValue());
this.updateIgnoreNull(dbRecord);
return Result.success();
}
private void normalizeInvoiceDetails(UnionReimburse unionReimburse) {
if (unionReimburse.getInvoiceDetails() == null) {
unionReimburse.setInvoiceDetails(new ArrayList<>());
@@ -360,6 +482,36 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
unionReimburse.setInvoiceNumber(String.valueOf(invoiceFileCount));
}
/**
* 慰问类报销允许不填发票;有发票时按发票汇总金额申请,没有发票时按慰问金额申请。
*/
private void fillCondolenceInvoiceSummary(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++;
}
}
Double applyMoney = invoiceFileCount > 0
? BigDecimal.valueOf(totalMoney).setScale(2, RoundingMode.HALF_UP).doubleValue()
: unionReimburse.getCondolenceMoney();
unionReimburse.setMoney(applyMoney);
unionReimburse.setInvoiceNumber(String.valueOf(invoiceFileCount));
}
/**
* 默认实际金额跟随申请金额,后续管理员可在汇总页按最终报销金额调整。
*/
private void fillDefaultRealMoney(UnionReimburse unionReimburse) {
unionReimburse.setRealMoney(getDeclaredApplyMoney(unionReimburse).doubleValue());
}
/**
* 提交前补一层后端校验,防止前端绕过校验直接提交。
*/
@@ -379,11 +531,8 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
// if (StrUtil.isBlank(unionReimburse.getBankOfDeposit())) {
// return Result.error("请填写开户行");
// }
if (StrUtil.isBlank(unionReimburse.getPaymentNotes())) {
return Result.error("请填写支付内容");
}
if (!needInvoiceDetails(unionReimburse)) {
return null;
return validateInvoiceDetails(unionReimburse, false);
}
if (StrUtil.isBlank(unionReimburse.getActivityName())) {
return Result.error("请填写活动名称");
@@ -397,7 +546,17 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
if (Lang.isEmpty(unionReimburse.getFiles())) {
return Result.error("请上传附件");
}
return validateInvoiceDetails(unionReimburse, true);
}
/**
* 发票明细提交校验:活动类必须至少一条,慰问类允许为空但填写后必须完整。
*/
private Result validateInvoiceDetails(UnionReimburse unionReimburse, boolean required) {
if (Lang.isEmpty(unionReimburse.getInvoiceDetails())) {
if (!required) {
return null;
}
return Result.error("请至少维护一条发票明细");
}
for (int i = 0; i < unionReimburse.getInvoiceDetails().size(); i++) {
@@ -426,7 +585,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
* 提交和审核前统一校验预算是否已分配、余额是否充足。
*/
private Result validateFundSourceBeforeSubmit(UnionReimburse unionReimburse) {
Result balanceResult = getBudgetBalance(unionReimburse.getReimburseFundSource(), unionReimburse.getClubId());
Result balanceResult = getBudgetBalance(unionReimburse.getReimburseFundSource(), unionReimburse.getClubId(), unionReimburse.getUnionId());
if (balanceResult == null || balanceResult.getCode() != 0) {
return balanceResult == null ? Result.error("经费余额校验失败") : balanceResult;
}
@@ -493,6 +652,117 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
return Result.error("不支持的经费来源");
}
/**
* 已通过报销单修改实际金额时,用差额同步预算主表和使用明细,避免重复全量扣减。
*/
private Result syncApprovedActualAmount(UnionReimburse unionReimburse, OutlayUseDetail oldDetail,
BigDecimal oldMoney, BigDecimal newMoney) {
BigDecimal diffMoney = newMoney.subtract(safeMoney(oldMoney)).setScale(2, RoundingMode.HALF_UP);
String reimburseFundSource = unionReimburse.getReimburseFundSource();
if ("UNION_REIMBURSE_FUND_SOURCE_1".equals(reimburseFundSource)) {
OutlayManageSchool outlayManageSchool = dao().fetch(OutlayManageSchool.class,
Cnd.where("year", "=", DateUtil.thisYear()));
if (outlayManageSchool == null) {
return Result.error("校工会经费未分配");
}
Result quotaResult = updateSchoolUsedQuota(outlayManageSchool, diffMoney);
if (quotaResult.getCode() != 0) {
return quotaResult;
}
saveOrUpdateUseDetail(unionReimburse, oldDetail, outlayManageSchool.getId(), newMoney);
return Result.success();
}
if ("UNION_REIMBURSE_FUND_SOURCE_2".equals(reimburseFundSource)) {
OutlayManageUnion outlayManageUnion = dao().fetch(OutlayManageUnion.class,
Cnd.where("unionId", "=", unionReimburse.getUnionId()).and("year", "=", DateUtil.thisYear()));
if (outlayManageUnion == null) {
return Result.error("分工会经费未分配");
}
Result quotaResult = updateUnionUsedQuota(outlayManageUnion, diffMoney);
if (quotaResult.getCode() != 0) {
return quotaResult;
}
saveOrUpdateUseDetail(unionReimburse, oldDetail, outlayManageUnion.getId(), newMoney);
return Result.success();
}
if ("UNION_REIMBURSE_FUND_SOURCE_3".equals(reimburseFundSource)) {
OutlayManageClub outlayManageClub = dao().fetch(OutlayManageClub.class,
Cnd.where("clubId", "=", unionReimburse.getClubId()).and("year", "=", DateUtil.thisYear()));
if (outlayManageClub == null) {
return Result.error("该协会本年度未生成预算记录,请先在协会预算分配中补分配");
}
Result quotaResult = updateClubUsedQuota(outlayManageClub, diffMoney);
if (quotaResult.getCode() != 0) {
return quotaResult;
}
saveOrUpdateUseDetail(unionReimburse, oldDetail, outlayManageClub.getId(), newMoney);
return Result.success();
}
return Result.error("不支持的经费来源");
}
private Result updateSchoolUsedQuota(OutlayManageSchool outlayManageSchool, BigDecimal diffMoney) {
BigDecimal newUsedQuota = buildNewUsedQuota(outlayManageSchool.getTotalQuota(), outlayManageSchool.getUsedQuota(), diffMoney, "校工会经费");
if (newUsedQuota == null) {
return Result.error("校工会经费余额不足");
}
outlayManageSchool.setUsedQuota(newUsedQuota);
dao().updateIgnoreNull(outlayManageSchool);
return Result.success();
}
private Result updateUnionUsedQuota(OutlayManageUnion outlayManageUnion, BigDecimal diffMoney) {
BigDecimal newUsedQuota = buildNewUsedQuota(outlayManageUnion.getTotalQuota(), outlayManageUnion.getUsedQuota(), diffMoney, "分工会经费");
if (newUsedQuota == null) {
return Result.error("分工会经费余额不足");
}
outlayManageUnion.setUsedQuota(newUsedQuota);
dao().updateIgnoreNull(outlayManageUnion);
return Result.success();
}
private Result updateClubUsedQuota(OutlayManageClub outlayManageClub, BigDecimal diffMoney) {
BigDecimal newUsedQuota = buildNewUsedQuota(outlayManageClub.getTotalQuota(), outlayManageClub.getUsedQuota(), diffMoney, "协会经费");
if (newUsedQuota == null) {
return Result.error("协会经费余额不足");
}
outlayManageClub.setUsedQuota(newUsedQuota);
dao().updateIgnoreNull(outlayManageClub);
return Result.success();
}
/**
* 计算调整后的已使用额度,调增时不能超过总额度,调减时不能小于0。
*/
private BigDecimal buildNewUsedQuota(BigDecimal totalQuota, BigDecimal usedQuota, BigDecimal diffMoney, String outlayName) {
BigDecimal safeTotalQuota = safeMoney(totalQuota);
BigDecimal safeUsedQuota = safeMoney(usedQuota);
BigDecimal safeDiffMoney = safeMoney(diffMoney);
BigDecimal newUsedQuota = safeUsedQuota.add(safeDiffMoney).setScale(2, RoundingMode.HALF_UP);
if (safeDiffMoney.compareTo(BigDecimal.ZERO) == 0) {
return newUsedQuota;
}
if (newUsedQuota.compareTo(BigDecimal.ZERO) < 0) {
return null;
}
if (newUsedQuota.compareTo(safeTotalQuota) > 0) {
log.warn("{}实际金额调增后超出预算,总额度:{}, 已使用:{}, 调整差额:{}", outlayName, safeTotalQuota, safeUsedQuota, diffMoney);
return null;
}
return newUsedQuota;
}
private void saveOrUpdateUseDetail(UnionReimburse unionReimburse, OutlayUseDetail oldDetail,
String outlayManageId, BigDecimal newMoney) {
if (oldDetail == null) {
insertUseDetail(unionReimburse, outlayManageId, newMoney);
return;
}
oldDetail.setOutlayManageId(outlayManageId);
oldDetail.setAdjustMoney(newMoney);
dao().updateIgnoreNull(oldDetail);
}
/**
* 一张报销单只写入一条预算使用详情,避免重复通过或重复点击造成重复明细。
*/
@@ -516,11 +786,31 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
}
private BigDecimal getApplyMoney(UnionReimburse unionReimburse) {
Double money = "UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject())
? unionReimburse.getCondolenceMoney() : unionReimburse.getMoney();
if (unionReimburse.getRealMoney() != null) {
return BigDecimal.valueOf(unionReimburse.getRealMoney()).setScale(2, RoundingMode.HALF_UP);
}
return getDeclaredApplyMoney(unionReimburse);
}
private BigDecimal getDeclaredApplyMoney(UnionReimburse unionReimburse) {
Double money = unionReimburse.getMoney();
if (money == null && "UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject())) {
money = unionReimburse.getCondolenceMoney();
}
return BigDecimal.valueOf(money == null ? 0D : money).setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal normalizeActualMoney(Double realMoney) {
if (realMoney == null || realMoney.isNaN() || realMoney.isInfinite()) {
return null;
}
return realMoney == null ? null : BigDecimal.valueOf(realMoney).setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal safeMoney(BigDecimal money) {
return money == null ? BigDecimal.ZERO : money.setScale(2, RoundingMode.HALF_UP);
}
private String buildUseDetailProjectName(UnionReimburse unionReimburse) {
if (StrUtil.isNotBlank(unionReimburse.getActivityName())) {
return unionReimburse.getActivityName();
@@ -851,7 +1141,7 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
}
/**
* 当前仅活动类报销维护发票明细,慰问类仍按原有逻辑处理金额
* 非慰问类报销必须维护发票明细,慰问类发票明细为可选
*/
private boolean needInvoiceDetails(UnionReimburse unionReimburse) {
return unionReimburse != null && !"UNION_REIMBURSE_PROJECT_1".equals(unionReimburse.getReimburseProject());
@@ -59,7 +59,7 @@ public class ProposalDashboardController {
nodes.add(NutMap.NEW().addv("name", "提案总数").addv("id", "total").addv("type", "total").addv("count", 0));
// 节点
ProcessDefine define = processDefineService.getLastByName("JDHTA");
ProcessDefine define = processDefineService.getLastByName("JDHTA_NC");
ProcessModel processModel = processDefineService.processDefineToModel(define);
List<TaskModel> taskModels = processModel.getTasks();
@@ -141,7 +141,7 @@ public class ProposalQueryChartController {
FROM
wf_process_design
WHERE
NAME = 'JDHTA'
NAME = 'JDHTA_NC'
""");
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
@@ -120,6 +120,7 @@ public class ProposalSecondedController {
Cnd cnd = Cnd.NEW();
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
cnd.and("info.createUserId", "!=", SecurityUtil.getUserId());
if (approval) {
// 已附议页签只展示当前登录附议人已经处理过的记录
@@ -179,7 +179,7 @@ public class TeacherCongressDelegatePushController {
""");
sql.setParam("sessionId", sessionId);
Cnd cnd = Cnd.NEW();
// cnd.and("u.member", "=", 1);
cnd.and("u.member", "=", 1);
cnd.and("u.username", "is not", null);
cnd.andEX("u.id", "not in", userIds);
cnd.and("tcd.userId", "is", null);
@@ -12,6 +12,8 @@ import com.budwk.app.bpm.service.BpmService;
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.staffmanage.member.constant.MemberChangeOrigin;
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType;
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberChangePageForm;
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
@@ -22,6 +24,7 @@ import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
@@ -111,6 +114,12 @@ public class MemberChangeMineController {
cnd.and("info.userId", "=", SecurityUtil.getUserId());
}
}
// Filter manual input records created by member info input
cnd.and(new Static(
"(info.changeOrigin <> '" + MemberChangeOrigin.HAND_MOVEMENT.name()
+ "' OR info.changeOrigin IS NULL OR info.changeType <> '" + MemberChangeType.NEW.name()
+ "' OR info.changeType IS NULL)"
));
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("info.applyDateTime");
} else {
@@ -46,6 +46,8 @@ import org.nutz.lang.Lang;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@@ -298,6 +300,7 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
@Override
public void compareChangeInfoAndUpdateMember(MemberChangeRecord record) {
normalizeArrivalAtSchoolDate(record);
List<NutMap> changeList = getChangeInfos(record);
// 如果有工会关系人员这个user用来存储数据
@@ -503,6 +506,7 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
@Override
public void validateChangeAndSetBasicData(MemberChangeRecord record, MemberChangeOrigin origin) {
normalizeArrivalAtSchoolDate(record);
// 校验有没有发生变更
List<NutMap> changeInfos = getChangeInfos(record);
if (Lang.isEmpty(changeInfos)) {
@@ -521,6 +525,39 @@ public class MemberManageServiceImpl extends BaseServiceImpl<Sys_user> implement
}
/**
* 统一规范变更记录中的到校日期避免 Date 对象复制后以英文字符串形式参与比较或直接落库
*/
private void normalizeArrivalAtSchoolDate(MemberChangeRecord record) {
if (record == null || StrUtil.isBlank(record.getArrivalAtSchoolDate())) {
return;
}
String arrivalAtSchoolDate = StrUtil.trim(record.getArrivalAtSchoolDate());
try {
Date parsedDate = parseArrivalAtSchoolDate(arrivalAtSchoolDate);
record.setArrivalAtSchoolDate(DateUtil.format(parsedDate, "yyyy-MM-dd"));
} catch (Exception e) {
throw new BaseException("到校日期格式不正确,请使用yyyy-MM-dd格式");
}
}
/**
* 兼容页面手工变更时从视图对象复制出的英文日期字符串
*/
private Date parseArrivalAtSchoolDate(String arrivalAtSchoolDate) throws ParseException {
if (arrivalAtSchoolDate.matches("\\d{4}-\\d{2}-\\d{2}")) {
return DateUtil.parseDate(arrivalAtSchoolDate);
}
try {
return DateUtil.parse(arrivalAtSchoolDate);
} catch (Exception ignore) {
SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
format.setLenient(false);
return format.parse(arrivalAtSchoolDate);
}
}
/**
* 验证申请或变更是否正在进行
* @param userId 用户id
@@ -214,7 +214,7 @@ public class WelfareProject extends BaseModel implements SysHomeConvert {
sysHomeActivity.setH5Url("/platform/h5/welfare/userSelect/index?id=" + this.getId());
sysHomeActivity.setStartDate(this.getChoiceTimeStart());
sysHomeActivity.setEndDate(this.getChoiceTimeEnd());
sysHomeActivity.setAllowUserSql("select userId from welfare_list where projectId = '" + this.getId() + "' and userId = @userId");
sysHomeActivity.setAllowUserSql("select userId from welfare_list where projectId = (select id from welfare_project where id='" + this.getId() + "' and provideMode!=1) and userId = @userId ");
sysHomeActivity.setEnable(true);
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeActivity;