change
This commit is contained in:
@@ -211,6 +211,12 @@ public class Sys_user extends BaseModel implements Serializable {
|
||||
@DataCenterColumn(name = "教职工类别码", key = "RYLX", dict = "USER_PERSON_TYPE")
|
||||
private String personType;
|
||||
|
||||
@Column
|
||||
@Comment("待确认状态:0不用确认、1待确认、2已确认")
|
||||
@ColDefine(type = ColType.INT, width = 1)
|
||||
@Default("0")
|
||||
private Integer pendingConfirmStatus;
|
||||
|
||||
@Column
|
||||
@Comment("编制类别码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
|
||||
+4
-4
@@ -163,17 +163,17 @@ public class MeetingManageController {
|
||||
cnd.andEX("u.unitId", "=", unitId);
|
||||
cnd.and(Cnd.likeEX("u.sex", sex));
|
||||
if (StrUtil.isNotBlank(roleId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s')".formatted(roleId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and enable='1')".formatted(roleId)));
|
||||
}
|
||||
// 选择届次但未选择角色时,按届次筛选全部相关人员;同时选择角色时限定为同一角色关联记录。
|
||||
if (StrUtil.isBlank(roleId) && StrUtil.isNotBlank(teacherCongressSessionId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where tcSessionId = '%s')".formatted(teacherCongressSessionId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where enable='1' and tcSessionId = '%s')".formatted(teacherCongressSessionId)));
|
||||
}
|
||||
if (StrUtil.isNotBlank(roleId) && StrUtil.isNotBlank(teacherCongressSessionId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and tcSessionId = '%s')".formatted(roleId, teacherCongressSessionId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and enable='1' and tcSessionId = '%s')".formatted(roleId, teacherCongressSessionId)));
|
||||
}
|
||||
if (StrUtil.isNotBlank(roleId) && StrUtil.isNotBlank(workerCongressSessionId)) {
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and wcSessionId = '%s')".formatted(roleId, workerCongressSessionId)));
|
||||
cnd.and(new Static("u.id in (select userId from sys_user_role where roleId = '%s' and enable='1' and wcSessionId = '%s')".formatted(roleId, workerCongressSessionId)));
|
||||
}
|
||||
if (Strings.isNotBlank(pageForm.getSearchName()) && Strings.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
cnd.and(pageForm.getSearchName(), "like", "%" + pageForm.getSearchKeyword() + "%");
|
||||
|
||||
+4
-2
@@ -115,8 +115,10 @@ public class MeetingOnlineController {
|
||||
@At("/getRealTimeData/?")
|
||||
@Ok("json:full")
|
||||
@SaCheckLogin
|
||||
public Result getRealTimeData(String timePeriodId) {
|
||||
List<MeetingTimePeriodUser> signInUser = onlineService.getSignInUser(timePeriodId);
|
||||
public Result getRealTimeData(String timePeriodId,
|
||||
@Param(value = "signRankOrder") String signRankOrder) {
|
||||
// 签到人员排序统一交由Service处理,控制器只负责接收前端排序方向并返回实时数据。
|
||||
List<MeetingTimePeriodUser> signInUser = onlineService.getSignInUser(timePeriodId, signRankOrder);
|
||||
List<Map<String, Object>> countData = onlineService.getRealTimeData(timePeriodId);
|
||||
return Result.success(Map.of("countData", countData, "userData", signInUser));
|
||||
}
|
||||
|
||||
+10
-1
@@ -18,6 +18,15 @@ import java.util.Map;
|
||||
public interface MeetingOnlineService extends BaseService<MeetingInfo> {
|
||||
|
||||
List<MeetingTimePeriodUser> exportSignature(String timePeriodId);
|
||||
List<MeetingTimePeriodUser> getSignInUser(String timePeriodId);
|
||||
|
||||
/**
|
||||
* 查询指定会议时段的签到人员,并根据签到名次要求排序。
|
||||
*
|
||||
* @param timePeriodId 会议时段ID
|
||||
* @param signRankOrder 签到名次排序方向,ascending为升序,其他值按降序处理
|
||||
* @return 已签到人员列表
|
||||
*/
|
||||
List<MeetingTimePeriodUser> getSignInUser(String timePeriodId, String signRankOrder);
|
||||
|
||||
List<Map<String, Object>> getRealTimeData(String timePeriodId);
|
||||
}
|
||||
|
||||
+12
-8
@@ -70,13 +70,16 @@ public class MeetingOnlineServiceImpl extends BaseServiceImpl<MeetingInfo> imple
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MeetingTimePeriodUser> getSignInUser(String timePeriodId) {
|
||||
return dao().query(
|
||||
MeetingTimePeriodUser.class,
|
||||
Cnd.where(MeetingTimePeriodUser::getTimePeriodId, "=", timePeriodId)
|
||||
.and(MeetingTimePeriodUser::getSignStatus, "=", true)
|
||||
.desc("signTime")
|
||||
);
|
||||
public List<MeetingTimePeriodUser> getSignInUser(String timePeriodId, String signRankOrder) {
|
||||
Cnd cnd = Cnd.where(MeetingTimePeriodUser::getTimePeriodId, "=", timePeriodId)
|
||||
.and(MeetingTimePeriodUser::getSignStatus, "=", true);
|
||||
// 签到名次由签到时间确定,只识别固定方向参数,禁止将前端排序内容直接拼接到SQL中。
|
||||
if ("ascending".equals(signRankOrder)) {
|
||||
cnd.asc("signTime");
|
||||
} else {
|
||||
cnd.desc("signTime");
|
||||
}
|
||||
return dao().query(MeetingTimePeriodUser.class, cnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,7 +136,8 @@ public class MeetingOnlineServiceImpl extends BaseServiceImpl<MeetingInfo> imple
|
||||
Sys_role formalRole = roleService.getByCode(formalRoleCode);
|
||||
Sys_role attendanceRole = roleService.getByCode(attendanceRoleCode);
|
||||
|
||||
List<String> signedInUserIds = getSignInUser(timePeriodId).stream()
|
||||
// 代表签到统计只关心已签到人员集合,使用默认降序查询即可。
|
||||
List<String> signedInUserIds = getSignInUser(timePeriodId, "descending").stream()
|
||||
.map(MeetingTimePeriodUser::getUserId)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
+1
-7
@@ -36,7 +36,6 @@ 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.MemberManagePageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.service.PendingConfirmUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -80,8 +79,6 @@ public class MemberChangeManageController {
|
||||
private CommonService commonService;
|
||||
@Inject
|
||||
private MemberCommonService memberCommonService;
|
||||
@Inject
|
||||
private PendingConfirmUserService pendingConfirmUserService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/member/change/manage/index.html")
|
||||
@@ -106,8 +103,7 @@ public class MemberChangeManageController {
|
||||
@SLog(tag = "会员高级管理-会员变更", msg = "分工会/校工会会员管理员提交变更")
|
||||
@SaCheckPermission(value = {
|
||||
"member.change.mange",
|
||||
"staff.member.change.mange",
|
||||
"staff.sourcechange.pending"
|
||||
"staff.member.change.mange"
|
||||
}, mode = SaMode.OR)
|
||||
public Result doSubmitChange(MemberChangeRecord record){
|
||||
// 检验是否有变更
|
||||
@@ -169,8 +165,6 @@ public class MemberChangeManageController {
|
||||
if (schoolRoleBool) {
|
||||
memberCommonService.compareChangeInfoAndUpdateMember(record);
|
||||
}
|
||||
// 待确认人员完成变更提交后转为已确认;普通人员调用时不会产生状态变化。
|
||||
pendingConfirmUserService.markConfirmed(record.getUserId());
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.budwk.app.zhgh.staffmanage.member.controller.statistics;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberPersonTypeStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
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 org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 人员类型统计控制器。
|
||||
* 统计业务统一由 MemberStatisticsService 处理,控制器仅负责参数接收和结果输出。
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/member/statistics/personType")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "人员类型统计")
|
||||
public class MemberPersonTypeStatisticsController {
|
||||
|
||||
@Inject
|
||||
private MemberStatisticsService memberStatisticsService;
|
||||
|
||||
/** 进入人员类型统计页面。 */
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/staffmanage/member/statistics/personType/index.html")
|
||||
@SaCheckPermission("member.statistics.personType")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询按分工会或单位汇总的人员类型数据。
|
||||
*
|
||||
* @param pageForm 分页、统计维度及机构查询条件
|
||||
* @return 人员类型统计结果
|
||||
*/
|
||||
@At
|
||||
@ApiOperation("查询人员类型统计")
|
||||
@SaCheckPermission("member.statistics.personType")
|
||||
public Result pageData(@Param("..") MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
return Result.success(memberStatisticsService.getPersonTypeStatistics(pageForm));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出人员类型统计结果。
|
||||
*
|
||||
* @param pageForm 统计维度及机构查询条件
|
||||
* @param response HTTP 响应
|
||||
*/
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("导出人员类型统计")
|
||||
@SaCheckPermission("member.statistics.personType")
|
||||
public void doExport(@Param("..") MemberPersonTypeStatisticsPageForm pageForm, HttpServletResponse response) {
|
||||
try {
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition", "attachment;filename="
|
||||
+ new String("人员类型统计.xlsx".getBytes("UTF-8"), "ISO8859-1"));
|
||||
Workbook workbook = memberStatisticsService.exportPersonTypeStatistics(pageForm);
|
||||
workbook.write(response.getOutputStream());
|
||||
workbook.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("人员类型统计导出失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.budwk.app.zhgh.staffmanage.member.param.pageform;
|
||||
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 人员类型统计分页查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MemberPersonTypeStatisticsPageForm extends PageForm {
|
||||
|
||||
/** 统计维度:union 按分工会统计,unit 按单位统计。 */
|
||||
private String queryType;
|
||||
|
||||
/** 所属分工会ID。 */
|
||||
private String unionId;
|
||||
|
||||
/** 所属单位ID。 */
|
||||
private String unitId;
|
||||
}
|
||||
+17
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.staffmanage.member.service;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberPersonTypeStatisticsPageForm;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
@@ -96,4 +97,20 @@ public interface MemberStatisticsService extends BaseService<Sys_user> {
|
||||
* @return
|
||||
*/
|
||||
Workbook exportStatisticsExcel(List<NutMap> list, String queryType);
|
||||
|
||||
/**
|
||||
* 按分工会或单位统计各人员类型的会员数量。
|
||||
*
|
||||
* @param pageForm 分页、统计维度及机构查询条件
|
||||
* @return 统计数据、人员类型动态列及实际统计维度
|
||||
*/
|
||||
NutMap getPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 导出人员类型统计结果。
|
||||
*
|
||||
* @param pageForm 统计维度及机构查询条件
|
||||
* @return XSSF 格式工作簿
|
||||
*/
|
||||
Workbook exportPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm);
|
||||
}
|
||||
|
||||
+227
@@ -8,11 +8,15 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
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.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.param.pageform.MemberPersonTypeStatisticsPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberStatisticsService;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -22,6 +26,7 @@ import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -41,6 +46,11 @@ import java.util.stream.Collectors;
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> implements MemberStatisticsService {
|
||||
|
||||
private static final String PERSON_TYPE_DICT_CODE = "USER_PERSON_TYPE";
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
public MemberStatisticsServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -574,4 +584,221 @@ public class MemberStatisticsServiceImpl extends BaseServiceImpl<Sys_user> imple
|
||||
exportParams.setHeight((short) 10);
|
||||
return ExcelExportUtil.exportExcel(exportParams, exportEntities, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 V4 人员视图 vw_user 动态统计 USER_PERSON_TYPE 字典中的各类会员人数。
|
||||
* 非校级管理员固定按当前所属工会下的单位统计,防止通过请求参数越权查询全校数据。
|
||||
*
|
||||
* @param pageForm 分页、统计维度及机构查询条件
|
||||
* @return 统计列表、动态列配置及实际查询维度
|
||||
*/
|
||||
@Override
|
||||
public NutMap getPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
boolean schoolAdmin = isPersonTypeStatisticsSchoolAdmin();
|
||||
String actualQueryType = schoolAdmin && "union".equals(pageForm.getQueryType()) ? "union" : "unit";
|
||||
List<Sys_dict> personTypes = sysDictService.getSubListByCode(PERSON_TYPE_DICT_CODE);
|
||||
String personTypeColumns = buildPersonTypeColumns(personTypes.size());
|
||||
|
||||
// 分页查询只返回当前页机构数据,避免机构数量增加后一次加载全部列表。
|
||||
Sql pageSql = createPersonTypeStatisticsSql(actualQueryType, personTypeColumns, schoolAdmin, pageForm);
|
||||
setPersonTypeParams(pageSql, personTypes);
|
||||
Sql countSql = createPersonTypeStatisticsCountSql(actualQueryType, schoolAdmin, pageForm);
|
||||
Pagination pagination = listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), pageSql, countSql);
|
||||
List<NutMap> pageList = pagination.getList(NutMap.class);
|
||||
pageList.forEach(row -> row.setv("totalNum", calculatePersonTypeTotal(row, personTypes.size())));
|
||||
|
||||
// 合计重新查询当前权限和筛选范围内的全部机构,不受当前页码及每页条数影响。
|
||||
Sql allSql = createPersonTypeStatisticsSql(actualQueryType, personTypeColumns, schoolAdmin, pageForm);
|
||||
setPersonTypeParams(allSql, personTypes);
|
||||
List<NutMap> allList = listMap(allSql);
|
||||
allList.forEach(row -> row.setv("totalNum", calculatePersonTypeTotal(row, personTypes.size())));
|
||||
|
||||
return NutMap.NEW()
|
||||
.setv("pageData", pagination)
|
||||
.setv("summary", buildPersonTypeSummary(allList, personTypes.size()))
|
||||
.setv("personTypeOptions", buildPersonTypeOptions(personTypes))
|
||||
.setv("queryType", actualQueryType)
|
||||
.setv("schoolAdmin", schoolAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据人员类型统计结果生成动态列 Excel,表头与 USER_PERSON_TYPE 当前启用字典保持一致。
|
||||
*
|
||||
* @param pageForm 统计维度及机构查询条件
|
||||
* @return XSSF 格式工作簿
|
||||
*/
|
||||
@Override
|
||||
public Workbook exportPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
boolean schoolAdmin = isPersonTypeStatisticsSchoolAdmin();
|
||||
String actualQueryType = schoolAdmin && "union".equals(pageForm.getQueryType()) ? "union" : "unit";
|
||||
List<Sys_dict> personTypes = sysDictService.getSubListByCode(PERSON_TYPE_DICT_CODE);
|
||||
Sql sql = createPersonTypeStatisticsSql(
|
||||
actualQueryType, buildPersonTypeColumns(personTypes.size()), schoolAdmin, pageForm);
|
||||
setPersonTypeParams(sql, personTypes);
|
||||
List<NutMap> statisticsList = listMap(sql);
|
||||
statisticsList.forEach(row -> row.setv("totalNum", calculatePersonTypeTotal(row, personTypes.size())));
|
||||
List<NutMap> personTypeOptions = buildPersonTypeOptions(personTypes);
|
||||
|
||||
List<ExcelExportEntity> exportEntities = new ArrayList<>();
|
||||
if ("union".equals(actualQueryType)) {
|
||||
exportEntities.add(new ExcelExportEntity("分工会代码", "unionCode", 20));
|
||||
exportEntities.add(new ExcelExportEntity("分工会名称", "unionName", 25));
|
||||
} else {
|
||||
exportEntities.add(new ExcelExportEntity("单位代码", "unitCode", 20));
|
||||
exportEntities.add(new ExcelExportEntity("单位名称", "unitName", 25));
|
||||
if (schoolAdmin) {
|
||||
exportEntities.add(new ExcelExportEntity("所属分工会", "unionName", 25));
|
||||
}
|
||||
}
|
||||
exportEntities.add(new ExcelExportEntity("小计", "totalNum", 15));
|
||||
personTypeOptions.forEach(option -> exportEntities.add(
|
||||
new ExcelExportEntity(option.getString("name"), option.getString("prop"), 20)));
|
||||
|
||||
NutMap sumRow = buildPersonTypeSummary(statisticsList, personTypes.size());
|
||||
sumRow.setv("union".equals(actualQueryType) ? "unionName" : "unitName", "合计");
|
||||
statisticsList.add(sumRow);
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
exportParams.setHeight((short) 10);
|
||||
return ExcelExportUtil.exportExcel(exportParams, exportEntities, statisticsList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建人员类型统计 SQL。动态部分只包含后端生成的固定别名,字典值全部通过参数绑定传入。
|
||||
*/
|
||||
private Sql createPersonTypeStatisticsSql(String queryType, String personTypeColumns, boolean schoolAdmin,
|
||||
MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
Sql sql;
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if ("union".equals(queryType)) {
|
||||
// 动态列必须先写入完整源 SQL,再由 Sqls.create 解析其中的 @personTypeN 参数。
|
||||
String sqlSource = """
|
||||
SELECT
|
||||
un.id AS unionId,
|
||||
un.unionCode,
|
||||
un.name AS unionName
|
||||
%s
|
||||
FROM sys_union un
|
||||
LEFT JOIN vw_user u ON u.unionId = un.id AND u.member = 1
|
||||
$condition
|
||||
""".formatted(personTypeColumns);
|
||||
sql = Sqls.create(sqlSource);
|
||||
cnd.andEX("un.id", "=", pageForm.getUnionId());
|
||||
cnd.groupBy("un.id", "un.unionCode", "un.name");
|
||||
cnd.asc("un.unionCode");
|
||||
} else {
|
||||
// 单位统计同样在创建 Sql 对象前完成动态列拼装,避免参数被当作 MySQL 会话变量。
|
||||
String sqlSource = """
|
||||
SELECT
|
||||
unit.id AS unitId,
|
||||
unit.unitcode AS unitCode,
|
||||
unit.name AS unitName,
|
||||
un.id AS unionId,
|
||||
un.unionCode,
|
||||
un.name AS unionName
|
||||
%s
|
||||
FROM sys_unit unit
|
||||
LEFT JOIN sys_union un ON un.id = unit.unionId
|
||||
LEFT JOIN vw_user u ON u.unitId = unit.id AND u.member = 1
|
||||
$condition
|
||||
""".formatted(personTypeColumns);
|
||||
sql = Sqls.create(sqlSource);
|
||||
cnd.and("unit.unitLevel", "=", 2);
|
||||
if (!schoolAdmin) {
|
||||
cnd.and("unit.unionId", "=", SecurityUtil.getUnionId());
|
||||
} else {
|
||||
cnd.andEX("unit.unionId", "=", pageForm.getUnionId());
|
||||
}
|
||||
cnd.andEX("unit.id", "=", pageForm.getUnitId());
|
||||
cnd.groupBy("unit.id", "unit.unitcode", "unit.name", "un.id", "un.unionCode", "un.name");
|
||||
cnd.asc("unit.unitcode");
|
||||
}
|
||||
sql.setCondition(cnd);
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建机构分页总数 SQL。分组统计 SQL 不直接用于计数,避免 GROUP BY 导致分页总数取值不准确。
|
||||
*/
|
||||
private Sql createPersonTypeStatisticsCountSql(String queryType, boolean schoolAdmin,
|
||||
MemberPersonTypeStatisticsPageForm pageForm) {
|
||||
Sql countSql;
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if ("union".equals(queryType)) {
|
||||
countSql = Sqls.create("SELECT COUNT(1) FROM sys_union un $condition");
|
||||
cnd.andEX("un.id", "=", pageForm.getUnionId());
|
||||
} else {
|
||||
countSql = Sqls.create("SELECT COUNT(1) FROM sys_unit unit $condition");
|
||||
cnd.and("unit.unitLevel", "=", 2);
|
||||
if (!schoolAdmin) {
|
||||
cnd.and("unit.unionId", "=", SecurityUtil.getUnionId());
|
||||
} else {
|
||||
cnd.andEX("unit.unionId", "=", pageForm.getUnionId());
|
||||
}
|
||||
cnd.andEX("unit.id", "=", pageForm.getUnitId());
|
||||
}
|
||||
countSql.setCondition(cnd);
|
||||
return countSql;
|
||||
}
|
||||
|
||||
/** 构建人员类型动态统计列,参数名称在 Sqls.create 前写入完整 SQL。 */
|
||||
private String buildPersonTypeColumns(int personTypeCount) {
|
||||
StringBuilder columns = new StringBuilder();
|
||||
for (int i = 0; i < personTypeCount; i++) {
|
||||
columns.append(", SUM(CASE WHEN u.personType = @personType")
|
||||
.append(i)
|
||||
.append(" THEN 1 ELSE 0 END) AS personType")
|
||||
.append(i);
|
||||
}
|
||||
return columns.toString();
|
||||
}
|
||||
|
||||
/** 将 USER_PERSON_TYPE 字典编码填充到已经完成参数解析的 SQL 对象。 */
|
||||
private void setPersonTypeParams(Sql sql, List<Sys_dict> personTypes) {
|
||||
for (int i = 0; i < personTypes.size(); i++) {
|
||||
sql.setParam("personType" + i, personTypes.get(i).getCode());
|
||||
}
|
||||
}
|
||||
|
||||
/** 将人员类型字典转换为前端动态列定义。 */
|
||||
private List<NutMap> buildPersonTypeOptions(List<Sys_dict> personTypes) {
|
||||
List<NutMap> options = new ArrayList<>();
|
||||
for (int i = 0; i < personTypes.size(); i++) {
|
||||
Sys_dict personType = personTypes.get(i);
|
||||
options.add(NutMap.NEW()
|
||||
.setv("code", personType.getCode())
|
||||
.setv("name", personType.getName())
|
||||
.setv("prop", "personType" + i));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/** 计算当前权限及筛选范围内全部机构的人员类型合计。 */
|
||||
private NutMap buildPersonTypeSummary(List<NutMap> statisticsList, int personTypeCount) {
|
||||
NutMap summary = NutMap.NEW();
|
||||
summary.setv("totalNum", statisticsList.stream().mapToInt(row -> row.getInt("totalNum", 0)).sum());
|
||||
for (int i = 0; i < personTypeCount; i++) {
|
||||
String prop = "personType" + i;
|
||||
summary.setv(prop, statisticsList.stream().mapToInt(row -> row.getInt(prop, 0)).sum());
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
/** 计算一行中所有人员类型数量的小计。 */
|
||||
private int calculatePersonTypeTotal(NutMap row, int personTypeCount) {
|
||||
int total = 0;
|
||||
for (int i = 0; i < personTypeCount; i++) {
|
||||
total += row.getInt("personType" + i, 0);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** 判断当前用户是否具备查看全校人员类型统计的校级角色。 */
|
||||
private boolean isPersonTypeStatisticsSchoolAdmin() {
|
||||
return AuthUtil.hasRoleOr(
|
||||
RoleConstant.SYSADMIN.name(),
|
||||
RoleConstant.SCHOOL_UNION_ADMIN.name(),
|
||||
RoleConstant.SCHOOL_UNION_MEMBER_ADMIN.name());
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -2,6 +2,10 @@ package com.budwk.app.zhgh.staffmanage.sourcechange.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.annotation.RepeatSubmit;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatchChangeParam;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.service.PendingConfirmUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
@@ -44,4 +48,38 @@ public class PendingConfirmUserController {
|
||||
public Result pageData(PendingConfirmUserPageForm pageForm) {
|
||||
return Result.success(pendingConfirmUserService.pageData(pageForm));
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接提交待确认人员变更,不创建会员变更申请及审批流程。
|
||||
*
|
||||
* @param record 待确认人员变更内容
|
||||
* @return 提交结果
|
||||
*/
|
||||
@At
|
||||
@Ok("json")
|
||||
@RepeatSubmit
|
||||
@SLog(tag = "待确认人员记录", msg = "直接提交人员变更")
|
||||
@ApiOperation("直接提交待确认人员变更")
|
||||
@SaCheckPermission("staff.sourcechange.pending")
|
||||
public Result submitChange(MemberChangeRecord record) {
|
||||
boolean changed = pendingConfirmUserService.submitChange(record);
|
||||
return Result.success(changed ? "变更成功" : "确认成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量修改勾选人员的人员类型并完成确认。
|
||||
*
|
||||
* @param changeParam 用户ID集合及目标人员类型
|
||||
* @return 批量处理结果
|
||||
*/
|
||||
@At
|
||||
@Ok("json")
|
||||
@RepeatSubmit
|
||||
@SLog(tag = "待确认人员记录", msg = "批量变更人员类型")
|
||||
@ApiOperation("批量变更待确认人员类型")
|
||||
@SaCheckPermission("staff.sourcechange.pending")
|
||||
public Result batchChange(PendingConfirmUserBatchChangeParam changeParam) {
|
||||
int changedCount = pendingConfirmUserService.batchChangePersonType(changeParam);
|
||||
return Result.success("已成功变更" + changedCount + "人");
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.budwk.app.zhgh.staffmanage.sourcechange.param;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 待确认人员批量变更参数。
|
||||
*/
|
||||
@Data
|
||||
public class PendingConfirmUserBatchChangeParam {
|
||||
|
||||
/**
|
||||
* 前端勾选的用户ID JSON数组。
|
||||
*/
|
||||
private String userIds;
|
||||
|
||||
/**
|
||||
* 批量设置的人员类型。
|
||||
*/
|
||||
private String personType;
|
||||
}
|
||||
+18
@@ -3,6 +3,8 @@ package com.budwk.app.zhgh.staffmanage.sourcechange.service;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatchChangeParam;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,22 @@ public interface PendingConfirmUserService extends BaseService<Sys_user> {
|
||||
*/
|
||||
Pagination pageData(PendingConfirmUserPageForm pageForm);
|
||||
|
||||
/**
|
||||
* 直接提交待确认人员变更,不生成会员变更申请及流程数据。
|
||||
*
|
||||
* @param record 待确认人员变更内容
|
||||
* @return true 表示人员字段发生变化,false 表示仅完成确认
|
||||
*/
|
||||
boolean submitChange(MemberChangeRecord record);
|
||||
|
||||
/**
|
||||
* 批量修改待确认人员的人员类型,并将处理成功的人员标记为已确认。
|
||||
*
|
||||
* @param changeParam 批量勾选的用户ID及目标人员类型
|
||||
* @return 实际完成确认的人员数量
|
||||
*/
|
||||
int batchChangePersonType(PendingConfirmUserBatchChangeParam changeParam);
|
||||
|
||||
/**
|
||||
* 人员变更提交成功后,将待确认状态更新为已确认。
|
||||
*
|
||||
|
||||
+149
@@ -1,10 +1,21 @@
|
||||
package com.budwk.app.zhgh.staffmanage.sourcechange.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeOrigin;
|
||||
import com.budwk.app.zhgh.staffmanage.member.models.MemberChangeRecord;
|
||||
import com.budwk.app.zhgh.staffmanage.member.service.MemberCommonService;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserBatchChangeParam;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.param.PendingConfirmUserPageForm;
|
||||
import com.budwk.app.zhgh.staffmanage.sourcechange.service.PendingConfirmUserService;
|
||||
import org.nutz.dao.Cnd;
|
||||
@@ -13,8 +24,15 @@ import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 待确认人员记录业务实现。
|
||||
@@ -24,6 +42,14 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl<Sys_user> imp
|
||||
|
||||
private static final int STATUS_PENDING = 1;
|
||||
private static final int STATUS_CONFIRMED = 2;
|
||||
/** 待确认人员只允许转为以下两个字典名称对应的编码。 */
|
||||
private static final Set<String> ALLOWED_PERSON_TYPE_NAMES = Set.of("离退休人员", "其他");
|
||||
|
||||
@Inject
|
||||
private MemberCommonService memberCommonService;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
public PendingConfirmUserServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
@@ -81,6 +107,129 @@ public class PendingConfirmUserServiceImpl extends BaseServiceImpl<Sys_user> imp
|
||||
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接更新待确认人员及历史记录,不写会员变更记录,也不创建任何流程实例或任务。
|
||||
*
|
||||
* @param record 待确认人员变更内容
|
||||
* @return true 表示人员字段发生变化,false 表示仅完成确认
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public boolean submitChange(MemberChangeRecord record) {
|
||||
if (record == null || StrUtil.isBlank(record.getUserId())) {
|
||||
throw new BaseException("未获取到待确认人员数据");
|
||||
}
|
||||
if (StrUtil.isBlank(record.getPersonType())) {
|
||||
throw new BaseException("请选择人员类型");
|
||||
}
|
||||
validatePersonType(record.getPersonType());
|
||||
// 单人页面虽然提交完整表单,服务层只采用用户ID和人员类型,防止请求篡改其他只读字段。
|
||||
return changePersonType(record.getUserId(), record.getPersonType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 在同一事务内批量处理选中人员;任意人员状态异常时整体回滚,避免出现部分成功。
|
||||
*
|
||||
* @param changeParam 批量用户ID及目标人员类型
|
||||
* @return 完成确认的人员数量
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public int batchChangePersonType(PendingConfirmUserBatchChangeParam changeParam) {
|
||||
if (changeParam == null || StrUtil.isBlank(changeParam.getUserIds())) {
|
||||
throw new BaseException("请选择需要变更的人员");
|
||||
}
|
||||
if (StrUtil.isBlank(changeParam.getPersonType())) {
|
||||
throw new BaseException("请选择人员类型");
|
||||
}
|
||||
validatePersonType(changeParam.getPersonType());
|
||||
|
||||
List<String> userIds;
|
||||
try {
|
||||
userIds = JSONUtil.parseArray(changeParam.getUserIds()).toList(String.class);
|
||||
} catch (Exception e) {
|
||||
throw new BaseException("批量变更人员参数格式不正确");
|
||||
}
|
||||
// 去除空ID和重复ID,防止同一人员在一次请求内被重复处理。
|
||||
LinkedHashSet<String> normalizedUserIds = new LinkedHashSet<>();
|
||||
for (String userId : userIds) {
|
||||
if (StrUtil.isNotBlank(userId)) {
|
||||
normalizedUserIds.add(userId);
|
||||
}
|
||||
}
|
||||
if (normalizedUserIds.isEmpty()) {
|
||||
throw new BaseException("请选择需要变更的人员");
|
||||
}
|
||||
|
||||
for (String userId : new ArrayList<>(normalizedUserIds)) {
|
||||
changePersonType(userId, changeParam.getPersonType());
|
||||
}
|
||||
return normalizedUserIds.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅修改指定待确认人员的人员类型,并复用会员公共服务生成用户历史记录。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param personType 目标人员类型
|
||||
* @return true 表示人员类型发生变化,false 表示类型未变化但已完成确认
|
||||
*/
|
||||
private boolean changePersonType(String userId, String personType) {
|
||||
Sys_user user = dao().fetch(Sys_user.class, userId);
|
||||
if (user == null || !Integer.valueOf(STATUS_PENDING).equals(user.getPendingConfirmStatus())) {
|
||||
throw new BaseException("当前人员不属于待确认人员记录");
|
||||
}
|
||||
|
||||
// View_user 是数据库视图且未声明 @Name,必须使用条件查询,不能调用主键值重载。
|
||||
View_user userView = dao().fetch(View_user.class, Cnd.where("id", "=", userId));
|
||||
if (userView == null) {
|
||||
throw new BaseException("未获取到人员基础信息");
|
||||
}
|
||||
// 从数据库当前值重建完整变更对象,除人员类型外不信任前端提交的任何人员字段。
|
||||
MemberChangeRecord safeRecord = new MemberChangeRecord();
|
||||
BeanUtil.copyProperties(userView, safeRecord);
|
||||
safeRecord.setUserId(userId);
|
||||
safeRecord.setPersonType(StrUtil.trim(personType));
|
||||
safeRecord.setMember(Integer.valueOf(1).equals(userView.getMember()));
|
||||
safeRecord.setWelfareMember(Integer.valueOf(1).equals(userView.getWelfareMember()));
|
||||
safeRecord.setArrivalAtSchoolDate(userView.getArrivalAtSchoolDate() == null
|
||||
? null : DateUtil.formatDate(userView.getArrivalAtSchoolDate()));
|
||||
safeRecord.setWelfareStopDate(userView.getWelfareStopDate() == null
|
||||
? null : DateUtil.formatDate(userView.getWelfareStopDate()));
|
||||
|
||||
try {
|
||||
// 复用公共校验和历史记录生成口径,确保单人、批量变更结果保持一致。
|
||||
memberCommonService.validateChangeAndSetBasicData(safeRecord, MemberChangeOrigin.SYSTEM);
|
||||
} catch (BaseException e) {
|
||||
// 人员类型与当前值相同时允许直接完成确认;其他业务异常必须继续抛出并触发事务回滚。
|
||||
if ("未获取到异动数据".equals(e.getMessage())) {
|
||||
markConfirmed(userId);
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
memberCommonService.compareChangeInfoAndUpdateMember(safeRecord);
|
||||
markConfirmed(userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验待确认人员允许变更的目标人员类型。
|
||||
*
|
||||
* @param personType 目标人员类型
|
||||
*/
|
||||
private void validatePersonType(String personType) {
|
||||
String targetPersonType = StrUtil.trim(personType);
|
||||
List<Sys_dict> personTypeDicts = sysDictService.getSubListByCode("USER_PERSON_TYPE");
|
||||
boolean allowed = personTypeDicts != null && personTypeDicts.stream().anyMatch(dict ->
|
||||
ALLOWED_PERSON_TYPE_NAMES.contains(dict.getName())
|
||||
&& StrUtil.equals(targetPersonType, dict.getCode()));
|
||||
if (!allowed) {
|
||||
throw new BaseException("人员类型只能选择离退休人员或其他");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 变更提交成功后仅把待确认人员更新为已确认,重复调用不会改变其他状态。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user