diff --git a/src/main/java/com/budwk/app/sys/models/Sys_user.java b/src/main/java/com/budwk/app/sys/models/Sys_user.java index 85a8349c..20985e24 100644 --- a/src/main/java/com/budwk/app/sys/models/Sys_user.java +++ b/src/main/java/com/budwk/app/sys/models/Sys_user.java @@ -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) diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingManageController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingManageController.java index 87d1a810..d76f0c6f 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingManageController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingManageController.java @@ -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() + "%"); diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingOnlineController.java b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingOnlineController.java index 13c2f331..207f9301 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingOnlineController.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/controller/MeetingOnlineController.java @@ -115,8 +115,10 @@ public class MeetingOnlineController { @At("/getRealTimeData/?") @Ok("json:full") @SaCheckLogin - public Result getRealTimeData(String timePeriodId) { - List signInUser = onlineService.getSignInUser(timePeriodId); + public Result getRealTimeData(String timePeriodId, + @Param(value = "signRankOrder") String signRankOrder) { + // 签到人员排序统一交由Service处理,控制器只负责接收前端排序方向并返回实时数据。 + List signInUser = onlineService.getSignInUser(timePeriodId, signRankOrder); List> countData = onlineService.getRealTimeData(timePeriodId); return Result.success(Map.of("countData", countData, "userData", signInUser)); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/MeetingOnlineService.java b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/MeetingOnlineService.java index f4da1c87..5f3cbf22 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/MeetingOnlineService.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/MeetingOnlineService.java @@ -18,6 +18,15 @@ import java.util.Map; public interface MeetingOnlineService extends BaseService { List exportSignature(String timePeriodId); - List getSignInUser(String timePeriodId); + + /** + * 查询指定会议时段的签到人员,并根据签到名次要求排序。 + * + * @param timePeriodId 会议时段ID + * @param signRankOrder 签到名次排序方向,ascending为升序,其他值按降序处理 + * @return 已签到人员列表 + */ + List getSignInUser(String timePeriodId, String signRankOrder); + List> getRealTimeData(String timePeriodId); } diff --git a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/impl/MeetingOnlineServiceImpl.java b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/impl/MeetingOnlineServiceImpl.java index 34103c5b..94827fc2 100644 --- a/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/impl/MeetingOnlineServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/dayofficework/meeting/service/impl/MeetingOnlineServiceImpl.java @@ -70,13 +70,16 @@ public class MeetingOnlineServiceImpl extends BaseServiceImpl imple } @Override - public List getSignInUser(String timePeriodId) { - return dao().query( - MeetingTimePeriodUser.class, - Cnd.where(MeetingTimePeriodUser::getTimePeriodId, "=", timePeriodId) - .and(MeetingTimePeriodUser::getSignStatus, "=", true) - .desc("signTime") - ); + public List 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 imple Sys_role formalRole = roleService.getByCode(formalRoleCode); Sys_role attendanceRole = roleService.getByCode(attendanceRoleCode); - List signedInUserIds = getSignInUser(timePeriodId).stream() + // 代表签到统计只关心已签到人员集合,使用默认降序查询即可。 + List signedInUserIds = getSignInUser(timePeriodId, "descending").stream() .map(MeetingTimePeriodUser::getUserId) .filter(Objects::nonNull) .toList(); diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeManageController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeManageController.java index f1d05b87..29fa21a3 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeManageController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/change/MemberChangeManageController.java @@ -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(); } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberPersonTypeStatisticsController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberPersonTypeStatisticsController.java new file mode 100644 index 00000000..34fabf19 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/controller/statistics/MemberPersonTypeStatisticsController.java @@ -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); + } + } +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/param/pageform/MemberPersonTypeStatisticsPageForm.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/param/pageform/MemberPersonTypeStatisticsPageForm.java new file mode 100644 index 00000000..826abeb3 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/param/pageform/MemberPersonTypeStatisticsPageForm.java @@ -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; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberStatisticsService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberStatisticsService.java index 9acb34db..f7b3ab77 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberStatisticsService.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/MemberStatisticsService.java @@ -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 { * @return */ Workbook exportStatisticsExcel(List list, String queryType); + + /** + * 按分工会或单位统计各人员类型的会员数量。 + * + * @param pageForm 分页、统计维度及机构查询条件 + * @return 统计数据、人员类型动态列及实际统计维度 + */ + NutMap getPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm); + + /** + * 导出人员类型统计结果。 + * + * @param pageForm 统计维度及机构查询条件 + * @return XSSF 格式工作簿 + */ + Workbook exportPersonTypeStatistics(MemberPersonTypeStatisticsPageForm pageForm); } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberStatisticsServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberStatisticsServiceImpl.java index 3eff9365..0b9007e3 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberStatisticsServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberStatisticsServiceImpl.java @@ -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 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 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 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 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 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 personTypes = sysDictService.getSubListByCode(PERSON_TYPE_DICT_CODE); + Sql sql = createPersonTypeStatisticsSql( + actualQueryType, buildPersonTypeColumns(personTypes.size()), schoolAdmin, pageForm); + setPersonTypeParams(sql, personTypes); + List statisticsList = listMap(sql); + statisticsList.forEach(row -> row.setv("totalNum", calculatePersonTypeTotal(row, personTypes.size()))); + List personTypeOptions = buildPersonTypeOptions(personTypes); + + List 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 personTypes) { + for (int i = 0; i < personTypes.size(); i++) { + sql.setParam("personType" + i, personTypes.get(i).getCode()); + } + } + + /** 将人员类型字典转换为前端动态列定义。 */ + private List buildPersonTypeOptions(List personTypes) { + List 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 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()); + } } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java index 5c439c8b..7629df7f 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/controller/PendingConfirmUserController.java @@ -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 + "人"); + } } diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/param/PendingConfirmUserBatchChangeParam.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/param/PendingConfirmUserBatchChangeParam.java new file mode 100644 index 00000000..9c4fef01 --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/param/PendingConfirmUserBatchChangeParam.java @@ -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; +} diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java index 865a1274..85c37013 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/PendingConfirmUserService.java @@ -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 { */ Pagination pageData(PendingConfirmUserPageForm pageForm); + /** + * 直接提交待确认人员变更,不生成会员变更申请及流程数据。 + * + * @param record 待确认人员变更内容 + * @return true 表示人员字段发生变化,false 表示仅完成确认 + */ + boolean submitChange(MemberChangeRecord record); + + /** + * 批量修改待确认人员的人员类型,并将处理成功的人员标记为已确认。 + * + * @param changeParam 批量勾选的用户ID及目标人员类型 + * @return 实际完成确认的人员数量 + */ + int batchChangePersonType(PendingConfirmUserBatchChangeParam changeParam); + /** * 人员变更提交成功后,将待确认状态更新为已确认。 * diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java index 053be69a..8dfecbfb 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/sourcechange/service/impl/PendingConfirmUserServiceImpl.java @@ -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 imp private static final int STATUS_PENDING = 1; private static final int STATUS_CONFIRMED = 2; + /** 待确认人员只允许转为以下两个字典名称对应的编码。 */ + private static final Set 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 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 userIds; + try { + userIds = JSONUtil.parseArray(changeParam.getUserIds()).toList(String.class); + } catch (Exception e) { + throw new BaseException("批量变更人员参数格式不正确"); + } + // 去除空ID和重复ID,防止同一人员在一次请求内被重复处理。 + LinkedHashSet 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 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("人员类型只能选择离退休人员或其他"); + } + } + /** * 变更提交成功后仅把待确认人员更新为已确认,重复调用不会改变其他状态。 * diff --git a/src/main/resources/static/assets/platform/css/meeting-list-layout.css b/src/main/resources/static/assets/platform/css/meeting-list-layout.css new file mode 100644 index 00000000..46f4e137 --- /dev/null +++ b/src/main/resources/static/assets/platform/css/meeting-list-layout.css @@ -0,0 +1,75 @@ +/* + * 会务管理列表页统一满屏布局:查询区和分页区固定,表格使用剩余高度并在内部滚动。 + */ +.meeting-list-layout { + height: calc(100vh - 84px); + min-height: 0; + overflow: hidden; +} + +.meeting-list-layout > .transition-item { + height: 100%; + min-height: 0; + display: flex; + flex-direction: column; + gap: 20px; + overflow: hidden; +} + +.meeting-list-layout > .transition-item > .meeting-search-card { + flex: 0 0 auto; +} + +.meeting-list-layout > .transition-item > .meeting-table-card { + flex: 1 1 0; + min-height: 0; + margin-top: 0 !important; + overflow: hidden; +} + +.meeting-list-layout > .transition-item > .meeting-table-card > .el-card__body { + height: 100%; + min-height: 0; + display: flex; + flex-direction: column; + box-sizing: border-box; + overflow: hidden; +} + +.meeting-list-layout .meeting-table-card .ele-table-tool { + flex: 0 0 auto; +} + +.meeting-list-layout .meeting-table-card .meeting-main-table { + flex: 1 1 0; + min-height: 0; +} + +.meeting-list-layout .meeting-table-card .el-pagination-container { + flex: 0 0 52px; + min-height: 52px; + margin-top: 12px !important; + margin-bottom: 0 !important; + overflow: visible; +} + +.meeting-list-layout .meeting-main-table .el-table__body-wrapper { + overflow-y: auto; +} + +@media (max-height: 760px) { + .meeting-list-layout > .transition-item { + gap: 12px; + } + + .meeting-list-layout > .transition-item > .meeting-table-card > .el-card__body { + padding-top: 14px; + padding-bottom: 10px; + } + + .meeting-list-layout .meeting-table-card .el-pagination-container { + flex-basis: 48px; + min-height: 48px; + margin-top: 8px !important; + } +} diff --git a/src/main/resources/views/platform/zhgh/dayofficework/meeting/delegationApproval/index.html b/src/main/resources/views/platform/zhgh/dayofficework/meeting/delegationApproval/index.html index e96c6872..9ae2ce99 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/meeting/delegationApproval/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/meeting/delegationApproval/index.html @@ -2,10 +2,12 @@ layout("/layouts/platform.html"){ #--> -
- + - +
+ + + - + 已审核 未审核 - + + +
- - + + - + - + item.id === this.pageForm.moduleId) - return module && module.name === '民主管理' + return module && module.name === '提案工作' } }, methods: { diff --git a/src/main/resources/views/platform/zhgh/dayofficework/meeting/manage/index.html b/src/main/resources/views/platform/zhgh/dayofficework/meeting/manage/index.html index 391716c6..ff6bb94f 100644 --- a/src/main/resources/views/platform/zhgh/dayofficework/meeting/manage/index.html +++ b/src/main/resources/views/platform/zhgh/dayofficework/meeting/manage/index.html @@ -2,14 +2,16 @@ layout("/layouts/platform.html"){ #--> + +
- +